TOPに戻る

センサ・シリーズ 温湿度①SHT31 その4 BLE

  SHT31で測定した温湿度をBLEで通信できるようにします。ラズパイはサーバの立場のペリフェラル動作です。ラズパイで使えるpythonのBLEライブラリはさほど多くはありません。

 使用環境
  • Raspberry Pi Raspberry Pi 4 Model B 2MB
  • OS Raspberry Pi OS(32ビット)5.4.72
  • Python3 3.7.3
  • smbusライブラリ 4.1-1
  • i2ctoolsパッケージ 4.1
  • pybleno 0.11

 使用するpyblenoは、内部で使用するhci関連が管理者権限で動くので、次のようにインストールします。

  sudo pip3 install pybleno

 ソースや事例は、https://github.com/Adam-Langley/pyblenoにあります。

examples/batteryservice/をベースに拡張する

 サンプルは三つのファイルに分かれていますが、一つにします。

 関数sht31()で、SHT31の読み取りはCRCは省略しました。戻り値は、温度と湿度の値(実数)のリストです。

 関数sht31Service()は、サービスを定義します。UUIDは任意な文字列を使いました。Characteristics(

以下、キャラ)などでは最後の数字を変更しています。

 関数sht31_temp_Characteristic()は、サービスsht31Service()の内部の定義です。read属性だけがあります。onReadRequest()は、セントラル側がread要求を出すと呼ばれます。内容は、list(struct.pack('>f', sht31()[0])) で、温度を固定小数点形式で送ります。

 起動は、sudo python3 xxx.pyです。実行を終了するのには、何らかのキーをターミナル内で打ちます。

from pybleno import*
import time
import sys

def sht31():
    import smbus
    i2c = smbus.SMBus(1)
    i2c.write_byte_data(0x44, 0x23, 0x34)
    time.sleep(0.015)
    dataTmsb, dataTlsb, dataTcrc, dataHmsb, dataHlsb, dataTcrc = i2c.read_i2c_block_data(0x44, 0xff, 6)
    rawT = dataTmsb << 8 | dataTlsb
    rawR = dataHmsb << 8 | dataHlsb
    temp = -45 + rawT * 175 / 65535
    RH = 100 * rawR / 65535
    # print(' temp= {0:.1f} `C,  humi= {1:.1f} RH%'.format(temp, RH))
    return [temp, RH]

print(sht31()[0], sht31()[1])

class sht31Service(BlenoPrimaryService):
    def __init__(self):
        BlenoPrimaryService.__init__(self, {
            'uuid':'1234-5678-1234-5678-1234-5678-9abc-def0',
            'characteristics':[sht31_temp_Characteristic()]
        }
    )

class sht31_temp_Characteristic(Characteristic):
    def __init__(self):
        Characteristic.__init__(self,{
            'uuid':'1234-5678-1234-5678-1234-5678-9abc-def7',
            'properties':['read'],
            'value':None
        }
    )

    def onReadRequest(self, offset, callback):
        callback(Characteristic.RESULT_SUCCESS, array.array('B', list(struct.pack('>f', sht31()[0])) ))


# main
bleno = Bleno()
primaryService = sht31Service();

def onStateChange(state):
    print('on->stateChange:'+state);
    if(state=='poweredOn'):
        bleno.startAdvertising('SHT31',[primaryService.uuid]);
    else:
        bleno.stopAdvertising();

bleno.on('stateChange',onStateChange)

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

    if not error:
        def on_setServiceError(error):
            print('setServices: %s'  % ('error ' + error if error else 'success'))

        bleno.setServices([
            primaryService
        ], on_setServiceError)
bleno.on('advertisingStart', onAdvertisingStart)

bleno.start()

print('Hittodisconnect')

if(sys.version_info>(3,0)):
    input()
else:
    raw_input()

bleno.stopAdvertising()
bleno.disconnect()

print('terminated.')
sys.exit(1)

確認

 動作中のターミナルの様子です。

 オン・セミコンダクターのRSL10-USB001GEVK: RSL10 USB DongleとそのツールRSL10 Bluetooth Low Enaergy Exploerをつかってセントラル側の動作をします。

 local NameがSHT31なので、見つけたらConnectします。この時点でAdvertisingが止まります。Discover Serviceでサービスを表示します。UUIDの1234-5678-1234-5678-1234-5678-9abc-def7が温度データです。

 読み取った0x41acd91dは固定小数点形式です。struct.pack('>f', はビッグ・エンディアンの実数を指定しています。

 Floating Point to Hex Converterのサイトに行き、この数値を入力し、変換します。21.606でした。正しく届いているようです。

温度に加えて湿度を送る

 温度のキャラsht31_temp_Characteristic(Characteristic)と同様に湿度のキャラを作ります。そしてサービスにそのキャラを追加します。

from pybleno import *
import time
import sys

def sht31():
    import smbus
    i2c = smbus.SMBus(1)
    i2c.write_byte_data(0x44, 0x23, 0x34)
    time.sleep(0.015)
    dataTmsb, dataTlsb, dataTcrc, dataHmsb, dataHlsb, dataTcrc = i2c.read_i2c_block_data(0x44, 0xff, 6)
    rawT = dataTmsb << 8 | dataTlsb
    rawR = dataHmsb << 8 | dataHlsb
    temp = -45 + rawT * 175 / 65535
    RH = 100 * rawR / 65535
    return [temp, RH]

print(sht31()[0], sht31()[1])

class sht31Service(BlenoPrimaryService):
    def __init__(self):
        BlenoPrimaryService.__init__(self, {
            'uuid':'1234-5678-1234-5678-1234-5678-9abc-def0',
            'characteristics':[sht31_temp_Characteristic(), sht31_humi_Characteristic()]
        }
    )

class sht31_temp_Characteristic(Characteristic):
    def __init__(self):
        Characteristic.__init__(self, {
            'uuid':'1234-5678-1234-5678-1234-5678-9abc-def7',
            'properties':['read'],
            'value':None
        }
    )

    def onReadRequest(self, offset, callback):
        callback(Characteristic.RESULT_SUCCESS, array.array('B', list(struct.pack('>f', sht31()[0])) ))

class sht31_humi_Characteristic(Characteristic):
    def __init__(self):
        Characteristic.__init__(self,{
            'uuid':'1234-5678-1234-5678-1234-5678-9abc-def8',
            'properties':['read'],
            'value':None
        }
    )

    def onReadRequest(self, offset, callback):
        callback(Characteristic.RESULT_SUCCESS, array.array('B', list(struct.pack('>f', sht31()[1])) ))

# main
bleno = Bleno()
primaryService = sht31Service()

def onStateChange(state):
    print('on->stateChange:'+state)
    if(state=='poweredOn'):
        bleno.startAdvertising('SHT31 temp humi',[primaryService.uuid])
    else:
        bleno.stopAdvertising()

bleno.on('stateChange', onStateChange)

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

    if not error:
        def on_setServiceError(error):
            print('setServices: %s' % ('error ' + error if error else 'success'))

        bleno.setServices([
            primaryService
        ], on_setServiceError)

bleno.on('advertisingStart', onAdvertisingStart)

bleno.start()

print('Hittodisconnect')

if(sys.version_info > (3,0)):
    input()
else:
    raw_input()

bleno.stopAdvertising()
bleno.disconnect()

print('terminated.')
sys.exit(1)

 セントラル側の情報です。

UARTでデータを送る

 ラズパイのBLEでUARTを使うメリットはわかっていません。ノルディックBLEデバイスなどを使ったボードではよく使われます。物理的にUART用のピンに何かをつなぐという利用方法があるかもしれませんが、実験していません。

 UARTのUUIDは決まっています。

UARTサービスUUID "6e400001-b5a3-f393-e0a9-e50e24dcca9e"
送信キャラUUID "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
受信キャラUUID "6e400003-b5a3-f393-e0a9-e50e24dcca9e"

 sht31()関数では、温度と湿度を文字列のリストにしました。

 onReadRequest()関数では、温度と湿度の区切りを';'にし、バイト列で送ります。

from pybleno import *
import time
import sys

def sht31():
    import smbus
    i2c = smbus.SMBus(1)
    i2c.write_byte_data(0x44, 0x23, 0x34)
    time.sleep(0.015)
    dataTmsb, dataTlsb, dataTcrc, dataHmsb, dataHlsb, dataTcrc = i2c.read_i2c_block_data(0x44, 0xff, 6)
    rawT = dataTmsb << 8 | dataTlsb
    rawR = dataHmsb << 8 | dataHlsb
    temp = -45 + rawT * 175 / 65535
    RH = 100 * rawR / 65535
    #print(str(temp))
    #print(str(RH))
    return [str(round(temp,2)),str(round(RH,1))]

class sht31Service(BlenoPrimaryService):
    def __init__(self):
        BlenoPrimaryService.__init__(self, {
            'uuid':'6e400001-b5a3-f393-e0a9-e50e24dcca9e',
            'characteristics':[sht31_Characteristic()]
        }
    )

class sht31_Characteristic(Characteristic):
    def __init__(self):
        Characteristic.__init__(self, {
            'uuid':'6e400002-b5a3-f393-e0a9-e50e24dcca9e',
            'properties':['read'],
            'value':None
        }
    )

    def onReadRequest(self, offset, callback):
        callback(Characteristic.RESULT_SUCCESS, (sht31()[0]+ ';' +sht31()[1]).encode('utf-8'))

# main
bleno = Bleno()
primaryService = sht31Service()

def onStateChange(state):
    print('on->stateChange:'+state)
    if(state=='poweredOn'):
        bleno.startAdvertising('SHT31 UART',[primaryService.uuid])
    else:
        bleno.stopAdvertising()

bleno.on('stateChange',onStateChange)

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

    if not error:
        def on_setServiceError(error):
            print('setServices: %s' % ('error ' + error if error else 'success'))

        bleno.setServices([
            primaryService
        ], on_setServiceError)

bleno.on('advertisingStart', onAdvertisingStart)

bleno.start()

print('Hittodisconnect')

if(sys.version_info > (3,0)):
    input()
else:
    raw_input()

bleno.stopAdvertising()
bleno.disconnect()

print('terminated.')
sys.exit(1)

 セントラル側です。UTF8の文字列でみると、そのまま数字で読み取れます。

連載 ラズパイ センサ・シリーズ

(1) 温度①確度±0.1℃ TMP117 その1 温度の読み出し

(2) 温度①確度±0.1℃ TMP117 その2 アラートの設定

(3) 温度①確度±0.1℃ TMP117 その3 アラートでLED点灯

(4) 温度①確度±0.1℃ TMP117 その4 アラートでリレー駆動

(5) 温度②温度調節器 その1 Modbusの設定

(6) 温度②温度調節器 その2 Modbusで読み書き

(7) 温湿度①SHT31 その1 測定準備

(8) 温湿度①SHT31 その2 測定と波形

(9) 温湿度①SHT31 その3 CRC

(10) 温湿度①SHT31 その4 BLE