ラズパイで自動ライトON/OFF機能付きの温湿度計をつくる

こんにちは。

自宅のテレワーク部屋にこもるとすぐに風邪をひくので「乾燥してるからだ！」と思い、**封印されていたラズベリーパイで『オレオレ温湿度計』を作ってみました。**

## できたもの
![オレオレ温湿度計.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183744/804d8b7c-ad32-c44b-8334-4a5329b5d929.jpeg)

ラズパイの起動と同時に**LCDに部屋の温度と湿度（ついでにCPU温度も）を表示します。**

ただ、人がいない間も常時LCDが点灯しているのがなんか嫌だったので、**カメラで動体検知したらLCDのバックライトを点灯するようにしてみました。**  
![無題のプレゼンテーション.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183744/8c4967b0-f086-0401-7094-f99b5b83c210.png)



## 環境
- RaspberryPi 4 Model B
- RaspberryPi OS 5.4.51
- Python 3.7.3
- 赤外線付きカメラ
- LCDモジュール（I2Cインタフェース付）

## 実装手順
### LCDにI2Cインターフェースをつける
まず温湿度を表示するためのLCDを調達しました。

Amazonでいろいろと安く売られており私は[ラズパイ接続が簡単なI2Cインターフェース付きのこちらの商品](https://www.amazon.co.jp/WayinTop-%E3%83%87%E3%82%A3%E3%82%B9%E3%83%97%E3%83%AC%E3%82%A4%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB-16%C3%972%E3%82%AD%E3%83%A3%E3%83%A9%E3%82%AF%E3%82%BF-%E3%83%90%E3%83%83%E3%82%AF%E3%83%A9%E3%82%A4%E3%83%88%E4%BB%98%E3%81%8D-%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%AB%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%82%A4%E3%82%B9/dp/B07WNQPZHV/ref=asc_df_B07WNQPZHV/?tag=jpgo-22&linkCode=df0&hvadid=342654740765&hvpos=&hvnetw=g&hvrand=1718085193643187013&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009129&hvtargid=pla-858645258913&psc=1)を購入しました。

![IMG_20211124_222919.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183744/d05a6ee6-3692-0522-e361-99f9171cd10d.jpeg)


ただしこのモジュールは**I2Cインターフェースを使うためにはんだ付けが必要です。**

はんだ付けの道具を持っていない人は[このラズパイキット](https://www.amazon.co.jp/SunFounder-Raspberry-Pico-%E3%82%AA%E3%83%B3%E3%83%A9%E3%82%A4%E3%83%B3%E3%83%9E%E3%83%8B%E3%83%A5%E3%82%A2%E3%83%AB%E3%80%81%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E9%9B%BB%E5%AD%90%E5%B7%A5%E4%BD%9C%E3%81%AE%E3%83%AF%E3%83%B3%E3%82%B9%E3%83%88%E3%83%83%E3%83%97%E5%AD%A6%E7%BF%92%E3%80%81%E3%83%A9%E3%82%BA%E3%83%91%E3%82%A4%E3%83%94%E3%82%B3%E5%88%9D%E5%BF%83%E8%80%85%E5%90%91%E3%81%91%E5%B7%A5%E4%BD%9C%E3%82%AD%E3%83%83%E3%83%88/dp/B093V4F5DF/ref=sr_1_1_sspa?keywords=%E3%83%A9%E3%82%BA%E3%83%91%E3%82%A4+lcd&qid=1639578060&sr=8-1-spons&psc=1&spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUEzVFVDOTIzWjVQSzhNJmVuY3J5cHRlZElkPUEwMDU0MzQ3MUtFQ1VVSVI2QlNCSSZlbmNyeXB0ZWRBZElkPUEzVlNETU9FWjhDUlVIJndpZGdldE5hbWU9c3BfYXRmJmFjdGlvbj1jbGlja1JlZGlyZWN0JmRvTm90TG9nQ2xpY2s9dHJ1ZQ==)にはんだ付けされた、同様のLCDモジュールが付いているようなのでそちらをお勧めします。


私もはんだ付け道具持っていませんでしたが、LCDモジュール届いた後に気づいたので泣く泣くはんだ付け道具も購入しました……

こんな感じではんだ付けしました。はんだ付けはど素人ですが問題なく動いています。

![IMG_20211206_222932.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183744/893520d4-d3d0-a7e2-d164-6b644953c954.jpeg)


![IMG_20211206_222943.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183744/8d5aff7c-79c4-8be1-979a-be9d1c5077b9.jpeg)

### ラズパイにLCDをつなげる
I2CインターフェースはVCC,GND,SDA,SCLをラズパイ側と接続します。

ラズパイはデフォルトでI2Cバス1がGPIOピン2と3で有効になっています。しかし私のラズパイのGPIOピン3は[ワンタッチでラズパイを起動／停止するボタン](https://monomonotech.jp/kurage/raspberrypi/shutdown_button.html)で使っているので、**I2Cバス3を使います。**

無効になっているI2Cバスを有効にするには`/boot/config.txt`で設定をオーバーライドします。

```properties:/boot/config.txt
# 以下の設定を追加する
dtoverlay=i2c3
```

このバスがどのGPIOピンを使うのかは`dtoverlay -h`で確認します。バス3はデフォルトでGPIOピン4,5を使うようです。

```shell
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/](https://osoyoo.com/)で[そのまま動くサンプル](http://osoyoo.com/driver/i2clcda.py)が公開されているのでこれを実行してみます。

注意点としてサンプルではI2Cのバス1を使っているので、私の場合はバス3に書き換える必要があります。

```python:i2clcda.py
#bus = smbus.SMBus(1) # Rev 2 Pi uses 1
bus = smbus.SMBus(3) 
```

また、`i2cdetect`で確認したI2Cアドレスが`0x27`以外の場合は下記の部分も書き換える必要があります。

```python
# Define some device parameters
I2C_ADDR  = 0x27 # I2C device address, if any error, change this address to 0x3f
```
スクリプトを実行してLCDに文字が表示されればOKです。

```shell
python3 icslcda.py
```

![IMG_20211217_005831.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183744/fd5a3c4b-5b46-62f9-82f3-4bcd523271e7.jpeg)



### 温湿度モジュールを接続する
温湿度モジュールは過去にこちらの記事で紹介しています。  
[ラズパイ×Amazon TimeStreamで部屋の温湿度を可視化](https://qiita.com/quotto/items/faf43b1ca739d2e38627)

&nbsp;&nbsp;

温湿度モジュールはAmazonで購入できる[こちらの商品](https://www.amazon.co.jp/DSD-TECH-Arduino-Raspberry-Pi%E7%94%A8%E6%B8%A9%E6%B9%BF%E5%BA%A6%E3%82%BB%E3%83%B3%E3%82%B5%E3%83%BC%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB/dp/B07CM2VLBK/ref=sr_1_2_sspa?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=NBMHC6N4G2FR&keywords=%E6%B8%A9%E6%B9%BF%E5%BA%A6%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB&qid=1639745874&sprefix=%E6%B8%A9%E6%B9%BF%E5%BA%A6%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%2Caps%2C221&sr=8-2-spons&psc=1&spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUEzUFNOSFdLSVdWVFhVJmVuY3J5cHRlZElkPUEwODU5NTU3TDNTVDFaTUtMQUUyJmVuY3J5cHRlZEFkSWQ9QThMN0o2VkE0RFFFViZ3aWRnZXROYW1lPXNwX2F0ZiZhY3Rpb249Y2xpY2tSZWRpcmVjdCZkb05vdExvZ0NsaWNrPXRydWU=)を使います。

VCC,GND,DATをラズパイにつないでPythonの[Adafruit_CircuitPython_DHT](https://github.com/adafruit/Adafruit_CircuitPython_DHT)ライブラリを使えば、簡単に温度と湿度が取得できます。

![IMG_20211217_000214.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183744/069cfae2-422c-ab0f-8083-776574daf4b4.jpeg)


```shell
pip3 install adafruit-circuitpython-dht
```

```python
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：動体検知と通知機能の実装](https://qiita.com/quotto/items/0f7d6847d87cd0432260)

カメラの接続や設定は上の記事を参照してください。

動体検知には**motion**を使います。

```shell
$ sudo apt install motion
```

`/etc/motion/motion.conf`の以下の部分を修正します。

```conf:/etc/motion/motion.conf
# 動体検知してから次の検知を始めるまでの待機時間（秒）
event_gap 60

# 動体検知で保存される動画の最大時間
max_movie_time 60

# 動体検知した時に実行するコマンドを設定
on_event_start bash -c "touch /tmp/motion_detect"
```

`motion`コマンドでサービスが起動します。

```shell
$ motion
```


### プログラムの実装
いよいよ最終工程です。定期的に温湿度表示を更新しつつ、motionの動体検知に連動してLCDのバックライトが点灯するようにpythonスクリプトを作成します。

といっても仕組みはめちゃくちゃシンプルで

1. 定期的に温湿度を表示するメインスレッド
1. 早い間隔でmotionの`on_event_start`で作られる空ファイル`/tmp/motion_detect`を監視するスレッド

を実装します。

2.のスレッドが空ファイルを検知すればバックライトを点灯させ、`/tmp/motion_detect`を削除します。その後一定間隔以上、`/tmp/motion_detect`が作成されなければバックライトを消す、という流れです。

&nbsp;&nbsp;

こんな感じのプログラムに仕上げました。`i2clcda.py`はバックライトのON/OFFを指定できるように少し改修しています。

[ソースコード(GitHub)](https://github.com/quotto/thermo-hygromter)

```python:thermo-hygrometer.py
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()
```

こんな感じで動きます！
![ダウンロード.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183744/c45873c1-2f53-a57d-5c04-e87622c06257.gif)



以上です。皆さんもよきラズパイ生活をお送りください！！