ESP32でBluetooth(BLE)で相互通信 その1

2022年11月24日

MP-101リモコンがとある環境で動作不安定となる場面に遭遇して、ちょっと無線の限界、やはり業務的な環境ではより確実な優先接続も必要と最近自覚したところです。有線接続に改造するためには接続方法をどおするかという問題があり数十mの距離となるとarduino系ハードでシリアルコントロールしやすさとか考えるとRS-485になるのですが、コネクターやケーブルの保守などを考えるとか最近はEthernet のほうが利便性が高いように考えました。arduino 系のハードでEthernet を利用するにはシールドを使う手もありますが、最終的には基盤作成するので、SPI接続でEthernet 制御できる パーツを使うのが小型で便利です。

通信のプロトコルはTCPかUDPにしてハブをつなげばかなりの距離まで延長可能です。ここで一番の問題は機材がどうしても大きくなるところです。Ethernet コネクター自体が大きいし、このパーツ自体も結構厚みがあります。親機はすでにかなり無理して実装してありもともとケースの厚み自体がこのパーツの高さとほぼ同じです。(この製作記事結局書いてないです。MP-101用ワイヤレスリモコンの製作 その9になるはずでしたが、書く前に他の興味に移ってました。今度の制作でそのあたりもきちんとまとめます。)

今現在プログラム系というより、そういった実装方法で悩み中。その間に、有線にするけど問題ない時はやっぱり無線が現場での設営などでは楽なので、安定して動く現場ではそれも使いたい。そのため無線と優先両方で動かせるような仕様にしたいと思います。

ちょうど以前の記事で書いていたM5Stamp C3 や M5Stamp Pico があるし、最初から無線機能が実装されているのだから使わない手はないです。

一対多通信を行いたいためwifiがいいと思うのですが、使ったことがないBluetoothをまず試してみようと。

参考にさせていただいたのは、ESP32環境導入時についてくるサンプルソースのBLE_server,BLE_cliant,BLE_uart
ま~ちゃんの趣味のIT より ESP32による近距離無線通信の実験② BLE通信(2019/04/15~07/15) こちらがかなり詳しく解説していただいておりBLEの基本が良くわかりました。

根本的にはBLEは一対一通信ですが、コネクションが速いため、同時でなければ一対多通信的な動作もできるかと思い実験開始。

まず単独一対一で相互通信から

相互通信なのでどちらがサーバーでクライアントと言うことはないのですが、一応オリジナルサンプルソースがサーバー、クライアントと呼称しているのでそれに沿った名前で

//サーバー側ソース

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

BLEServer *pServer = NULL;
BLECharacteristic *pTxCharacteristic;
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 SERVER_NAME "esp32ble"
#define Tx_value 'M'
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 *pCharacteristic) {
      std::string rxValue = pCharacteristic->getValue();
      if (rxValue.length() > 0) {
        for (int i = 0; i < rxValue.length(); i++)
rx_Data=rx_Data+rxValue[i];
      }
    }
};
void setup() {
  Serial.begin(115200);
  Serial.println("BLE_Server_start");

  // Create the BLE Device
  BLEDevice::init(SERVER_NAME);

  // 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(1000);
  }
      // 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;
    }
}
//clian側ソース
#include "BLEDevice.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 SERVER_NAME         "esp32ble"
#define Tx_value 'S'

static BLEUUID  serviceUUID(SERVICE_UUID);
static BLEUUID  charUUID_RX(CHARACTERISTIC_UUID_RX);
static BLEUUID  charUUID_TX(CHARACTERISTIC_UUID_TX);

static BLEAddress *pServerAddress;
static boolean doConnect = false;
static boolean connected = false;
static BLERemoteCharacteristic* pRemoteCharacteristicRX;
static BLERemoteCharacteristic* pRemoteCharacteristicTX;

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());
    BLEClient*  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){
      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(10); 
  Serial.println("");
  Serial.println("End of scan");
}


void setup() {
  Serial.begin(115200);
  Serial.println("BLE_Cliant_start");
  BLEDevice::init("");
  get_scan();
  Serial.println("End of setup");
}


void loop() {
  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(Tx_value);
  } else{
    Serial.println("Not connected");
  //conectできないときにsscanループさせておくため
    doConnect = false;
    get_scan();
  }
  delay(1000);
}

これでとりあえずloop内で双方向にデーターのやりとりができるようになりました。

一応相手先が消えたときも待っていて再接続もすぐできるようになっているので、これを応用して多方向接続できれば複数へ切り替えての接続もなんとかなりそうです。再接続は電波状況とかにもよると思いますがかなり早い印象で運用上は問題にならないくらいの速度です。

ちなみに今回STAMP-PICOとC3で両方相互に試しましたが、同じソースで動作はできました。実装するときには小型のPICOを使う予定です。ちなみにMP-101のコントローラー親機はIOがかなり必要で以前のXbee使用していたときは、IOエキスパンダー使い16個増設さらにXbee上のIOを使いなんとか実装してました。今回Xbee使用しませんのでこのIO部分も使用できません。PICOやC3もそれほどIOは多くないし、Ethernet のSPI接続にもIO使うため、IOエキスパンダーは2個使う必要があります。最近秋月さんで売っていたお安いMCP23017がI2C、SPI共売り切れになっており、以前使用したスイッチサイエンスさんの PCAL9555APW I2C GPIOエクスパンダを2個使って基板に押し込む予定。

しかし半導体不足と聞いておりましたが、アマチュアしか使わなそうなものはこれから消えていくのでしょうか。

さて次回は複数通信に挑戦。

MP-101ワイヤレスリモコンの作成 シリーズ

その1 その2 その3 その4 その5  その6 その7 その8

関連

ESP32でBluetooth(BLE)で相互通信 その1 

ESP32でBluetooth(BLE)で相互通信 その2

M5stamp PICO UDP相互通信テスト

 

電子工作

Posted by akiba