bleno / pybleno で Raspberry Pi を BLE Peripheral として動かしてみる

# 前置き
Raspberry Pi でセンシングしたデータを Android に送信するために BLE 通信 (Central : Android, Peripheral : Raspberry Pi) を実装します。

この記事では

* Node.js のライブラリ [bleno](https://github.com/noble/bleno) 
* Python のライブラリ [pybleno](https://github.com/Adam-Langley/pybleno)

を利用して Raspberry Pi 3 Model B を BLE Peripheral として動作させます。

# Peripheral : Raspberry Pi
bleno も pybleno も処理の流れはほとんど同じです。

1. Characteristic を作成
2. Characteristic に handler(onReadRequest, onWriteRequest, onSubscribe, onUnsubscribe, etc.) を設定
3. PrimaryService に Characteristic を追加
4. setServices で Peripheral が利用可能な PrimaryService を設定
5. Advertising を開始


# bleno (Node.js)
### blenoのインストール
bleno の [README.md](https://github.com/noble/bleno/blob/master/README.md) (Prerequisites, Install) を参照して下さい。
Node.js v9.11.1 で動作を確認しました。

```
$ sudo apt-get install bluetooth bluez libbluetooth-dev libudev-dev
$ npm install bleno
```

### サンプルプログラム
このプログラムでは、センシングしたデータに変化がある度に Peripheral : Raspberry Pi から Central : Android に通知(notify)する仕組みを想定しています。デバッグ用にreadも実装しています。

実際に Raspberry Pi からセンシングしたデータを利用するのではなく、センサーから得られる値としては、プログラム上で1秒毎に値が +1 される counter を利用します。

```javascript:bleno_peripheral.js
var util = require('util');
var bleno = require('bleno');

const APPROACH_SERVICE_UUID = '13A28130-8883-49A8-8BDB-42BC1A7107F4';
const APPROACH_CHARACTERISTIC_UUID = 'A2935077-201F-44EB-82E8-10CC02AD8CE1';

let counter = 0;

var Characteristic = bleno.Characteristic;

var ApproachCharacteristic = function() {
    ApproachCharacteristic.super_.call(this, {
        uuid : APPROACH_CHARACTERISTIC_UUID,
        properties: ['read', 'notify'],
        value : null
    });

    this._value = 0;
    this._updateValueCallback = null;
};

util.inherits(ApproachCharacteristic, Characteristic);

ApproachCharacteristic.prototype.onReadRequest = function(offset, callback) {
    console.log(counter);
    callback(this.RESULT_SUCCESS, this._value);
}

ApproachCharacteristic.prototype.onSubscribe = function(maxValueSize, updateValueCallback) {
    console.log('ApproachCharacteristic - onSubscribe');

    this._updateValueCallback = updateValueCallback;
};

ApproachCharacteristic.prototype.onUnsubscribe = function(maxValueSize, updateValueCallback) {
    console.log('ApproachCharacteristic - onUnsubscribe');

    this._updateValueCallback = null;
};

var PrimaryService = bleno.PrimaryService;

bleno.on('stateChange', function(state) {
    console.log('on -> stateChange: ' + state);

    if (state === 'poweredOn') {
        bleno.startAdvertising('Approach', [APPROACH_SERVICE_UUID]);
    } else {
        bleno.stopAdvertising();
    }
});

var approachCharacteristic = new ApproachCharacteristic();

bleno.on('advertisingStart', function(error) {
    console.log('on -> advertisingStart: ' + (error ? 'error ' + error : 'success'));

    if(!error) {
        bleno.setServices([
            new PrimaryService({
                uuid: APPROACH_SERVICE_UUID,
                characteristics: [
                    approachCharacteristic
                ]
            })
        ]);
    }
});

setInterval(()=>{
    counter++;
    approachCharacteristic._value = counter;
    if (approachCharacteristic._updateValueCallback) {
        console.log(`Sending notification with value : ${approachCharacteristic._value}`);

        const notificationBytes = Buffer.from(String(approachCharacteristic._value));
        approachCharacteristic._updateValueCallback(notificationBytes);
    }
}, 1000);

```

### 実行結果
```
$ sudo bleno_peripheral.js
on -> stateChange: poweredOn
on -> advertisingStart: success
ApproachCharacteristic - onSubscribe
Sending notification with value 18
Sending notification with value 19
Sending notification with value 20
Sending notification with value 21
```

# pybleno (Python)
### pyblenoのインストール
pybleno の [README.md](https://github.com/Adam-Langley/pybleno/blob/master/README.md) (Install) を参照して下さい。

```
$ sudo pip3 install pybleno
```

### サンプルプログラム
このプログラムでは、センシングしたデータに変化がある度に Peripheral : Raspberry Pi から Central : Android に通知(notify)する仕組みを想定しています。デバッグ用にreadも実装しています。

実際に Raspberry Pi からセンシングしたデータを利用するのではなく、センサーから得られる値としては、プログラム上で1秒毎に値が +1 される counter を利用します。

```python:pybleno_peripheral.py
from pybleno import *

bleno = Bleno()

APPROACH_SERVICE_UUID = '13A28130-8883-49A8-8BDB-42BC1A7107F4'
APPROACH_CHARACTERISTIC_UUID = 'A2935077-201F-44EB-82E8-10CC02AD8CE1'


class ApproachCharacteristic(Characteristic):

    def __init__(self):
        Characteristic.__init__(self, {
            'uuid': APPROACH_CHARACTERISTIC_UUID,
            'properties': ['read', 'notify'],
            'value': None
        })

        self._value = str(0).encode()
        self._updateValueCallback = None

    def onReadRequest(self, offset, callback):
        print('ApproachCharacteristic - onReadRequest')
        callback(result=Characteristic.RESULT_SUCCESS, data=self._value)

    def onSubscribe(self, maxValueSize, updateValueCallback):
        print('ApproachCharacteristic - onSubscribe')

        self._updateValueCallback = updateValueCallback

    def onUnsubscribe(self):
        print('ApproachCharacteristic - onUnsubscribe')

        self._updateValueCallback = None


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

    if (state == 'poweredOn'):
        bleno.startAdvertising(name='Approach', service_uuids=[APPROACH_SERVICE_UUID])
    else:
        bleno.stopAdvertising()


bleno.on('stateChange', onStateChange)

approachCharacteristic = ApproachCharacteristic()


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

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


bleno.on('advertisingStart', onAdvertisingStart)

bleno.start()


import time

counter = 0

def task():
    global counter
    counter += 1
    approachCharacteristic._value = str(counter).encode()
    if approachCharacteristic._updateValueCallback:

        print('Sending notification with value : ' + str(approachCharacteristic._value))

        notificationBytes = str(approachCharacteristic._value).encode()
        approachCharacteristic._updateValueCallback(data=notificationBytes)


while True:
    task()
    time.sleep(1)

```

### 実行結果
```
$ sudo python3 pybleno_peripheral.py
on -> stateChange: poweredOn
on -> advertisingStart: success
ApproachCharacteristic - onSubscribe
Sending notification with value : 22
Sending notification with value : 23
Sending notification with value : 24
Sending notification with value : 25
```

# Central : Android
[【Android】BLE通信ざっくりまとめ](https://qiita.com/HideMatsu/items/6a6758eca86500881126)
こちらの記事が大変参考になりました。

* scan : 対象のデバイスと connect する処理
* onServicesDiscovered : 対象の Characteristic を検索する処理
* onCharacteristicChanged : 対象の Characteristic の更新されたデータを受け取る処理

を[【Android】BLE通信ざっくりまとめ](https://qiita.com/HideMatsu/items/6a6758eca86500881126) のソースコードに追記して実装しました。下のソースコードに実装した関数を載せています。

```java:追記部分.java
    public void scan(boolean enable) {
        scanCallback = initScanCallback();

        if (enable) {
            stopScanHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    isScanning = false;
                    bluetoothLeScanner.stopScan(scanCallback);
                    for (BluetoothDevice device : deviceList) {

                        Log.e("scan", device.getAddress());
                        // BD Addressを指定
                        if ("B8:27:EB:B1:5F:BD".equals(device.getAddress())){
                            Log.d("scan", "***** Discoverd Raspberry Pi *****");
                            connect(getApplicationContext(), device);
                        }
                    }
                }
            }, SCAN_PERIOD);

            isScanning = true;
            bluetoothLeScanner.startScan(scanCallback);
        } else {
            isScanning = false;
            stopScan();
        }
    }

            @Override
            public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
                super.onCharacteristicChanged(gatt, characteristic);

                if ("a2935077-201f-44eb-82e8-10cc02ad8ce1".equals(characteristic.getUuid().toString())) {
                    byte[] notified_value = characteristic.getValue();
                    String notified_message = new String(notified_value);
                }
            }

            @Override
            public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                super.onServicesDiscovered(gatt, status);

                List<BluetoothGattService> serviceList = gatt.getServices();

                for (BluetoothGattService service : serviceList) {
                    List<BluetoothGattCharacteristic> characteristicList = service.getCharacteristics();

                    for (BluetoothGattCharacteristic characteristic : characteristicList) {
                        String characteristicUuid = characteristic.getUuid().toString();
                        if ("a2935077-201f-44eb-82e8-10cc02ad8ce1".equals(characteristicUuid)) {
                            // Notificationを要求する
                            boolean registered = gatt.setCharacteristicNotification(characteristic, true);

                            String CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID = "00002902-0000-1000-8000-00805f9b34fb";

                            // CharacteristicのNotificationを有効化する
                            if (registered) {
                                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
                                        UUID.fromString(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID)
                                );
                                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                                gatt.writeDescriptor(descriptor);
                            }
                        }
                    }
                }
            }
```

### BD Address の確認
上記のソースコードでは、対象のデバイスと接続するために BD Address を指定していますが、Raspberry Pi の BD Address は hciconfig で確認することができます。

```
$ hciconfig
hci0:	Type: Primary  Bus: UART
	BD Address: B8:27:EB:B1:5F:BD  ACL MTU: 1021:8  SCO MTU: 64:1
	UP RUNNING 
	RX bytes:2277 acl:23 sco:0 events:174 errors:0
	TX bytes:4041 acl:40 sco:0 commands:114 errors:0
```


# BLE デバッグ用のアプリ
Central : Android のプログラムを用意しなくても、下記のアプリが Central としての役割を果たせます。

* Androd : [BLE Scanner: Read,Write,Notify](https://play.google.com/store/apps/details?id=com.macdom.ble.blescanner&hl=ja)
* iPhone : [LightBlue® Explorer](https://itunes.apple.com/jp/app/lightblue-explorer/id557428110?mt=8)

# まとめ
この記事では、bleno で Raspberry Pi に BLE Peripheral の実装を行いました。
Raspberry Pi でセンシングしたデータを BLE で扱う際の参考にして頂ければと思います。