Using rpm->speed/power functions display distance, calories, speed and power (Not yet sending to backend,, need to confirm this is working first)
This commit is contained in:
@@ -5,304 +5,386 @@
|
||||
<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="{{ url_for('static', filename='js/bikes.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">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">
|
||||
<nav class="bg-white shadow-lg">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex justify-between">
|
||||
<div class="flex items-center">
|
||||
<a class="text-gray-800 text-xl font-bold" href="/">
|
||||
<div class="flex py-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="w-6 h-6 mr-2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3" />
|
||||
</svg>
|
||||
|
||||
<div>
|
||||
{{ user.name }}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
|
||||
<div class="flex justify-center items-center bg-gray-200">
|
||||
<svg id="graph" class="bg-white shadow-md"></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
|
||||
<script>
|
||||
// Get DOM elements
|
||||
const rpmDisplay = document.getElementById('rpm-display');
|
||||
const durationDisplay = document.getElementById('duration-display');
|
||||
const toggleButton = document.getElementById('toggle-button');
|
||||
// Initialize variables
|
||||
let rpm = 0;
|
||||
let power = 0;
|
||||
let isRunning = false;
|
||||
let startTime = 0;
|
||||
let intervalId = null;
|
||||
let workout = [];
|
||||
<div class="mx-auto max-w-md p-6 bg-white rounded-md shadow-md md:mt-5">
|
||||
<div class="">
|
||||
|
||||
const integerNumber = (num) => parseInt(num);
|
||||
const decimalNumber = (num) => parseFloat(num.toFixed(1));
|
||||
<div class="">
|
||||
<div class="flex mb-4 h-12 leading-10">
|
||||
<div class="w-1/3 text-lg font-medium">Time</div>
|
||||
<div class="w-3/4 bg-gray-200 text-3xl font-bold pl-3 rounded-md" id="duration-display">00:00</div>
|
||||
</div>
|
||||
<div class="flex mb-4 h-12 leading-10">
|
||||
<div class="w-1/3 text-lg font-medium">Distance</div>
|
||||
<div class="w-3/4 bg-gray-200 text-3xl font-bold pl-3 rounded-md" id="distance-display">0.0</div>
|
||||
</div>
|
||||
<div class="flex mb-4 h-12 leading-10">
|
||||
<div class="w-1/3 text-lg font-medium">Calories</div>
|
||||
<div class="w-3/4 bg-gray-200 text-3xl font-bold pl-3 rounded-md" id="calories-display">0.0</div>
|
||||
</div>
|
||||
|
||||
// Update RPM and Power
|
||||
function updateRpm(rpm) {
|
||||
rpmDisplay.textContent = integerNumber(rpm);
|
||||
}
|
||||
<div class="flex mb-4">
|
||||
<div class="w-1/3 bg-gray-200 text-center mx-2 rounded-md">
|
||||
<div class="flex flex-col justify-between">
|
||||
<div class="pb-2 text-lg font-medium">Watts</div>
|
||||
<div class="text-3xl font-bold pb-2" id="watts-display">0</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1/3 bg-gray-200 text-center mx-2 rounded-md">
|
||||
<div class="flex flex-col justify-between">
|
||||
<div class="pb-2 text-lg font-medium">Speed</div>
|
||||
<div class="text-3xl font-bold pb-2" id="speed-display">0.0</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1/3 bg-gray-200 text-center mx-2 rounded-md">
|
||||
<div class="flex flex-col justify-between">
|
||||
<div class="pb-2 text-lg font-medium">RPM</div>
|
||||
<div class="text-3xl font-bold pb-2" id="rpm-display">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// Update Duration
|
||||
function updateDuration() {
|
||||
const now = new Date();
|
||||
const diff = Math.abs(now - startTime) / 1000; // get difference in seconds
|
||||
let hours = Math.floor(diff / (60 * 60));
|
||||
let minutes = Math.floor(diff / 60) % 60;
|
||||
let seconds = Math.floor(diff);
|
||||
<div class="mt-4">
|
||||
<div class="flex justify-center items-center bg-gray-200">
|
||||
<svg id="graph" class="bg-white shadow-xl"></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
let timeSegments = [];
|
||||
if (hours > 0) {
|
||||
timeSegments.push(hours);
|
||||
<div class="mt-4 sm:w-full">
|
||||
<button id="toggle-button"
|
||||
class="px-4 py-2 text-white bg-blue-500 rounded-md hover:bg-blue-600 w-full">Start</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
|
||||
<script>
|
||||
// Get DOM elements
|
||||
const rpmDisplay = document.getElementById('rpm-display');
|
||||
const durationDisplay = document.getElementById('duration-display');
|
||||
const distanceDisplay = document.getElementById('distance-display');
|
||||
const caloriesDisplay = document.getElementById('calories-display');
|
||||
const wattsDisplay = document.getElementById('watts-display');
|
||||
const speedDisplay = document.getElementById('speed-display');
|
||||
|
||||
const toggleButton = document.getElementById('toggle-button');
|
||||
// Initialize variables
|
||||
let rpm = 0;
|
||||
let distance = 0;
|
||||
let calories = 0;
|
||||
let isRunning = false;
|
||||
let startTime = 0;
|
||||
let intervalId = null;
|
||||
let workout = [];
|
||||
let previousReadingTime = null;
|
||||
|
||||
const integerNumber = (num) => parseInt(num);
|
||||
const decimalNumber = (num) => parseFloat(num.toFixed(1));
|
||||
|
||||
function updateBikeDisplay(distance, calories, watts, speed, rpm) {
|
||||
distanceDisplay.textContent = decimalNumber(distance);
|
||||
caloriesDisplay.textContent = decimalNumber(calories);
|
||||
wattsDisplay.textContent = integerNumber(watts);
|
||||
speedDisplay.textContent = decimalNumber(speed);
|
||||
rpmDisplay.textContent = integerNumber(rpm);
|
||||
}
|
||||
timeSegments.push(minutes.toString().padStart(2, '0'));
|
||||
timeSegments.push(seconds.toString().padStart(2, '0'));
|
||||
|
||||
durationDisplay.textContent = timeSegments.join(':'); // format as "1:32"
|
||||
}
|
||||
// Update Duration
|
||||
function updateDuration() {
|
||||
const now = new Date();
|
||||
const diff = Math.abs(now - startTime) / 1000; // get difference in seconds
|
||||
let hours = Math.floor(diff / (60 * 60));
|
||||
let minutes = Math.floor(diff / 60) % 60;
|
||||
let seconds = Math.floor(diff);
|
||||
|
||||
// Start workout
|
||||
function startWorkout() {
|
||||
// Connect to cadence sensor
|
||||
connect({
|
||||
onChange: parseCSC,
|
||||
}).then(() => {
|
||||
isRunning = true;
|
||||
startTime = Date.now();
|
||||
toggleButton.textContent = 'Stop';
|
||||
intervalId = setInterval(() => {
|
||||
updateDuration();
|
||||
}, 1000);
|
||||
})
|
||||
.catch((err) => {
|
||||
swal("Oops", err.message, "error");
|
||||
});
|
||||
}
|
||||
let timeSegments = [];
|
||||
if (hours > 0) {
|
||||
timeSegments.push(hours);
|
||||
}
|
||||
timeSegments.push(minutes.toString().padStart(2, '0'));
|
||||
timeSegments.push(seconds.toString().padStart(2, '0'));
|
||||
|
||||
// Stop workout
|
||||
function stopWorkout() {
|
||||
isRunning = false;
|
||||
clearInterval(intervalId);
|
||||
toggleButton.textContent = 'Start';
|
||||
disconnect();
|
||||
durationDisplay.textContent = timeSegments.join(':'); // format as "1:32"
|
||||
}
|
||||
|
||||
fetch("{{ url_for('workouts', user_id=user.id) }}", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ workout: workout }),
|
||||
}).then(res => res.json())
|
||||
.then(res => {
|
||||
workout = [];
|
||||
swal("Submitted", JSON.stringify(res), "success").then(isConfirm => window.location.href = '/');
|
||||
// Start workout
|
||||
function startWorkout() {
|
||||
// Connect to cadence sensor
|
||||
connect({
|
||||
onChange: parseCSC,
|
||||
}).then(() => {
|
||||
isRunning = true;
|
||||
startTime = Date.now();
|
||||
toggleButton.textContent = 'Stop';
|
||||
intervalId = setInterval(() => {
|
||||
updateDuration();
|
||||
}, 1000);
|
||||
})
|
||||
.catch(err => swal("Failed to submit workout", err.message, "error"));
|
||||
|
||||
}
|
||||
|
||||
// Toggle workout
|
||||
function toggleWorkout() {
|
||||
if (isRunning) {
|
||||
stopWorkout();
|
||||
} else {
|
||||
startWorkout();
|
||||
.catch((err) => {
|
||||
swal("Oops", err.message, "error");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Attach event listener to toggle button
|
||||
toggleButton.addEventListener('click', toggleWorkout);
|
||||
// Stop workout
|
||||
function stopWorkout() {
|
||||
isRunning = false;
|
||||
clearInterval(intervalId);
|
||||
toggleButton.textContent = 'Start';
|
||||
disconnect();
|
||||
|
||||
// Set up the live plot
|
||||
const svg = d3.select('#graph');
|
||||
fetch("{{ url_for('workouts', user_id=user.id) }}", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ workout: workout }),
|
||||
}).then(res => res.json())
|
||||
.then(res => {
|
||||
workout = [];
|
||||
swal("Submitted", JSON.stringify(res), "success").then(isConfirm => window.location.href = '/');
|
||||
})
|
||||
.catch(err => swal("Failed to submit workout", err.message, "error"));
|
||||
|
||||
// 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.tension(0.25));
|
||||
|
||||
// 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();
|
||||
|
||||
|
||||
// BLE code
|
||||
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");
|
||||
}
|
||||
|
||||
async function disconnect() {
|
||||
if (characteristic) {
|
||||
try {
|
||||
await characteristic.stopNotifications();
|
||||
log("> Notifications stopped");
|
||||
characteristic.removeEventListener(
|
||||
"characteristicvaluechanged",
|
||||
handleNotifications
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Argh! " + error);
|
||||
swal("Oops", error, "error");
|
||||
// Toggle workout
|
||||
function toggleWorkout() {
|
||||
if (isRunning) {
|
||||
stopWorkout();
|
||||
} else {
|
||||
startWorkout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
// 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.tension(0.25));
|
||||
|
||||
// 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');
|
||||
};
|
||||
if (hasWheel) {
|
||||
res.wheelRevs = value.getUint32(index, true);
|
||||
index += 4;
|
||||
res.wheelTime = value.getUint16(index, true);
|
||||
index += 2;
|
||||
|
||||
// Call the updateGraph function to start the live updates
|
||||
updateGraph();
|
||||
|
||||
|
||||
// BLE code
|
||||
let characteristic = null;
|
||||
let prevRes = null;
|
||||
|
||||
function delay(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
if (hasCrank) {
|
||||
res.crankRevs = value.getUint16(index, true);
|
||||
index += 2;
|
||||
res.crankTime = value.getUint16(index, true);
|
||||
index += 2;
|
||||
|
||||
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");
|
||||
}
|
||||
console.log("CSC", res);
|
||||
if (prevRes) {
|
||||
let rpm = revsToRPM(prevRes, res);
|
||||
if (rpm > 0) {
|
||||
const newData = {
|
||||
x: data.length,
|
||||
y: decimalNumber(rpm),
|
||||
};
|
||||
|
||||
// Add the new data point to the data array
|
||||
data.push(newData);
|
||||
|
||||
updateRpm(rpm);
|
||||
updateGraph();
|
||||
|
||||
workout.push({ rpm, timestamp: new Date() })
|
||||
async function disconnect() {
|
||||
if (characteristic) {
|
||||
try {
|
||||
await characteristic.stopNotifications();
|
||||
log("> Notifications stopped");
|
||||
characteristic.removeEventListener(
|
||||
"characteristicvaluechanged",
|
||||
handleNotifications
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Argh! " + error);
|
||||
swal("Oops", error, "error");
|
||||
}
|
||||
}
|
||||
}
|
||||
prevRes = res;
|
||||
return res;
|
||||
}
|
||||
|
||||
function revsToRPM(prevRes, res) {
|
||||
const deltaRevs = res.crankRevs - prevRes.crankRevs;
|
||||
if (deltaRevs === 0) {
|
||||
// no rotation
|
||||
return 0;
|
||||
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) {
|
||||
const newData = {
|
||||
x: data.length,
|
||||
y: decimalNumber(rpm),
|
||||
};
|
||||
|
||||
// Add the new data point to the data array
|
||||
data.push(newData);
|
||||
|
||||
let watts = generators.aab.rpm.power(rpm)
|
||||
let speed = generators.aab.rpm.speed(rpm)
|
||||
if (previousReadingTime) {
|
||||
distance += calculateDistance(previousReadingTime, new Date(), speed)
|
||||
calories += calculateCalories(previousReadingTime, new Date(), watts)
|
||||
}
|
||||
|
||||
previousReadingTime = new Date();
|
||||
updateBikeDisplay(distance, calories, watts, speed, rpm);
|
||||
updateGraph();
|
||||
|
||||
workout.push({ rpm, timestamp: new Date() })
|
||||
}
|
||||
}
|
||||
prevRes = res;
|
||||
return res;
|
||||
}
|
||||
|
||||
let deltaTime = (res.crankTime - prevRes.crankTime) / 1024;
|
||||
if (deltaTime < 0) {
|
||||
// time counter wraparound
|
||||
deltaTime += Math.pow(2, 16) / 1024;
|
||||
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;
|
||||
}
|
||||
deltaTime /= 60; // seconds to minutes
|
||||
|
||||
const rpm = deltaRevs / deltaTime;
|
||||
return rpm;
|
||||
}
|
||||
function calculateDistance(startDate, endDate, speed) {
|
||||
// Convert speed from km/h to km/ms
|
||||
const speedPerMs = speed / (60 * 60 * 1000);
|
||||
|
||||
</script>
|
||||
// Calculate the time difference between the start and end dates in milliseconds
|
||||
const timeDiffMs = endDate.getTime() - startDate.getTime();
|
||||
|
||||
// Calculate the distance traveled based on speed and time difference
|
||||
const distance = speedPerMs * timeDiffMs;
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
function calculateCalories(startDate, endDate, power) {
|
||||
// Calculate the time difference between the start and end dates in seconds
|
||||
const timeDiffSec = (endDate.getTime() - startDate.getTime()) / 1000;
|
||||
|
||||
// Calculate the energy(joules) expended based on power and time difference
|
||||
const energy = powerPerSec * timeDiffSec;
|
||||
|
||||
//Convert joules to calories
|
||||
return energy * 0.238902957619;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user