BLE通信を1から理解する - デパートで学ぶBluetooth通信の仕組み
同じコードなのに、iOSだけエラーになる謎
「Androidでは完璧に動くのに、iOSでだけBluetooth接続がエラーになる」
BLE(Bluetooth Low Energy)アプリを開発した経験者なら、一度は遭遇したことがあるはずです。エラーメッセージは Write is not permitted。デバイスへのデータ送信が、なぜかiOSだけ拒否されるのです。
実はこれ、iOSとAndroidでBluetooth通信の「ルールの厳しさ」が違うことが原因です。 そして、この違いを理解するには、BLE通信の仕組みそのものを理解する必要があります。
この記事では、プログラミング経験者向けに、デパートの例えを使ってBLE通信の全体像を徹底解説します。読み終わる頃には、「サービス」「キャラクタリスティック」「UUID」といった専門用語が、すんなり理解できるようになります。
そもそもBLE通信って何?
BLE(Bluetooth Low Energy)は、スマートウォッチ、ワイヤレスイヤホン、IoTデバイスなど、低消費電力で通信する機器に使われる無線通信技術です。
従来のBluetooth(クラシックBluetooth)と比べて:
- 消費電力が1/10以下
- 接続が高速(数百ミリ秒)
- 小さなデータを頻繁に送る用途に最適
だからこそ、腕時計のような小型デバイスに採用されているわけです。
BLE通信は「デパート」だと考えよう
BLE通信を理解する最大のコツは、デバイスを「デパート」として想像することです。
デパートの構造で理解するBLE
想像してください。あなた(スマホアプリ)が、大きなデパート(BLEデバイス)に買い物に行く場面を。
デパートには複数のフロアがあります。
- 3階:心拍計測フロア
- 4階:バッテリー情報フロア
- 5階:デバイス情報フロア
このフロアが、BLEでは「サービス (Service)」と呼ばれるものです。機能ごとに分かれた大きな区画です。
各フロアには、いくつかの棚や窓口があります。
- 心拍フロアの「現在の心拍数の掲示板」
- 心拍フロアの「測定間隔の設定カウンター」
- バッテリーフロアの「残量表示の棚」
この棚や窓口が「キャラクタリスティック (Characteristic)」です。実際にデータを読み書きするのは、必ずこの棚を経由します。
そして、各フロアと棚には固有の番号札がついています。
- 心拍フロア:UUID
0000180D-0000-1000-8000-00805F9B34FB - 現在の心拍数棚:UUID
00002A37-0000-1000-8000-00805F9B34FB
この長い番号が「UUID (Universally Unique Identifier)」です。世界中で重複しない、住所のようなものです。
実際の通信の流れ
1. スマホ「心拍数が知りたいな」
2. スマホ「心拍フロア (UUID: 180D) に行こう」
3. スマホ「心拍数の棚 (UUID: 2A37) を見つけた!」
4. スマホ「この棚のデータを読み取ろう」
5. デバイス「現在の心拍数は72です」
このように、サービス(フロア)を特定してから、キャラクタリスティック(棚)にアクセスするという2段階構造になっています。
なぜこんなに複雑な構造なの?
「単純にデータを送受信すればいいじゃん」と思いますよね。でも、BLEデバイスは1台で複数の機能を持っています。
例えば、スマートウォッチは:
- 心拍数を測る機能
- 歩数を記録する機能
- 通知を表示する機能
- 時刻を合わせる機能
- バッテリー残量を報告する機能
これらすべてが1つのデバイスに同居しています。もしフロア(サービス)で分けずに、すべての棚が1階に並んでいたら...混乱しますよね。
だからこそ、機能ごとに「フロア(サービス)」を分けて、その中に関連する「棚(キャラクタリスティック)」を配置するという構造が採用されているのです。
棚には「使い方のルール」がある - iOSエラーの正体
ここが、冒頭の「iOSでだけエラーになる」問題の核心です。
デパートの棚には、それぞれ使い方のルールがあります。BLEでは、これを「プロパティ (Property)」と呼びます。
Write(書き込み)権限の棚
「商品を置ける棚」です。お客さん(スマホ)が自由に物(データ)を置けます。
例:アラーム設定の棚プロパティ: Write
→ スマホから「朝7時にアラーム」というデータを書き込める
Read(読み取り)権限の棚
「商品を見るだけの棚」です。お客さんは中身を確認できますが、勝手に変更はできません。
例:バッテリー残量の棚プロパティ: Read
→ スマホから「今何%?」と読み取れるが、書き換えはできない
Notify(通知)専用の窓口
これが少し特殊です。お客さんは触れませんが、中身が変わると店員(デバイス)が自動的に「変わったよ!」と教えてくれる窓口です。
例:心拍数の掲示板プロパティ: Notify
→ デバイスが心拍数を測定するたびに、自動的にスマホに通知が飛んでくる
→ スマホからは書き込みも読み取りもできない(通知を受けるだけ)
iOSとAndroidで動作が違う理由
ここが重要です。
iOSは「超厳格な門番」
- デバイスが接続時に「0001番の棚はNotify(通知)専用です」と宣言する
- iOSは「わかりました。この棚は見る専用ですね」と厳密に記録する
- 開発者が誤って「0001番の棚にデータを書き込もう!」とコードを書く
- iOS「待て! その棚は通知専用だ。Write権限がないぞ!」 →
Write is not permittedエラー
Androidは「ゆるい門番」
同じ状況でも:
- デバイスが「0001番はNotify専用です」と宣言
- 開発者が「0001番に書き込もう!」とする
- Android「ルール違反だけど...とりあえずデバイスに投げてみるか」
- デバイス側が拒否すればエラーになるが、場合によっては通ってしまう
このため、Androidで動いたコードがiOSでエラーになるという現象が起きるのです。
UUIDは長すぎて不便...「ハンドル」という受付番号
UUIDは 0000180D-0000-1000-8000-00805F9B34FB のように、非常に長い文字列です。毎回この長い住所を通信で送るのは効率が悪いですよね。
そこで、デバイスに接続すると、**接続中だけ使える短い「受付番号」**が自動的に発行されます。これを「ハンドル (Handle)」と呼びます。
長いUUID: 00002A37-0000-1000-8000-00805F9B34FB
↓ 接続すると自動的に割り当てられる短いハンドル: 0x0020 (10進数で32番)
通信中は、いちいち長いUUIDを使わず、この短いハンドル番号でやり取りします。デパートで言えば「心拍フロアの現在値棚」ではなく「20番窓口」と呼ぶようなものです。
パケットキャプチャツールでBLE通信を覗くと、このハンドル番号が頻繁に出てきます。
Nordic UART サービス - RXとTXの話
実際の市販BLEデバイス(Pavlok、Arduino系モジュール、M5Stackなど)では、「Nordic UART サービス」という仕組みが非常によく使われます。
これは、昔ながらのシリアル通信(RS-232のような文字のやり取り)を模した通信方法です。
RXとTXって何?
電子工作や通信の世界では、データの送受信を「RX(受信)」「TX(送信)」と呼びます。
ここが混乱しやすいポイントです。立場によって名前が逆転します。
デバイス視点:
- RX (Receive): デバイス側が受け取る端子 = スマホから見ると「送信先」
- TX (Transmit): デバイス側が送る端子 = スマホから見ると「受信元」
デパートの例えで言うと:
RXの棚(Write権限あり)
→ お客さん(スマホ)が商品(データ)を置く棚
→ 店員(デバイス)がそこから商品を受け取る
→ スマホ視点では「送信先」
TXの窓口(Notify権限あり)
→ 店員(デバイス)が情報を掲示する窓口
→ お客さん(スマホ)がそこから情報を受け取る
→ スマホ視点では「受信元」
実際のコードで正しい棚を探す
iOSのSwiftコードで、書き込み先(RX)を探すなら:
for characteristic in service.characteristics ?? [] {
// 書き込み可能な棚を探す(RX)
if characteristic.properties.contains(.write) ||
characteristic.properties.contains(.writeWithoutResponse) {
print("これがRX(書き込み用)の棚!")
rxCharacteristic = characteristic
}
// 通知を受け取る窓口を探す(TX)
if characteristic.properties.contains(.notify) {
print("これがTX(通知用)の窓口!")
txCharacteristic = characteristic
// 通知の購読を開始
peripheral.setNotifyValue(true, for: characteristic)
}
}
よくあるミス: UUIDが 0001 だから書き込めるだろうと決めつけるのは危険です。必ずプロパティを確認しましょう。
HEX(16進数)データの読み方 - 実例で理解する
最後に、実際に送受信されるデータの話です。
BLE通信では、データは「HEX(16進数)」という形式で送られます。
HEXって何?
普段私たちは「0, 1, 2, 3...9」の10種類の数字を使います(10進数)。
HEXは「0〜9」に加えて「A, B, C, D, E, F」も使う、16種類の数字です。
10進数: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
HEX: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
なぜHEXを使うのか? コンピュータの内部では、すべてのデータは「0と1」(2進数)で表現されています。HEXは、この0と1の長い羅列を人間が読みやすく、かつコンパクトに表記するための方法なんです。
2進数: 10100000 01000000
↓ 4ビットずつ区切る
HEX: A0 40
実際のデバイス通信例
Pavlokという電気ショックウェアラブルデバイスの例:
送信: A0:40
意味: 「これから設定変更しますよ」という準備コマンド
送信: A8:4E:01:32:00:00
意味: 「振動モードで強度50で作動させて」という命令
A8 = 振動コマンド
4E = サブコマンド
01 = モード指定
32 = 強度(16進数の32 = 10進数の50)
00:00 = オプション
送信: 6B:01:00:00:00
意味: 「デバイス