秋月電子で売られている温度湿度センサDHT20(380円) をRaspberry Pi 4に取り付けました。これから温度・湿度を取り込み、MQTTに流すPythonプログラムを書きました。それをHomebridgeで受け取ってHomeKitから使えるようにしました。以下の構成です。これで左端のセンサからのデータが、右端のiPhone/Macに表示されます。温度・湿度の値に応じたオートメーションも設定できます。
温度湿度センサDHT20
これから乾燥する時期を迎えて、湿度によって加湿器をコントロールするなどやってみたくなりました。そうなるとちゃんとした湿度センサが欲しくなります。I2C接続の温度・湿度センサならRaspberry PiやArduinoと簡単に接続できると聞いていたので、秋月電子のセンサを買いました。I2Cを使うのは初めてのことでしたが、線を4本接続するだけで簡単に使えました。
ASAIRというブランドのセンサで、3.3Vでも5Vでも稼働し、キャリブレーションされた温度と湿度が、I2Cインタフェースでデジタルに得られます。精度は温度が+/- 0.5度、湿度が+/- 3%だそうです。申し分ない性能で、しかも安いです。下の写真はセンサの裏側です。
I2C接続
I2Cは2本の信号線を、複数のセンサやコンピュータが共有し、データをシリアルに交換する規格です。信号線の1本がデータ線で、もう一本は同期タイミングのための線です。接続されたそれぞれのデバイスは固有のアドレスをもち、アドレスとデータの組み合わせを送受信します。
Raspberry PiやArduinoには、I2C接続のためのファームウェアが組み込まれていますし、開発環境にはI2Cのライブラリが用意されているので、あまり苦労することなくインタフェースが作れます。
DHT20をRaspberry Piに接続する
秋月電子の商品ページからDHT20のデータシートがダウンロードできます。
これによると4本のピンは、左から、電源、SDA, GND, SCLです。SDAとSCLがI2Cの信号線です。
電圧レベルは3.3Vと5Vに対応しているようです。Raspberry PiのGPIOは3.3Vなので、3.3Vを供給して使います。また、Raspberry PiのGPIOピンの3番と4番がI2CのSDAとSCLピンです。これらのピンは、GPIO2とGPIO3として普通のデジタル入出力にも使えます。I2Cで使う場合には、I2C通信を実現する際に必要なクロック回路やバッファに接続されるのだと思います。なので、DHT20の1番をRaspberry Piの1番に、2番を3番に、3番を9番に、4番を5番に接続しました。
こんな感じです。
こういう接続には、フラットケーブルになったジャンパ線が便利です。割いて使えるので、必要な本数をまとめて配線できます。たくさん入っていて安いです。Amazonで買いました。
メスーメスのジャンパ線を使ったところ、センサの足にも、GPIOにもささりました。半田付けすることなく、センサとコンピュータを接続できました。Raspberry Piのケースからジャンパ線を引き出して、センサをケースの側面に両面テープで貼り付けました。
ちなみに使用しているケースはこれです。蓋が開けやすくて便利です。
Raspberry PiのI2Cを有効にする
まずはRaspberry PiのI2Cピンを有効にするよう設定します。$ sudo raspi-configとタイプして、設定画面を出します。
ここで、Interface Optionsを選んで、
I2CをEnableします。この後Raspberry Piを一応再起動しましたが、しなくても良かったかもしれません。
I2C関係のコマンドがインストールされていると便利なようです。i2cdetectというコマンドで、接続されたデバイスが確認できるらしいのですが、コマンドがありませんでした。aptでインストールします。長らくupdateしていなかったので、一応アップデートしました。
$ sudo apt update $ sudo apt upgrade
この後、i2c-toolsというのをインストールします。
$ sudo apt install i2c-tools
これで、i2c関係のコマンドが使えるようになります。例えば、以下のようにすると、
$ i2cdetect -y 1 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- 38 -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --
接続されているI2Cデバイスを検出し、そのアドレスを表示してくれます。この結果によると0x38番のアドレスを持ったデバイスが接続されていることがわかりました。前出のDHT20のデータシートによると、このセンサは0x38番のアドレスを使っています。正しく接続されているようでした。
DHT20用プラグインを探す(該当なし)
有名なI2Cセンサなので、Homebridgeのプラグインで読めるかもしれないと、最初は思っていました。HomebridgeのwebインタフェースのプラグインのページでDHTで検索すると13個ほどのプラグインが見つかりました。説明によると、Raspberry PiにI2C接続したDHTセンサを動かすプラグインもありました。
ただ、DHT20を対象としたものは無く、DHT11 / DHT12 / DHT21 / DHT22 / DHT33 / DHT44などが対象でした。DHT20は、前述のデータシートによるとDHT11の改良版らしいです。なので別の型番で動くかなと思ったのですが、どれもエラーが出たり、おかしな値が表示されたりします。
データフォーマットが異なっているのかもしれません。またDHTセンサというのはditigal humidity and temperatureセンサの略だそうです。一般的な名前なので、同じ名前を使った他社のセンサがあるのかもしれません。
ということで、専用プラグインでお手軽に設定するのは諦めて、Pythonプログラム、MQTT、Mqttthingプラグインの組み合わせで進めることにしました。
追記:DHT11とDHT21をネット上で見つけたのですが、なんとI2C方式じゃないのですね。シリアルでデータが出てくるだけのようでした。GNDピンの位置も違います。どうりで使えなかったはずです。秋月にはDHT11も売っているようです(DHT20よりも高い)。そのうち試したいと思います。
Pythonでセンサ値を読む
こちらを参考にさせていただき、Pythonでセンサ値を読むプログラムを作りました。
DHT20のデータシートには、I2Cでセンサ値を読む手順が説明されています。それによると、
- 電源投入後100ms待つ
- 0x71を送ってstatus wordを得る
- status wordと0x18のANDが0x18だったら次に進む。そうでない場合は、レジスタを初期化する必要がある
- 10ms待つ
- 測定をトリガするコマンド0xAC, 0x33, 0x00を送る
- 80ms待つ
- 7バイトのデータを読む。1バイト目のMSBが1だったらまだ測定できていないので、もう少し待つ。0だったら次に進む
- 2,3,4,5,6バイトに、湿度・温度の情報が12ビットずつ入っているので分離する
- 7バイト目にはCRCが入っているので必要ならば確認する
- 湿度、温度の情報は、正規化された仮数のような表現になっているので、換算して浮動小数点10進数に変換する
- 必要に応じて5以下を繰り返す。測定の間隔は2秒以上にする
という手順のようです。これをPythonでプログラムします。ただし、I2C用のライブラリが足りませんでした。
pip3 install smbus
でインストールします。プログラムは、以下のようになりました。30秒間隔で測定します。
#!/usr/bin/python3
import smbus
import time
i2c = smbus.SMBus(1)
ADDRESS = 0x38
#setup for DHT20
statusword = i2c.read_byte_data(ADDRESS, 0x71)
if statusword & 0x18 != 0x18:
print("Registers should be initialized.")
sys.exit()
time.sleep(0.01) #10ms sleep after setup
while True:
#trigger measurement
i2c.write_i2c_block_data(ADDRESS,0,[0xAC,0x33,0x00])
while True:
#wait the measurement to be completed
time.sleep(0.08)
data = i2c.read_i2c_block_data(ADDRESS, 0x00, 0x07)
if data[0] & 0x80 ==0:
break
#Concatenation
humi = data[1] << 12 | data[2] << 4 | data[3] >> 4
temp = ((data[3] & 0x0F) << 16) | data[4] << 8 | data[5]
#Conversion
humidity = round(humi / 2**20 * 100)
temperature = round((temp / 2**20 * 200 - 50),1)
#Print results
results='{"temperature":'+str(temperature)+',"humidity":'+str(humidity)+'}'
print(results)
time.sleep(30)
これを動かすと、以下のように、30秒間隔で温度・湿度が表示されました。温度は四捨五入して小数点以下第1位で、湿度は整数で表示しています。
$ ./dhtread.py {"temperature":23.7,"humidity":64} {"temperature":23.7,"humidity":63} {"temperature":23.7,"humidity":63}
print文はjson風にしてみました。
センサ値をMQTTにパブリッシュする
この結果を、MQTTブローカーパブリッシュします。MQTTに関しては以下の記事をご覧ください。
MQTTブローカーを、同じRaspberry Piで動かしているので、IPアドレスはlocalhostです。トピックは、後でHomebridgeのmqttthingプラグインを使うので、mqttthing/dht20/getにしました。ここにjson形式で測定結果を流します。またデバッグ用のメッセージをmqttthing/dht20/debugに流すことにしました。MQTTライブラリにはmahoを使います。
長くなったのでメインプログラムをmain()にしました。初期化、データ読み込み、メインを分けて分かりやすくしました。
#!/usr/bin/python3
import smbus
import time
from paho.mqtt import client as mqtt_client ## for MQTT
## for I2C ##
i2c = smbus.SMBus(1)
ADDRESS = 0x38
## MQTT ##
address='localhost'
port=1883
debug_topic='mqttthing/dht20/debug'
pub_topic='mqttthing/dht20/get'
client_id=b'dht20_02648259' #something random
username='xxxxxxxx'
password='XXXXXXXX'
def setup():
## setup for MQTT ##
client=mqtt_client.Client(client_id)
client.username_pw_set(username,password)
client.connect(address,port)
time.sleep(2) #wait for MQTT connection
## setup for DHT20 ##
statusword = i2c.read_byte_data(ADDRESS, 0x71)
if statusword & 0x18 != 0x18:
print("The 0x1B, 0x1C, 0x1E registers should be initialized.")
client.publish(debug_topic,"Initialization required.")
sys.exit()
time.sleep(0.01) #10ms sleep after setup
client.publish(debug_topic,"DHT20 is ready.")
return client
def readData():
#trigger measurement
i2c.write_i2c_block_data(ADDRESS,0,[0xAC,0x33,0x00])
#read data
while True:
#wait the measurement to be completed
time.sleep(0.08)
data = i2c.read_i2c_block_data(ADDRESS, 0x00, 0x07)
if data[0] & 0x80 ==0:
break
#Concatenation
humi = data[1] << 12 | data[2] << 4 | data[3] >> 4
temp = ((data[3] & 0x0F) << 16) | data[4] << 8 | data[5]
#Conversion
humidity = round(humi / 2**20 * 100)
temperature = round((temp / 2**20 * 200 - 50),1)
results='{"temperature":'+str(temperature)+',"humidity":'+str(humidity)+'}'
return results
def main():
client=setup()
while True:
results=readData()
print(results)
client.publish(pub_topic,results)
client.loop(). ## MQTT loop
time.sleep(180)
if __name__ == "__main__":
main()
更新頻度を3分にしました。別のターミナルウィンドウでmosquitto_subコマンドを動かし、サブスクライブしておきます。結果は以下のようになりました。
mqttthing/dht20/debug DHT20 is ready. mqttthing/dht20/get {"temperature":22.8,"humidity":59} mqttthing/dht20/get {"temperature":22.8,"humidity":59} mqttthing/dht20/get {"temperature":22.9,"humidity":59}
プログラムを自動起動する
センサから値を読んでMQTTに流すこのプログラムは、Raspberry Piが起動したらいつでも動いていて欲しいです。なので、systemctlを使って自動起動させます。systemctlに関しては、いろいろなサイトに分かりやすい説明がありますので、ここはメモ程度に書いておきます。
まずは設定ファイルを作ります。/etc/systemd/systemに、dht20.serviceという名前のファイルを作り、内容を以下にしました。ホームディレクトリがpiの場合です。そこに、servicesというディレクトリを作り、その中に上記で作成したファイルをdht20.pyという名前で置いておくという設定です。
[Unit] Description=dht20 After=network.target [Service] Type=simple WorkingDirectory=/home/pi/services ExecStart=/home/pi/services/dht20.py TimeoutStopSec=5 StandardOutput=null Restart=always [Install] WantedBy = multi-user.target
この後、
$ sudo systemctl daemon-reload #設定ファイル有効化 $ sudo systemctl start dht20.service #スタートさせる $ sudo systemctl status dht20.service #動作を確認 $ sudo systemctl stop dht20.service #ストップする $ sudo systemctl enable dht20.service #電源を入れた時に動くように設定する
などのコマンドで動作させたり、次回のリブートで自動起動させたりできます。
Homebridgeで受け取る
HomebridgeにはMqttthingプラグインを入れてあります。MQTTメッセージで動くアクセサリを実装できます。今回もこれを使います。
Mqttthinの設定から、tenmeratureSensorとhumiditySensorを使いました。MQTTの設定は、プラグインの設定画面からGUIで入力できます。最終的に出来上がったconfigの部分は以下になりました。
{
"type": "temperatureSensor",
"name": "DHT20_temp",
"username": "xxxxxxxx",
"password": "XXXXXXXX",
"topics": {
"getCurrentTemperature": "mqttthing/dht20/get$.temperature"
},
"accessory": "mqttthing"
},
{
"type": "humiditySensor",
"name": "DHT20_humi",
"username": "xxxxxxxx",
"password": "XXXXXXXX",
"topics": {
"getCurrentRelativeHumidity": "mqttthing/dht20/get$.humidity"
},
"accessory": "mqttthing"
}
Mqttthingは、json形式のデータも扱えます。上の設定で、
mqttthing/dht20/get$.temperature
としたことで、mqttthing/dht20/getトピックに流れてくるjson形式のデータ:
{"temperature":23.0,"humidity":56}
のtemperatureキーの部分(23.0という値)を取得できます。humidityも同様です。
HomeKitから使ってみる
この結果、iPhoneやMacのホーム.appの上に、
というような表示が現れ、これをクリックすると、
のように、今回取り付けたセンサーの値が表示されました。これをもとにオートメーションも作れます。室温が下がったらエアコンを動作させるとか、湿度が下がったら加湿器をonにするなどの自動化が可能です。例えば、上のDHT20_humiの表示をクリックして、「オートメーションを追加」のメニューを選択すると、数クリックの操作で、下のようなオートメーションが完成します。
まとめ
秋月で380円で買えるI2C温度・湿度センサをRaspberry Piに接続して、iPhoneやMacのホーム.appから利用できるように設定しました。Raspberry PiでHomebridgeを動かしているなら、4本の線を配線するだけで温度・湿度センサが作れます。結果をMQTTのメッセージで流すようにしたので、他のプログラムやスマートホームシステムからも利用できると思います。
コメント
if data[0] & 0x80 ==0:
break;
ここですが 準備ができたときにブレイクになりませんか
ビット7が0のとき次に進むと思いますが
はい、ご指摘の通り、準備が出来た時にwhile Trueの無限ループをbreakして、温度湿度を表示します。
準備が出来てなければbreakしないので、無限ループが繰り返されて、80ms待機して再度データを取得します。
(break;の最後の;はうっかり書いてしまっていたので消しました)
ご丁寧なアドバイスありがとうございました
私が勘違いしていました
while Trueの無限ループを 準備完了で抜け出すのですね
ありがとうございました
こちらこそ、読んでいただきありがとうございます