【ESP32 × Flutter】Wi-Fiスキャン結果をBLEでチャンク分割送信する方法
はじめに
以前、猫型おしゃべりロボット「ミーア」に関する開発記事で、「Wi-Fiネットワークの探索機能をESP32に実装し、Bluetooth経由でFlutterアプリに送信する機能」について記載した。
最初はエラーなくスキャンしたWi-Fiリストが表示されていたが、ある時下記のエラーが出現した。
PlatformIO(ESP32)のログを見ると、下記のエラーが表示されていた。
設定しようとしているデータのサイズは724バイトだが、ESP32のBLE特性に許可されている最大サイズは600バイトであり、データのサイズが大きすぎるためBLE送信できないというエラー。
BLEServer connected
Scanning WiFi networks...
Scan complete.
Available networks:
Size of data to send: 724 bytes
[ 68429][E][BLECharacteristic.cpp:662] setValue(): Size 724 too large, must be no bigger than 600
現状のコード確認
現状、ESP32からWi-Fiスキャン結果をBluetooth通信でFlutterアプリに送信しているコードがこちら。Wi-Fiスキャン結果をJSON形式の文字列にシリアル化して、一括送信している。
// src/BLEManager.cpp
// Wi-FiネットワークリストをJSON形式の文字列にシリアル化
String serializeNetworks(const std::vector<NetworkInfo> &networks) {
DynamicJsonDocument doc(1024);
JsonArray array = doc.to<JsonArray>();
for (const auto &network : networks) {
JsonObject obj = array.createNestedObject();
obj["ssid"] = network.ssid;
obj["rssi"] = network.rssi;
obj["encryptionType"] = network.encryptionType;
}
String result;
serializeJson(doc, result);
return result;
}
void BLEManager::CharacteristicCallbacks::onWrite(
BLECharacteristic *pCharacteristic) {
std::string value = pCharacteristic->getValue();
if (value.length() > 0) {
std::string ssid;
std::string password;
std::string response;
// Wi-Fiネットワークリストの要求を処理する
if (value == "request_wifi_list") {
// 利用可能なWi-Fiネットワークをスキャン
std::vector<NetworkInfo> networks = scanNetworks();
// スキャン結果をシリアル化
String networkListJson = serializeNetworks(networks);
Serial.printf("networkListJson: %s\n", networkListJson.c_str());
// 送信データのサイズをログ出力
Serial.printf("Size of data to send: %d bytes\n", networkListJson.length());
// シリアル化されたリストをBLE経由で送信
pCharacteristic->setValue(networkListJson.c_str());
pCharacteristic->notify();
Serial.println("WiFi network list sent via BLE");
return;
}
}
}
受け取り側のFlutterでは、fluter_blue_plusプラグインを使用しており、AndroidデバイスでのデフォルトMTU(Maximum Transmission Unit:BLEデバイス間で交換される最大データサイズを定義する値)は512バイトとして自動的にリクエストされ、iOSおよびmacOSではMTUが自動的に交渉される。
というわけで、今回の場合2つの対応策があると思う。
1)大きなデータを複数のメッセージに分割して送信する
2)キー名を短くすることでJSONのサイズを削減する
どちらも行おうと思う。まずは、簡易な2から。
JSONキー名を短くしてJSONサイズを削減
現状、スキャンしたWifiリストをBLE送信する際は、下記のようなリスト構造になっていてキー名がやや長い。
{"ssid":"XXXXXXX-X-XXXXX","rssi":-56,"encryptionType":3}
Wi-FiネットワークリストをJSON形式の文字列にシリアル化する部分で、**ssid
をs
に、rssi
をr
に、encryptionType
をe
**にするなどして、キー名を短縮する。
// src/BLEManager.cpp
// Wi-FiネットワークリストをJSON形式の文字列にシリアル化
String serializeNetworks(const std::vector<NetworkInfo> &networks) {
DynamicJsonDocument doc(1024);
JsonArray array = doc.to<JsonArray>();
for (const auto &network : networks) {
JsonObject obj = array.createNestedObject();
obj["s"] = network.ssid;
obj["r"] = network.rssi;
obj["e"] = network.encryptionType;
}
String result;
serializeJson(doc, result);
return result;
}
JSONキー名を変更したので、Flutter側(アプリ)のコードもそれに合わせて変更する必要がある。具体的には、**WifiNetwork
**クラスのファクトリメソッド fromJson
とそれを使用している部分で、JSONのキー名を新しい短いキーに合わせて更新する。
// lib/services/ble_service.dart
// Wi-Fiネットワーク情報を保持するクラス
class WifiNetwork {
final String ssid;
final int rssi;
final int encryptionType;
WifiNetwork(
{required this.ssid, required this.rssi, required this.encryptionType});
factory WifiNetwork.fromJson(Map<String, dynamic> json) {
return WifiNetwork(
ssid: json['ssid'] as String,
rssi: json['rssi'] as int,
encryptionType: json['encryptionType'] as int,
);
}
}
動作確認
600バイトを超えていてBLE送信エラーとなっていたWifiリストが、JSONキーの短縮により479バイトまで抑えられて無事送信できた。
execute 1 minute action
Scanning WiFi networks...
12
Scan complete.
Available networks:
SSID / RSSI / Encryption
Size of data to send: 479 bytes
WiFi network list sent via BLE
しかし、これは根本的な解決策にはなっておらず、仮に商業施設など多くのWi-Fiが飛び交っている状況ではWi-Fiスキャン結果が100近くになることも想定されるため、その場合は、BLE送信サイズ超過エラーは解消できない。
なので、BLE分割送信を実装する必要がある。
データ分割による複数メッセージでの送信
PlatformIO (ESP32) :チャンクに分けて送信
WiFiネットワークリストをチャンクに分けて送信する関数を作成する。
データがBLEの最大ペイロードサイズ(通常は512バイト)に達するたびにBLEを通じてデータを送信し、新しいチャンクを開始する。
// BLEManager.hに追加
void sendDataInChunks(const std::string& data, unsigned int chunkSize);
// BLEManager.cppに実装を追加
void BLEManager::sendDataInChunks(const std::string& data, unsigned int chunkSize) {
size_t numChunks = data.length() / chunkSize + (data.length() % chunkSize != 0);
for (size_t i = 0; i < numChunks; i++) {
size_t start = i * chunkSize;
std::string chunk = data.substr(start, chunkSize);
_pCharacteristic->setValue(chunk);
_pCharacteristic->notify();
delay(100);
}
}
void BLEManager::CharacteristicCallbacks::onWrite(BLECharacteristic *pCharacteristic) {
std::string value = pCharacteristic->getValue();
if (value == "request_wifi_list") {
std::vector<NetworkInfo> networks = scanNetworks();
String networkListJson = serializeNetworks(networks);
Serial.printf("networkListJson: %s\n", networkListJson.c_str());
Serial.printf("Size of data to send: %d bytes\n", networkListJson.length());
// データをチャンクに分けて送信。512はBLEのペイロードサイズ
sendDataInChunks(networkListJson.c_str(), 512);
Serial.println("WiFi network list sent via BLE in chunks");
}
}
BLEの特性の一部として定義されるDescriptorにCCCDを追加
分割送信を行う場合、BLEの通知(Notify)機能を利用することで、、、
続きは、こちらで記載しています。
Discussion