You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

87 lines
2.9 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 1⃣ Request a HRmonitor device
const hrOptions = {
// Show only devices that advertise the Heart Rate service (0x180D)
filters: [{ services: [0x180d] }],
// After the user picks a device we also want to read the Device
// Information service (optional, no extra prompt)
optionalServices: [0x180a], // Device Information
};
navigator.bluetooth
.requestDevice(hrOptions)
.then((device) => {
console.log("✅ Selected:", device.name);
// 2⃣ Connect to the GATT server
return device.gatt.connect();
})
.then((server) => {
// 3⃣ Get the Heart Rate service
return server.getPrimaryService(0x180d);
})
.then((hrService) => {
// 4⃣ Get the Heart Rate Measurement characteristic (0x2A37)
return hrService.getCharacteristic(0x2a37);
})
.then((hrChar) => {
// 5⃣ Enable notifications the monitor will push new readings
return hrChar.startNotifications().then(() => hrChar);
})
.then((hrChar) => {
console.log("🔔 Listening for heartrate measurements...");
hrChar.addEventListener("characteristicvaluechanged", (ev) => {
const value = ev.target.value; // DataView
const heartRate = parseHeartRate(value);
console.log("❤️ Heart Rate:", heartRate, "bpm");
});
})
.catch((err) => console.error("❌ Bluetooth error:", err));
/**
* Parse the Heart Rate Measurement characteristic (Bluetooth SIG spec).
* Returns the BPM as a Number.
*/
function parseHeartRate(dataView) {
// Flags are in the first byte
const flags = dataView.getUint8(0);
const hrFormatUint16 = flags & 0x01; // 0 = 8bit, 1 = 16bit
if (hrFormatUint16) {
return dataView.getUint16(1, /*littleEndian=*/ true);
}
return dataView.getUint8(1);
}
// After the HR connection succeeds, you can also fetch the Device Info service:
navigator.bluetooth
.requestDevice(hrOptions) // reuse the same options
.then((d) => d.gatt.connect())
.then((server) =>
Promise.all([
server.getPrimaryService(0x180a), // Device Information
server.getPrimaryService(0x180d), // Heart Rate
]),
)
.then(([infoService, hrService]) =>
Promise.all([
// Manufacturer Name (0x2A29)
infoService.getCharacteristic(0x2a29).then((c) => c.readValue()),
// Model Number (0x2A24)
infoService.getCharacteristic(0x2a24).then((c) => c.readValue()),
// Heart Rate Measurement (as before)
hrService
.getCharacteristic(0x2a37)
.then((c) => c.startNotifications().then(() => c)),
]),
)
.then(([manufVal, modelVal, hrChar]) => {
const dec = new TextDecoder("utf-8");
console.log("🏭 Manufacturer:", dec.decode(manufVal));
console.log("📦 Model:", dec.decode(modelVal));
hrChar.addEventListener("characteristicvaluechanged", (ev) =>
console.log("❤️", parseHeartRate(ev.target.value), "bpm"),
);
})
.catch(console.error);