115 lines
3.1 KiB
JavaScript
115 lines
3.1 KiB
JavaScript
let socket = null;
|
|
let characteristic = null;
|
|
let prevRes = null;
|
|
|
|
let btn = document.querySelector("#ble-connect");
|
|
|
|
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");
|
|
socket = io();
|
|
|
|
socket.on("connect", () => {
|
|
btn.classList.remove("bg-red-600");
|
|
btn.classList.add("bg-green-600");
|
|
});
|
|
|
|
socket.on("disconnect", (reason) => {
|
|
if (reason === "io server disconnect") {
|
|
// the disconnection was initiated by the server, you need to reconnect manually
|
|
socket.connect();
|
|
}
|
|
// else the socket will automatically try to reconnect
|
|
});
|
|
}
|
|
|
|
async function disconnect() {
|
|
if (characteristic) {
|
|
try {
|
|
await characteristic.stopNotifications();
|
|
log("> Notifications stopped");
|
|
characteristic.removeEventListener(
|
|
"characteristicvaluechanged",
|
|
handleNotifications
|
|
);
|
|
} catch (error) {
|
|
log("Argh! " + 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)
|
|
socket.emit("message", { 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;
|
|
}
|
|
|
|
btn.addEventListener("click", () =>
|
|
connect({
|
|
onChange: parseCSC,
|
|
}).catch(console.error)
|
|
);
|