#背景
IoTデバイスを作成するために、センサで取得したデータをBLEでRaspberryPiに送り、取得したデータを使ってRaspberryPiでアクチュエータ(モータやソレノイドなど)を制御したいと考えました。
そんなときに、BLEnano2というArduinoで開発可能なBLEマイコンボードがあることを知りました。今回はそれを用いて、圧力センサの検出値をBLE通信でRaspberryPiに送り、pythonで取得してみました。
参考サイト: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Ω(選択する抵抗値の大きさで、圧力センサで取得できる値の範囲が変わります)
###実装の確認
ハード・ソフトともに準備ができたら、まずこの状態できちんと値が取れているか確認します。
確認には、先ほどの参考サイトに記載の方法で行います。
スマホに"nRF Connect for Mobile"というアプリをインストールします。参考サイト記載の通りに進め、notifyをタップします。その状態で圧力センサを指でおさえ、下図のようにValueの値が変化すれば成功です!(下図では00-00から02-E7に変わっています)
ここで、このアプリで検出した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を入れるのを忘れずに。
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 ←この記事の場合
これで必要な情報を取得できました。
最後に、以下の実行ファイルを作成します。
# -*- 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を使った遠隔デバイスを作ったり、いろいろな用途に活用できそうです。