2024年02月24日 更新
この記事はプログラム更新前のものです。記事に記載されているプログラム内容を更新した最新の記事を投稿しました。
詳しくは以下のリンク先をご確認ください。
https://fa-vivorock-mura.com/m5stick-c-plus-ble-ver240224/
はじめに
今日はM5stick c PlusとRaspberry Pi 4B間をBLE(Bluetooth Low Energy)で通信するプログラミングを実施します。この試みを実施する狙いは、将来的にM5stick c Plusのような小型マイコン系と工場でよく使用されるPLC間を通信できるようにするための布石です。M5stick c Plusはそれ単体だけでwifiもBluetooth通信もできる画期的なアイテムですが、PLC側がそれらに対応していないことが多いです。工場現場ではPLCが主流ですし、私も含め現場の設備管理、設備メーカーも使い慣れたPLC前提で仕事をすることがほとんどです。Raspberry Pi 4BにはRJ45コネクタが装備されているため、これを用いてPLCと通信したいと考えています。
M5stick c Plus ⇔ Raspberry Pi 4B ⇔ PLCのような経路を考えており、M5stick c PlusとPLCの橋渡しにRaspberry Pi 4Bを使用するイメージです。最近のPLCであればRJ45コネクタを内蔵、もしくは追加装備で取り付けることが可能で、modbus TCP/IP形式などの通信が可能であり、それらをわかりやすく実現するための補助機能も充実しています。
今回はM5stick c PlusとRaspberry Pi 4B間の通信に挑戦していきます。
使用するもの
M5stick c Plus
ENVIIIセンサ
|
Raspberry Pi 4B
|
プログラム環境
M5stick c Plus : Arduino IDE(C++)
Raspberry Pi 4B : Spyder(Python 3.11)
プログラム
M5stick c Plusのプログラムは以下のようになりました。
#include <M5StickCPlus.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <esp_sleep.h>
#include "M5_ENV.h"
#define T_PERIOD 10 // アドバタイジングパケットを送る秒数
#define S_PERIOD 20 // Deep Sleepする秒数
SHT3X sht30;
QMP6988 qmp6988;
float temperature = 0.0;
float humidity = 0.0;
float pressure = 0.0;
RTC_DATA_ATTR static uint8_t seq; // 送信SEQ
// BLEデータを設定する関数
void setAdvData(BLEAdvertising *pAdvertising, float temperature, float humidity, float pressure) {
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
// アドバタイジングパケットの設定
oAdvertisementData.setFlags(0x06);
std::string strServiceData = "";
strServiceData += (char)0x0a; //長さ12byte
strServiceData += (char)0xff; //AD Type
strServiceData += (char)0xff; //会社ID(ここではダミーIDを使用)
strServiceData += (char)0xff; //会社ID(ここではダミーIDを使用)
strServiceData += (char)seq; //シーケンス番号
strServiceData += (char)((uint16_t)(temperature * 100) & 0xFF); //温度の下位バイト
strServiceData += (char)(((uint16_t)(temperature * 100) >> 8) & 0xFF); //温度の上位バイト
strServiceData += (char)((uint16_t)(humidity * 100) & 0xFF); //湿度の下位バイト
strServiceData += (char)(((uint16_t)(humidity * 100) >> 8) & 0xFF); //湿度の上位バイト
strServiceData += (char)((uint16_t)(pressure / 100) & 0xFF); //圧力の下位バイト
strServiceData += (char)(((uint16_t)(pressure / 100) >> 8) & 0xFF); //圧力の上位バイト
oAdvertisementData.addData(strServiceData);
pAdvertising->setAdvertisementData(oAdvertisementData);
}
// センサーデータをディスプレイに表示する関数
void displaySensorData(float temperature, float humidity, float pressure) {
M5.Lcd.setCursor(0, 0, 1);
M5.Lcd.printf("Temperature: %4.1f'C\r\n", temperature);
M5.Lcd.printf("Humidity: %4.1f%%\r\n", humidity);
M5.Lcd.printf("Pressure: %4.0fhPa\r\n", pressure);
}
void setup() {
M5.begin();
M5.Axp.ScreenBreath(10);
M5.Lcd.setRotation(1);
M5.Lcd.setTextSize(2);
M5.Lcd.setTextColor(WHITE, BLACK);
Wire.begin(32,33);
sht30.init();
qmp6988.init();
pressure = qmp6988.calcPressure();
if (sht30.get() == 0) {
temperature = sht30.cTemp;
humidity = sht30.humidity;
} else {
temperature = 0, humidity = 0;
}
displaySensorData(temperature, humidity, pressure); // センサーデータをディスプレイに表示
BLEDevice::init("blepub-01");
BLEServer *pServer = BLEDevice::createServer();
BLEAdvertising *pAdvertising = pServer->getAdvertising();
setAdvData(pAdvertising, temperature, humidity, pressure); // BLEデータを設定
pAdvertising->start();
delay(T_PERIOD * 1000);
pAdvertising->stop();
seq++;
delay(10);
esp_deep_sleep(1000000LL * S_PERIOD);
}
void loop() {
// このプログラムではloop()は使用されないので、何も記述しない
}
ここで嵌ったので補足しておきます。
「strServiceData += (char)0x0a;
」の部分で送信するデータ長さを16進数で指定します。
今回はAD Typeで1オクテット、会社IDで2オクテット、シーケンス番号で1オクテット、そして温度、湿度、圧力でそれぞれ2オクテットを使用しており、合計で10byteとなります。そのためデータの長さも0x0aとしました。
当初は理解せず使用していたため、指定したデータ長さと送信するデータ数に差異があったことから通信がうまくいきませんでした。仮に上記のプログラムに追加してほかのデータも送信する場合や減らして送信する場合は、それに伴いデータ長さも変更する必要があります。
(すみません。この辺はまだ勉強中でそこまで詳しくないです。)
また今回は試験用のため会社IDにダミーの0xff×2を使用しましたが、本格的に使用する場合Bluetooth SIG(Special Interest Group)に参加してCompany IDを申請する必要があるそうです。
Raspberry Pi 4Bのプログラムは以下のようになりました。
from bluepy.btle import DefaultDelegate, Scanner, BTLEException
import sys
import struct
from datetime import datetime
class ScanDelegate(DefaultDelegate):
def __init__(self): # コンストラクタ
DefaultDelegate.__init__(self)
self.lastseq = None
self.lasttime = datetime.fromtimestamp(0)
def handleDiscovery(self, dev, isNewDev, isNewData):
if isNewDev or isNewData: # 新しいデバイスまたは新しいデータ
for (adtype, desc, value) in dev.getScanData(): # データの数だけ繰り返す
if desc == 'Manufacturer' and value[0:4] == 'ffff': # テスト用companyID
__delta = datetime.now() - self.lasttime
# アドバタイズする10秒の間に複数回測定されseqが加算されたものは捨てる(最初に取得された1個のみを使用する)
if value[4:6] != self.lastseq and __delta.total_seconds() > 11:
self.lastseq = value[4:6] # Seqと時刻を保存
self.lasttime = datetime.now()
(temperature, humidity, pressure) = struct.unpack('<hhh', bytes.fromhex(value[6:])) # hは2Byte整数(3つ取り出す)
print('温度= {0} 度、 湿度= {1} %、 気圧 = {2} hPa'.format( temperature / 100, humidity / 100, pressure))
if __name__ == "__main__":
scanner = Scanner().withDelegate(ScanDelegate())
while True:
try:
scanner.scan(5.0) # スキャンする。デバイスを見つけた後の処理はScanDelegateに任せる
except BTLEException:
ex, ms, tb = sys.exc_info()
print('BLE exception '+str(type(ms)) + ' at ' + sys._getframe().f_code.co_name)
このプログラムをRaspberry Pi 4Bのホームフォルダにbleという名前のフォルダを作り、その中にble_sub.pyというファイル名で保存しました。
プログラム実行結果
M5stick c Plus側では30秒ごとにスリープから復帰して測定データを画面表示+データ送信を実施しています。
送信されたデータをRaspberry Pi 4B側で確認する方法を説明します。
- まずRaspberry Pi 4B側でBluetooth機能がONになっているかを確認してください。右上のBluetoothアイコンからONとOFFを切り替えられます。
- 次にLXTerminal(左上の左から4番目のアイコン)を立ち上げます。
- 立ち上げ直後のLXTerminalではホームフォルダのままですので、「cd ble」と打ち込みbleフォルダへ移動します(フォルダ名や場所が変わる場合はコマンドの入力内容が変わります)。
- 「sudo python ble_sub.py」と入力し、実行すると下写真のように30秒ごとにM5stick c Plusで測定した温度、湿度、圧力の測定値が改行とともに入力されます。