React Native BLE PLXでBLEデバイスを接続する手順と実装方法

Published on: | Last updated:

最近、React NativeでBLEを触る案件があって、まあまあハマったというか、思い出し作業が大変だったので、自分用のメモも兼ねて思考の過程を書き出してみる。正直、BLEって「Bluetoothでしょ?」くらいに思ってると、思ったよりお作法が多くて戸惑うんだよね。

使うのは、まあ定番の `react-native-ble-plx`。これが一番情報多いし、安定してる気がする。でも、これ使ってもハマる時はハマる。特にパーミッション周りとか、AndroidとiOSでの微妙な違いとか。

TL;DR

先に結論から言うと、`react-native-ble-plx`を使ったBLE通信のキモは、「正しい順序で、非同期処理を待ってあげること」「OSからのお許し(パーミッション)をちゃんと貰うこと」。この2つに尽きる。コードをコピペするだけだと、だいたいどっちかでコケる。

そもそもBLE通信ってどんな流れ?

コードに入る前に、頭の中を整理したい。BLEデバイスとのやり取りって、人間同士のコミュニケーションに似てるなと個人的には思ってる。

  1. スキャン (辺りを見回す): まず、周りに誰かいるかなーって探す感じ。これが`scan()`。
  2. 接続 (挨拶する): 「あ、〇〇さんだ」って見つけたら、「こんにちは」って声をかける。これが`connect()`。
  3. サービス発見 (何の話ができるか聞く): 挨拶したら、「今日はどんなお話ができますか?」って相手の持ってる話題(サービス)と、その詳細(キャラクタリスティック)を聞き出す。これが`discoverAllServicesAndCharacteristics()`。これ、意外と忘れがちだけど超重要。
  4. 通信 (会話する): 話題がわかったら、実際に「このデータくださいな (`read`)」とか、「これ書いといて (`write`)」とか、「何かあったら教えて (`notify`)」ってやり取りが始まる。

この流れを無視して、いきなり接続しようとしたり、いきなりデータを読み書きしようとしても、まあ無理だよね、っていう。BLEライブラリは、この手続きを簡単にしてくれる魔法、みたいなもの。

BLE通信の基本的な流れのイメージ
BLE通信の基本的な流れのイメージ

じゃ、実際にどうやるの? - セットアップと一番めんどくさい権限の話

ここからが本番。まずはプロジェクトにライブラリを入れるところから。


# yarnでもnpmでもお好きな方で
yarn add react-native-ble-plx
# podも忘れずに
cd ios && pod install

インストールはまあ、いい。問題はここから。パーミッション。これが最初の関門。

正直、ここが一番ややこしい。特にAndroid。バージョンによって要求されるものが変わるから、もう大変。

Androidのパーミッション地獄

Androidは本当に…うん。まず、`AndroidManifest.xml`に色々書かないといけない。















で、`AndroidManifest.xml`に書くだけじゃダメで、アプリ実行時にユーザーに許可を求めるコードも必要になる。`PermissionsAndroid`を使って、`BLUETOOTH_SCAN`と`BLUETOOTH_CONNECT`、そして`ACCESS_FINE_LOCATION`をリクエストする感じ。この辺のコードは長くなるから省略するけど、まあ定型文みたいなもの。

あ、ちなみに、`react-native-ble-plx`の公式ドキュメント(これはグローバルな情報源だよね)だけ見てるとハマることがあって。日本の開発者ブログとかを漁ってると、「国内メーカーの特定のAndroid機種だと、位置情報サービス自体がOFFになってるとスキャンが全く動かない」みたいな知見がゴロゴロ出てくる。だから、パーミッションを許可してもらうだけじゃなく、端末のBluetoothと位置情報がONになってるかもチェックする処理を入れるのが安全。

Androidのパーミッション設定、いつ見てもややこしい…
Androidのパーミッション設定、いつ見てもややこしい…

iOSは比較的シンプル

iOSはAndroidに比べれば、まだ分かりやすい。`Info.plist`に「なんでBluetooth使いたいの?」っていう理由を書いておくだけ。


NSBluetoothAlwaysUsageDescription
近くのBLEデバイスと接続して、データをやり取りするために利用します。

これだけ。あとはライブラリが良しなにやってくれる。平和だ…。

コードで追うBLE通信のライフサイクル

パーミッションという最大の山を越えたら、あとはコードで流れを組み立てていくだけ。ここでもいくつか「お作法」がある。

1. Managerインスタンスは1つだけ!

これが最初の「お作法」。`BleManager`のインスタンスは、アプリ全体で一つだけ作るのが原則。よくある間違いが、Reactコンポーネントが再レンダリングされるたびに`new BleManager()`しちゃうやつ。これをやるとマジで挙動が不安定になる。


import { BleManager } from 'react-native-ble-plx';

// アプリのどこか、グローバルな場所で一度だけ生成する
export const manager = new BleManager();

コンポーネントのstateとかで管理するんじゃなくて、exportしちゃうのが一番手軽で確実かもね。

2. Bluetoothの状態を監視する

アプリを起動しても、すぐにBluetoothが使えるわけじゃない。OSが準備してくれるのを待つ必要がある。そのために`onStateChange`を監視する。


useEffect(() => {
  // Bluetoothの状態が変わるたびに呼ばれるリスナー
  const subscription = manager.onStateChange((state) => {
    if (state === 'PoweredOn') {
      // よっしゃ、準備OK!スキャン開始できる
      scanAndConnect(); // ←自分で定義したスキャン開始関数
      subscription.remove(); // 目的達成したらすぐ解除するのがお作法
    }
  }, true);

  return () => {
    subscription.remove(); // クリーンアップ
  };
}, [manager]);

`state`が`PoweredOn`になったら、そこがスタート地点。

3. デバイスをスキャンして見つける

準備ができたら、いよいよスキャン。`startDeviceScan`を呼ぶ。


const scanAndConnect = () => {
  manager.startDeviceScan(null, null, (error, device) => {
    if (error) {
      // エラー処理
      console.error(error);
      manager.stopDeviceScan(); // エラーでもスキャンは止める
      return;
    }

    // 見つかったデバイスの名前とかでフィルタリング
    if (device.name === 'MySensor' || device.id === 'XX:XX:XX:XX:XX:XX') {
      
      // 見つけたら、すぐにスキャンを停止するのが鉄則!
      // これをしないと、バッテリーを無駄に消費し続ける
      manager.stopDeviceScan();

      // 接続処理へ進む
      connectToDevice(device);
    }
  });
};

ここでのポイントは、目的のデバイスを見つけたらすぐに`stopDeviceScan()`を呼ぶこと。スキャンは結構バッテリーを食う処理だから、ダラダラ続けないのが大事。

4. 接続して、サービスとキャラクタリスティックを発見する

デバイスが見つかったら、いよいよ接続。そして「何ができるか」を聞き出すフェーズ。


const connectToDevice = async (device) => {
  try {
    const connectedDevice = await device.connect();
    console.log(`Connected to ${connectedDevice.name}`);

    // 接続できたら、次はサービスとキャラクタリスティックを発見する
    // これをやらないと、データの読み書きができない
    await connectedDevice.discoverAllServicesAndCharacteristics();
    console.log('Discovery complete');

    // これでようやく通信の準備が整った
    // readとかwriteとかの処理をここから呼び出す
    
  } catch (error) {
    console.error('Connection/Discovery failed', error);
  }
};

`discoverAllServicesAndCharacteristics()`、名前が長いけど、これをやらないと次のステップに進めない。超重要。

ServiceとCharacteristicの関係って、こんな感じ
ServiceとCharacteristicの関係って、こんな感じ

5. データの読み書き (Read / Write / Notify)

やっと会話の時間。データのやり取りには、主に3つの方法がある。

  • Read: こっちから「データちょうだい」って能動的にもらいにいく。
  • Write: こっちから「このデータ書き込んどいて」って送る。
  • Notify/Indicate (Monitor): 「何か変化があったら勝手に送ってきて」ってお願いしておく。

どれを使うにしても、`serviceUUID`と`characteristicUUID`っていう住所みたいなものが必要になる。これは、通信したいデバイスの仕様書とかを見て確認するしかない。

特に`Write`には2種類あって、使い分けが大事。

書き込み方法 特徴 使いどころ
writeCharacteristicWithResponse 確実だけど、ちょっと待たされる感じ。「書けたよー」って返事を待つから、通信に一往復半ぶんの時間がかかる。 ファームウェアのアップデートとか、絶対に失敗したくない重要なデータの書き込み。
writeCharacteristicWithoutResponse 送りっぱなし。速いけど、届いたかは神のみぞ知る。UDPみたいなもんかな。 LEDの色をリアルタイムに変えるとか、多少取りこぼしても問題ない連続的なデータ送信。

あと、送受信するデータは`base64`形式にエンコード/デコードする必要があるのを忘れずに。ライブラリがそういう仕様なので。`base64-js`みたいなライブラリを使うと楽。


import { Base64 } from 'js-base64';

// 書き込むとき
const stringValue = 'hello';
const base64Value = Base64.encode(stringValue);
device.writeCharacteristicWithResponseForDevice(
  // ...
  base64Value
);

// 読み取ったとき
const characteristic = await device.readCharacteristicForDevice(...);
const receivedBase64 = characteristic.value;
const receivedString = Base64.decode(receivedBase64);
console.log(receivedString); // 'hello'

6. 後片付け (Disconnect)

通信が終わったら、ちゃんと「さようなら」をする。`cancelDeviceConnection`を呼んで接続を切る。これをやらないと、デバイス側が接続中だと思い続けて、他のアプリから接続できなくなったりする。

そして、アプリが完全に終了するときとか、もうBLE機能が不要になったら、`manager.destroy()`を呼んでリソースを解放する。これで一連の流れは終わり。

よくあるハマりどころまとめ

最後に、自分がハマった、あるいはよく見かける失敗例をまとめておく。未来の自分のための備忘録。

  • BleManagerを再レンダリングのたびにnewしてる: さっきも言ったけど、マジで挙動が不安定になる。やめよう。
  • スキャンを止め忘れる: バッテリーが溶ける。見つけたら即`stopDeviceScan()`。
  • `discoverAllServicesAndCharacteristics`を呼び忘れる: 接続はできてるのに、Read/Writeが全部エラーになる。だいたいこいつが原因。
  • Androidのパーミッション設定漏れ: `AndroidManifest.xml`の記述、実行時のリクエスト、どっちかが漏れてる。Android 12以上なら`BLUETOOTH_SCAN`と`BLUETOOTH_CONNECT`が必須。
  • データのBase64変換を忘れる: なんか文字化けしたり、うまく書き込めなかったりする。送る前、受け取った後のお作法。
  • 非同期処理を`await`し忘れる: `connect()`も`discoverAllServicesAndCharacteristics()`も全部Promiseを返す。`await`するか`.then()`で繋がないと、処理の順番がぐちゃぐちゃになって死ぬ。

まあ、こんな感じかな。BLEは一見とっつきにくいけど、この辺の「お作法」さえ守れば、思ったより素直に動いてくれる。…はず。デバイス側の実装にもよるけどね!


みんながBLE開発で一番ハマったのって、やっぱりパーミッション周り?それとも、特定のデバイスの謎挙動?もしよかったらコメントで教えてもらえると、僕も勉強になります。

Related to this topic:

Comments

  1. Guest 2025-11-23 Reply
    うちの子、BLEデバイス使って自由研究やりたいとか言い出した。でも自分、React Native BLE PLXとか…はっきり言ってよくわからない。スマホとBLE繋ぐには便利みたい?ネットではそう書いてある。でも、日本語で詳しい手順とかサンプル全然見つからなくて。親として応援したいけど、自分も開発の知識そんなにないし、どうしたらいいんだろう。 もし「これ見ればだいたい流れわかる」みたいなシンプルなコードや資料あったらめっちゃ助かるんだけどな。特に接続するまでのところ、なんとなく壁が高すぎる感じして…。アプリ作りなんて大人でも面倒なのに、子どもにも理解できるものってどんなだろう。最初つまずいて当たり前だと思うけど、それでも、とりあえず一歩動き出せるヒントほしい。誰か同じことで困った経験ある人いません?こういう時どうしてたんだろうって思ってしまう。
  2. Guest 2025-11-09 Reply
    React Native BLE PLXっていうやつ、実は海外のIoT案件でがっつり使ったことあるんだよね。最初は本当に「無理じゃない?」みたいな気持ちで、とにかくBLEデバイスと繋ぐのハードル高く見えてた。でも、一度やり方掴むと…あー意外と普通にいけるもんなんだなーって。まあそれでも、国ごとに規制とかデバイス自体も微妙〜に仕様が違うから、これ本当にこのパターン通じる?って、やっぱ時々不安になるし、その都度調べて試して、ちょっと落ち着かないというか。 あと開発リソース、本当に足りなくなる場面多かった。こっちは現地チームともめっちゃ連絡取って、「この設定どうなってる?」とか細かくやり取りしながら進めたり。ほんとはもっとテスト端末揃えたり技術サポート用意できてれば、かなり安心して進められたはずなのにな…そのへん今後一緒に作戦考えたい感じです。 そういえばBluetoothペアリング関連、日本国内だと比較的スムーズなんだけど、ヨーロッパだと権限系とか急に謎のエラー出たりして面倒だった印象強い。「え?また何コレ?」みたいな。こういう細かい体験談とか情報、お互いシェアできたら助かる人多そう。 現場寄りの動き方というか、生で困った所ベースで相談しながら進めていけるほうが絶対いいと思うから、良かったらそういうスタイルで協力させてもらいたいです。
  3. Guest 2025-11-05 Reply
    正直なところ、React Native BLE PLXでBLEデバイス繋げようとすると…いや、自分も前にやったことあるんだけどね、思ったより上手くいかなかった。あれ、本当に簡単に安定して動くの?自分は端末によって急につながらない時とか謎の現象結構あってさ、うまく動かないの普通なのか、それとも何かコツあるのか知りたいんだよね。他の人はどうだったんだろ…みんな問題なくできてたの?端末ごとの相性っぽい気もしたけど、何か対策とかもし考えてたりする人いたら、ほんと聞きたい。
  4. Guest 2025-09-17 Reply
    へえ、BLEの実装って意外と奥が深いんですね!モバイルアプリ開発で使えそうな技術だと思います。最近のIoTプロジェクトでちょっと興味あって、この記事めっちゃ参考になりそう!
  5. Guest 2025-08-22 Reply
    あ、BLE PLXについて勉強してるんですけど、初心者なもんで、具体的な実装とか、デバイススキャンの部分がちょっと難しくて…。誰か詳しい人、アドバイスとかありますか?
  6. Guest 2025-06-19 Reply
    へえ〜、BLE周りって結構複雑そうだよね。デバイス接続するのって、正直めんどくさそう。セキュリティとか権限周りで躓きそうな気がするし、初心者には厳しいんじゃない?もっとシンプルな方法ないの?