ReactとWeb Bluetooth APIでリアルタイム心拍数ダッシュボードを構築
ReactウェブアプリをBluetooth心拍数モニターに接続し、BLEデータの処理、フックでの接続状態の管理、Rechartsでライブデータを表示を学ぶステップバイステップのチュートリアル
このページの内容
Internet of Things(IoT)は、もはや未来の話ではなく、現在の現実です。Web開発者にとって、これはブラウザからハードウェアに直接連携する必要性が高まっていることを意味します。 fitness stats(フィットネスの統計情報)をリアルタイムで監視するWebアプリケーションを構築したり、スマートホームデバイスを制御したり、環境センサーと接続したりすることを想像してみてください。これらはすべて、Web Bluetooth APIのおかげで可能です。
このチュートリアルでは、実用的でエキサイティングなプロジェクトを構築します:Reactを使用したリアルタイム心拍数ダッシュボードです。Web Bluetooth APIを活用して nearby(近くにある)Bluetooth Low Energy(BLE)心拍数モニターを検出に接続します。その後、受信したデータを読み取り、解析し、ライブ更新されるチャートに表示します。
このプロジェクトは、WebベースのIoTの世界への完璧な entry point(入口)です。強力なブラウザAPIでの実践的な経験を積み、Reactアプリケーションでリアルタイムデータストリームを処理する方法を学びます。
前提条件:
- ReactおよびReact HooksSolid(しっかりした)理解。
- Node.jsとnpm(またはyarn)がインストール済み。
- Bluetooth Low Energy(BLE)心拍数モニターをお持ちでない場合は、nRF Connect for Mobileなどのエミュレーターアプリを使用できます。
- Web Bluetooth APIをサポートしているブラウザ(ChromeやOperaなど)。
開発者にとってなぜ重要か: Web Bluetooth APIは、Webアプリケーションにとって新たな領域を開きネイティブアプリなしで物理的な世界と直接やりとりできるようにします。health tech(ヘルステック)、フィットネスアプリ、無数の他のIoTドメインにとって、これはgame-changer(ゲームチェンジャー)です。
問題を理解する
Webアプリをハードウェアに接続することは複雑そうに聞こえるかもしれませんが、Web Bluetooth APIはそれを大幅に簡素化します。核心的な課題は、いくつかの主要な分野にあります:
- デバイスの検出と接続: セキュリティ上の理由から、ユーザーが接続を開始する必要があります。私たちのアプリでは、スキャンを開始し、デバイスを選択するための明確な方法を提供する必要があります。
- GATTプロトコル: BLEデバイスはGeneric Attribute Profile(GATT)を使用して通信します。必要なデータを取得するために、サービスと characteristic(特性)の構造を理解する必要があります。
- バイナリデータの処理: 心拍数データは単純な数字として送信されません。Bluetooth仕様に従って解析する必要があるバイナリ形式でエンコードされています。
- リアルタイム状態管理: 接続ステータスと受信データは非同期的で動的です。UIが常に同期していることを確認するために、Reactで効果的にこの状態を管理する必要があります。
私たちのアプローチは、クリーンで現代的なReact実装でこれらの課題に直接取り組みます。
前提条件
コーディングを開始する前に、開発環境を設定しましょう。
- 新しいReactアプリを作成:
bash npx create-react-app heart-rate-dashboard cd heart-rate-dashboard - チャートライブラリをインストール:
React用のシンプルで構成可能なチャートライブラリである
rechartsを使用します。bash npm install recharts - 心拍数モニターを準備: 心拍数モニターがオンで検出可能になっていることを確認してください。胸バンドを使用している場合は、放送を開始するために着用が必要な場合があります。
ステップ1:BLEデバイスへの接続
最初の手順は、心拍数モニターを検出して接続するためのメカニズムを作成することです。Web Bluetooth APIでは、ボタンのクリックなどのユーザーアクションが必要です。
何をするのか
接続、切断、デバイス状態の管理のためのロジックをカプセル化するカスタムフックuseBluetoothを作成します。これにより、コンポーネントロジックがクリーンになり、Bluetooth機能が再利用できるようになります。
実装
新しいファイル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;
次に、このフックをメインビルド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;
動作の仕組み
navigator.bluetooth.requestDevice:これはWeb Bluetooth APIへのエントリーポイントです。標準の'heart_rate'サービス(0x180D)を advertise(広告)するデバイスのみを表示するためのフィルタを提供します。これにより、ユーザーがデバイスを選択するためのブラウザプロンプトが開きます。device.gatt.connect():デバイスが選択されると、そのGATTサーバーに接続します。GATTサーバーはデバイスのすべてのサービスとcharacteristicを保持します。- 状態管理:
deviceとserverオブジェクトを保存するためにuseStateを使用します。これにより、接続ステータスが変更されたときにコンポーネントが再レンダリングされます。 - イベントリスナー: デバイスの切断時に状態をクリーンアップするために、
gattserverdisconnectedイベントリスナーを追加します。
一般的な落とし穴
- HTTPSが必要: Web Bluetooth APIはセキュアコンテキスト(HTTPSまたはlocalhost)でのみ動作します。
- ユーザー gesture(ジェスチャー):
requestDevice()は、クリックなどのユーザーアクションのイベントハンドラー内で呼び出す必要があります。ページ読み込み時にプログラム的にトリガーすることはできません。 - サポートなし:
navigator.bluetoothが未定義の場合、ブラウザはAPIをサポートしていません。コードにはこれに対するチェックが含まれています。
ステップ2:心拍数データの読み取りと解析
接続されているので、次はデータを取得します。「心拍数測定」characteristicを見つけ、その通知を購読する必要があります。
何をするのか
通知の開始と受信バイナリデータの解析を処理するために、useBluetoothフックを拡張します。心拍数値はデコードする必要がある特定の形式でエンコードされています。
実装
src/useBluetooth.jsに追加しましょう:
// src/useBluetooth.js (updated)
import { useState, useCallback } from 'react';
// ... (previous code for 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 functions)
return { device, server, error, connect, disconnect, startNotifications, heartRate };
};
export default useBluetooth;
接続されるとApp.jsでstartNotificationsを呼び出す必要もあります。
// src/App.js (updated)
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;
動作の仕組み
server.getPrimaryService('heart_rate'):標準の心拍数サービスを要求します。service.getCharacteristic('heart_rate_measurement'):そのサービス内で、心拍数測定を提供するcharacteristicを取得します(0x2A37)。characteristic.startNotifications():これにより、アプリのcharacteristicの値の変化を購読します。心拍数が変化するたびに、デバイスからデータがプッシュされます。characteristicvaluechangedイベント: データを受信するためにこのイベントを listen(リスン)します。値はDataViewオブジェクトで、これはバイナリデータを読み取るための低レベルのインターフェースです。parseHeartRate:この関数はDataViewオブジェクトをデコードします。心拍数のBluetooth GATT仕様に 따르면、最初の一バイトは「フラグ」フィールドです。このフラグの最深の1ビットは、心拍数値が8ビットか16ビットかを示します。このフラグをチェックして、適切なバイト数を読み取ります。
ステップ3:チャートでデータを視覚化する
BPMを表示するのは素晴らしいですが、チャードは時間経過に伴うデータのより詳細なビューを提供します。
何をするのか
新しい心拍数データを受信するたびにリアルタイムで更新される折れ線グラフを作成するために、rechartsを統合します。
実装
HeartRateChartコンポーネントを作成し、チャートのデータ履歴を管理するようにApp.jsを更新しましょう。
// 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;
次に、App.jsを更新して、心拍数データの履歴を保存し、新しいチャートコンポーネントに渡します。
// src/App.js (updated)
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;
動作の仕組み
- データ履歴: 心拍数読み取り値の配列を 저장(保存)するために、
App.jsで新しいstate変数dataを作成します。 - データ更新用の
useEffect:heartRateが変化するたびに実行されるuseEffectフックを使用します。タイムスタンプと心拍数で新しいデータエントリを作成し、data配列に追加します。 - スライディングウィンドウ: チャートが乱雑になるのを防ぐために、最後の30件のデータポイントのみを保持します。これにより、「スライディングウィンドウ」効果が作成されます。
rechartsコンポーネント: チャートを構築するためにrechartsライブラリのいくつかのコンポーネントを使用します。LineChartはコンテナで、Lineはデータシリーズを定義し、XAxis、YAxis、およびCartesianGridはチャートの構造を描画します。
すべてをまとめる
明確にするために、useBluetooth.jsフックの最終バージョンは以下のとおりです。
// 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;
これでApp.jsはよりシンプルになり、フックが内部的に通知の開始を処理します。
// 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;
セキュリティのベストプラクティス
- ユーザーの同意が重要: Web Bluetooth APIはセキュリティを考慮して設計されています。サイトはデバイスピッカーを通じてユーザーの明確な許可なしにはデバイスをスキャンできません。
- セキュアコンテキスト: 本番環境では常にHTTPS経由でアプリケーションを提供してください。ブラウザは man-in-the-middle attack(中間者攻撃)を防ぐためにこれを強制します。
- 限られたスコープ: 絶対に必要なサービスのみを要求してください。私たちの場合、
'heart_rate'のみを要求し、アプリがアクセスできるデータを最小限に抑えます。
結論
ReactとWeb Bluetooth APIを使用して、実用的なリアルタイム心拍数ダッシュボードを構築しました。BLEデバイスの検出と接続、カスタムフックでの接続状態の管理、デバイスからのバイナリデータの解析、ライブチャートでの視覚化方法を学びました。
このプロジェクトは可能性の表面を引っ掻いただけです。以下によってこれを拡張できます:
- 平均心拍数の計算。
localStorageを使用したセッションデータの保存と取得。- バッテリー残量など、他のBLEサービスのサポートの追加。
- Progressive Web App(PWA)としてのデプロイによるネイティブのような体験。
Web Bluetooth APIは、Webと物理的な世界の間のギャップを埋め、インタラクティブで有用なアプリケーションのための无尽的(エンドレスな)可能性を作り出します。 ✨
リソース
- 公式仕様: Web Bluetooth API on MDN
- BLE仕様: Bluetooth GATT Services
- Reactチャートライブラリ: Recharts Documentation
- サンプルコードリポジトリ: GitHub: Web Bluetooth Heart Rate Monitor Example
クイックリファレンス
| 側面 | 主要情報 |
|---|---|
| トピック | 開発 |
| 関連性 | 患者教育と理解促進 |
| 対応 | 医療提供者との相談 |
| 頻度 | ケアチーム推奨通り |
| エビデンス | 現在の臨床ガイドラインに基づく |
よくある質問
Q: これが自分に該当する怎么看(どのように)知ることができますか? A: 個人的な病歴と家族歴について医療提供者と相談し、個人のリスク因子を評価し、適切な次のステップを決定してください。
Q: 医師に何を開けばよいですか? A: 良い質問には以下が含まれます:「結果は何を意味していますか?」「オプションは何ですか?」「どのような生活習慣の変更が有帮助(有帮助)ですか?」「いつフォローアップすべきですか?」
Q: 帮助(帮助)になれる生活習慣の変更はありますか? A: ほとんどの健康状態は基本的な対策から benefit(恩恵)を受けます:バランスの取れた栄養、定期的な身体活動(週150分以上)、質の良い睡眠(7〜9時間)、ストレス管理,以及(および)禁煙。
医療免責事項
この記事は情報提供のみを目的としており、医学的 advice(アドバイス)を構成しません。健康、治療、またはケアに関する決定を行う前は、常に資格のある医療提供者と相談してください。個人の状況により異なり、一个人に適切なものが別の人に必ずしも appropriate(適切)とは限りません。
Medical emergency(医療上の緊急事態)に遭遇した場合は、直ちに119番(または地元の緊急電話番号)に电话してください。