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({ reconnection: true, reconnectionDelay: 1000, reconnectionAttempts: 20, forceNew: true, }); 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((err) => { swal("Oops", err, "error"); }) );