RapberryPi Zero Wを使い、PythonのpyblenoライブラリでBLE機器のPeripheralを作成します。
pyblenoというライブラリを使ってペリフェラルを立ち上げることにしましたが,2021年6月現在,ペリフェラルからセントラルに値が変化したときに通知する「Notification」という機能が2018-11-13以降のラズベリーパイOSでは動作しません.公式ドキュメントによると,リナックスカーネルのBluetoothモジュールのバグが原因ということで,通知機能が必須な場合はラズベリーパイのOSを2018-11-13以前にする必要があります.
2022/07時点でのラズベリーパイOSでpyblenoライブラリが動作することが確認できました。「Adam-Langley/pybleno」の「Troubleshooting」を見るとFixになっています。
ここで作成したPeripheralプログラムはWindows PCとの接続が上手くいきません。「BLEにより RapberryPi Zero WをPCに接続」を参照してください。
pyblenoライブラリのインストール
Python言語でBLE Peripheralを作成するためにpyblenoライブラリを使用します。次のコマンドによりpyblenoライブラリをインストールします。
$ sudo pip3 install pybleno
pyblenoライブラリを使ったサンプルコードは、「pyblenoライブラリ」にあります。
2014/02/07時点で、上記のコマンドを実行したところ、python version 3.11では実行エラー「ValueError: invalid mode: ‘rU’」となります。Raspberry OS「bullseye」に変更し、python version 3.9にして実行したところ、実行できることを確認しました。
作成するBLE Peripheralプログラムの仕様
使用するUUIDを次に示します。
0000fff0-0000-1000-8000-00805f9b34fb:サービスUUID
0000fff0-0000-1000-8000-00805f9b34fb:Notifyによるデータの読み込み
0000fff3-0000-1000-8000-00805f9b34fb:初期設定用のコマンド
0000fff4-0000-1000-8000-00805f9b34fb:動作を指示するコマンド
二種類のコマンドを受け取るごとに、グローバルに定義されたカウンターの値を1づつ更新して、ASCIIコードに変換し、Notifyにより通知します。
BLE Peripheralプログラムの作成
BLE Peripheralプログラムを次に示します。
- 102-104行目で 、各クラス( ReadCharacteristic、CommandCharacteristic、SettingCharacteristic)をインスタンス化して、66行目でサービスUUIDとしてまとめます。
- 各クラスのCHARACTERISTIC UUIDを13行目等、propertiesを14行目等にそれぞれ設定し、必要な「onxxx」を定義します。
- 105、106行目でコールバック関数「onWriteRequest」をオーバライドして、クラス内でなく、外部関数として定義し、それぞれのUUIDにデータが書き込まれると呼び出されるように変更します。
bleperipheral.py
import time from pybleno import * TOMOSOFT_SERVICE_UUID = '0000fff0-0000-1000-8000-00805f9b34fb' READ_CHARACTERISTIC_UUID = '0000fff0-0000-1000-8000-00805f9b34fb' SETTING_CHARACTERISTIC_UUID = '0000fff3-0000-1000-8000-00805f9b34fb' COMMAND_CHARACTERISTIC_UUID = '0000fff4-0000-1000-8000-00805f9b34fb' class ReadCharacteristic(Characteristic): def __init__(self): Characteristic.__init__(self, { 'uuid': READ_CHARACTERISTIC_UUID, 'properties': ['read', 'notify'], 'value': None }) self._value = 0 self._updateValueCallback = None def onSubscribe(self, maxValueSize, updateValueCallback): print('ApproachCharacteristic - onSubscribe') self._updateValueCallback = updateValueCallback def onUnsubscribe(self): print('ApproachCharacteristic - onUnsubscribe') self._updateValueCallback = None class SettingCharacteristic(Characteristic): def __init__(self): Characteristic.__init__(self, { 'uuid': SETTING_CHARACTERISTIC_UUID, 'properties': ['write'], 'value': None }) def onWriteRequest(self, data, offset, withoutResponse, callback): callback(self.RESULT_SUCCESS) class CommandCharacteristic(Characteristic): def __init__(self): Characteristic.__init__(self, { 'uuid': COMMAND_CHARACTERISTIC_UUID, 'properties': ['write'], 'value': None }) def onWriteRequest(self, data, offset, withoutResponse, callback): callback(self.RESULT_SUCCESS) def onStateChange(state): print('on -> stateChange: ' + state) if (state == 'poweredOn'): bleno.startAdvertising('TomoSoftx', [TOMOSOFT_SERVICE_UUID]) else: bleno.stopAdvertising() def onAdvertisingStart(error): print('on -> advertisingStart1: ' + ('error ' + error if error else 'success')) if not error: print('onAdvertisingStart') bleno.setServices([ BlenoPrimaryService({ 'uuid': TOMOSOFT_SERVICE_UUID, 'characteristics': [ tomosoftCharacteristic_cmd, tomosoftCharacteristic_read, tomosoftCharacteristic_set ] }) ]) def onWriteRequest_cmd(data, offset, withoutResponse, callback): global counter counter += 1 tomosoftCharacteristic_cmd._value = counter if tomosoftCharacteristic_read._updateValueCallback: print('Sending notification with value-cmd : ' + str(tomosoftCharacteristic_cmd._value)) notificationBytes = str(tomosoftCharacteristic_cmd._value).encode() tomosoftCharacteristic_read._updateValueCallback(notificationBytes) def onWriteRequest_set(data, offset, withoutResponse, callback): global counter counter += 1 tomosoftCharacteristic_cmd._value = counter if tomosoftCharacteristic_read._updateValueCallback: print('Sending notification with value-set : ' + str(tomosoftCharacteristic_cmd._value)) notificationBytes = str(tomosoftCharacteristic_cmd._value).encode() tomosoftCharacteristic_read._updateValueCallback(notificationBytes) if __name__ == '__main__': bleno = Bleno() bleno.on('stateChange', onStateChange) bleno.on('advertisingStart', onAdvertisingStart) tomosoftCharacteristic_read = ReadCharacteristic() tomosoftCharacteristic_cmd = CommandCharacteristic() tomosoftCharacteristic_set = SettingCharacteristic() tomosoftCharacteristic_cmd.on('writeRequest', onWriteRequest_cmd) tomosoftCharacteristic_set.on('writeRequest', onWriteRequest_set) bleno.start() counter = 0 while True: time.sleep(1)
BLE Peripheralプログラムの実行
もう一台のRapberryPiで次のコマンドを使って、今回作成するデバイス名「TomoSoftx」のアドレスを調べます。
$ sudo hcitool lescan LE Scan ... B8:27:EB:ED:D3:E5 (unknown) B8:27:EB:ED:D3:E5 TomoSoftx C4:64:E3:FC:13:07 (unknown)
次のコマンドでBLE Peripheralプログラムを実行します。
- BLE Peripheralに接続すると「onAdvertisingStart」が表示されます。
- Notifyを受け取ると「ApproachCharacteristic – onSubscribe」が表示されます。
- uuid「0000fff3-0000-1000-8000-00805f9b34fb」にデータが書き込まれると、「Sending notification with value-set : 1」が表示されます。
- uuid「0000fff4-0000-1000-8000-00805f9b34fb」にデータが書き込まれると、「Sending notification with value-cmd : 2」が表示されます。
$ sudo python3 bleperipheral.py on -> stateChange: poweredOn on -> advertisingStart1: success onAdvertisingStart ApproachCharacteristic - onSubscribe Sending notification with value-set : 1 Sending notification with value-cmd : 2
もう一台のRapberryPiで次のコマンドによりBLE Peripheralプログラムと接続して、データを転送します。
- 「0000fff0-0000-1000-8000-00805f9b34fb」をNotityとして使用したいので、char-descにより表示された「00002902-0000-1000-8000-00805f9b34fb」に対応するハンドル「0x0f 」を使用し、「char-write-req 0x0f 0100」で書き込みます。
- それぞれのコマンドのハンドルを、characteristicsで表示された一覧から調べます。
$ sudo gatttool -b B8:27:EB:ED:D3:E5 --interactive [B8:27:EB:ED:D3:E5][LE]> connect Attempting to connect to B8:27:EB:ED:D3:E5 Connection successful [B8:27:EB:ED:D3:E5][LE]> characteristics handle: 0x0002, char properties: 0x02, char value handle: 0x0003, uuid: 00002a00-0000-1000-8000-00805f9b34fb handle: 0x0004, char properties: 0x02, char value handle: 0x0005, uuid: 00002a01-0000-1000-8000-00805f9b34fb handle: 0x0007, char properties: 0x20, char value handle: 0x0008, uuid: 00002a05-0000-1000-8000-00805f9b34fb handle: 0x000b, char properties: 0x08, char value handle: 0x000c, uuid: 0000fff4-0000-1000-8000-00805f9b34fb handle: 0x000d, char properties: 0x12, char value handle: 0x000e, uuid: 0000fff0-0000-1000-8000-00805f9b34fb handle: 0x0010, char properties: 0x08, char value handle: 0x0011, uuid: 0000fff3-0000-1000-8000-00805f9b34fb [B8:27:EB:ED:D3:E5][LE]> char-desc handle: 0x0001, uuid: 00002800-0000-1000-8000-00805f9b34fb handle: 0x0002, uuid: 00002803-0000-1000-8000-00805f9b34fb handle: 0x0003, uuid: 00002a00-0000-1000-8000-00805f9b34fb handle: 0x0004, uuid: 00002803-0000-1000-8000-00805f9b34fb handle: 0x0005, uuid: 00002a01-0000-1000-8000-00805f9b34fb handle: 0x0006, uuid: 00002800-0000-1000-8000-00805f9b34fb handle: 0x0007, uuid: 00002803-0000-1000-8000-00805f9b34fb handle: 0x0008, uuid: 00002a05-0000-1000-8000-00805f9b34fb handle: 0x0009, uuid: 00002902-0000-1000-8000-00805f9b34fb handle: 0x000a, uuid: 00002800-0000-1000-8000-00805f9b34fb handle: 0x000b, uuid: 00002803-0000-1000-8000-00805f9b34fb handle: 0x000c, uuid: 0000fff4-0000-1000-8000-00805f9b34fb handle: 0x000d, uuid: 00002803-0000-1000-8000-00805f9b34fb handle: 0x000e, uuid: 0000fff0-0000-1000-8000-00805f9b34fb handle: 0x000f, uuid: 00002902-0000-1000-8000-00805f9b34fb handle: 0x0010, uuid: 00002803-0000-1000-8000-00805f9b34fb handle: 0x0011, uuid: 0000fff3-0000-1000-8000-00805f9b34fb [B8:27:EB:ED:D3:E5][LE]> char-write-req 0x0f 0100 Characteristic value was written successfully [B8:27:EB:ED:D3:E5][LE]> char-write-req 0x11 020 Characteristic value was written successfully Notification handle = 0x000e value: 31 [B8:27:EB:ED:D3:E5][LE]> char-write-req 0x0c 020 Characteristic value was written successfully Notification handle = 0x000e value: 32 [B8:27:EB:ED:D3:E5][LE]>