pyblenoライブラリを使ってBLE Peripheralの作成」でRapberryPi Zero WをBLE Peripheralとして作成しましたが、Windows PCに接続できませんでした。今回は、NotifyによりRapberryPi Zero WからWindows PCにデータを送信します。Python言語でBLE Peripheralを作成するために前回と同様にpyblenoライブラリを使用します。

pyblenoというライブラリを使ってペリフェラルを立ち上げることにしましたが,2021年6月現在,ペリフェラルからセントラルに値が変化したときに通知する「Notification」という機能が2018-11-13以降のラズベリーパイOSでは動作しません.公式ドキュメントによると,リナックスカーネルのBluetoothモジュールのバグが原因ということで,通知機能が必須な場合はラズベリーパイのOSを2018-11-13以前にする必要があります.

2022/07時点でのラズベリーパイOSでpyblenoライブラリが動作することが確認できました。「Adam-Langley/pybleno」の「Troubleshooting」を見るとFixになっています。

作成するBLE Peripheralプログラムの仕様

使用するUUIDを次に示します。
0000ec00-0000-1000-8000-00805f9b34fb:サービスUUID
0000ec0f-0000-1000-8000-00805f9b34fb:Notifyによるデータの読み込み

1秒ごとに文字「20」をNotifyにより通知します。

BLE Peripheralプログラムの作成

RapberryPi Zero W上で、Python言語により作成したBLE Peripheralプログラムを次に示します。

  • 56行目で 、クラス「EchoCharacteristic」をインスタンス化して、41行目でサービスUUIDとしてまとめます。
  • 各クラスのCHARACTERISTIC UUIDを11行目等、propertiesを12行目等にそれぞれ設定し、必要な「onxxx」を定義します。
  • 61行目でNotifyが受信できる状態化を確認し、64行目でNotifyによりデータを送信します。

bleperipheralPC.py

from pybleno import *
import sys
import signal
import time


class EchoCharacteristic(Characteristic):

    def __init__(self, uuid):
        Characteristic.__init__(self, {
            'uuid': uuid,
            'properties': ['read', 'write', 'notify'],
            'value': None
        })
        self._value = array.array('B', [0] * 0)
        self._updateValueCallback = None

    def onSubscribe(self, maxValueSize, updateValueCallback):
        print('EchoCharacteristic - onSubscribe')
        self._updateValueCallback = updateValueCallback

    def onUnsubscribe(self):
        print('EchoCharacteristic - onUnsubscribe');

        self._updateValueCallback = None


def onStateChange(state):
    print('on -> stateChange: ' + state);

    if (state == 'poweredOn'):
        bleno.startAdvertising('bleperipheralPC', ['0000ec00-0000-1000-8000-00805f9b34fb'])
    else:
        bleno.stopAdvertising();


def onAdvertisingStart(error):
    print('on -> advertisingStart: ' + ('error ' + error if error else 'success'));

    if not error:
        bleno.setServices([
            BlenoPrimaryService({
                'uuid': 'ec00',
                'characteristics': [
                    tomosoftCharacteristic_read
                ]
            })
        ])


if __name__ == '__main__':
    print('bleno - echo');
    bleno = Bleno()
    bleno.on('stateChange', onStateChange)
    bleno.on('advertisingStart', onAdvertisingStart)
    tomosoftCharacteristic_read = EchoCharacteristic('0000ec0f-0000-1000-8000-00805f9b34fb')
    bleno.start()

    while True:
        time.sleep(1)
        if tomosoftCharacteristic_read._updateValueCallback:
            print('Sending notification with value-cmd : ')
            notificationBytes = str(20).encode()
            tomosoftCharacteristic_read._updateValueCallback(notificationBytes)

BLE Centrallプログラムの作成

Windows PC上で、C#言語により作成したBLE Centrallプログラムを次に示します。

  • 23行目にはRapberryPi Zero Wのデバイス名、25行目にはNotifyのUUIDを設定します。
  • RapberryPi Zero Wが検出されると、75行目に制御が移ります。
  • 90行目でCCCD への書き込みを行います。この設定によりNotifyでパソコンに通知(コールバック)されます。
  • 95行目で、データが変化したときに実行されるコールバック関数「Changed_data」を登録します。
  • 106行目のコールバック関数「Changed_data」では、受信したデータの最初のデータを表示します。
using System;
using System.Linq;
using System.Threading;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Devices.Bluetooth.GenericAttributeProfile;

namespace BleCentral
{
    class Program
    {
       static SensorModule sensormodule;
 
        static void Main(string[] args)
        {
            sensormodule = new SensorModule();
            sensormodule.Start();
        }
    }

    class SensorModule
    {
        const string SENSOR_NAME = "raspberrypi";
        // Bluetooth®サービス
        static Guid UUID_TEMP_SERV = new Guid("0000ec00-0000-1000-8000-00805f9b34fb");
        // 計測センサデータやモジュール内部ステータスをパソコンに通知
        static Guid UUID_TEMP_DATA = new Guid("0000ec0f-0000-1000-8000-00805f9b34fb");

        public BluetoothLEAdvertisementWatcher advWatcher;

        public void Start()
        {
            Console.WriteLine("Start");
            this.advWatcher = new BluetoothLEAdvertisementWatcher();
            this.advWatcher.ScanningMode = BluetoothLEScanningMode.Passive;

            this.advWatcher.Received += this.Watcher_Received;

            // スキャン開始
            this.advWatcher.Start();
            Thread.Sleep(500000);
            this.advWatcher.Stop();
            Console.WriteLine("Stop");
        }

        private void Watcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
        {
            this.CheckArgs(args);
        }

        public async void CheckArgs(BluetoothLEAdvertisementReceivedEventArgs args)
        {
            Console.WriteLine("Receivedi:{0}", args.BluetoothAddress);
            BluetoothLEDevice dev = await BluetoothLEDevice.FromBluetoothAddressAsync(args.BluetoothAddress);

            Console.WriteLine("---Received---");
            var bleServiceUUIDs = args.Advertisement.ServiceUuids;

            Console.WriteLine("Found");
            Console.WriteLine("MAC:" + args.BluetoothAddress.ToString());
            Console.WriteLine("NAME:" + args.Advertisement.LocalName.ToString());
            Console.WriteLine("ServiceUuid");
            foreach (var uuidone in bleServiceUUIDs)
            {
                Console.WriteLine(uuidone.ToString());
            }
            Console.WriteLine("---END---");
            Console.WriteLine("");

            if (dev.Name == SENSOR_NAME)
            {
                // 検出
                try
                {
                    Console.WriteLine($"Service Find!");

                    // スキャンStop
                    this.advWatcher.Stop();

                    BluetoothLEDevice device = await BluetoothLEDevice.FromBluetoothAddressAsync(args.BluetoothAddress);

                    var services = await device.GetGattServicesForUuidAsync(UUID_TEMP_SERV);
                    var characteristics = await services.Services[0].GetCharacteristicsForUuidAsync(UUID_TEMP_DATA);

                    if (characteristics.Status == GattCommunicationStatus.Success)
                    {
                        var gattCharacteristic = characteristics.Characteristics.First();

                        // CCCD への書き込みが必要。これがないとイベントハンドラが呼ばれない。
                        GattCommunicationStatus status
                            = await gattCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify);

                        if (status == GattCommunicationStatus.Success)
                        {
                            gattCharacteristic.ValueChanged += Changed_data;
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Exception...{ex.Message})");
                }
            }
        }

        public void Changed_data(GattCharacteristic sender, GattValueChangedEventArgs eventArgs)
        {
            Console.WriteLine($"characteristicChanged...Length={eventArgs.CharacteristicValue.Length}");
            byte[] data = new byte[eventArgs.CharacteristicValue.Length];
            Windows.Storage.Streams.DataReader.FromBuffer(eventArgs.CharacteristicValue).ReadBytes(data);
            Console.WriteLine($"characteristicChanged...{data} " + data[0]); ;

            return;
        }
    }
}

作成したプログラムの実行

RapberryPi Zero W上で次のコマンドによりBLE Peripheralプログラムを実行します。

  • Notify要求を受け取ると「EchoCharacteristic – onSubscribe」が表示されます。
  • Notifyでデータを送信すると「Sending notification with value-cmd」が表示されます。
$ sudo python3 bleperipheralPC.py
bleno - echo
on -> stateChange: poweredOn
on -> advertisingStart: success
EchoCharacteristic - onSubscribe
Sending notification with value-cmd :
Sending notification with value-cmd :
Sending notification with value-cmd :
Sending notification with value-cmd :
Sending notification with value-cmd :
Sending notification with value-cmd :
Sending notification with value-cmd :
Sending notification with value-cmd :
Sending notification with value-cmd :
Sending notification with value-cmd :

Windows PC上でBLE Centrallプログラムを実行し、BLE Peripheralとペアリングできると、次のように受信されたデータを表示します。

Start
Receivedi:202481601467365
---Received---
Found
MAC:202481601467365
NAME:
ServiceUuid
0000ec00-0000-1000-8000-00805f9b34fb
---END---

Service Find!
characteristicChanged...Length=2
characteristicChanged...System.Byte[] 50
characteristicChanged...Length=2
characteristicChanged...System.Byte[] 50
characteristicChanged...Length=2
characteristicChanged...System.Byte[] 50
characteristicChanged...Length=2
characteristicChanged...System.Byte[] 50
characteristicChanged...Length=2
characteristicChanged...System.Byte[] 50
characteristicChanged...Length=2
characteristicChanged...System.Byte[] 50

Windows PCからRapberryPi Zero Wへのデータ転送(2023/12/07追加)

Windows PCからRapberryPi Zero Wへデータを転送します。

Windows PCでの追加コード

次のコードを追加します。WriteValueAsyncメソッドのパラメータは「GattWriteOption.WriteWithResponse」とします。

var characteristics1 = await services.Services.First().GetCharacteristicsForUuidAsync(UUID_TEMP_CONF);
cmd = characteristics1.Characteristics[0];

     ・・・
await cmd.WriteValueAsync(new byte[] { 0x2F, 0x03, 0x03 }.AsBuffer(), GattWriteOption.WriteWithResponse);
await cmd.WriteValueAsync(new byte[] { 0x01, 0x03, 0x03 }.AsBuffer(), GattWriteOption.WriteWithResponse);
await cmd.WriteValueAsync(new byte[] { 0x04, 0x03, 0x01 }.AsBuffer(), GattWriteOption.WriteWithResponse);
await cmd.WriteValueAsync(new byte[] { 0x06, 0x04, 0x64, 0x00 }.AsBuffer(), GattWriteOption.WriteWithResponse);  // 100msec
await cmd.WriteValueAsync(new byte[] { 0x2F, 0x03, 0x01 }.AsBuffer(), GattWriteOption.WriteWithResponse);
await cmd.WriteValueAsync(new byte[] { 0x20, 0x03, 0x01 }.AsBuffer(), GattWriteOption.WriteWithResponse);
     ・・・

RapberryPi Zero Wでの追加コード

次のコードを追加します。


class PiBleCharacteristic(Characteristic):
     ・・・
   def onWriteRequest(self, data, offset, withoutResponse, callback):
        self._value = data

        print('EchoCharacteristic - %s - onWriteRequest: value = %s' % (self['uuid'], [hex(c) for c in self._value]))

        if self._updateValueCallback:
            print('EchoCharacteristic - onWriteRequest: notifying');

            self._updateValueCallback(self._value)

        callback(Characteristic.RESULT_SUCCESS)
     ・・・

Windows PCとRapberryPi Zero W上で変更したプログラムを実行すると、RapberryPi Zero WでWindows PCから受け取ったデータが次のように表示されます。

     ・・・
EchoCharacteristic - onSubscribe--253
EchoCharacteristic - 0000ec0f00001000800000805f9b34fb - onWriteRequest: value = ['0x2f', '0x3', '0x3']
EchoCharacteristic - onWriteRequest: notifying
EchoCharacteristic - 0000ec0f00001000800000805f9b34fb - onWriteRequest: value = ['0x1', '0x3', '0x3']
EchoCharacteristic - onWriteRequest: notifying
EchoCharacteristic - 0000ec0f00001000800000805f9b34fb - onWriteRequest: value = ['0x4', '0x3', '0x1']
EchoCharacteristic - onWriteRequest: notifying
EchoCharacteristic - 0000ec0f00001000800000805f9b34fb - onWriteRequest: value = ['0x6', '0x4', '0x64', '0x0']
EchoCharacteristic - onWriteRequest: notifying
EchoCharacteristic - 0000ec0f00001000800000805f9b34fb - onWriteRequest: value = ['0x2f', '0x3', '0x1']
EchoCharacteristic - onWriteRequest: notifying
EchoCharacteristic - 0000ec0f00001000800000805f9b34fb - onWriteRequest: value = ['0x20', '0x3', '0x1']
EchoCharacteristic - onWriteRequest: notifying
Sending notification with value-cmd :
     ・・・