ここでは「ESP32」と「Raspberry Pi」をBLE通信を使って接続し、ESP32から取得したセンサ値をRaspberry Piへ送信する方法を紹介します。
この記事を読むことで
ESP32、ラズパイ間のBLE通信方法
Bleakを使ったデータの受信方法
非同期処理:asyncioの使い方
などを知ることができます。
その中で今回は2軸ジョイスティックのセンサ値をESP32で取得し、ラズパイへ送信をしていきます。
実際の動作は下図の動画のようになります。
上の動画では、ESP32の電源をPCから取り、BLE通信で離れたラズパイへデータを送信しています。
ジョイスティックのセンサ値を送受信できるということは、温度・湿度なども取得が可能なので、チャレンジしてみると面白いと思います。
それでは始めていきますね。
BLE通信で送受信する前に
まずBLE通信でESP32とRaspberry Piのデータを送受信する前に、いくつか確認をしておきます。
BLE通信と使用するライブラリ
「BLE通信」とは
の略で、省電力で通信する方式になります。
詳しく知りたい方は調べて頂けれ場と思います。
通信屋さんでないので、ここではBLE通信を行うのに必要なライブラリを紹介しておきます。
PythonでBLE通信をするパッケージで有名どころなのは
pybluez
bleak
などだと思います。
どちらもネットに情報が少ないですが
などで調べれば何かしらヒットするので、頑張って基礎知識を身に付けましょう。
2つの中で今回は
Gitで見つけたコードを使う
様々なOSに対応できる
という理由で「Bleak」というパッケージを使い、データの送受信をしていきたいと思います。
したがって、ラズパイの方に「Bleak」のパッケージをインストールしておきましょう。
インストールできたら、パッケージ類の準備は完了です。
開発環境や必要な物
ESP32とRaspberry Piでデータの送受信をする際に必要な環境・物は以下の通りです。
開発環境&必要なもの
OS:Windows10
IDE_1:Arduino IDE 1.8.9
IDE_2:VS code
TeraTerm :ラズパイSSH用
Raspberry Pi4 model B
ESP32 ボードマネージャ(Arduino IDE書き込み時に必須)
USB – Micro-B
ESP32本体
2軸ジョイスティック
ジャンパ線複数
抵抗
ブレッドボード
開発環境で特にこれが必要!というものはありません。
PC、マイコンが扱える方なら問題ないかと思います。
BLE通信の参考・引用と理由
BLE通信でESP32とラズパイを送受信させるのに参考・引用したのは以下の通りです。
理由とともに記述しておきます。
BLE通信プログラムのベース
この記事で紹介するBLE通信プログラムのベースは、naikasannさんのGitHubにて公開されているコードになります。
日本語で記述してあり、非常に分かりやすかったので参考にさせていただきました。
2021/02/14日現在、彼のGitHubに「License」の記載がなかったので、引用&参考という形で当サイトで使用させていただきます。
naikasannさんには
「勝手を申しますが、プログラムを使用させていただきます。ありがとうございます。」
と心より感謝申し上げます。
BLE通信の基礎知識な文法
先ほど紹介したnaikasannのプログラムを解読するのにお世話になりました。
特に、BLEのキャラスタリック、アドバタイジングのお話は勉強になるので、下記を見る前に一読しておくと良いかと思います。
Arduino IDEでプログラムを書き込むときに、何をしているかが分かりやすくなりますよ。
BLE通信下のESP32-AnalogReadについて
ソースに絡まるエスカルゴ:ESP32 Wifi接続時のanalogReadについて
この記事を探すまでは、ESP32のデータをラズパイにすら送信できなかったので、非常に助かりました。
ESP32で2軸ジョイスティックのセンサ値を取るときは下記記事に記載したように
ESP32のピン25、26を使用していたのですが、25、26番ピンに接続していると
といった現象が起こります。
これは、Wifi環境下でも同じことが起こるようなので、ESP32とジョイスティックの接続は
ESP32 ⇔ ジョイスティックGND ⇔ GND
5V ⇔ +5V
33PIN ⇔ VRx
32PIN ⇔ VRy
35PIN ⇔ SW
としています。
ESP32を使い始めたばかりだったので、これにかなり時間を取られました。(-ω-)/
notification_handlerの使い方
ladvien.com : How to send Data between PC and Arduino using Bluetooth LE
BleakでESP32とRaspberry Piを通信させるときに、関数の「notification_handler」から取得するデータを扱うのに困っていました。
そんなときに、悩みを一瞬で解決してくれたサイトが上になります。
「notification_handler」関数の説明をしっかりしているサイトはたぶん上のサイトだけだと思います。
英語ですが、非常に分かりやすく、Args(引数)の説明もあったので非常に助かりました。
一応、ここで簡単に「notification_handler」の説明をしておくと
●「notification_handler」はキャラスタリックを更新するたびに「BleakClient(Bleakパッケージの一つ)」によって呼び出されるもの。
●2つの引数の内、「sender」は更新を行うデバイス名で、「data」はバイナリアレイ型の受信データになる。
という関数になります。
ここでは、ESP32から送信されたデータをこちらに記載されている「notification_handler」関数のプログラムに従って処理をしていきます。
データはバイナリアレイ型だと見にくいので
として出力すると良いかと思います。
asynicoのKeyboardInterrupt
ladvien.com : How to send Data between PC and Arduino using Bluetooth LE
先ほど紹介した記事からもう一つ、どうしても記載しておきたいのが「asynico」のKeyboardInterrupt。
ESP32とラズパイの通信をKeyboardInterruptで止めてしまうと
という事態になります。
これの何が困ったかというと、BLE通信を切断するために毎回Arduino IDEでESP32にプログラムを書き込まなければいけなくなったということです。
BLE通信を行うために、言い換えるとBLE通信を行うために使用する非同期処理「asynico」のループをしっかりと止めないと、大変だということです。
上記の記事では、非同期処理のイベントループについても説明が十分に記載されているので、目を通しておくのをオススメします。
BLE通信のプログラム全文
上記まででプログラムで使用しているパッケージ・内容を一通り知ることができるので、あとは自分がBLE通信でやってみたいことをプログラムするだけです。
文頭でも記載しましたが、以下は
になります。
以下のプログラムを書き換えれば
BLE通信による温度・湿度管理
自作百葉箱
なんかも簡単にできると思いますよ。
ESP32:データ送信プログラム
使用するセンサによって、ピンやデータ受信数を変えてください。
ESP32_BLE_test.ino
#include <BLEDevice.h> #include <BLEServer.h> #include <BLEUtils.h> #include <BLE2902.h> // BLE characteristic #define SERVICE_UUID "28b0883b-7ec3-4b46-8f64-8559ae036e4e" #define CHARACTERISTIC_UUID_TX "2049779d-88a9-403a-9c59-c7df79e1dd7c" // BLE Device name #define DEVICENAME "ESP32" // serial speed #define SPI_SPEED 115200 // Get VRX and VRY data from joystick module const int JOY_STICK_VRX = 33; const int JOY_STICK_VRY = 32; const int PUSH_BUTTON = 35; // characteristic valueable BLECharacteristic *pCharacteristicTX; bool deviceConnected = false; // send data int senddata = 0; int senddata2 = 0; int senddata3 = 0; // Server Callbacks of Connection class funcServerCallbacks: public BLEServerCallbacks{ void onConnect(BLEServer* pServer){ deviceConnected = true; } void onDisconnect(BLEServer* pServer){ deviceConnected = false; } }; // Characteristic void doPrepare(BLEService *pService){ // Create Characteristic of Notify pCharacteristicTX = pService->createCharacteristic( CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY ); pCharacteristicTX->addDescriptor(new BLE2902()); } void doInitialize() { pinMode(JOY_STICK_VRX, INPUT); pinMode(JOY_STICK_VRY, INPUT); pinMode(PUSH_BUTTON, INPUT_PULLUP); Serial.begin(SPI_SPEED); } void setup() { // Initialize the pinMode doInitialize(); // Initialize the BLE environment BLEDevice::init(DEVICENAME); // Create the server BLEServer *pServer = BLEDevice::createServer(); // Callback the server pServer->setCallbacks(new funcServerCallbacks); // Create the service BLEService *pService = pServer->createService(SERVICE_UUID); // Create the characteristic doPrepare(pService); // Start the service pService->start(); BLEAdvertising *pAdvertising = pServer->getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pAdvertising->start(); // Wait Connect Serial.println("Waiting to connect..."); while(!deviceConnected){delay(100);} // Connection Serial.println("Connection!"); delay(100); } void loop() { char c[100]; char d[100]; char e[100]; // Device Connected if(deviceConnected){ // AnalogRead range of ESP32 is 0 to 4095 float x_axis = analogRead(JOY_STICK_VRX); float y_axis = analogRead(JOY_STICK_VRY); int sw = digitalRead(PUSH_BUTTON); int axis_max = 4095; // Convert joy stick value from -45 to 45 x_axis = (x_axis - axis_max / 2) / 45.51; y_axis = (y_axis - axis_max / 2) / 45.51; senddata = x_axis; senddata2 = y_axis; senddata3 = sw; sprintf(c,"%d",senddata); sprintf(d,"%d",senddata2); sprintf(e,"%d",senddata3); // ser Characteristic and send data pCharacteristicTX->setValue(c); pCharacteristicTX->notify(); pCharacteristicTX->setValue(d); pCharacteristicTX->notify(); pCharacteristicTX->setValue(e); pCharacteristicTX->notify(); // debag value Serial.print("X_axis: "); Serial.println(senddata); Serial.print("Y_axis: "); Serial.println(senddata2); Serial.print("SW: "); Serial.println(senddata3); Serial.print("\n"); }else{ // Device disconnected Serial.println("disconnect"); } delay(500); }
プログラムのコピペができたら、「ツール」>「シリアルモニタ」でシリアルモニタのウィンドウを開き、ラズパイとの接続を監視できます。
Raspberry Pi:データ受信プログラム
ラズパイへ下記のプログラムを書き込むときに
を各自変えてください。
BLEのアドレスを検知するにはラズパイのコマンド上で
を実行すれば、Bluetoothのアドレスと、デバイス名が表示されます。
デバイス名はArduino IDEでESP32に書き込んだ時に指定した名前(上のプログラムでは”ESP32”)になります。
指定したデバイス名のBLEアドレスへ、値を変えておきましょう。
notification.py
"""BLE connection between ESP32 and RaspberryPi""" import asyncio import sys from bleak import BleakClient # set characteristic uuid of Arduino, ESP32, etc... CHARACTERISTIC_UUID = "2049779d-88a9-403a-9c59-c7df79e1dd7c" # set ESP32 BLE address ADDRESS = "**:**:**:**:**:**" class Esp32Ble(object): """Base model for BLE connect""" def __init__(self, data_dump_size: int=2048): """Initialize the value. """ self.value = 0 self.rx_data = [] self.dump_size = data_dump_size def clear_lists(self): """rx_data is clear. """ self.rx_data.clear() def value_get(self): """TX Value is get and print. ※ This program doesn't use the return value. Returns: vrx (int): VRx axis value of joy stick vry (int): VRy axis value of joy stick sw (int): sw ON/OFF of joy stick """ try: vrx = self.rx_data[0] vry = self.rx_data[1] sw = self.rx_data[2] except Exception: vrx, vry, sw = 0, 0, 0 print("VRx: {} VRy: {} SW: {}".format(vrx, vry, sw) + "\n") return vrx, vry, sw def notification_handler(self, sender, data): """Simple notification handler which prints the data received. Args: data (bytearray): bytearray data. For example b'***' """ self.rx_data.append(int.from_bytes(data, byteorder="big")) if len(self.rx_data) >= self.dump_size: self.clear_lists() self.value_get() self.rx_data[self.value] = data.decode() if self.value == 2: self.value = 0 else: self.value += 1 async def run(self, address): """BLE asyncio loop function. Args: address (string): ESP32 address """ async with BleakClient(address) as client: x = await client.is_connected() # Check connection status print("Connected: {}".format(x)) await client.start_notify( CHARACTERISTIC_UUID, self.notification_handler ) while True: try: await asyncio.sleep(1.0) except Exception: break def main(self): """BLE main function. """ loop = asyncio.get_event_loop() task = asyncio.ensure_future(self.run(ADDRESS)) try: loop.run_until_complete(task) except KeyboardInterrupt: print("KeyboardInterrupt!") task.cancel() loop.run_forever() finally: loop.close() if __name__ == "__main__": ble = Esp32Ble() ble.main()
実行は、ESP32のプログラム書き込みが完了している状態で上記のPythonプログラムを実行するだけです。
今後の展望と課題
ここまでBLE通信をESP32とラズパイでやってみて、見えてきたことをメモしておきます。
非同期による問題点
非同期処理の「asynico」は、イベントループをプログラマー側の都合で書き換えるのがかなり難しいです。
特に、別のプログラムから呼び出すときには非常に使いにくく、マルチプロセスと併用して使ってみたところ、上手くいきませんでした。
これは非同期処理と並列処理が混在して、何かかしら問題が発生しているのではないかと思っています。(この辺は知識不足なので、要勉強)
上記プログラムをマルチプロセスで呼び出して、値の共有が出来るよ!という方はTwitterのDMでご教示していただけると幸いです。(>_<)
BLE通信によるデータ送受信の代替案
取り敢えず、BLE通信でデータの送受信はできたが、値の共有をしたいので、ESP32をMicroPython 用にしてデータの送受信を試してみようと思います。
~さいごに~
最後までご覧いただきありがとうございます。
BLE通信を行う際に、当サイトがお役に立てば幸いです。
また、この記事を引用、参考にする際には「サイト名・URL」を記載していただけると大変うれしく思います。
お疲れ様でした。