M5StackでBluetooth® LE通信を実現するまでの手順メモ②ビーコン発信編

こんにちは、ムセンコネクトCCOの伊藤です。(プロフィール紹介はこちら)
「M5StackでBluetooth® LE(BLE)通信を実現するまでの手順メモ」の第2回目です。今回は「ビーコン発信編」です。
M5Stack自体はBluetooth認証を取得していないため、M5Stackで開発した最終製品のBluetooth認証取得は困難な場合があります。M5Stackを用いた製品化をご検討の際は、この点に十分ご注意ください。

ビーコン発信を試す
前回、開発環境の構築を済ませましたので、さっそくM5Stackを使ってBluetooth LE通信を体験していきます。

今回は、デバイスのふるまいとして、非接続でデータをブロードキャストするだけのブロードキャスターにチャレンジします。ビーコン発信、といったほうがなじみがあるかもしれません。わかりやすく、ビーコンの形式は『iBeacon』にしようと思います。
iBeaconの仕様は、こちらのページでも解説しています。合わせてご確認ください。

前回の開発環境の構築時にお伝えしたとおり、AIであるGemini 2.5 Proを利用してコーディングの支援をしてもらいます。Arduino IDEで開発するための事前情報収集などは行っていませんが、AIでどこまでできるのか楽しみです。
プロンプト指示
あまり難しいことを考えず、以下のプロンプトを入力しました。
手元に M5Stack Gray Core V1.0がある。
以下の条件のプログラムコードを提示してほしい。
開発環境
Arduino IDE
目的
Bluetooth LEのアドバタイズ発信のみを行うビーコン装置を作る
機能
ビーコン形式はiBeacon形式とする
UUID, Major, Minorなどの各値は仮の値でよい
すると、Arduinoスケッチ(プログラムコード)だけでなく、環境構築方法や、実行方法、さらには動作確認方法まで提案してくれました。AIって便利だな、と再認識する体験ができました。
コンパイル・実行
生成されたコードをコンパイルした際、エラーが発生しました。
2018年発売のM5Stack Grayでは、M5Stackライブラリのバージョン不整合が起きてしまうようです。今回はBluetooth LE機能のみが必要だったため、M5Stackライブラリに依存する機能をコードから削除し、再度コンパイルすることでエラーを解消しました。それ以外のBluetooth LE機能の精査は行っていません、あくまでもAIが生成したコードを信じてみます。
コードは以下のとおりです。
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLEBeacon.h>
// =======================================================
// iBeaconの情報を設定 (これらの値を自由に変更してください)
// =======================================================
// UUID: 下記のようなサイトで新規に生成できます。
// https://www.uuidgenerator.net/
#define BEACON_UUID "11223344-5566-7788-99AA-BBCCDDEEFF00"
// Major値 (0-65535の範囲)
#define MAJOR_VALUE 101
// Minor値 (0-65535の範囲)
#define MINOR_VALUE 25
// TX Power: ビーコンから1mの距離で測定したRSSI値
// この値は受信側が距離を推定するために使用します。
// 一般的な値は-59dBmです。環境に応じて調整してください。
#define TX_POWER -59
// =======================================================
// BLEアドバタイジング用のポインタ
BLEAdvertising *pAdvertising;
void setup() {
// BLEデバイスを初期化
BLEDevice::init("M5Stack-Beacon");
// iBeaconデータフレームを作成するためのBLEBeaconオブジェクトを生成
BLEBeacon beacon = BLEBeacon();
// iBeaconの仕様に従い、製造元IDとしてAppleのID (0x004c) を設定
beacon.setManufacturerId(0x004c);
// 上記で定義したiBeaconの識別情報を設定
beacon.setProximityUUID(BLEUUID(BEACON_UUID));
beacon.setMajor(MAJOR_VALUE);
beacon.setMinor(MINOR_VALUE);
beacon.setSignalPower(TX_POWER);
// アドバタイズするデータを作成
BLEAdvertisementData advertisementData = BLEAdvertisementData();
advertisementData.setFlags(0x04); // BR_EDR_NOT_SUPPORTED
// Manufacturer Specific DataとしてiBeaconのデータをセット
advertisementData.setManufacturerData(beacon.getData());
// アドバタイジングオブジェクトを取得し、データを設定
pAdvertising = BLEDevice::getAdvertising();
pAdvertising->setAdvertisementData(advertisementData);
pAdvertising->setScanResponse(false);
// アドバタイジングの間隔を設定 (オプション)
// 100msに設定 (0x64 * 0.625ms = 100ms)
pAdvertising->setMinPreferred(0x64);
pAdvertising->setMaxPreferred(0x64);
// アドバタイジングを開始
BLEDevice::startAdvertising();
}
void loop() {
// アドバタイジングはバックグラウンドで継続的に実行されるため、
// loop()関数内で何かを記述する必要はありません。
// CPUを休ませて消費電力を抑えます。
delay(2000);
}
では、iBeaconとして発信されているか動作を確認してみます。確認用のアプリケーションとしてnRF Connect for Mobileを使用します。

動作確認・問題点の把握・修正
結論から言うと、iBeaconの仕様を満たしていませんでした。

iBeaconの仕様と照らし合わせてみると、以下の問題点がありそうです。
- Company IDが0x4c00になっている(仕様は0x004c:Apple, Inc.)
- AD Type:Flagsのパラメータが0x04になっている(仕様は0x06)
- 発信頻度が40msになっている(仕様は100ms)
- 接続が可能な状態になっている(仕様は非接続)
iBeaconの仕様は、こちらのページでも解説しています。合わせてご確認ください。

AIも万能ではないということがわかりましたので、問題解決のため自分でコードを直しました。修正箇所はコメント内★修正1〜4の部分です。
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLEBeacon.h>
// =======================================================
// iBeaconの情報を設定 (これらの値を自由に変更してください)
// =======================================================
// UUID: 下記のようなサイトで新規に生成できます。
// https://www.uuidgenerator.net/
#define BEACON_UUID "11223344-5566-7788-99AA-BBCCDDEEFF00"
// Major値 (0-65535の範囲)
#define MAJOR_VALUE 101
// Minor値 (0-65535の範囲)
#define MINOR_VALUE 25
// TX Power: ビーコンから1mの距離で測定したRSSI値
// この値は受信側が距離を推定するために使用します。
// 一般的な値は-59dBmです。環境に応じて調整してください。
#define TX_POWER -59
// =======================================================
// BLEアドバタイジング用のポインタ
BLEAdvertising *pAdvertising;
void setup() {
// BLEデバイスを初期化
BLEDevice::init("M5Stack-Beacon");
// iBeaconデータフレームを作成するためのBLEBeaconオブジェクトを生成
BLEBeacon beacon = BLEBeacon();
// iBeaconの仕様に従い、製造元IDとしてAppleのID (0x004c) を設定
// beacon.setManufacturerId(0x004c);
// ★修正1:CompanyIDはリトルエンディアンで指定する
beacon.setManufacturerId(0x4c00);
// 上記で定義したiBeaconの識別情報を設定
beacon.setProximityUUID(BLEUUID(BEACON_UUID));
beacon.setMajor(MAJOR_VALUE);
beacon.setMinor(MINOR_VALUE);
beacon.setSignalPower(TX_POWER);
// アドバタイズするデータを作成
BLEAdvertisementData advertisementData = BLEAdvertisementData();
// advertisementData.setFlags(0x04); // BR_EDR_NOT_SUPPORTED
// ★修正2:ESP_BLE_ADV_FLAG_GEN_DISC(0x02) | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT(0x04) を指定する
advertisementData.setFlags(0x06);
// Manufacturer Specific DataとしてiBeaconのデータをセット
advertisementData.setManufacturerData(beacon.getData());
// アドバタイジングオブジェクトを取得し、データを設定
pAdvertising = BLEDevice::getAdvertising();
pAdvertising->setAdvertisementData(advertisementData);
pAdvertising->setScanResponse(false);
// アドバタイジングの間隔を設定 (オプション)
// 100msに設定 (0x64 * 0.625ms = 100ms)
// pAdvertising->setMinPreferred(0x64);
// pAdvertising->setMaxPreferred(0x64);
// ★修正3:アドバタイズ間隔の計算ミス、および設定関数の間違いを修正
// 100msに設定 (0xa0 * 0.625ms = 100ms)
pAdvertising->setMinInterval(0xa0);
pAdvertising->setMaxInterval(0xa0);
// ★修正4:アドバタイズタイプを設定 (非接続である必要がある)
pAdvertising->setAdvertisementType(ADV_TYPE_NONCONN_IND);
// アドバタイジングを開始
BLEDevice::startAdvertising();
}
void loop() {
// アドバタイジングはバックグラウンドで継続的に実行されるため、
// loop()関数内で何かを記述する必要はありません。
// CPUを休ませて消費電力を抑えます。
delay(2000);
}
このコードで、改めてiBeaconとして発信されているか動作を確認してみます。

iBeaconとして認識されました。iBeaconの仕様を満たした動作に改善できているようです。
プロンプトではM5Stack Gray向けで指示しましたが、M5Stack CoreS3 SEでも同一のプログラムコードで動作することも確認できました。

まとめ
無事、iBeacon仕様に則ったビーコン発信ができました。
AIは便利ですが、生成されたコードを安易に信用すべきではありませんね。正しいコードになっているのか見抜くためには、やはり知識や経験が求められるのだと実感しました。
特に重要なのは、作成したいもの(今回の場合はiBeacon)と使用するデバイス(M5Stackに搭載されているESP32のBluetooth LE機能)それぞれの仕様を深く理解することだと思います。
とはいえ、AIのおかげで様々な情報を収集し、手を動かしてコーディングする手間が省けたのは事実です。プロンプトを考えるところから始めて、iBeaconの仕様を満たしてビーコン発信できるまで1時間もかからずに実現できました。強力な相棒として活用していきたいですね。
次回からは、相手と1対1で接続して双方向のデータ通信を行うペリフェラルとセントラルにそれぞれチャレンジします。お楽しみに!