1.はじめに
ABEJA大田黒です。これはABEJAアドベントカレンダー2024の14日目の記事です。最近、趣味でESP32を触っています。ESP32では、Bluetooth Low Energy (BLE)を簡単に扱うことができます。手軽に使えてしまう反面、具体的にどういう通信が行われているか気になったので、Snifferデバイスを購入して実際に調べてみる事にしました。
- 筆者環境
- OS: Windows 10
- Wireshark: 4.4.1
- nRF Sniffer for bluetooth le: 4.1.1
2.セットアップ手順
2.1 Snifferの購入
今回、Adafruit ADA-2269を利用しました。身近なところだと、Amazonや千石電商さん、スイッチサイエンスさんで入手が可能です。2024年11月現在で5000円前後という金額感になっています。
重要:必ず技術基準適合証明や工事設計認証を受けているデバイスを使用しましょう。ADA-2269に搭載されているBLEモジュール(RAYRAC社 MDBT40)は工事設計認証を取得しています。
2.2 Wiresharkのインストール
下記からダウンロードが可能です。インストール手順の解説はここでは省略します。
2.3 nRF Snifferのインストール
Fig ダウンロードページ
下記ページ(nRF Sniffer for Bluetooth LE)から、Wiresharkのバージョンに合わせたソフトウェアをダウンロードします。筆者のWiresharkは4系なので「nrf_sniffer_for_bluetooth_le_4.1.1.zip」を使いました。
extcapフォルダのコピー
nrf_sniffer_for_bluetooth_le_4.1.1.zipを展開するとextcap
フォルダが出現します。このextcapフォルダの中身をWiresharkのextcapフォルダ配下にコピーします。
Profileのコピー
nrf_sniffer_for_bluetooth_le_4.1.1.zipを展開するとProfile_nRF_Sniffer_Bluetooth_LE
フォルダが出現します。このフォルダをWiresharkのprofilesフォルダ配下にコピーします。
pyserialのインストール
依存関係であるpyserial
をインストールします。この作業を忘れると正しく動作しません。
$ pip3 install pyserial
動作確認
エラーメッセージではなく、下記のようなメッセージが出てきたら成功です。
$ python nrf_sniffer_ble.py --extcap-interfaces
2.4 Wiresharkの起動
うまく設定ができていると、下記のようにSnifferデバイスを認識する事ができます。
Snifferデバイスを選択すると、実際に飛び交っているアドバタイズパケットのキャプチャが始まります。
2.5 Snifferコードの一部修正
キャプチャを停止すると、invalid escape sequence \s
というエラーメッセージが表示されます。このエラーを調べたところ、下記フォーラムに対応方法が記載されていました。コードを2箇所修正すると良さそうです。(2024年11月20日現在)
invalid escape sequence \s - Nordic Q&A - Nordic DevZone - Nordic DevZone
Line 187 Installed: "{validation=^\s*((37|38|39)\s*,\s*){0,2}(37|38|39){1}\s*$}{required=true}" % CTRL_ARG_ADVHOP) Line 187 My Edit: "{validation=^\\s*((37|38|39)\\s*,\\s*){0,2}(37|38|39){1}\\s*$}{required=true}" % CTRL_ARG_ADVHOP) Line 716 Installed: m = re.search("^\s*rssi\s*(>=?)\s*(-?[0-9]+)\s*$", capture_filter, re.IGNORECASE) Line 716 My Edit: m = re.search("^\\s*rssi\\s*(>=?)\\s*(-?[0-9]+)\\s*$", capture_filter, re.IGNORECASE)
3.実験
今回はペリフェラルとしてESP32、セントラルとしてAndroidスマホを用意しました。Androidスマホには事前にnRF Connect for Mobile
をインストールしておきます。
3.1 ESP32を使った準備
下記コードをArduino IDEもしくはPlatformIOを用いて、ESP32搭載ボードに書き込みます。
このコードは、iBeacon
として振る舞います。また、接続可能になっておりGATT通信によるService・Characteristicを提供しています。
#include <Arduino.h> #include <BLEDevice.h> #include <BLEServer.h> #include <BLEUtils.h> #include <BLE2902.h> #include <BLEBeacon.h> #define DEVICE_NAME "ESP32" #define BEACON_UUID_REV "A134D0B2-1DA2-1BA7-C94C-E8E00C9F7A2D" #define SERVICE_UUID "7A0247E7-8E88-409B-A959-AB5092DDB03E" #define CHARACTERISTIC_UUID "82258BAA-DF72-47E8-99BC-B73D7ECD08A5" BLEServer *pServer; BLECharacteristic *pCharacteristic; bool deviceConnected = false; uint8_t value = 0; uint16_t loopcnt = 0; class MyServerCallbacks : public BLEServerCallbacks { void onConnect(BLEServer *pServer) { deviceConnected = true; Serial.println("deviceConnected = true"); }; void onDisconnect(BLEServer *pServer) { deviceConnected = false; Serial.println("deviceConnected = false"); // Restart advertising to be visible and connectable again BLEAdvertising *pAdvertising; pAdvertising = pServer->getAdvertising(); pAdvertising->start(); Serial.println("iBeacon advertising restarted"); } }; class MyCallbacks : public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pCharacteristic) { std::string rxValue = pCharacteristic->getValue(); if (rxValue.length() > 0) { Serial.println("*********"); Serial.print("Received Value: "); for (int i = 0; i < rxValue.length(); i++) { Serial.print(rxValue[i]); } Serial.println(); Serial.println("*********"); } } }; void init_service() { BLEAdvertising *pAdvertising; pAdvertising = pServer->getAdvertising(); pAdvertising->stop(); BLEService *pService = pServer->createService(BLEUUID(SERVICE_UUID)); pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_NOTIFY ); pCharacteristic->setCallbacks(new MyCallbacks()); pCharacteristic->addDescriptor(new BLE2902()); pAdvertising->addServiceUUID(BLEUUID(SERVICE_UUID)); pService->start(); pAdvertising->start(); } void init_beacon(uint16_t seq_num) { BLEAdvertising *pAdvertising; pAdvertising = pServer->getAdvertising(); pAdvertising->stop(); BLEBeacon myBeacon; myBeacon.setManufacturerId(0x4c00); myBeacon.setMajor(5); myBeacon.setMinor(88); myBeacon.setSignalPower(0xc5); myBeacon.setProximityUUID(BLEUUID(BEACON_UUID_REV)); BLEAdvertisementData advertisementData; advertisementData.setFlags(0x1A); advertisementData.setManufacturerData(myBeacon.getData()); pAdvertising->setAdvertisementData(advertisementData); pAdvertising->start(); } void setup() { Serial.begin(115200); Serial.println("Initializing..."); BLEDevice::init(DEVICE_NAME); pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); init_service(); init_beacon(loopcnt); } void loop() { if (deviceConnected) { Serial.printf("*** NOTIFY: %d ***\n", value); pCharacteristic->setValue(&value, 1); pCharacteristic->notify(); value++; } delay(1000); }
参考:
www.programmingelectronics.com
3.2 ESP32アドバタイズの様子
Passive Scan過程で飛んでいるADV_IND
のパケットが見えますね。ADV_INDはAdvertising PDUの一種で「コネクション可能、スキャン可能」を表します。 iBeacon互換の信号を出しているので、Manufacture SpecificのCompayIDがApple, Inc.
となっています。
Active Scan過程で飛んでいるSCAN_REQ
も見えました。何かしらのデバイスが情報を取りに行っているみたいです。
SCAN_REQ
に対するSCAN_RSP
も見えますね。デバイス名や送信電力やサービスUUIDを返却している様子が見えます。
3.3 セントラルからの接続
CONNECT_REQ
が見えました。(CONNECT_IND
と表示されていますが、 PDU Type (0x05)から推測するにおそらく CONNECT_REQ
信号なはず)
リンク層の制御をしている様子も見えますね。
接続後はセントラル(スマホ)とペリフェラル(ESP32)の間でKeepAliveのような通信が行われている事が確認できます。かなりのハイペースでEmpty PDU
が飛び交っています。
3.4 GATTによるデータ通信
セントラル→ペリフェラルへのデータ取得要求 (Read)
Read ReqestとRead Responseが確認できました。
ペリフェラル→セントラルへのNotify (通知)
値に変化がある度に、下記のようなパケットが飛んでくる事がわかりました。
セントラル→ペリフェラルへの書き込み (Write)
スマホアプリから0x30
を送信した時の様子です。Write Requestが見れました。
4. 最後に
今回、実際のBLE通信をSnifferデバイスを用いて覗いてみました。ブラックボックスとして使っている通信技術の解像度が少し上がった気がします。
We Are Hiring!
ABEJAは、テクノロジーの社会実装に取り組んでいます。 技術はもちろん、技術をどのようにして社会やビジネスに組み込んでいくかを考えるのが好きな方は、下記採用ページからエントリーください! (新卒の方やインターンシップのエントリーもお待ちしております!)
特に下記ポジションの募集を強化しています!ぜひ御覧ください!
プラットフォームグループ:シニアソフトウェアエンジニア | 株式会社ABEJA