1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2020-11-25

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

BLEnanoとは?
blenano.png

参考サイト:https://jellyware.jp/kurage/blenano/blenano_start.html

・サイズが非常に小さい
書き込み用ボードから分離してBLEマイコン部だけで動作可能
BLEマイコン部のみであれば約20mm x 20mm

・低消費電力
コイン電池で動作可能

・入力電圧が扱いやすい
VDDを使う場合は1.8V〜3.3VでOK
VINを使う場合は3.3〜13VまでOK

・ピン数は少ないが、多彩なペリフェラル
ピンヘッダはVIN、VDD、GND含めて12pinしかないが、最低限必要なペリフェラルが揃っている

#開発環境
BLENano(送信側)
BLENano2
Arduino IDE 1.8.13

RaspberryPi(受信側)
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での書き込みのやり方はコチラを参考にしてください。

#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
PXL_20201121_062607394.jpg

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

スクリーンショット 2020-11-25 21.57.35.png

ここで、このアプリで検出した2つの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を取得します。先ほどインストールしたスマホアプリ"nRF Connect for Mobile"とデバイスの接続を切断してから、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を使った遠隔デバイスを作ったり、いろいろな用途に活用できそうです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?