RapberryPi Zero Wを使い、PythonのpyblenoライブラリでBLE機器のPeripheralを作成します。

pyblenoライブラリのインストール

Python言語でBLE Peripheralを作成するためにpyblenoライブラリを使用します。。次のコマンドによりpyblenoライブラリをインストールします。

$ sudo pip3 install pybleno

pyblenoライブラリを使ったサンプルコードは、「pyblenoライブラリ」にあります。

作成する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、ettingCharacteristic)をインスタンス化して、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プログラムの実行

次のコマンドを使って、今回作成するデバイス名「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

次のコマンドで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]>