ブロックを積みながら(19) micro:bit BLE, Pythonで「特性」読み出し

Joseph Halfmoon

前回、bluepy モジュールを使ってラズパイ上のPython3のスクリプトでBBC micro:bit のBLEアトリビュート内の「特性(characteristic)」のハンドルをリストいたしました。ハンドルが分かれば実際の値にアクセスできます。今回は、読み出すだけの特性を中心に「読んで」みたいと思います。

※「ブロックを積みながら」投稿順 index はこちら

今回も、前回に引き続き micro:bit側は、同じオブジェクトHEXファイルを使っているのでブロックは積みません。作業は、Raspberry Pi 3 model B+上のPython3スクリプトであります。今回使用したスクリプト(とりあえず版)は末尾に全文を置いてあります。

さて、micro:bitがPeriphal、ラズパイがCentralというBLE上の役割分担です。micro:bit内のアトリビュートにアクセスするのに参照しなくてはならないのは、bluepy ドキュメンテーション内の以下のクラスの部分です。

The Peripheral class

添付のスクリプトは、

  • 知りたいことをコマンドライン引数としてスクリプトに与える
  • 指定のBLEアドレスへconnectできれば、その知りたいことを標準出力にダンプ
  • connectできなければエラーで終わる

という単純なものです。ペアリングおよび、BLEアドレスのSCANは済との前提です。SCANが不要なので、sudo する必要はありません。とりあえずテストに使っている手元のmicro:bitのBLEデバイスアドレスをデフォルト値として内部にハードコードしてあるので以下の試行ではBLEアドレスを与えていません。引数に

--ADR xx:xx:xx:xx:xx:xx

などとBLEアドレスを与えれば他のmicro:bitでも動作するんじゃないかと思います(とりあえず版なので確かめてません、すみません。)

UUIDの一覧

まずは先週調べた、UUIDとハンドル、プロパティの一覧の取得です。以下のように -uuid オプションを与えれば一覧が出力されます。こんな感じ。

$ python3 microbitBLEutil.py -uuid
microbitBLEutil v0.1
CONNECTED: F5:D1:5F:02:68:25
UUID: 00002a00-0000-1000-8000-00805f9b34fb Handle: 0x0003 Prop: READ WRITE 
UUID: 00002a01-0000-1000-8000-00805f9b34fb Handle: 0x0005 Prop: READ 
UUID: 00002a04-0000-1000-8000-00805f9b34fb Handle: 0x0007 Prop: READ 
UUID: 00002a05-0000-1000-8000-00805f9b34fb Handle: 0x000a Prop: INDICATE 
UUID: e95d93b1-251d-470a-a062-fa1922dfa9a8 Handle: 0x000e Prop: READ WRITE 
UUID: e97d3b10-251d-470a-a062-fa1922dfa9a8 Handle: 0x0011 Prop: WRITE NO RESPONSE NOTIFY 
UUID: 00002a24-0000-1000-8000-00805f9b34fb Handle: 0x0015 Prop: READ 
UUID: 00002a25-0000-1000-8000-00805f9b34fb Handle: 0x0017 Prop: READ 
UUID: 00002a26-0000-1000-8000-00805f9b34fb Handle: 0x0019 Prop: READ 
UUID: e95d9775-251d-470a-a062-fa1922dfa9a8 Handle: 0x001c Prop: READ NOTIFY 
UUID: e95d5404-251d-470a-a062-fa1922dfa9a8 Handle: 0x001f Prop: WRITE NO RESPONSE WRITE 
UUID: e95d23c4-251d-470a-a062-fa1922dfa9a8 Handle: 0x0021 Prop: WRITE 
UUID: e95db84c-251d-470a-a062-fa1922dfa9a8 Handle: 0x0023 Prop: READ NOTIFY 
UUID: 6e400002-b5a3-f393-e0a9-e50e24dcca9e Handle: 0x0027 Prop: INDICATE 
UUID: 6e400003-b5a3-f393-e0a9-e50e24dcca9e Handle: 0x002a Prop: WRITE NO RESPONSE WRITE 
DISCONNECTED.
デバイス固有の情報など

ここで調べたHandleの値が変わらぬものとして、デバイス名など「読み取れば意味がとれる特性」を読み出すのには -info オプションが使えます。末尾のコードを見ればわかりますが、先週調べたハンドルが変わらぬ前提でハードコードしてあるので、装置によっては異なる可能性も無きにしもあらず。BLEの規定により、少なくとも手元のmicro:bitとラズパイの「ペア」に関してはこの番号で変化しない筈。

デバイス名、シリアル番号、ファームウエアのリビジョンなどリストされていますが、それらしい感じなので多分良さそう(いい加減な。)

$ python3 microbitBLEutil.py -info
microbitBLEutil v0.1
CONNECTED: F5:D1:5F:02:68:25
DeviceName = BBC micro:bit
ModelNumber = BBC micro:bit
SerialNumber = 6239613436
FirmwareRevision = 2.1.1--g
DISCONNECTED.
サービスの列挙

前回も書きましたが、GATTサーバーのアトリビュートは複数のサービスを含み、サービスは複数の特性を含み、特性にはデスクリプタというものを含めることができるという階層構造をなしています。このmicro:bit(MakeCodeエディタで明示的に置いたのは UARTサービスのみ)においてサービスを列挙するには  -service オプションを使います。サービス毎にハンドルがまとめられていることが分かります。こんな感じ。

$ python3 microbitBLEutil.py -service
microbitBLEutil v0.1
CONNECTED: F5:D1:5F:02:68:25
Service <uuid=Generic Access handleStart=1 handleEnd=7>
Service <uuid=Generic Attribute handleStart=8 handleEnd=11>
Service <uuid=e95d93b0-251d-470a-a062-fa1922dfa9a8 handleStart=12 handleEnd=14>
Service <uuid=e97dd91d-251d-470a-a062-fa1922dfa9a8 handleStart=15 handleEnd=18>
Service <uuid=Device Information handleStart=19 handleEnd=25>
Service <uuid=e95d93af-251d-470a-a062-fa1922dfa9a8 handleStart=26 handleEnd=36>
Service <uuid=6e400001-b5a3-f393-e0a9-e50e24dcca9e handleStart=37 handleEnd=65535>
DISCONNECTED.
デスクリプタの列挙

特性(characteristic)は先ほどの -uuid で列挙できるので、その下の階層、デスクリプタレベルで列挙するのは、-desc オプションです。デスクリプタは数が多いのでリストを途中で打ち切っています。全部列挙しても使い道に困るので、特性かサービス毎に取得する方が良さそうです。

$ python3 microbitBLEutil.py -desc
microbitBLEutil v0.1
CONNECTED: F5:D1:5F:02:68:25
Descriptor <Primary Service Declaration>
Descriptor <Characteristic Declaration>
Descriptor <Device Name>
Descriptor <Characteristic Declaration>
Descriptor <Appearance>
Descriptor <Characteristic Declaration>
Descriptor <Peripheral Preferred Connection Parameters>
Descriptor <Primary Service Declaration>
Descriptor <Characteristic Declaration>
Descriptor <Service Changed>
Descriptor <Client Characteristic Configuration>
Descriptor <Primary Service Declaration>
Descriptor <Characteristic Declaration>
Descriptor <e95d93b1-251d-470a-a062-fa1922dfa9a8>
Descriptor <Primary Service Declaration>
Descriptor <Characteristic Declaration>
Descriptor <e97d3b10-251d-470a-a062-fa1922dfa9a8>
Descriptor <Client Characteristic Configuration>
Descriptor <Primary Service Declaration>
Descriptor <Characteristic Declaration>
Descriptor <Model Number String>
~以下略~

まあこれで、一方通行のデータ読み出しはできることが確認できたので、次は双方向でのBLEデータ通信ですかね。そろそろ「ブロックを積まないと」羊頭狗肉です。

ブロックを積みながら(18) BLE、ハンドル、UUID、CHARACTERISTICS へ戻る

ブロックを積みながら(20) micro:bit、BLE UARTへPythonから書き出し へ進む

micro:bit のBLE「特性」など読み出しユーティリティ(初版)
#! /usr/bin/python3
import argparse
import bluepy
import sys
import time

versionSTR = "microbitBLEutil v0.1"

class microbitBLE:
    
    def __init__(self, adr):
        self.devadr = adr
        self.pobj = None

    def conPobj(self):
        try:
            self.pobj = bluepy.btle.Peripheral()
            self.pobj.connect(self.devadr, bluepy.btle.ADDR_TYPE_RANDOM)
        except:
            print("ERROR: connect.")
            return False
        return True

    def listCharacteristics(self):
        chrLis = self.pobj.getCharacteristics()
        for item in chrLis:
            print(" UUID: {0} Handle: 0x{1:04x} Prop: {2}".format(item.uuid, item.getHandle(), item.propertiesToString()))

    def getDeviceInfo(self):
        work = dict()
        work['DeviceName'] = str(self.pobj.readCharacteristic(0x0003), 'utf-8')
        work['ModelNumber'] = str(self.pobj.readCharacteristic(0x0015), 'utf-8')
        work['SerialNumber'] = str(self.pobj.readCharacteristic(0x0017), 'utf-8')
        work['FirmwareRevision'] = str(self.pobj.readCharacteristic(0x0019), 'utf-8')
        return work

    def getServiceInfo(self):
        return self.pobj.getServices()

    def getDescInfo(self):
        return self.pobj.getDescriptors()

    def close(self):
        self.pobj.disconnect()

def main():
    parser = argparse.ArgumentParser(description='microbitBLEutil.')
    parser.add_argument('--ADR', nargs=1, help='BLE address.')
    parser.add_argument('-info', dest='info', help='device Info.', action='store_true', default=False)
    parser.add_argument('-service', dest='service', help='available service list.', action='store_true', default=False)
    parser.add_argument('-desc', dest='descriptor', help='available descriptor list.', action='store_true', default=False)
    parser.add_argument('-uuid', dest='uuid', help='list all UUIDs.', action='store_true', default=False)
    parser.add_argument('-V', dest='VERSION', help='Show Version, then exit', action='store_true', default=False)
    args = parser.parse_args()

    print(versionSTR)
    if args.VERSION:
        sys.exit(0)

    devadr = "F5:D1:5F:02:68:25" # Default BLE address
    if args.ADR is not None:
        devadr = args.ADR[0]

    mb = microbitBLE(devadr)

    if mb.conPobj():
        print("CONNECTED: ", devadr)
    else:
        sys.exit(1)

    if args.uuid:
        mb.listCharacteristics()

    if args.info:
        for key, value in mb.getDeviceInfo().items():
            print(key, "=", value) 

    if args.service:
        for srv in mb.getServiceInfo():
            print(str(srv))
        
    if args.descriptor:
        for desc in mb.getDescInfo():
            print(str(desc))

    mb.close()
    print("DISCONNECTED.")

if __name__ == "__main__":
    main()