Beta: Switch to using static site instead(doesnt yet post data back to server)

This commit is contained in:
Peter Stockings
2023-03-07 23:45:03 +11:00
parent 5c1a3acec3
commit a1a14b5aaf
3 changed files with 312 additions and 3 deletions

302
templates/attemptv2.html Normal file
View File

@@ -0,0 +1,302 @@
<!DOCTYPE html>
<html>
<head>
<title>Exercise Bike Display</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
<style>
.graph-container {
width: 100%;
height: 200px;
}
#graph {
background-color: #ffffff;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body class="bg-gray-200">
<div class="mx-auto max-w-md p-6 bg-white rounded-md shadow-md md:mt-5">
<div class="mt-6">
<div class="flex items-center justify-between">
<div>
<p class="text-lg font-medium">RPM</p>
<p id="rpm-display" class="text-3xl font-bold">0</p>
</div>
<div>
<p class="text-lg font-medium">Power</p>
<p id="power-display" class="text-3xl font-bold">0</p>
</div>
<div>
<p class="text-lg font-medium">Duration</p>
<p id="duration-display" class="text-3xl font-bold">00:00</p>
</div>
</div>
<div class="mt-8">
<button id="toggle-button"
class="px-4 py-2 text-white bg-blue-500 rounded-md hover:bg-blue-600">Start</button>
</div>
<div class="mt-8">
<div class="flex justify-center items-center bg-gray-200">
<svg id="graph" class="bg-white shadow-md"></svg>
</div>
</div>
</div>
</div>
<script>
// Get DOM elements
const rpmDisplay = document.getElementById('rpm-display');
const powerDisplay = document.getElementById('power-display');
const durationDisplay = document.getElementById('duration-display');
const toggleButton = document.getElementById('toggle-button');
// Initialize variables
let rpm = 0;
let power = 0;
let duration = 0;
let isRunning = false;
let startTime = 0;
let intervalId = null;
// Update RPM and Power
function updateRpmPower(rpm, power) {
// Update displays
rpmDisplay.textContent = rpm;
powerDisplay.textContent = power;
const newData = {
x: data.length,
y: rpm,
};
// Add the new data point to the data array
data.push(newData);
}
// Update Duration
function updateDuration() {
duration++;
// Format duration as mm:ss
const minutes = Math.floor(duration / 60).toString().padStart(2, '0');
const seconds = (duration % 60).toString().padStart(2, '0');
durationDisplay.textContent = `${minutes}:${seconds}`;
}
// Start workout
function startWorkout() {
isRunning = true;
startTime = Date.now();
intervalId = setInterval(() => {
updateDuration();
}, 1000);
// Connect to cadence sensor
connect({
onChange: parseCSC,
}).catch((err) => {
swal("Oops", err.message, "error");
});
toggleButton.textContent = 'Stop';
}
// Stop workout
function stopWorkout() {
isRunning = false;
clearInterval(intervalId);
toggleButton.textContent = 'Start';
disconnect();
}
// Toggle workout
function toggleWorkout() {
if (isRunning) {
stopWorkout();
} else {
startWorkout();
}
}
// Attach event listener to toggle button
toggleButton.addEventListener('click', toggleWorkout);
// Set up the live plot
const svg = d3.select('#graph');
// Set the SVG viewBox and dimensions
svg.attr('viewBox', '0 0 600 400');
svg.attr('width', '100%');
svg.attr('height', '100%');
// Set up the x-axis and y-axis scales
const xScale = d3.scaleLinear().range([50, 550]);
const yScale = d3.scaleLinear().range([350, 50]);
// Set up the x-axis and y-axis
const xAxis = d3.axisBottom(xScale).tickSize(0).tickFormat('');
const yAxis = d3.axisLeft(yScale).tickSize(0).tickFormat('');
// Add the x-axis and y-axis to the SVG
svg.append('g')
.attr('transform', 'translate(0, 350)')
.call(xAxis);
svg.append('g')
.attr('transform', 'translate(50, 0)')
.call(yAxis);
// Set up the line function
const line = d3.line()
.x((d) => xScale(d.x))
.y((d) => yScale(d.y))
.curve(d3.curveCardinal);
// Create an empty data array
let data = [];
// Set up the graph update function
const updateGraph = () => {
// Update the x-axis and y-axis scales
xScale.domain([0, data.length]);
yScale.domain([0, d3.max(data, d => d.y)]);
// Remove the old line path from the SVG
svg.selectAll('path').remove();
// Add a new line path to the SVG with the updated data
svg.append('path')
.datum(data)
.attr('d', line)
.attr('fill', 'none')
.attr('stroke', 'blue')
.attr('stroke-width', '2');
};
// Call the updateGraph function to start the live updates
updateGraph();
</script>
<script>
let characteristic = null;
let prevRes = null;
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function connect(props) {
const device = await navigator.bluetooth.requestDevice({
filters: [{ services: ["cycling_speed_and_cadence"] }],
acceptAllDevices: false,
});
console.log(`%c\n👩🏼‍⚕️`, "font-size: 82px;", "Starting CSC...\n\n");
const server = await device.gatt.connect();
await delay(500);
console.log("Getting Service...");
const service = await server.getPrimaryService("cycling_speed_and_cadence");
console.log("Getting Characteristic...");
characteristic = await service.getCharacteristic("csc_measurement");
await characteristic.startNotifications();
console.log("> Notifications started");
characteristic.addEventListener("characteristicvaluechanged", props.onChange);
console.log("> Characteristic value changed event listener added");
btn.classList.remove("bg-blue-600");
btn.classList.add("bg-green-600");
}
async function disconnect() {
if (characteristic) {
try {
await characteristic.stopNotifications();
log("> Notifications stopped");
characteristic.removeEventListener(
"characteristicvaluechanged",
handleNotifications
);
btn.classList.remove("bg-green-600");
btn.classList.add("bg-blue-600");
} catch (error) {
console.log("Argh! " + error);
swal("Oops", error, "error");
}
}
}
function parseCSC(e) {
const value = e.target.value;
const flags = value.getUint8(0, true);
const hasWheel = !!(flags & 0x01);
const hasCrank = !!(flags & 0x02);
let index = 1;
const res = {
wheelRevs: null,
wheelTime: null,
crankRevs: null,
crankTime: null,
};
if (hasWheel) {
res.wheelRevs = value.getUint32(index, true);
index += 4;
res.wheelTime = value.getUint16(index, true);
index += 2;
}
if (hasCrank) {
res.crankRevs = value.getUint16(index, true);
index += 2;
res.crankTime = value.getUint16(index, true);
index += 2;
}
console.log("CSC", res);
if (prevRes) {
let rpm = revsToRPM(prevRes, res);
if (rpm > 0) {
updateRpmPower(rpm, 0);
updateGraph();
/*
fetch("/cadence", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ rpm: parseFloat(rpm.toFixed(2)), id: 1 }),
});
*/
}
}
prevRes = res;
return res;
}
function revsToRPM(prevRes, res) {
const deltaRevs = res.crankRevs - prevRes.crankRevs;
if (deltaRevs === 0) {
// no rotation
return 0;
}
let deltaTime = (res.crankTime - prevRes.crankTime) / 1024;
if (deltaTime < 0) {
// time counter wraparound
deltaTime += Math.pow(2, 16) / 1024;
}
deltaTime /= 60; // seconds to minutes
const rpm = deltaRevs / deltaTime;
return rpm;
}
</script>
</body>
</html>

View File

@@ -1,6 +1,9 @@
<h1
class="mb-4 text-4xl font-extrabold leading-none tracking-tight text-gray-900 md:text-5xl lg:text-6xl dark:text-white text-center">
{{last_cadence}} rpm</h1>
<h1
class="mb-4 text-4xl font-extrabold leading-none tracking-tight text-gray-900 md:text-5xl lg:text-6xl dark:text-white text-center">
{{power}} Watts</h1>
<h3 class="mb-4 text-3xl font-extrabold leading-none tracking-tight text-gray-700 md:text-3xl lg:text-3xl dark:text-white text-center">
{{duration}}</h3>