注目キーワード
  1. Photoshop
  2. Python
  3. Raspberry Pi
  4. Arduino

BLE通信でESP32から取得したセンサ値をRaspberryPiへ送信してみた

ここでは「ESP32」と「Raspberry Pi」をBLE通信を使って接続し、ESP32から取得したセンサ値をRaspberry Piへ送信する方法を紹介します。

 

この記事を読むことで

ESP32、ラズパイ間のBLE通信方法

Bleakを使ったデータの受信方法

非同期処理:asyncioの使い方

などを知ることができます。

 

その中で今回は2軸ジョイスティックのセンサ値をESP32で取得し、ラズパイへ送信をしていきます。

実際の動作は下図の動画のようになります。

joystick_ble

 

上の動画では、ESP32の電源をPCから取り、BLE通信で離れたラズパイへデータを送信しています。

ジョイスティックのセンサ値を送受信できるということは、温度・湿度なども取得が可能なので、チャレンジしてみると面白いと思います。

 

それでは始めていきますね。

 

BLE通信で送受信する前に

まずBLE通信でESP32とRaspberry Piのデータを送受信する前に、いくつか確認をしておきます。

 

BLE通信と使用するライブラリ

BLE通信」とは

Bluetooth Low Energy

の略で、省電力で通信する方式になります。

詳しく知りたい方は調べて頂けれ場と思います。

通信屋さんでないので、ここではBLE通信を行うのに必要なライブラリを紹介しておきます。

 

PythonでBLE通信をするパッケージで有名どころなのは

pybluez

bleak

などだと思います。

 

どちらもネットに情報が少ないですが

Git、Qiita、Zenn、Udemy、note

などで調べれば何かしらヒットするので、頑張って基礎知識を身に付けましょう。

 

2つの中で今回は

Gitで見つけたコードを使う

様々なOSに対応できる

という理由で「Bleak」というパッケージを使い、データの送受信をしていきたいと思います。

 

したがって、ラズパイの方に「Bleak」のパッケージをインストールしておきましょう。

sudo pip3 install 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通信プログラムのベース

GitHub:BLE_bleak_memo

 

この記事で紹介するBLE通信プログラムのベースは、naikasannさんのGitHubにて公開されているコードになります。

日本語で記述してあり、非常に分かりやすかったので参考にさせていただきました。

2021/02/14日現在、彼のGitHubに「License」の記載がなかったので、引用&参考という形で当サイトで使用させていただきます。

naikasannさんには

「勝手を申しますが、プログラムを使用させていただきます。ありがとうございます。」

と心より感謝申し上げます。

 

BLE通信の基礎知識な文法

ESP32によるBLEアプリケーション開発の基礎知識

 

先ほど紹介したnaikasannのプログラムを解読するのにお世話になりました。

特に、BLEのキャラスタリック、アドバタイジングのお話は勉強になるので、下記を見る前に一読しておくと良いかと思います。

Arduino IDEでプログラムを書き込むときに、何をしているかが分かりやすくなりますよ。

 

BLE通信下のESP32-AnalogReadについて

ソースに絡まるエスカルゴ:ESP32 Wifi接続時のanalogReadについて

 

この記事を探すまでは、ESP32のデータをラズパイにすら送信できなかったので、非常に助かりました。

ESP32で2軸ジョイスティックのセンサ値を取るときは下記記事に記載したように

ESP32でジョイスティックからXY値、SW値を取得してみた

 

ESP32のピン25、26を使用していたのですが、25、26番ピンに接続していると

BLE通信下では値が固まる(一定値のまま)

といった現象が起こります。

 

これは、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」関数のプログラムに従って処理をしていきます。

データはバイナリアレイ型だと見にくいので

data.decode()

として出力すると良いかと思います。

 

asynicoのKeyboardInterrupt

ladvien.com : How to send Data between PC and Arduino using Bluetooth LE

 

先ほど紹介した記事からもう一つ、どうしても記載しておきたいのが「asynico」のKeyboardInterrupt。

 

ESP32とラズパイの通信をKeyboardInterruptで止めてしまうと

BEL通信が切断されないまま、ラズパイのみプログラムが終了する

という事態になります。

 

これの何が困ったかというと、BLE通信を切断するために毎回Arduino IDEでESP32にプログラムを書き込まなければいけなくなったということです。

 

BLE通信を行うために、言い換えるとBLE通信を行うために使用する非同期処理「asynico」のループをしっかりと止めないと、大変だということです。

 

上記の記事では、非同期処理のイベントループについても説明が十分に記載されているので、目を通しておくのをオススメします。

 

BLE通信のプログラム全文

上記まででプログラムで使用しているパッケージ・内容を一通り知ることができるので、あとは自分がBLE通信でやってみたいことをプログラムするだけです。

 

文頭でも記載しましたが、以下は

ESP32から2軸ジョイスティックのセンサ値を取得し、ラズパイへ送信するプログラム

になります。

 

以下のプログラムを書き換えれば

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);
}

 

プログラムのコピペができたら、「ツール」>「シリアルモニタ」でシリアルモニタのウィンドウを開き、ラズパイとの接続を監視できます。

 

joystick_ble_01

 

Raspberry Pi:データ受信プログラム

ラズパイへ下記のプログラムを書き込むときに

ADDRESS = “**:**:**:**:**:**”

を各自変えてください。

 

BLEのアドレスを検知するにはラズパイのコマンド上で

sudo hcitool lescan

を実行すれば、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」を記載していただけると大変うれしく思います。

 

お疲れ様でした。

joystick_ble_top
学びに関する情報をチェック!