猛暑到来
5月では観測事情まれにみる猛暑。我が家もあっついです。家では猫が留守番しているのですが、高齢というのもあってちと心配。部屋が高温になったらお知らせするボットを作れないかなあと、とりあえずセンサの巻。
調達する
Amazonか秋月のような電子部品屋に売ってます。Amazonすごいな。
DHT11が定番だけど
とりあえずラズパイで温度といえばDHT11。安いしGPIOで繋げられるし、ネットにライブラリが転がってる。ですけど、、、測定誤差±2℃っていくらなんでも大きすぎじゃないですか?そんなに精度はいらないけど、もう一息ほしいところです。
ということでDHT22を購入
DHT22を購入。DHT11に比べると高いけど、まあ安い(800円位)。
買うときはセンサ単体と基盤に実装済のものがあります。
板に実装する予定がないときは実装済みのほうが楽だと思います。ジャンパピンまでついてるし。
基盤実装品で買う場合
同じ外見をしていても制作業者によって質が様々のようです。レビューを見て慎重に買ってください。
僕はHiletgo制を買いました。
あとピンのアサインが業者やロットによって様々なので、配線は基盤の表記を見て行ってください。僕は何も考えずにネットのとおりに配線したらDHT11が焼け落ちました。
センサ単体で買う場合
こんな感じに抵抗を入れる必要があるそうです。詳しくはデータシートを。
とりあえず使ってみる。
とりあえずDHT11のライブラリそのままで使えないかな、ということでやってみます。
配線
僕の買ったものはDAT/VCC/GNDの3つがあったので以下のように配線
DAT -> 7(GPIO4) | VCC -> 1 | GND -> 9
VCCはデータシートを見ると6.0Vまで動くようなので、3.3Vのピンが余ってなければ5Vピンでも行けると思います。
DHT11用ライブラリの導入
gitに転がっているDHT11ライブラリをcloneします。
gitをインストールしていない人はインストール
sudo apt-get install git
そんで
git clone https://github.com/szazo/DHT11_Python.git
そうするとカレントディレクトリに必要なファイルがダウンロードされます。
tree
├── DHT11_Python
├── dht11_example.py
├── dht11.py
├── __init__.py
├── LICENSE.md
└── README.md
参考:Raspberry PiとDHT11で温度・湿度を測る
https://qiita.com/mininobu/items/1ba0223af84be153b850
使ってみる
ピン番号だけ書き換え。
instance = dht11.DHT11(pin=4)
そんで実行
python dht11_example.py
Last valid input: 2019-05-29 03:23:42.870835
Temperature: 1 C
Humidity: 2 %
Last valid input: 2019-05-29 03:23:46.116768
Temperature: 1 C
Humidity: 2 %
だめです!
我が家はそんなに寒くない。
dht11.pyを改造する
DHT22ってなんか情報が少ないんですよね、調べ不足かもしれませんが。dht11のライブラリ書き換えてなんとかなんないかなあ。
のぞいてみる
センサーからの読み出しの核になってるのはdht11.py、これをのぞいてみるとCRCまでチェックしてくれててエラー出てない。読み出しのフォーマットとかバイト数はDHT11と一緒みたい。
ソースのぞくとDHT11/22が送ってくるシリアルデータは5バイト。DHT11のビット配置は下のようになっています。DHT11は測定範囲が狭いから温度データは1バイトで十分なんですね。
順当に考えると、精度の高いDHT22は湿度/温度2バイトづつなんですけど、2バイトの小数扱う変数ってありましたっけ?
ローデータを出してみる。
root@raspberrypi:/home/pi# python dht22_dump.py
[1, 241, 1, 10, 253]
これを単純に2バイト整数として見ると
湿度:497 | 温度:266
小数点型を使ってるわけではなくて、2バイト整数型で読んで、10で割るといいみたいです。
dht22.pyを作る
dht11.pyからの変更点はこちら。
もっとスマートな方法があるのかな、
#追加
temp = (float(the_bytes[2]) * 256 + the_bytes[3]) / 10
hum = (float(the_bytes[0]) * 256 + the_bytes[1]) / 10
#変更
return DHT22Result(DHT22Result.ERR_NO_ERROR, temp, hum)
あとclass名をDHT22にした。
# -*- coding: utf-8 -*-
import time
import RPi
class DHT22Result:
'DHT22 sensor result returned by DHT22.read() method'
ERR_NO_ERROR = 0
ERR_MISSING_DATA = 1
ERR_CRC = 2
error_code = ERR_NO_ERROR
temperature = -1
humidity = -1
def __init__(self, error_code, temperature, humidity):
self.error_code = error_code
self.temperature = temperature
self.humidity = humidity
def is_valid(self):
return self.error_code == DHT22Result.ERR_NO_ERROR
class DHT22:
'DHT22 sensor reader class for Raspberry'
__pin = 0
def __init__(self, pin):
self.__pin = pin
def read(self):
RPi.GPIO.setup(self.__pin, RPi.GPIO.OUT)
# send initial high
self.__send_and_sleep(RPi.GPIO.HIGH, 0.05)
# pull down to low
self.__send_and_sleep(RPi.GPIO.LOW, 0.02)
# change to input using pull up
RPi.GPIO.setup(self.__pin, RPi.GPIO.IN, RPi.GPIO.PUD_UP)
# collect data into an array
data = self.__collect_input()
# parse lengths of all data pull up periods
pull_up_lengths = self.__parse_data_pull_up_lengths(data)
# if bit count mismatch, return error (4 byte data + 1 byte checksum)
if len(pull_up_lengths) != 40:
return DHT22Result(DHT22Result.ERR_MISSING_DATA, 0, 0)
# calculate bits from lengths of the pull up periods
bits = self.__calculate_bits(pull_up_lengths)
# we have the bits, calculate bytes
the_bytes = self.__bits_to_bytes(bits)
# calculate checksum and check
checksum = self.__calculate_checksum(the_bytes)
if the_bytes[4] != checksum:
return DHT22Result(DHT22Result.ERR_CRC, 0, 0)
#dump時に使用
#print(str(the_bytes) )
#追加
temp = (float(the_bytes[2]) * 256 + the_bytes[3]) / 10
hum = (float(the_bytes[0]) * 256 + the_bytes[1]) / 10
#変更
return DHT22Result(DHT22Result.ERR_NO_ERROR, temp, hum)
def __send_and_sleep(self, output, sleep):
RPi.GPIO.output(self.__pin, output)
time.sleep(sleep)
def __collect_input(self):
# collect the data while unchanged found
unchanged_count = 0
# this is used to determine where is the end of the data
max_unchanged_count = 100
last = -1
data = []
while True:
current = RPi.GPIO.input(self.__pin)
data.append(current)
if last != current:
unchanged_count = 0
last = current
else:
unchanged_count += 1
if unchanged_count > max_unchanged_count:
break
return data
def __parse_data_pull_up_lengths(self, data):
STATE_INIT_PULL_DOWN = 1
STATE_INIT_PULL_UP = 2
STATE_DATA_FIRST_PULL_DOWN = 3
STATE_DATA_PULL_UP = 4
STATE_DATA_PULL_DOWN = 5
state = STATE_INIT_PULL_DOWN
lengths = [] # will contain the lengths of data pull up periods
current_length = 0 # will contain the length of the previous period
for i in range(len(data)):
current = data[i]
current_length += 1
if state == STATE_INIT_PULL_DOWN:
if current == RPi.GPIO.LOW:
# ok, we got the initial pull down
state = STATE_INIT_PULL_UP
continue
else:
continue
if state == STATE_INIT_PULL_UP:
if current == RPi.GPIO.HIGH:
# ok, we got the initial pull up
state = STATE_DATA_FIRST_PULL_DOWN
continue
else:
continue
if state == STATE_DATA_FIRST_PULL_DOWN:
if current == RPi.GPIO.LOW:
# we have the initial pull down, the next will be the data pull up
state = STATE_DATA_PULL_UP
continue
else:
continue
if state == STATE_DATA_PULL_UP:
if current == RPi.GPIO.HIGH:
# data pulled up, the length of this pull up will determine whether it is 0 or 1
current_length = 0
state = STATE_DATA_PULL_DOWN
continue
else:
continue
if state == STATE_DATA_PULL_DOWN:
if current == RPi.GPIO.LOW:
# pulled down, we store the length of the previous pull up period
lengths.append(current_length)
state = STATE_DATA_PULL_UP
continue
else:
continue
return lengths
def __calculate_bits(self, pull_up_lengths):
# find shortest and longest period
shortest_pull_up = 1000
longest_pull_up = 0
for i in range(0, len(pull_up_lengths)):
length = pull_up_lengths[i]
if length < shortest_pull_up:
shortest_pull_up = length
if length > longest_pull_up:
longest_pull_up = length
# use the halfway to determine whether the period it is long or short
halfway = shortest_pull_up + (longest_pull_up - shortest_pull_up) / 2
bits = []
for i in range(0, len(pull_up_lengths)):
bit = False
if pull_up_lengths[i] > halfway:
bit = True
bits.append(bit)
return bits
def __bits_to_bytes(self, bits):
the_bytes = []
byte = 0
for i in range(0, len(bits)):
byte = byte << 1
if (bits[i]):
byte = byte | 1
else:
byte = byte | 0
if ((i + 1) % 8 == 0):
the_bytes.append(byte)
byte = 0
return the_bytes
def __calculate_checksum(self, the_bytes):
return the_bytes[0] + the_bytes[1] + the_bytes[2] + the_bytes[3] & 255
クラス名をDHT22に変更したのでサンプルも少し変更。
あとGPIOは結構入れ替えるので、ピン番号を最初に出しました。
import RPi.GPIO as GPIO
import dht22
import time
import datetime
GPIO_PIN = 4
# initialize GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()
# read data using pin GPIO_PIN
instance = dht22.DHT22(pin=GPIO_PIN)
while True:
result = instance.read()
if result.is_valid():
print("Last valid input: " + str(datetime.datetime.now()))
print("Temperature: %s C" % result.temperature)
print("Humidity: %s %%" % result.humidity)
time.sleep(1)
動かしてみるよ
python dht22_example.py
Last valid input: 2019-05-29 04:27:36.415902
Temperature: 28.5 C
Humidity: 53.7 %
温度は結構正確だけど、湿度は手元の温度湿度計と比べると4%くらい高め。
他のひとの結果を見てもそんな感じだからここは補正が必要かな。
これから
とりあえず自宅の温度は確認できるようになりました。ただこののままじゃ不便なので、敷居の温度を超えたらSlackに通知して、冷房のONを促す仕組みを作ろうと思います。
夏がくる前に!