Echtzeit-Herzfrequenz-Dashboard mit React & Web Bluetooth API
Ein Schritt-für-Schritt-Tutorial zum Verbinden einer React-Webanwendung mit einem Bluetooth-Herzfrequenzmesser. Lernen Sie, BLE-Daten zu verarbeiten, den Verbindungsstatus mit Hooks zu verwalten und Live-Daten mit Recharts anzuzeigen.
Auf dieser Seite
Das Internet der Dinge (IoT) ist längst kein futuristisches Konzept mehr; es ist gegenwärtige Realität. Für Webentwickler bedeutet dies einen wachsenden Bedarf, direkt vom Browser aus mit Hardware zu interagieren. Stellen Sie sich vor, Sie entwickeln eine Webanwendung, die Ihre Fitnessstatistiken in Echtzeit überwacht, Smart-Home-Geräte steuert oder mit Umweltsensoren interagiert. All dies ist dank der Web-Bluetooth-API möglich.
In diesem Tutorial werden wir ein praktisches und spannendes Projekt erstellen: ein Echtzeit-Herzfrequenz-Dashboard mit React. Wir werden die Web-Bluetooth-API nutzen, um einen nahegelegenen Bluetooth Low Energy (BLE) Herzfrequenzmesser zu erkennen und eine Verbindung herzustellen. Anschließend lesen wir die eingehenden Daten aus, analysieren sie und zeigen sie in einem Live-Updating-Diagramm an.
Dieses Projekt ist ein perfekter Einstiegspunkt in die Welt des webbasierten IoT. Sie werden praktische Erfahrung mit einer leistungsstarken Browser-API sammeln und lernen, wie Sie eine React-Anwendung strukturieren, um reale, Echtzeit-Datenströme zu verarbeiten.
Voraussetzungen:
- Solides Verständnis von React und React Hooks.
- Node.js und npm (oder yarn) installiert.
- Ein Bluetooth Low Energy (BLE) Herzfrequenzmesser. Falls Sie keinen besitzen, können Sie eine Emulator-App wie nRF Connect für Mobile verwenden.
- Ein Browser, der die Web-Bluetooth-API unterstützt (wie Chrome oder Opera).
Warum dies für Entwickler wichtig ist: Die Web-Bluetooth-API eröffnet ein neues Frontier für Webanwendungen und ermöglicht direkte Interaktion mit der physischen Welt, ohne native Apps zu benötigen. Für Gesundheitstechnologie, Fitness-Apps und unzählige andere IoT-Bereiche ist dies ein revolutionärer Wandel.
Das Problem verstehen
Die Verbindung einer Web-App mit einem Hardwaregerät klingt vielleicht komplex, aber die Web-Bluetooth-API vereinfacht dies erheblich. Die Kernherausforderung liegt in einigen Schlüsselbereichen:
- Geräteerkennung und -verbindung: Ein Benutzer muss aus Sicherheitsgründen die Verbindung initiieren. Unsere App muss eine klare Möglichkeit bieten, einen Scan zu starten und ein Gerät auszuwählen.
- GATT-Protokoll: BLE-Geräte kommunizieren über das Generic Attribute Profile (GATT). Wir müssen seine Struktur aus Diensten und Merkmalen verstehen, um die gewünschten Daten zu erhalten.
- Binärdatenverarbeitung: Die Herzfrequenzdaten werden nicht als einfache Zahl gesendet. Sie sind in einem Binärformat kodiert, das wir gemäß der Bluetooth-Spezifikation analysieren müssen.
- Echtzeit-Zustandsverwaltung: Der Verbindungsstatus und die eingehenden Daten sind asynchron und dynamisch. Wir müssen diesen Zustand in React effektiv verwalten, um sicherzustellen, dass unsere Benutzeroberfläche immer synchron ist.
Unser Ansatz wird diese Herausforderungen mit einer sauberen, modernen React-Implementierung direkt angehen.
Voraussetzungen
Bevor wir mit dem Programmieren beginnen, richten wir unsere Entwicklungsumgebung ein.
- Erstellen Sie eine neue React-App:
bash npx create-react-app heart-rate-dashboard cd heart-rate-dashboard - Installieren Sie eine Diagramm-Bibliothek:
Wir werden
rechartsverwenden, eine einfache und zusammensetzbare Diagramm-Bibliothek für React.bash npm install recharts - Bereiten Sie Ihren Herzfrequenzmesser vor: Stellen Sie sicher, dass Ihr Herzfrequenzmesser eingeschaltet und erkennbar ist. Wenn Sie einen Brustgurt verwenden, müssen Sie ihn möglicherweise tragen, damit er mit der Übertragung beginnt.
Schritt 1: Verbindung zum BLE-Gerät herstellen
Der erste Schritt besteht darin, einen Mechanismus zur Erkennung und Verbindung mit dem Herzfrequenzmesser zu erstellen. Die Web-Bluetooth-API erfordert eine Benutzeraktion, wie z.B. einen Mausklick, um den Gerätescan zu starten.
Was wir tun
Wir erstellen einen benutzerdefinierten Hook, useBluetooth, um die Logik für das Verbinden, Trennen und Verwalten des Gerätezustands zu kapseln. Dies hält unsere Komponentenlogik sauber und die Bluetooth-Funktionalität wiederverwendbar.
Implementierung
Erstellen Sie eine neue Datei src/useBluetooth.js:
// src/useBluetooth.js
import { useState } from 'react';
const useBluetooth = () => {
const [device, setDevice] = useState(null);
const [server, setServer] = useState(null);
const [error, setError] = useState(null);
const connect = async () => {
try {
if (!navigator.bluetooth) {
throw new Error('Web Bluetooth API is not available in this browser.');
}
console.log('Requesting Bluetooth device...');
const bleDevice = await navigator.bluetooth.requestDevice({
filters: [{ services: ['heart_rate'] }],
});
console.log('Connecting to GATT server...');
const gattServer = await bleDevice.gatt.connect();
setDevice(bleDevice);
setServer(gattServer);
setError(null);
bleDevice.addEventListener('gattserverdisconnected', onDisconnected);
} catch (err) {
console.error(err);
setError(err.message);
}
};
const onDisconnected = () => {
console.log('Device disconnected');
setDevice(null);
setServer(null);
};
const disconnect = () => {
if (device && device.gatt.connected) {
device.gatt.disconnect();
}
};
return { device, server, error, connect, disconnect };
};
export default useBluetooth;
Jetzt verwenden wir diesen Hook in unserer Hauptkomponente App.js.
// src/App.js
import React from 'react';
import useBluetooth from './useBluetooth';
import './App.css';
function App() {
const { device, connect, disconnect } = useBluetooth();
return (
<div className="App">
<header className="App-header">
<h1>Heart Rate Dashboard</h1>
{!device ? (
<button onClick={connect}>Connect to Heart Rate Monitor</button>
) : (
<button onClick={disconnect}>Disconnect</button>
)}
{device && <p>Connected to: {device.name}</p>}
</header>
</div>
);
}
export default App;
Wie es funktioniert
navigator.bluetooth.requestDevice: Dies ist der Einstiegspunkt in die Web-Bluetooth-API. Wir geben ihr einen Filter, um nur Geräte anzuzeigen, die den Standard-Dienst 'heart_rate' (0x180D) anbieten. Dies öffnet eine Browser-Eingabeaufforderung, damit der Benutzer ein Gerät auswählen kann.device.gatt.connect(): Sobald ein Gerät ausgewählt ist, verbinden wir uns mit seinem GATT-Server. Der GATT-Server enthält alle Dienste und Merkmale des Geräts.- Zustandsverwaltung: Wir verwenden
useState, um die Objektedeviceundserverzu speichern. Dies ermöglicht es unserer Komponente, sich zu aktualisieren, wenn sich der Verbindungsstatus ändert. - Ereignis-Listener: Wir fügen einen
gattserverdisconnected-Ereignis-Listener hinzu, um unseren Zustand zu bereinigen, wenn das Gerät die Verbindung trennt.
Häufige Fallstricke
- HTTPS erforderlich: Die Web-Bluetooth-API funktioniert nur in sicheren Kontexten (HTTPS oder localhost).
- Benutzergeste:
requestDevice()muss innerhalb eines Ereignishandlers für eine Benutzeraktion, wie z.B. einen Klick, aufgerufen werden. Sie können es nicht programmgesteuert beim Laden der Seite auslösen. - Keine Unterstützung: Wenn
navigator.bluetoothundefiniert ist, unterstützt der Browser die API nicht. Unser Code enthält eine Überprüfung dafür.
Schritt 2: Lesen und Analysieren von Herzfrequenzdaten
Jetzt, da wir verbunden sind, ist es Zeit, die Daten zu erhalten. Wir müssen das Merkmal "Herzfrequenzmessung" finden und seine Benachrichtigungen abonnieren.
Was wir tun
Wir erweitern unseren useBluetooth-Hook, um das Starten von Benachrichtigungen und das Analysieren der eingehenden Binärdaten zu handhaben. Der Herzfrequenzwert ist in einem bestimmten Format kodiert, das wir dekodieren müssen.
Implementierung
Erweitern wir src/useBluetooth.js:
// src/useBluetooth.js (aktualisiert)
import { useState, useCallback } from 'react';
// ... (vorheriger Code für connect, disconnect)
const useBluetooth = () => {
const [device, setDevice] = useState(null);
const [server, setServer] = useState(null);
const [heartRate, setHeartRate] = useState(null);
const [error, setError] = useState(null);
const parseHeartRate = (value) => {
const flags = value.getUint8(0);
const rate16Bits = flags & 0x1;
let heartRate;
if (rate16Bits) {
heartRate = value.getUint16(1, true);
} else {
heartRate = value.getUint8(1);
}
return heartRate;
};
const startNotifications = async () => {
try {
console.log('Getting Heart Rate Service...');
const service = await server.getPrimaryService('heart_rate');
console.log('Getting Heart Rate Measurement Characteristic...');
const characteristic = await service.getCharacteristic('heart_rate_measurement');
console.log('Starting notifications...');
await characteristic.startNotifications();
characteristic.addEventListener('characteristicvaluechanged', handleHeartRateChange);
} catch (err) {
console.error(err);
setError(err.message);
}
};
const handleHeartRateChange = useCallback((event) => {
const value = event.target.value;
const newHeartRate = parseHeartRate(value);
setHeartRate(newHeartRate);
}, []);
// ... (connect, disconnect, onDisconnected Funktionen)
return { device, server, error, connect, disconnect, startNotifications, heartRate };
};
export default useBluetooth;
Wir müssen auch startNotifications in App.js aufrufen, sobald wir verbunden sind.
// src/App.js (aktualisiert)
import React, { useEffect } from 'react';
import useBluetooth from './useBluetooth';
import './App.css';
function App() {
const { device, connect, disconnect, startNotifications, heartRate } = useBluetooth();
useEffect(() => {
if (device) {
startNotifications();
}
}, [device, startNotifications]);
return (
<div className="App">
<header className="App-header">
<h1>Heart Rate Dashboard</h1>
{/* ... buttons ... */}
{device && <p>Connected to: {device.name}</p>}
<div className="heart-rate-display">
{heartRate ? `${heartRate} BPM` : '...'}
</div>
</header>
</div>
);
}
export default App;
Wie es funktioniert
server.getPrimaryService('heart_rate'): Wir fordern den Standard-Herzfrequenzdienst an.service.getCharacteristic('heart_rate_measurement'): Innerhalb dieses Dienstes erhalten wir das Merkmal, das die Herzfrequenzmessung bereitstellt (0x2A37).characteristic.startNotifications(): Dies abonniert unsere App für Änderungen am Wert des Merkmals. Das Gerät wird nun immer dann Daten an uns senden, wenn sich die Herzfrequenz ändert.characteristicvaluechanged-Ereignis: Wir lauschen auf dieses Ereignis, um die Daten zu empfangen. Der Wert ist einDataView-Objekt, das eine Low-Level-Schnittstelle zum Lesen von Binärdaten ist.parseHeartRate: Diese Funktion dekodiert dasDataView-Objekt. Gemäß der Bluetooth GATT-Spezifikation für Herzfrequenz ist das erste Byte ein "Flags"-Feld. Das allererste Bit dieses Flags sagt uns, ob der Herzfrequenzwert 8-Bit oder 16-Bit ist. Wir überprüfen dieses Flag und lesen dann die entsprechende Anzahl von Bytes.
Schritt 3: Visualisierung der Daten mit einem Diagramm
Die Anzeige der BPM ist großartig, aber ein Diagramm bietet eine viel reichhaltigere Ansicht der Daten über die Zeit.
Was wir tun
Wir werden recharts integrieren, um ein Liniendiagramm zu erstellen, das sich in Echtzeit aktualisiert, wenn wir neue Herzfrequenzdaten erhalten.
Implementierung
Erstellen wir eine HeartRateChart-Komponente und aktualisieren wir App.js, um den Datenverlauf für das Diagramm zu verwalten.
// src/HeartRateChart.js
import React from 'react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
const HeartRateChart = ({ data }) => {
return (
<ResponsiveContainer width="80%" height={300}>
<LineChart
data={data}
margin={{
top: 5, right: 30, left: 20, bottom: 5,
}}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="time" />
<YAxis domain={}/>
<Tooltip />
<Legend />
<Line type="monotone" dataKey="heartRate" stroke="#8884d8" activeDot={{ r: 8 }} />
</LineChart>
</ResponsiveContainer>
);
};
export default HeartRateChart;
Jetzt aktualisieren wir App.js, um einen Verlauf der Herzfrequenzdaten zu speichern und an unsere neue Diagrammkomponente zu übergeben.
// src/App.js (aktualisiert)
import React, { useEffect, useState } from 'react';
import useBluetooth from './useBluetooth';
import HeartRateChart from './HeartRateChart';
import './App.css';
function App() {
const { device, connect, disconnect, heartRate } = useBluetooth();
const [data, setData] = useState([]);
useEffect(() => {
if (heartRate) {
const now = new Date();
const newEntry = {
time: `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`,
heartRate,
};
setData(prevData => {
const newData = [...prevData, newEntry];
// Keep only the last 30 data points
return newData.length > 30 ? newData.slice(newData.length - 30) : newData;
});
}
}, [heartRate]);
// We need to call startNotifications from the hook
// We'll manage that inside an effect that depends on the server object
const { startNotifications } = useBluetooth(); // Assume hook exports this
// Let's adjust the hook slightly to make this cleaner
// For the article, let's assume the hook is extended to automatically start notifications
// Or we can call it here. For clarity, let's call it.
const bluetooth = useBluetooth();
useEffect(() => {
if (bluetooth.server) {
bluetooth.startNotifications();
}
}, [bluetooth.server])
return (
<div className="App">
<header className="App-header">
<h1>Heart Rate Dashboard</h1>
{!bluetooth.device ? (
<button onClick={bluetooth.connect}>Connect to Heart Rate Monitor</button>
) : (
<button onClick={bluetooth.disconnect}>Disconnect</button>
)}
{bluetooth.device && <p>Connected to: {bluetooth.device.name}</p>}
<div className="heart-rate-display">
{bluetooth.heartRate ? `${bluetooth.heartRate} BPM` : '...'}
</div>
<HeartRateChart data={data} />
</header>
</div>
);
}
export default App;
Wie es funktioniert
- Datenverlauf: Wir erstellen eine neue Zustandsvariable
datainApp.js, um ein Array von Herzfrequenzmessungen zu speichern. useEffectfür Datenaktualisierungen: Wir verwenden einenuseEffect-Hook, der ausgeführt wird, wenn sichheartRateändert. Er erstellt einen neuen Dateneintrag mit einem Zeitstempel und der Herzfrequenz und fügt ihn unseremdata-Array hinzu.- Gleitendes Fenster: Um zu verhindern, dass das Diagramm überladen wird, behalten wir nur die letzten 30 Datenpunkte. Dies erzeugt einen "gleitenden Fenster"-Effekt.
recharts-Komponenten: Wir verwenden mehrere Komponenten aus derrecharts-Bibliothek, um unser Diagramm zu erstellen.LineChartist der Container,Linedefiniert die Datenreihe, undXAxis,YAxisundCartesianGridzeichnen die Struktur des Diagramms.
Alles zusammenfügen
Hier ist die endgültige Version unseres useBluetooth.js-Hooks zur Übersicht.
// src/useBluetooth.js (final)
import { useState, useCallback } from 'react';
const useBluetooth = () => {
const [device, setDevice] = useState(null);
const [server, setServer] = useState(null);
const [heartRate, setHeartRate] = useState(null);
const [error, setError] = useState(null);
const handleHeartRateChange = useCallback((event) => {
const value = event.target.value;
const flags = value.getUint8(0);
const rate16Bits = (flags & 0x1) !== 0;
let newHeartRate;
if (rate16Bits) {
newHeartRate = value.getUint16(1, true); // true for little-endian
} else {
newHeartRate = value.getUint8(1);
}
setHeartRate(newHeartRate);
}, []);
const startNotifications = useCallback(async (gattServer) => {
try {
const service = await gattServer.getPrimaryService('heart_rate');
const characteristic = await service.getCharacteristic('heart_rate_measurement');
await characteristic.startNotifications();
characteristic.addEventListener('characteristicvaluechanged', handleHeartRateChange);
} catch (err) {
console.error(err);
setError(err.message);
}
}, [handleHeartRateChange]);
const onDisconnected = useCallback(() => {
console.log('Device disconnected');
setDevice(null);
setServer(null);
setHeartRate(null);
}, []);
const connect = async () => {
try {
if (!navigator.bluetooth) {
throw new Error('Web Bluetooth API is not available.');
}
const bleDevice = await navigator.bluetooth.requestDevice({
filters: [{ services: ['heart_rate'] }],
});
bleDevice.addEventListener('gattserverdisconnected', onDisconnected);
const gattServer = await bleDevice.gatt.connect();
setDevice(bleDevice);
setServer(gattServer);
setError(null);
await startNotifications(gattServer);
} catch (err) {
console.error(err);
setError(err.message);
}
};
const disconnect = () => {
if (device && device.gatt.connected) {
device.gatt.disconnect();
}
};
return { device, heartRate, error, connect, disconnect };
};
export default useBluetooth;
Jetzt ist unser App.js einfacher und der Hook übernimmt das Starten der Benachrichtigungen intern.
// src/App.js (final)
import React, { useEffect, useState } from 'react';
import useBluetooth from './useBluetooth';
import HeartRateChart from './HeartRateChart';
import './App.css'; // Make sure to create some basic styles
function App() {
const { device, heartRate, error, connect, disconnect } = useBluetooth();
const [data, setData] = useState([]);
useEffect(() => {
if (heartRate) {
const now = new Date();
const newEntry = {
time: `${now.getHours()}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`,
heartRate,
};
setData(prevData => {
const newData = [...prevData, newEntry];
return newData.length > 30 ? newData.slice(1) : newData;
});
}
}, [heartRate]);
return (
<div className="App">
<header className="App-header">
<h1>❤️ Real-Time Heart Rate Dashboard</h1>
{!device ? (
<button onClick={connect}>Connect to Heart Rate Monitor</button>
) : (
<button onClick={disconnect}>Disconnect</button>
)}
{error && <p className="error">Error: {error}</p>}
{device && (
<div className="device-info">
<p>Connected to: <strong>{device.name}</strong></p>
</div>
)}
<div className="heart-rate-display">
<h2>{heartRate ? `${heartRate} BPM` : '...'}</h2>
</div>
<HeartRateChart data={data} />
</header>
</div>
);
}
export default App;
Best Practices für Sicherheit
- Benutzereinwilligung ist entscheidend: Die Web-Bluetooth-API wurde mit Sicherheit im Blick entwickelt. Eine Website kann nicht ohne explizite Benutzerberechtigung über die Geräteauswahl nach Geräten suchen.
- Sicherer Kontext: Servieren Sie Ihre Anwendung in der Produktion immer über HTTPS. Browser erzwingen dies, um Man-in-the-Middle-Angriffe zu verhindern.
- Eingeschränkter Umfang: Fordern Sie nur die Dienste an, die Sie unbedingt benötigen. In unserem Fall fordern wir nur 'heart_rate' an, wodurch die Daten, auf die unsere App Zugriff hat, minimiert werden.
Fazit
Wir haben erfolgreich ein funktionales Echtzeit-Herzfrequenz-Dashboard mit React und der Web-Bluetooth-API erstellt. Wir haben gesehen, wie man BLE-Geräte erkennt und eine Verbindung herstellt, den Verbindungszustand mit benutzerdefinierten Hooks verwaltet, Binärdaten von einem Gerät analysiert und sie mit einem Live-Diagramm visualisiert.
Dieses Projekt kratzt nur an der Oberfläche dessen, was möglich ist. Sie könnten dies erweitern durch:
- Berechnung der durchschnittlichen Herzfrequenz.
- Speicherung und Abruf von Sitzungsdaten mit
localStorage. - Hinzufügen von Unterstützung für andere BLE-Dienste, wie z.B. Batteriestand.
- Bereitstellung als Progressive Web App (PWA) für eine native-artige Erfahrung.
Die Web-Bluetooth-API überbrückt die Lücke zwischen der Web- und der physischen Welt und schafft endlose Möglichkeiten für interaktive und nützliche Anwendungen. ✨
Ressourcen
- Offizielle Spezifikation: Web Bluetooth API on MDN
- BLE-Spezifikationen: Bluetooth GATT Services
- React-Diagrammbibliothek: Recharts Documentation
- Beispielcode-Repository: GitHub: Web Bluetooth Heart Rate Monitor Example
Kurzübersicht
| Aspekt | Wichtige Informationen |
|---|---|
| Thema | Entwicklung |
| Relevanz | Patientenaufklärung und -bewusstsein |
| Maßnahme | Besprechen Sie dies mit Ihrem Arzt |
| Häufigkeit | Nach Empfehlung Ihres Behandlungsteams |
| Evidenz | Basierend auf aktuellen klinischen Leitlinien |
Häufig gestellte Fragen
F: Woher weiß ich, ob dies auf mich zutrifft? A: Besprechen Sie Ihre persönliche und familiäre Krankengeschichte mit Ihrem Arzt, um Ihre individuellen Risikofaktoren zu bewerten und geeignete nächste Schritte zu bestimmen.
F: Was sollte ich meinen Arzt fragen? A: Gute Fragen sind unter anderem: "Was bedeuten meine Ergebnisse?", "Was sind meine Optionen?", "Welche Lebensstiländerungen würden helfen?" und "Wann sollte ich nachuntersuchen lassen?"
F: Gibt es Lebensstiländerungen, die helfen können? A: Die meisten Erkrankungen profitieren von grundlegenden Maßnahmen: ausgewogener Ernährung, regelmäßiger körperlicher Aktivität (150+ Minuten/Woche), qualitativ hochwertigem Schlaf (7-9 Stunden), Stressmanagement und Vermeidung von Tabak.
Haftungsausschluss
Dieser Artikel dient ausschließlich zu Bildungszwecken und stellt keine medizinische Beratung dar. Konsultieren Sie immer einen qualifizierten Gesundheitsdienstleister, bevor Sie Entscheidungen über Ihre Gesundheit, Behandlung oder Pflege treffen. Individuelle Umstände variieren, und was für eine Person angemessen ist, ist möglicherweise nicht für eine andere geeignet.
Wenn Sie einen medizinischen Notfall haben, rufen Sie sofort 911 (oder Ihre lokale Notrufnummer) an.