こんにちは。
自宅のテレワーク部屋にこもるとすぐに風邪をひくので「乾燥してるからだ!」と思い、封印されていたラズベリーパイで『オレオレ温湿度計』を作ってみました。
できたもの
ラズパイの起動と同時にLCDに部屋の温度と湿度(ついでにCPU温度も)を表示します。
ただ、人がいない間も常時LCDが点灯しているのがなんか嫌だったので、カメラで動体検知したらLCDのバックライトを点灯するようにしてみました。
環境
- RaspberryPi 4 Model B
- RaspberryPi OS 5.4.51
- Python 3.7.3
- 赤外線付きカメラ
- LCDモジュール(I2Cインタフェース付)
実装手順
LCDにI2Cインターフェースをつける
まず温湿度を表示するためのLCDを調達しました。
Amazonでいろいろと安く売られており私はラズパイ接続が簡単なI2Cインターフェース付きのこちらの商品を購入しました。
ただしこのモジュールはI2Cインターフェースを使うためにはんだ付けが必要です。
はんだ付けの道具を持っていない人はこのラズパイキットにはんだ付けされた、同様のLCDモジュールが付いているようなのでそちらをお勧めします。
私もはんだ付け道具持っていませんでしたが、LCDモジュール届いた後に気づいたので泣く泣くはんだ付け道具も購入しました……
こんな感じではんだ付けしました。はんだ付けはど素人ですが問題なく動いています。
ラズパイにLCDをつなげる
I2CインターフェースはVCC,GND,SDA,SCLをラズパイ側と接続します。
ラズパイはデフォルトでI2Cバス1がGPIOピン2と3で有効になっています。しかし私のラズパイのGPIOピン3はワンタッチでラズパイを起動/停止するボタンで使っているので、I2Cバス3を使います。
無効になっているI2Cバスを有効にするには/boot/config.txt
で設定をオーバーライドします。
# 以下の設定を追加する
dtoverlay=i2c3
このバスがどのGPIOピンを使うのかはdtoverlay -h
で確認します。バス3はデフォルトでGPIOピン4,5を使うようです。
pi@raspberrypi:~ $ dtoverlay -h i2c3
Name: i2c3
Info: Enable the i2c3 bus
Usage: dtoverlay=i2c3,<param>
Params: pins_2_3 Use GPIOs 2 and 3
pins_4_5 Use GPIOs 4 and 5 (default)
baudrate Set the baudrate for the interface (default
"100000")
I2CインターフェースのSDAを4、SCLを5番のピンにつないでラズパイを起動します。i2cdetect -y 3
で27
が出力されればモジュールが認識されています。
pi@raspberrypi:/usr/local/bin $ i2cdetect -y 3
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- 27 -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
この27
はラズパイにつないでいるデバイスのI2Cアドレスのため、もし私と違うデバイスを使っていれば異なるアドレスになる可能性があります。
このアドレスは後述のLCDに文字列を表示するプログラム中で利用します。
とりあえずLCDに文字を出力する
LCDへの文字出力はPythonを使います。
https://osoyoo.com/でそのまま動くサンプルが公開されているのでこれを実行してみます。
注意点としてサンプルではI2Cのバス1を使っているので、私の場合はバス3に書き換える必要があります。
#bus = smbus.SMBus(1) # Rev 2 Pi uses 1
bus = smbus.SMBus(3)
また、i2cdetect
で確認したI2Cアドレスが0x27
以外の場合は下記の部分も書き換える必要があります。
# Define some device parameters
I2C_ADDR = 0x27 # I2C device address, if any error, change this address to 0x3f
スクリプトを実行してLCDに文字が表示されればOKです。
python3 icslcda.py
温湿度モジュールを接続する
温湿度モジュールは過去にこちらの記事で紹介しています。
ラズパイ×Amazon TimeStreamで部屋の温湿度を可視化
温湿度モジュールはAmazonで購入できるこちらの商品を使います。
VCC,GND,DATをラズパイにつないでPythonのAdafruit_CircuitPython_DHTライブラリを使えば、簡単に温度と湿度が取得できます。
pip3 install adafruit-circuitpython-dht
import adafruit_dht
# D26はDATをつないでいるGPIOピン番号
dht_Device = adafruit_dht.DHT22(board.D26)
print("temp: {}'C".format(dht_Device.temperature))
print("humidity: {} %".format(dht_Device.humidity,temp))
動体検知カメラの設定
こちらも過去の記事で紹介しています。
ラズパイで見守りシステム開発その2:動体検知と通知機能の実装
カメラの接続や設定は上の記事を参照してください。
動体検知にはmotionを使います。
$ sudo apt install motion
/etc/motion/motion.conf
の以下の部分を修正します。
# 動体検知してから次の検知を始めるまでの待機時間(秒)
event_gap 60
# 動体検知で保存される動画の最大時間
max_movie_time 60
# 動体検知した時に実行するコマンドを設定
on_event_start bash -c "touch /tmp/motion_detect"
motion
コマンドでサービスが起動します。
$ motion
プログラムの実装
いよいよ最終工程です。定期的に温湿度表示を更新しつつ、motionの動体検知に連動してLCDのバックライトが点灯するようにpythonスクリプトを作成します。
といっても仕組みはめちゃくちゃシンプルで
- 定期的に温湿度を表示するメインスレッド
- 早い間隔でmotionの
on_event_start
で作られる空ファイル/tmp/motion_detect
を監視するスレッド
を実装します。
2.のスレッドが空ファイルを検知すればバックライトを点灯させ、/tmp/motion_detect
を削除します。その後一定間隔以上、/tmp/motion_detect
が作成されなければバックライトを消す、という流れです。
こんな感じのプログラムに仕上げました。i2clcda.py
はバックライトのON/OFFを指定できるように少し改修しています。
import i2clcda
import time
import adafruit_dht
import subprocess
import os
import threading
import traceback
import math
# 現在のバックライトの状態(スレッド間で共有するグローバルな値)
BACKLIGHT = i2clcda.LCD_BACKLIGHT_ON
BACKLIGHT_TOGGLE_INTERVAL_SECONDS=0.1
MOTION_DETECT_EVENT_GAP_SECONDS=60
# 動体検知されない間もLCDバックライトONにする閾値
THRESHOLD_BACKLIGHT_OFF=math.floor(MOTION_DETECT_EVENT_GAP_SECONDS/BACKLIGHT_TOGGLE_INTERVAL_SECONDS)
dht_Device = adafruit_dht.DHT22(board.D26)
lock = threading.Lock()
# i2clcda.pyを使ったLCD更新処理
def display_info():
global BACKLIGHT
try:
result=subprocess.run('vcgencmd measure_temp', shell=True, encoding='utf-8', stdout=subprocess.PIPE).stdout.split('=')
temp=result[1].replace("\n","")
i2clcda.lcd_string("{}'C | CPU".format(dht_Device.temperature),i2clcda.LCD_LINE_1,BACKLIGHT)
i2clcda.lcd_string("{} % | {}".format(dht_Device.humidity,temp),i2clcda.LCD_LINE_2,BACKLIGHT)
except RuntimeError:
print (traceback.format_exc())
# バックライト切替スレッド
# 0.1秒間隔で/tmp/motion_detectの存在チェックを行う
def toggle_backlight():
global BACKLIGHT
file_not_found_count = 0
while True:
if os.path.exists('/tmp/motion_detect'):
# 空ファイルがあればバックライトを点けてファイルは即座に削除
with lock:
file_not_found_count=0
BACKLIGHT = i2clcda.LCD_BACKLIGHT_ON
os.remove('/tmp/motion_detect')
display_info()
else:
file_not_found_count += 1
if file_not_found_count == THRESHOLD_BACKLIGHT_OFF:
# 空ファイルが無い状態が60秒続いたらバックライトを消す
with lock:
BACKLIGHT = i2clcda.LCD_BACKLIGHT_OFF
display_info()
time.sleep(BACKLIGHT_TOGGLE_INTERVAL_SECONDS)
# メインスレッド
# 20秒間隔で温湿度(とラズパイのCPU温度)を表示する
def main():
print('Start main')
while True:
with lock:
display_info()
time.sleep(20)
if __name__ == '__main__':
i2clcda.lcd_init()
thread_main = threading.Thread(target=main)
thread_toggle_backlight = threading.Thread(target=toggle_backlight)
thread_main.start()
thread_toggle_backlight.start()
thread_main.join()
thread_toggle_backlight.join()
以上です。皆さんもよきラズパイ生活をお送りください!!