BLE機器との接続

1.概要

R-MSMとは異なる外部センサ機器でBLE(Bluetooth Low Energy)送信を行う機器をPDH(Physical Data Hub)に接続します。

1-1. 接続するセンサ機器

ここでは、以下の2機種をPDHに接続します。
1) OWON社製 Digital Multimeter B35T
2) APERA Instruments社製 Smart Multiparameter Tester PC60-Z

1-2. システム概要

センサ機器とPDHとの接続はBLEで行います。PDHでNode-REDから起動されたPython3(bleakライブラリ使用)でセンサの測定情報を取得します。
その後、取得したデータをNode-REDにHTTPで送信します。受信したNode-REDのデータをダッシュボードのチャートノードでグラフ化します。

1-3. BLE接続に必要な情報

1) センサ機器のBluetoothのMACアドレス、2) NotifyコマンドのUUIDです。これらは、センサ機器を起動しておいて、PDHでPython3のプログラムを実行して調べます。

参考HP:  WT901BLECLBLE接続してデータを読み取る
  Github hbldh/bleak

2. センサ機器のBluetoothの情報を調べるPythonプログラム

1) センサ機器のBluetoothのMACアドレス、2) NotifyコマンドのUUID を調べるPythonプログラムを示します。
ライブラリ bleakは、インストールが必要です。各自の環境に合わせてインストールしてください。

$ pip install bleak

2-1. BluetoothのMACアドレスを調べるプログラム

BluetoothのMACアドレスを調べるプログラムを以下に示します。BleakScanner.discover()で情報を取得します。

”scan_ble_devices.py”

import asyncio
from bleak import BleakScanner

async def run():
    devices = await BleakScanner.discover()
    for d in devices:
        print(f"address: {d.address}, name: {d.name}, uuid: {d.metadata['uuids']}")

loop = asyncio.get_event_loop()
loop.run_until_complete(run())

2-2. NotifyコマンドのUUIDを調べるプログラム

上のコマンドで調べたMACアドレスを使って、サービスの情報を取得します。
addressをご自身の調べたいMACアドレスに変更してください。

get_device_uuids.py

import asyncio
from bleak import BleakClient

address = "EA:78:B5:4D:E3:21" # scan_ble_devices.pyで取得したMACアドレスを指定

async def run(address, loop):
    async with BleakClient(address, loop=loop) as client:
        x = client.is_connected
        print("Connected: {0}".format(x))
        for service in client.services:
            print(f"{service.uuid}: {service.description}: {[f'{c.properties},{c.uuid}' for c in service.characteristics]}")

loop = asyncio.get_event_loop()
loop.run_until_complete(run(address, loop))

3. OWON社製 Digital Multimeter B35T

B35Tは、BLE送信対応のデジタルマルチメータです。BLEに対応していることで、電圧や電流を長期間モニタ出来ることを売りにしています。また、熱電対を取付けることで、温度の測定もできます。温度もBLEで送れます。
このデジタルマルチメータ B35TのデータをPDHに送り、ダッシュボードに結果を表示させます。

3-1. BluetoothのMACアドレスの調査

2-1項の”scan_ble_devices.py“を実行してBluetoothのMACアドレスを調べます。
実行結果を以下に示します。nameに“BDM”と出ているのが、B35Tになります。
従って、MACアドレスは、18:45:16:a4:89:1c です。

3-2. NotifyコマンドのUUIDの調査

2-2項の” get_device_uuids.py“を実行してNotifyコマンドのUUIDを調べます。
実行結果を以下に示します。大きく3つに分かれています。
1) Device Information:read コマンドのUUIDが9つあります。
2) Generic Attribute Progile:indicate コマンドのUUIDが1つあります。
3) Vendor specific:readコマンドが2つ、writeコマンドが1つ、read/writeコマンドが1つ、notifyコマンドが1つあります。それぞれにUUIDが記載されています。
ここでは、notifyコマンドのUUID “0000fff4-0000-1000-8000-00805f9b34fb”を使用します。

3-3. Notifyコマンドを使ってデータの取込プログラム

上で調べたMACアドレスとUUIDを使って、センサ機器からデータを読み出します。
そして読み出したデータをHTTP requestを使って、Node-REDのhttp in ノードに送ります。urlは、’http://localhost:1880/temperature’としました。また、送るデータはjsonフォーマットの形式としています。

http_b35t.py

# -*- coding: utf-8 -*-
import sys
import asyncio
from bleak import BleakClient

import time
import json
import urllib.request

# you can change these to match your device or override them from the command line
CHARACTERISTIC_UUID = "0000fff4-0000-1000-8000-00805f9b34fb" # notify
ADDRESS = (
    "18:45:16:a4:89:1c" # BDM
)

url = 'http://localhost:1880/temperature'
sdata = {    # 辞書型データ
    'sensor': 'B35T',
    'temp': 20.0,
    'units':'deg'
}

def contact(data):   # Node-REDとの通信   
    headers = {'Content-Type': 'application/json',}

    req = urllib.request.Request(url, json.dumps(data).encode(), headers)
    try:
        with urllib.request.urlopen(req) as res:
            body = res.read()
    except urllib.error.HTTPError as err:
        print(err.code)
    except urllib.error.URLError as err:
        print(err.reason)
    
    print('code',res.getcode())
    return(body)

def notification_handler(sender, data):
    """Simple notification handler which prints the data received."""
    #print("{0}: {1}".format(sender, data))
    sensor = sender
    data1 = data.decode()
    data2 = float(data1[1:5])
    #sdata['sensor'] = sensor
    sdata['temp'] = data2
    print(sdata)
    res = contact(sdata)   # send temperature data

async def main(address, char_uuid):
    async with BleakClient(address) as client:
        print(f"Connected: {client.is_connected}")

        await client.start_notify(char_uuid, notification_handler)
        await asyncio.sleep(5.0)
        await client.stop_notify(char_uuid)

if __name__ == "__main__":
    asyncio.run(
        main(
            sys.argv[1] if len(sys.argv) > 1 else ADDRESS,
            sys.argv[2] if len(sys.argv) > 2 else CHARACTERISTIC_UUID,
        )
    )

3-4. Node-REDのエディタ・プログラミング

B35Tから送られたデータがPythonプログラムで処理され、HTTP requestでNode-REDに送られます。それを受けて、ダッシュボードに表示するNode-REDのエディタプログラムを説明します。Node-REDは大きく3つのブロックに分かれます。
1) Pythonプログラムの実行部
2) HTTPリクエストの受け部
3) ダッシュボード表示部
の3つです。

3-4-1. Pythonプログラムの実行部

Pythonプログラムの実行部は、injectノード、execノード、debugノードの3つのノードで構成されます。中身の変更は、execノードのコマンド欄です。コマンド欄に、実行コマンドとプログラム名を記載します。
python3  /home/pi/source/python_pro/enable_b35t/http_b35t.py

3-4-2. HTTPリクエストの受け部

HTTPリクエストの受け部は、http inノード、http responseノード、changeノード、Date/Time Formatterノード、debugノードの5つのノードから構成されます。http inノードのURL欄に、pythonプログラムのurlと同じ“/temperature”を記載します。そして、メソッドには、POSTを選びます。また、changeノードは、payloadの下のデータの整理を行います。

3-4-3.ダッシュボード表示部

ダッシュボード表示部は、changeノードとchartノードで構成されます。changeノードで値をmsg.payloadに移します。また、charノードは、グループとタブの設定を行い、最大値/最小値の設定も行います。

3-5. 実行結果

B35Tの電源を入れ、Bluetoothボタンを押してBLEでのデータ送信状態にしたうえで、injectノードのボタンを押します。するとpython3のプログラムが実行され、データがhttpで送られてきます。送られてきたデータはデバッグノードに表示されるとともに、ダッシュボードのチャート画面に表示されます。

4. APERA Instruments社製 Smart Multiparameter Tester PC60-Z

PC6-Zは、PH値、伝導率、TDS、塩分を測定できる水質測定計です。Bluetoothでスマートフォンのアプリとやり取りを行うことができますので、そのデータを読み取ります。そして、海産物の養殖でBluetoothを使ってIoT用に使用できないかを検討します。

4-1. BluetoothのMACアドレスの調査

2-1項の”scan_ble_devices.py“を実行してBluetoothのMACアドレスを調べます。
実行結果を以下に示します。一番上の行に出ているのが、PC6-Zになります。
ポイントはPC6-Zの電源を入れる前に、一度スキャンし、電源投入後と比較することです。その短期間に増えたBluetoothのアドレスが対称機器の可能性が高いです。
MACアドレスは、 B9:12:B3:20:7F:F8 です。

4-2. NotifyコマンドのUUIDの調査

2-2項の” get_device_uuids.py“を実行してNotifyコマンドのUUIDを調べます。
実行結果を以下に示します。大きく2つに分かれています。
1) Generic Attribute Profile:indicate コマンドのUUIDが1つあります。
2) Vendor specific:同一UUIDに、write-without-responseコマンドが1つとwriteコマンドが1つ、それと別のUUIDにnotifyコマンドが1つあります。
ここでは、notifyコマンドのUUID “0000ffe4-0000-1000-8000-00805f9b34fb”を使用します。

4-3. Notifyコマンドを使ってデータの解析

上で調べたMACアドレスとUUIDを使って、センサ機器からデータを読み出します。
プログラムは、enable_pc6.pyです。
データは、Binary配列として読み出されます。PHと温度の値の変化とデータを見比べてデータがどこに埋め込まれているかを推測します。
次の結果が、PC6-Zの表示とBluetoothのデータを比較した結果になります。
赤字の部分がPHの変化でデータが変わる部分、青字の部分が温度の変化で変わる部分です。それぞれ10進に直したのが右端の結果です。

さらに温度を上げると、一番右端のByteが xff から x00に変わり、その左側のByteが 0 から 1 に変わるのが分かります。一番右側のByteとその左側のByteで、温度を表していると考えられます。ただし、左側のByteの‘0’は文字コードです。数字に直すと b’0’= 48 = 0x30、同様に、b’1’= 49 = 0x31です。単純に計算には使えません。
そこで、bytearray[5]がb’1’なら、bytearray[6]に255を足すようにします。このため、下の例で分かるように温度25.5℃の表現の仕方は、’b0\xff’と’b1\x00’の2通りがあります。

この結果から、PHと温度は、次の計算式で求まることが分かります。
   PH値 = (bytearray[2:4]を10進化 - 6096) ÷ 100
  温度 :もし bytearray[5] = b’0’ = 48 なら 温度 = bytearry[6]を10進化 ÷ 10
     もし bytearray[5] = b’1’ = 49 なら 温度 = bytearry[6]を10進化 ÷ 10 +255

この計算式を組み込んで、プログラムを実行した結果を次に示します。5秒おきに5回測定しています。
測定器の表示結果を、PDHのPythonのプログラムの結果が一致しているのが分かります。

1) PH: 8.13pH   Temp: 18.7℃

2) PH: 4.44pH   Temp: 18.5℃

次にデータの解析に用いた、Pythonのプログラムを示します。
ADDRESSをそれぞれの機器に合わせて変更する必要があります。

enable_pc6.py

# -*- coding: utf-8 -*-
import sys
import asyncio

from bleak import BleakClient

CHARACTERISTIC_UUID = "0000ffe4-0000-1000-8000-00805f9b34fb" # notify
ADDRESS = (
    "B9:12:B3:20:7F:F8" # PC60-Z
)

def notification_handler(sender, data: bytearray):
    index = data[1]
    ph = int.from_bytes(data[2:4], byteorder = 'big', signed=False)
    ph = (ph - 6096) / 100
    if (data[5]==49):
        temp = 255 + data[6]
    else:
        temp = data[6]
    temp = temp / 10
    print(f"PH: {ph}, Temp: {temp}")
#    print(f"PH: {ph}, Temp: {temp}, data: {data}")

async def main(address, char_uuid):
    async with BleakClient(address) as client:
        print(f"Connected: {client.is_connected}")

        await client.start_notify(char_uuid, notification_handler)
        await asyncio.sleep(5.0)
        await client.stop_notify(char_uuid)

if __name__ == "__main__":
    asyncio.run(
        main(
            sys.argv[1] if len(sys.argv) > 1 else ADDRESS,
            sys.argv[2] if len(sys.argv) > 2 else CHARACTERISTIC_UUID,
        )
    )

4-4. Node-REDのダッシュボードへの表示

PC60-Zから送られたデータをPythonプログラムで処理し、HTTP requestでNode-REDに送ります。それを受けて、Node-REDのダッシュボードに表示する流れを、説明します。

4-4-1. Node-REDへの転送プログラム

Node-REDへは、HTTP requestで転送します。4-3の項で解析に使ったプログラムに、HTTP転送部(関数contact)を追加します。

http_pc60.py

# -*- coding: utf-8 -*-
import sys
import asyncio
from bleak import BleakClient

import time
import json
import urllib.request

CHARACTERISTIC_UUID = "0000ffe4-0000-1000-8000-00805f9b34fb" # notify
ADDRESS = (
    "B9:12:B3:20:7F:F8" # PC60-Z
)

url = 'http://localhost:1880/pH'
sdata = {    # 辞書型データ
    'sensor': 'pc60-z',
    'ph': 7.0,
    'temp': 20.0
}

def contact(data):   # Node-REDとの通信   
    headers = {'Content-Type': 'application/json',}

    req = urllib.request.Request(url, json.dumps(data).encode(), headers)
    try:
        with urllib.request.urlopen(req) as res:
            body = res.read()
    except urllib.error.HTTPError as err:
        print(err.code)
    except urllib.error.URLError as err:
        print(err.reason)
    
    print('code',res.getcode())
    return(body)

def notification_handler(sender, data):
    ph = int.from_bytes(data[2:4], byteorder = 'big', signed=False)
    ph = (ph - 6096) / 100
    if (data[5]==49):
        temp = 255 + data[6]
    else:
        temp = data[6]
    temp = temp / 10
    print(f"PH: {ph}, Temp: {temp}")
    sdata['ph'] = ph
    sdata['temp'] = temp
    res = contact(sdata)   # send measured data

async def main(address, char_uuid):
    async with BleakClient(address) as client:
        print(f"Connected: {client.is_connected}")

        await client.start_notify(char_uuid, notification_handler)
        await asyncio.sleep(5.0)
        await client.stop_notify(char_uuid)

if __name__ == "__main__":
    asyncio.run(
        main(
            sys.argv[1] if len(sys.argv) > 1 else ADDRESS,
            sys.argv[2] if len(sys.argv) > 2 else CHARACTERISTIC_UUID,
        )
    )
4-4-2. Node-REDのエディタ・プログラミング

Node-REDは大きく3つのブロックに分かれます。
1) Pythonプログラムの実行部
2) HTTPリクエストの受け部
3) ダッシュボード表示部
の3つです。

ノード内部の説明は、「3-4. Node-REDのエディタ・プログラミング」を参照ください。基本的に同じです。
相違点は、
(1) 実行するプログラム名(http_pc60.py)
(2) HTTP リクエストのurl名(/pH)
(3) ダッシュボードのチャートが2つ になっている
ところです。

4-5. 実行結果

PC60-Zの電源を入れ、PH測定を開始します。その後、injectノードのボタンを押します。するとpython3のプログラムが実行され、データがhttpで送られてきます。送られてきたデータはデバッグノードに表示されるとともに、ダッシュボードのチャート画面に表示されます。