IoTで使うPython入門Step2-MQTT (4) リアルなデータを発行

 ラズパイはマルチタスク・マルチユーザのOSが動いています。Step1ではLM75Bを用いて温度データを収集していました。
 ラズパイにI2Cバスでつながっているブレッドボード上には、二つのLM75Bと温湿度センサのSHT31が1個つながっていたので、温度と湿度を発行する三つのプログラムを最初に作ります。それぞれターミナルで動かすと、合計四つのメッセージが発行され、Brokerに届きます。


 それぞれが収集したデータをBrokerに例えば、3秒ごとに発行します。
 利用したのはsingle()関数です。

書式:single(topic, payload=None, qos=0, retain=False, hostname="localhost",
port=1883, client_id="", keepalive=60, will=None, auth=None, tls=None,
protocol=mqtt.MQTTv311, transport="tcp")

1個目のLM75B

 プログラム名はlm75s.pyです。Step1で作成したプログラムをベースに作りました。

#coding:utf-8
import paho.mqtt.publish as publish
from time import sleep
import smbus

i2c = smbus.SMBus(1)
addr = 0x4c # LM75B NXP. TI is 9bit
topic = "raspberrypi-2/lm75b-1/temp"
host = 'raspberrypi.local'

def sign16(x):
return (-(x & 0b1000000000000000) | (x & 0b0111111111111111))

# main
while 1:
data = i2c.read_i2c_block_data(addr, 0, 2)
raw = data[0] * 256 + data[1]
raw_s = sign16(raw)
temp = (raw_s >> 5) * 0.125
print temp, "C"
publish.single(topic, str(temp), hostname=host)
sleep(3)

2個目のLM75B

 プログラム名はlm75s2.pyです。lm75s.pyのスレーブ・アドレスとtopicを変更しました。

#coding:utf-8
import paho.mqtt.publish as publish
from time import sleep
import smbus

i2c = smbus.SMBus(1)
addr = 0x48 # LM75B NXP. TI is 9bit
topic = "raspberrypi-2/lm75b-2/temp"
host = 'raspberrypi.local'

def sign16(x):
return (-(x & 0b1000000000000000) | (x & 0b0111111111111111))

# main
while 1:
data = i2c.read_i2c_block_data(addr, 0, 2)
raw = data[0] * 256 + data[1]
raw_s = sign16(raw)
temp = (raw_s >> 5) * 0.125
print temp, "C"
publish.single(topic, str(temp), hostname=host)
sleep(3)

1個のSHT31

 プログラム名はsht31.pyです。CRCは計算していません。こちらの記事をベースに修正しました。topicは温度と湿度の両方をsingleで発行しました。

#coding:utf-8
import paho.mqtt.publish as publish
from time import sleep
import smbus

i2c = smbus.SMBus(1)
addr=0x44
topic = "raspberrypi-2/sht31-1/temp"
topic2 = "raspberrypi-2/sht31-1/RH"
host = 'raspberrypi.local'

# main
i2c.write_byte_data(addr,0x23,0x34)
sleep(0.5)
while 1:
i2c.write_byte_data(addr,0xe0,0x0)
data = i2c.read_i2c_block_data(addr,0x0,6)
rawT = ((data[0]) << 8) | (data[1])
rawR = ((data[3]) << 8) | (data[4])
temp = -45 + rawT * 175 / 65535
print (str(round(temp,1)) +"C")
RH = 100 * rawR / 65535
print (str(RH) +"%")
publish.single(topic, str(round(temp,1)), hostname=host)
publish.single(topic2, str(RH), hostname=host)
sleep(3)
print ("---")


 上記のプラグラムではsingle()関数を2回使って発行しました。multiple()関数を使うと、まとめて発行できます。

書式:multiple(msgs, hostname="localhost", port=1883, client_id="", keepalive=60,
will=None, auth=None, tls=None, protocol=mqtt.MQTTv311, transport="tcp")

#coding:utf-8
import paho.mqtt.publish as publish
from time import sleep
import smbus

i2c = smbus.SMBus(1)
addr=0x44
topic1 = "raspberrypi-2/sht31-1/temp"
topic2 = "raspberrypi-2/sht31-1/RH"
host = 'raspberrypi.local'

# main
i2c.write_byte_data(addr,0x23,0x34)
sleep(0.5)
while 1:
i2c.write_byte_data(addr,0xe0,0x0)
data = i2c.read_i2c_block_data(addr,0x0,6)
rawT = ((data[0]) << 8) | (data[1])
rawR = ((data[3]) << 8) | (data[4])
temp = round((-45 + rawT * 175 / 65535.0),2)
print (str(temp) +"C")
RH = 100 * rawR / 65535
print (str(RH) +"%")
msg = [{'topic':topic1, 'payload':str(temp)},
( topic2, str(RH), 0, False)]
publish.multiple(msg, hostname=host)
sleep(1)
print ("---")

 それぞれ実行権を付け、ターミナルを開いてpython lm75s.pyなどと実行します(※)。

Node-REDでデータが送られてくる様子を可視化

 前回のグラフを表示する部分を三つコピーし、修正して、四つのセンサ・データを表示します。グラフはリアルタイムで更新されます。

 プログラムです。リストの中でダブルクリックすると全選択になるので、CTRL-Cでコピーします。

[{"id":"1443c2e4.f7c51d","type":"tab","label":"フロー 2","disabled":false,"info":""},{"id":"6464b142.8c075","type":"mqtt in","z":"1443c2e4.f7c51d","name":"","topic":"raspberrypi-2/lm75b-1/temp","qos":"2","broker":"2f14cba6.10af64","x":180,"y":40,"wires":[["9e1c48a1.336008","cbc4161e.40f778"]]},{"id":"9e1c48a1.336008","type":"debug","z":"1443c2e4.f7c51d","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":470,"y":60,"wires":[]},{"id":"cbc4161e.40f778","type":"ui_gauge","z":"1443c2e4.f7c51d","name":"","group":"dbbc7f88.4e765","order":0,"width":0,"height":0,"gtype":"gage","title":"LM75B-1","label":"℃","format":"{{value}}","min":0,"max":"50","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","x":460,"y":120,"wires":[]},{"id":"702bb55b.25e76c","type":"mqtt in","z":"1443c2e4.f7c51d","name":"","topic":"raspberrypi-2/lm75b-2/temp","qos":"2","broker":"2f14cba6.10af64","x":180,"y":180,"wires":[["9be05c80.b6389","4f4a4e40.c736b"]]},{"id":"9be05c80.b6389","type":"debug","z":"1443c2e4.f7c51d","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":470,"y":220,"wires":[]},{"id":"4f4a4e40.c736b","type":"ui_gauge","z":"1443c2e4.f7c51d","name":"","group":"dbbc7f88.4e765","order":0,"width":0,"height":0,"gtype":"gage","title":"LM75B-2","label":"℃","format":"{{value}}","min":0,"max":"50","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","x":460,"y":280,"wires":[]},{"id":"a201b43b.93b488","type":"mqtt in","z":"1443c2e4.f7c51d","name":"","topic":"raspberrypi-2/sht31-1/temp","qos":"2","broker":"2f14cba6.10af64","x":170,"y":320,"wires":[["de32128d.66259","a05a18cf.8e9b88"]]},{"id":"de32128d.66259","type":"debug","z":"1443c2e4.f7c51d","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":470,"y":360,"wires":[]},{"id":"a05a18cf.8e9b88","type":"ui_gauge","z":"1443c2e4.f7c51d","name":"","group":"dbbc7f88.4e765","order":0,"width":0,"height":0,"gtype":"gage","title":"SHT31-1","label":"℃","format":"{{value}}","min":0,"max":"50","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","x":460,"y":420,"wires":[]},{"id":"19ea763a.1e736a","type":"mqtt in","z":"1443c2e4.f7c51d","name":"","topic":"raspberrypi-2/sht31-1/RH","qos":"2","broker":"2f14cba6.10af64","x":170,"y":460,"wires":[["911d9cae.4f2d","c7916f9b.97cfd"]]},{"id":"911d9cae.4f2d","type":"debug","z":"1443c2e4.f7c51d","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":570,"y":520,"wires":[]},{"id":"c7916f9b.97cfd","type":"ui_gauge","z":"1443c2e4.f7c51d","name":"","group":"dbbc7f88.4e765","order":0,"width":0,"height":0,"gtype":"wave","title":"SHT31-humidity","label":"%","format":"{{value}}","min":0,"max":"100","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","x":580,"y":580,"wires":[]},{"id":"2f14cba6.10af64","type":"mqtt-broker","z":"","name":"ラズパイ・ゼロ","broker":"raspberrypi.local","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"dbbc7f88.4e765","type":"ui_group","z":"","name":"温度センサ","tab":"26923c63.690754","disp":true,"width":"6","collapse":false},{"id":"26923c63.690754","type":"ui_tab","z":"","name":"ラズパイ2","icon":"dashboard"}]

 最初に作業していたのがフロー1です。+をクリックしてフロー2を出します。エディット・エリアでCTRL-Iを押すと、貼り込み画面が出ます(画面キャプチャの都合上フロー4で作業中)。CTRL-Vでペーストします。

 読み込みボタンを押すとマウスに全ノードがひっついて動くので、適当な場所でクリックすると、固定されます。デプロイします。それぞれのノードの設定値は、そのノードをダブルクリックして編集画面で見てください。

(※)paho-mqttのドキュメント paho.mqtt.pythonのソース

コラム round()

 C言語のround()は、四捨五入する組み込み関数です。カンマのあとの数字は小数点第何位まで表示するかを指定します。Pythonでは丸め機能です。四捨五入と同じになるときもありますが、そうでないときもあります。

 次の例は、小数点第2位が四捨五入?丸め?されます。

round(25.255,1)

 25.3が結果です。

round(25.255,2)

 この結果は25.25です。25.26だと思ったのですが、違います。python3.5.3、python2.7.13 どちらも同じ結果です。
 検索すると、round()は偶数への丸めという解説がありますが、上記の結果はそうなっていません。

 floatの計算では小数点以下の桁数がたくさん出てしまいます。温度センサは確度がそれほどあるわけではないので、有効桁数以上の温度を表示しても無駄なので、必要な桁数で表示するときに使います。

(※)python lm75s.pyという形で実行するとき、lm75s.py自体に実行権は不要です。このプログラムの先頭に、#!/usr/bin/env pythonをつけて単独で動かすときは実行権が必要です。入れ忘れたりするので、実行権を付けるようにしています。