// 1️⃣ Request a HR‑monitor 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 heart‑rate 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 = 8‑bit, 1 = 16‑bit 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);