BLENano2で取得したセンサのデータをRaspberryPiで受信してPythonで扱う

#背景
IoTデバイスを作成するために、センサで取得したデータをBLEでRaspberryPiに送り、取得したデータを使ってRaspberryPiでアクチュエータ（モータやソレノイドなど）を制御したいと考えました。
そんなときに、BLEnano2というArduinoで開発可能なBLEマイコンボードがあることを知りました。今回はそれを用いて、<strong>圧力センサの検出値をBLE通信でRaspberryPiに送り、pythonで取得</strong>してみました。

<strong>BLEnanoとは？</strong>
![blenano.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193530/ef05b46e-169f-537d-8c4b-da61d2fc9b6a.png)

参考サイト：https://jellyware.jp/kurage/blenano/blenano_start.html
><strong>・サイズが非常に小さい</strong>
>書き込み用ボードから分離してBLEマイコン部だけで動作可能
>BLEマイコン部のみであれば約20mm x 20mm
>
><strong>・低消費電力</strong>
>コイン電池で動作可能
>
><strong>・入力電圧が扱いやすい</strong>
>VDDを使う場合は1.8V〜3.3VでOK
>VINを使う場合は3.3〜13VまでOK
>
><strong>・ピン数は少ないが、多彩なペリフェラル</strong>
>ピンヘッダはVIN、VDD、GND含めて12pinしかないが、最低限必要なペリフェラルが揃っている

#開発環境
<strong>BLENano（送信側）</strong>
BLENano2
Arduino IDE 1.8.13

<strong>RaspberryPi（受信側）</strong>
RaspberryPi3 Model B
Description:    Raspbian GNU/Linux 9.13 (stretch)
Release:        9.13
Codename:       stretch

#BLENano側の実装
ハード・ソフト実装ともに以下のサイトを参考にさせていただきました。
https://jellyware.jp/kurage/blenano/blenano2_minibread.html

###ソフトウェアの実装
以下のコードをArduino IDEでBLEに書き込みます。Arduinoでの書き込みのやり方は[コチラ](https://robopara.co.jp/blenano2%E3%82%92arduinoide%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/)を参考にしてください。

```ruby:
#include <nRF5x_BLE_API.h>

//------------------------------------------------------------
//Definition
//------------------------------------------------------------ 
#define TXRX_BUF_LEN 20                              //max 20[byte]
#define DEVICE_LOCAL_NAME "BLEnano2"     
#define ADVERTISING_INTERVAL 160     //160 * 0.625[ms] = 100[ms]
#define TICKER_TIME 200000           //200000[us] = 200[ms]
#define DIGITAL_OUT_PIN P0_29
#define ANALOG_IN_PIN1 P0_5
#define ANALOG_IN_PIN2 P0_4

//------------------------------------------------------------
//Object generation
//------------------------------------------------------------ 
BLE ble;
DigitalOut      LED_SET(DIGITAL_OUT_PIN);
AnalogIn        ANALOG1(ANALOG_IN_PIN1);
AnalogIn        ANALOG2(ANALOG_IN_PIN2);

//------------------------------------------------------------
//Service & Characteristic Setting
//------------------------------------------------------------ 
//Service UUID
static const uint8_t base_uuid[] = { 0x71, 0x3D, 0x00, 0x00, 0x50, 0x3E, 0x4C, 0x75, 0xBA, 0x94, 0x31, 0x48, 0xF1, 0x8D, 0x94, 0x1E } ;
 
//Characteristic UUID
static const uint8_t tx_uuid[]   = { 0x71, 0x3D, 0x00, 0x03, 0x50, 0x3E, 0x4C, 0x75, 0xBA, 0x94, 0x31, 0x48, 0xF1, 0x8D, 0x94, 0x1E } ;
static const uint8_t rx_uuid[]   = { 0x71, 0x3D, 0x00, 0x02, 0x50, 0x3E, 0x4C, 0x75, 0xBA, 0x94, 0x31, 0x48, 0xF1, 0x8D, 0x94, 0x1E } ;
 
//Characteristic Value
uint8_t txPayload[TXRX_BUF_LEN] = {0,};
uint8_t rxPayload[TXRX_BUF_LEN] = {0,};
 
//Characteristic Property Setting etc
GattCharacteristic  txCharacteristic (tx_uuid, txPayload, 1, TXRX_BUF_LEN, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ);
GattCharacteristic  rxCharacteristic (rx_uuid, rxPayload, 1, TXRX_BUF_LEN, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY| GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ);
GattCharacteristic *myChars[] = {&txCharacteristic, &rxCharacteristic};
 
//Service Setting
GattService         myService(base_uuid, myChars, sizeof(myChars) / sizeof(GattCharacteristic *));

//======================================================================
//onDisconnection
//======================================================================
void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
{
    ble.startAdvertising();
}

//======================================================================
//onDataWritten
//======================================================================
void WrittenHandler(const GattWriteCallbackParams *Handler)
{   
    uint8_t buf[TXRX_BUF_LEN];
    uint16_t bytesRead;
 
    if (Handler->handle == txCharacteristic.getValueAttribute().getHandle()) 
    {
        ble.readCharacteristicValue(txCharacteristic.getValueAttribute().getHandle(), buf, &bytesRead);
        memset(txPayload, 0, TXRX_BUF_LEN);
        memcpy(txPayload, buf, TXRX_BUF_LEN); 
 
        if(buf[0] == 1)
            LED_SET = 1;
        else
            LED_SET = 0;
    }
}

//======================================================================
//onTimeout
//======================================================================
void m_status_check_handle(void)
{   
  uint8_t buf[2];
 
  //Read Analog port
  float s1 = ANALOG1;
  float s2 = ANALOG2;
  uint16_t value = s1/s2 * 1024;
  //uint16_t value = (s1-(s2-s1)*100)/s2 * 1024;
  //感度変更
 
  buf[0] = (value >> 8);
  buf[1] = value;
 
  //Send out
  ble.updateCharacteristicValue(rxCharacteristic.getValueAttribute().getHandle(), buf, 2);
}

//======================================================================
//convert reverse UUID
//======================================================================
void reverseUUID(const uint8_t* src, uint8_t* dst)
{
    int i;
 
    for(i=0;i<16;i++)
        dst[i] = src[15 - i];
}

//======================================================================
//main
//======================================================================
int main(void)
{
    uint8_t base_uuid_rev[16];
 
    //Timer Setting [us]
    Ticker ticker;
    ticker.attach_us(m_status_check_handle, TICKER_TIME);
 
    //BLE init
    ble.init();
 
    //EventListener
    ble.onDisconnection(disconnectionCallback);
    ble.onDataWritten(WrittenHandler);

    //------------------------------------------------------------
    //setup advertising 
    //------------------------------------------------------------
    //Classic BT not support
    ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED);

    //Connectable to Central
    ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);

    //Local Name
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME,
                                    (const uint8_t *)DEVICE_LOCAL_NAME, sizeof(DEVICE_LOCAL_NAME) - 1);

    //GAP AdvertisingData                                
    reverseUUID(base_uuid, base_uuid_rev);  
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS,
                                    (uint8_t *)base_uuid_rev, sizeof(base_uuid));

    //Advertising Interval 
    ble.setAdvertisingInterval(ADVERTISING_INTERVAL);

    //Add Service
    ble.addService(myService);
 
 
    //Start Advertising
    ble.startAdvertising();

    //------------------------------------------------------------
    //Loop
    //------------------------------------------------------------
    while(1)
    {
        ble.waitForEvent(); 
    }
}
```

###ハードウェアの実装
ハードは以下のような形でブレッドボード上に配置しました。
>圧力センサFSR402：https://akizukidenshi.com/catalog/g/gP-04002/
抵抗：100kΩ（選択する抵抗値の大きさで、圧力センサで取得できる値の範囲が変わります）

![PXL_20201121_062755959.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193530/b7c77fa0-2e6b-1dbb-a4fb-e80c111c80dd.jpeg)
![PXL_20201121_062607394.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193530/eb5afb31-58af-51da-1d60-e9a84c5715e4.jpeg)



###実装の確認
ハード・ソフトともに準備ができたら、まずこの状態できちんと値が取れているか確認します。
確認には、先ほどの[参考サイト](https://jellyware.jp/kurage/blenano/blenano2_minibread.html)に記載の方法で行います。
スマホに<strong>"nRF Connect for Mobile"</strong>というアプリをインストールします。[参考サイト](https://jellyware.jp/kurage/blenano/blenano2_minibread.html)記載の通りに進め、notifyをタップします。その状態で圧力センサを指でおさえ、下図のようにValueの値が変化すれば成功です！（下図では00-00から02-E7に変わっています）

![スクリーンショット 2020-11-25 21.57.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193530/14c5bafa-7a11-ab41-d3be-0462a562a9ed.png)

ここで、このアプリで検出した２つのUUIDをメモしておきます。
>Properties: READ,WRITE
UUID: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy

>Properties: NOTFY,READ
UUID: zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz

#RaspberryPi側の実装
BLENano2で取得した圧力センサの出力値をRaspberryPiで受信します。

参考にさせて頂いたのはコチラのサイトになります。
https://qiita.com/FiveOne/items/ea04b74271da0f382ed6

まず、RaspberryPiにbluepyとgattlibをインストールします。

```
# python3 の場合

$ sudo apt install libbluetooth3-dev libglib2.0 libboost-python-dev libboost-thread-dev
$ sudo apt install python3-pip
$ cd /usr/lib/arm-linux-gnueabihf/
$ sudo ln libboost_python-py35.so libboost_python-py34.so
$ sudo pip3 install gattlib
$ sudo pip3 install bluepy
$ sudo systemctl daemon-reload
$ sudo service bluetooth restart
```

次に、BLENano2を見つけます。下記をRaspberryPi上で実行します。実行にはroot権限が必要になります。頭にsudoを入れるのを忘れずに。

```scan.py
import bluepy

scanner = bluepy.btle.Scanner(0)
devices = scanner.scan(3) # 3秒間スキャンする

for device in devices:
print('======================================================')
print('address : %s' % device.addr)
print('addrType: %s' % device.addrType)
print('RSSI : %s' % device.rssi)
print('Adv data:')
for (adtype, desc, value) in device.getScanData():
print(' (%3s) %s : %s ' % (adtype, desc, value))
```


```:実行結果
$ sudo python3 scan.py
======================================================
address : XX:XX:XX:XX:XX:XX
addrType: random
RSSI    : -58
Adv data:
 (  1) Flags : 04 
 (  7) Complete 128b Services : xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 (  9) Complete Local Name : BLEnano2
```
BLENano2を発見できました！ここで、取得したaddressとaddrTypeをメモしておきます。
他のBluetoothデバイスを検出している場合は、間違えないようにしてください。
>address: XX:XX:XX:XX:XX:XX
>addrType: random


次に、先ほど取得したaddressを使って、GATT_handleを取得します。<strong>先ほどインストールしたスマホアプリ"nRF Connect for Mobile"とデバイスの接続を切断してから</strong>、RaspberryPi上で以下のように入力します。

```
$ gatttool -b XX:XX:XX:XX:XX:XX -t random -I
(今回取得したaddrTypeがrandomのため、引数にはrandomを入力します)
```
```
[XX:XX:XX:XX:XX:XX][LE]> connect
Attempting to connect to XX:XX:XX:XX:XX:XX
Connection successful
```
この表示が出れば接続成功です！
次いで、以下のように入力します。

```
[XX:XX:XX:XX:XX:XX][LE]> characteristics
handle: 0x0002, char properties: 0x0a, char value handle: 0x0003, uuid: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
handle: 0x0004, char properties: 0x02, char value handle: 0x0005, uuid: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb
handle: 0x0006, char properties: 0x02, char value handle: 0x0007, uuid: cccccccc-cccc-cccc-cccc-cccccccccccc
handle: 0x0009, char properties: 0x20, char value handle: 0x000a, uuid: dddddddd-dddd-dddd-dddd-dddddddddddd
handle: 0x000d, char properties: 0x0a, char value handle: 0x000e, uuid: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
handle: 0x000f, char properties: 0x12, char value handle: 0x0010, uuid: zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz
```

ここで、先ほどアプリから読み取ってメモしたUUIDに対応するchar value handleを控えます。
>Properties: READ,WRITE
UUID: yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
char value handle: 0x000e　←この記事の場合

>Properties: NOTFY,READ
UUID: zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz
char value handle: 0x0010　←この記事の場合

これで必要な情報を取得できました。
最後に、以下の実行ファイルを作成します。

```test.py
# -*- coding: utf-8 -*-
import math
import bluepy
import binascii

HANDLE_DEVNAME = 0x000e # READ,WRITEのchar value handle
HANDLE_SERIAL = 0x0010 # NOTFY,READのchar value handle

devadr = "XX:XX:XX:XX:XX:XX" # addressを記述

def main():
    peri = bluepy.btle.Peripheral()
    peri.connect(devadr, bluepy.btle.ADDR_TYPE_RANDOM)
    devname = peri.readCharacteristic(HANDLE_DEVNAME)
    print("Device Name: %s" % devname )
    
    while True:
        serialnum = peri.readCharacteristic(HANDLE_SERIAL)
        data = int(binascii.b2a_hex(serialnum),16) #取得したデータをバイナリデータにし10進に変換
        print("Serial Number: %s" % data )

if __name__ == "__main__":
    main()
```

```:実行結果
Serial Number: 0
Serial Number: 0
Serial Number: 0
Serial Number: 0
Serial Number: 164
Serial Number: 164
Serial Number: 306
Serial Number: 396
Serial Number: 396
Serial Number: 416
Serial Number: 416
Serial Number: 496
Serial Number: 514
Serial Number: 547
Serial Number: 589
Serial Number: 589
Serial Number: 619
Serial Number: 619
Serial Number: 641
Serial Number: 710
Serial Number: 746
Serial Number: 746
Serial Number: 782
Serial Number: 782
Serial Number: 801
Serial Number: 865
Serial Number: 865
Serial Number: 949
Serial Number: 949
Serial Number: 951
Serial Number: 951
```
出力値を取得できました！この結果は、圧力センサに徐々に力を加えたときの出力値で、値も徐々に大きくなっていることがわかります。

※ちなみに、以下のようなエラーが発生した時は、デバイスの電源のOFF/ONで接続できました。

```
bluepy.btle.BTLEDisconnectError: Failed to connect to peripheral XX:XX:XX:XX:XX:XX, addr type: random
```

#まとめ
RaspberryPiを使って出力値を取り出せば、例えば閾値を設けてLEDを光らせたり、サーボモータの回転量に変換したり、BLEを使った遠隔デバイスを作ったり、いろいろな用途に活用できそうです。