ESP32でBluetooth(BLE)で相互通信 その2
1対多通信を行うのに、multi advertisingと言う方法もあるようなのですが、それほど複雑なことを行わないし、割り込みとかも逆にたいへんそうなので、シンプルに毎回接続を変える、前のXbeeと同じ考え方で考えてみました。
とりあえずソースですが、接続を子機側が常時動作しているのに対してサーバー側から接続に行く方が、サンプルソースを少し手直しするだけで簡単だったので、子機側にサーバーのソースを。親機側にクライアント側のソースを使うことにしました。BLE関係のプログラムはC++で割り込み的なことも使用して書かれており、今回のような単純な機器の制御では処理が複雑すぎて逆にコントロールが難しくなっておりとりあえずこれで動かせそうと言うレベルで作ってみました。
子機が複数になってどちらを制御しているのかだんだんわからなくなってきたので、内蔵LEDで色分けできるようにしてます。
また親機側=クライアントにM5STAMP C3を使用して内蔵のボタンで子機を切り替えるようにしてます。これまではUSBをつないだだけで、配線を一切してない状態で実験できてます。
//クライアント側ソース for C3
#include "BLEDevice.h"
#include <Adafruit_NeoPixel.h>
#define SERVICE_UUID "ee64a0c8-6995-11ed-a1eb-0242ac120002"
#define CHARACTERISTIC_UUID_TX "ee64a49c-6995-11ed-a1eb-0242ac120002"
#define CHARACTERISTIC_UUID_RX "ee64a690-6995-11ed-a1eb-0242ac120002"
#define LED_PIN 2
// create a pixel strand with 1 pixel on PIN_NEOPIXEL
Adafruit_NeoPixel pixels(1, LED_PIN);
uint32_t colors[] = { pixels.Color(125, 0, 0), pixels.Color(0, 125, 0), pixels.Color(0, 0, 125), pixels.Color(125, 125, 125) };
static BLEUUID serviceUUID(SERVICE_UUID);
static BLEUUID charUUID_RX(CHARACTERISTIC_UUID_RX);
static BLEUUID charUUID_TX(CHARACTERISTIC_UUID_TX);
static BLEAddress* pServerAddress;
static BLEAddress* pServerAddress_1;
static BLEAddress* pServerAddress_2;
static BLEAddress* pServerAddress_3;
static boolean doConnect = false;
static boolean connected = false;
static BLERemoteCharacteristic* pRemoteCharacteristicRX;
static BLERemoteCharacteristic* pRemoteCharacteristicTX;
BLEClient* pClient;
const int buttonPin = 3;
int buttonState = LOW; // 状態
int lastButtonState = LOW;
char* SERVER_NAME[] = { "esp32ble1", "esp32ble2" };
int server_no = 0;
static void notifyCallback(
BLERemoteCharacteristic* pBLERemoteCharacteristic,
uint8_t* pData,
size_t length,
bool isNotify) {
Serial.print("Notify callback for characteristic ");
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
Serial.print(" of data length ");
Serial.println(length);
Serial.print("data: ");
Serial.println((char*)pData);
}
class MyClientCallback : public BLEClientCallbacks {
void onConnect(BLEClient* pclient) {
}
void onDisconnect(BLEClient* pclient) {
connected = false;
Serial.println("onDisconnect");
}
};
bool connectToServer(BLEAddress pAddress) {
Serial.print("Forming a connection to ");
Serial.println(pAddress.toString().c_str());
pClient = BLEDevice::createClient();
Serial.println(" - Created client");
pClient->setClientCallbacks(new MyClientCallback());
// Connect to the remove BLE Server.
pClient->connect(pAddress);
Serial.println(" - Connected to server");
//pClient->setMTU(517); //set client to request maximum MTU from server (default is 23 otherwise)
BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
Serial.print(" - Connected to server :");
Serial.println(serviceUUID.toString().c_str());
if (pRemoteService == nullptr) {
Serial.print("Failed to find our service UUID: ");
Serial.println(serviceUUID.toString().c_str());
pClient->disconnect();
return false;
}
pRemoteCharacteristicRX = pRemoteService->getCharacteristic(charUUID_RX);
if (pRemoteCharacteristicRX == nullptr) {
Serial.print("Failed to find characteristic RX ID: ");
Serial.println(charUUID_RX.toString().c_str());
pClient->disconnect();
return false;
}
Serial.print(" - Found characteristic RX ID: ");
Serial.println(charUUID_RX.toString().c_str());
// Send用キャラクタリスティックの参照を取得する
pRemoteCharacteristicTX = pRemoteService->getCharacteristic(charUUID_TX);
if (pRemoteCharacteristicTX == nullptr) {
Serial.print("Failed to find characteristic TX ID ");
Serial.println(charUUID_TX.toString().c_str());
pClient->disconnect();
return false;
}
Serial.print(" - Found characteristic TX ID: ");
Serial.println(charUUID_TX.toString().c_str());
// Read the value of the characteristic.
if (pRemoteCharacteristicRX->canRead()) {
std::string value = pRemoteCharacteristicRX->readValue();
Serial.print("The characteristic value was: ");
Serial.println(value.c_str());
}
if (pRemoteCharacteristicRX->canNotify())
// pRemoteCharacteristicRX->registerForNotify(notifyCallback);
// Serial.println("End of notify");
return true;
}
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.print("BLE Advertised Device found: ");
Serial.println(advertisedDevice.toString().c_str());
Serial.println(advertisedDevice.getName().c_str());
if (advertisedDevice.getName() == SERVER_NAME[server_no]) {
Serial.print("SERVER_found:");
Serial.println(advertisedDevice.getAddress().toString().c_str());
advertisedDevice.getScan()->stop();
pServerAddress = new BLEAddress(advertisedDevice.getAddress());
doConnect = true;
}
}
};
void get_scan() {
BLEScan* pBLEScan = BLEDevice::getScan();
Serial.println("getScan");
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
Serial.println("setAdvertisedDeviceCallbacks");
pBLEScan->setActiveScan(true);
pBLEScan->start(5);
Serial.println("End of scan");
}
void setup() {
//LED
pixels.begin(); // initialize the pixel
pixels.setPixelColor(0, colors[server_no]);
pixels.show();
Serial.begin(115200);
//input SW
pinMode(buttonPin, INPUT_PULLUP);
BLEDevice::init(SERVER_NAME[server_no]);
get_scan();
pServerAddress_1 = pServerAddress;
Serial.println("End of setup");
}
void loop() {
buttonState = digitalRead(buttonPin);
if (buttonState == LOW and lastButtonState == LOW) {
delay(100);
server_no = 1;
doConnect = false;
connected = false;
lastButtonState = HIGH;
Serial.println("button 1");
pixels.setPixelColor(0, colors[server_no]);
pixels.show();
} else if (buttonState == LOW and lastButtonState == HIGH) {
delay(100);
server_no = 0;
lastButtonState = LOW;
doConnect = false;
connected = false;
Serial.println("button 0");
pixels.setPixelColor(0, colors[server_no]);
pixels.show();
}
if (doConnect == true) {
if (connectToServer(*pServerAddress)) {
Serial.println("We are now connected to the BLE Server.");
connected = true;
} else {
Serial.println("We have failed to connect to the server; there is nothin more we will do.");
connected = false;
}
doConnect = false;
}
if (connected) {
std::string value = pRemoteCharacteristicRX->readValue();
String strVal = value.c_str();
int strNum = strVal.toInt();
Serial.println(strVal);
pRemoteCharacteristicTX->writeValue("abc");
} else {
Serial.println("Not connected");
//conectできないときにscanループさせておくため
doConnect = false;
pClient->disconnect();
Serial.println("Device_init");
get_scan();
}
delay(30);
}
//サーバー側ソース for PICO
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <Adafruit_NeoPixel.h>
BLEServer *pServer = NULL;
BLECharacteristic *pTxCharacteristic;
BLECharacteristic *pRxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
//serverとcliantでTX,RXは逆転するのに注意
#define SERVICE_UUID "ee64a0c8-6995-11ed-a1eb-0242ac120002"
#define CHARACTERISTIC_UUID_RX "ee64a49c-6995-11ed-a1eb-0242ac120002"
#define CHARACTERISTIC_UUID_TX "ee64a690-6995-11ed-a1eb-0242ac120002"
#define LED_PIN 27
// create a pixel strand with 1 pixel on PIN_NEOPIXEL
Adafruit_NeoPixel pixels(1, LED_PIN);
uint32_t colors[] = { pixels.Color(125, 0, 0), pixels.Color(0, 125, 0), pixels.Color(0, 0, 125), pixels.Color(125, 125, 125) };
char *SERVER_NAME[] = { "esp32ble1", "esp32ble2" };
int server_no = 1; //子機番号
#define Tx_value 'K' //テストなので子機によって変えている
std::string rx_Data;
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer *pServer) {
deviceConnected = true;
};
void onDisconnect(BLEServer *pServer) {
deviceConnected = false;
}
};
class MyCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pRxCharacteristic) {
std::string rxValue = pRxCharacteristic->getValue();
if (rxValue.length() > 0) {
rx_Data="";
for (int i = 0; i < rxValue.length(); i++)
rx_Data=rx_Data+rxValue[i];
}
}
};
void setup() {
//LED
pixels.begin(); // initialize the pixel
pixels.setPixelColor(0, colors[server_no]);
pixels.show();
Serial.begin(115200);
Serial.print("SERVER_NAME=:");
Serial.println(SERVER_NAME[server_no]);
// Create the BLE Device
BLEDevice::init(SERVER_NAME[server_no]);
// Create the BLE Server
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
pTxCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID_TX,
BLECharacteristic::PROPERTY_NOTIFY);
pTxCharacteristic->addDescriptor(new BLE2902());
BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID_RX,
BLECharacteristic::PROPERTY_WRITE);
pRxCharacteristic->setCallbacks(new MyCallbacks());
// Start the service
pService->start();
// Start advertising
pServer->getAdvertising()->start();
Serial.println("Waiting a client connection to notify...");
}
void loop() {
if (deviceConnected) {
Serial.printf("*** NOTIFY: %c ***\n", Tx_value);
char buffer[10];
sprintf(buffer, "%c", Tx_value);
pTxCharacteristic->setValue(buffer);
pTxCharacteristic->notify();
Serial.printf("%s",rx_Data.c_str());
delay(100);
}
// disconnecting
if (!deviceConnected && oldDeviceConnected) {
delay(500); // give the bluetooth stack the chance to get things ready
pServer->startAdvertising(); // restart advertising
Serial.println("start advertising");
oldDeviceConnected = deviceConnected;
}
// connecting
if (deviceConnected && !oldDeviceConnected) {
// do stuff here on connecting
oldDeviceConnected = deviceConnected;
}
}
当初悩んだ箇所ですが、親機側(クライアントソース)で接続を変えに行く動作でSERVER_NAMEをかえてget_scanすればすんなりいくと思ったのですが、1回目はすぐに切り替わっても2回目からおかしな動作をするようになりました。そこで pClient->disconnect();でクライアントを切断してSERVER_NAMEをかえて再度scan、その後は再び接続に行けば(connectToServer(*pServerAddress))別の子機に再接続できます。現在のところ約1秒で再接続できており、以前のXbeeとほぼ同じか少し早いくらいなので、実用上は問題無さそうです。
動作を動画で作成してみました。上記ソースで動かしているシンプルなやつ。
以下はより実運用に近いボタン運用と途中で子機の接続を止めて再接続させているところの動画。動作中の子機が停止した場合ボタン一つではリカバー動作できなかったので、2個それぞれの子機用にスイッチをつけました。
コネクションが切れると自動的にサーチモードに入ってしまいます。そうするとloopを回る時間がかかってしまうため、ボタンの長押しが必要だったためです。そろそろ、きちんと割り込み使ってコード書いた方が良い気がしてきました。ただし、BLEの処理で割り込み使って動作していますので、優先順位とか多重割り込みの処理とかやはり考えただけでたいへんそう。そこ考えたくないため、BLEは受信したデーターの処理も割り込みで受け取ってますけど、グローバル変数に渡して、loop内で処理してます。その場合タイミングによってはデーターの取りこぼしが発生する可能性がありますが、そこは運用でカバーするしかないです。電波使っている時点で、電波が切れるのはさすがに想定して作りますので。
さて、次はEthernet コネクターをつけて通信の実験です。TCPで確実な接続を目指すことも考えましたが、上に書いたとおり無線が元々確実な接続を想定してませんので、UDPでとりあえず接続できれば良いかと思ってます
MP-101ワイヤレスリモコンの作成 シリーズ
その1 その2 その3 その4 その5 その6 その7 その8
関連