エンジニアのはしがき

プログラミングの日々の知見を書き連ねているブログです

Raspberry Piで温湿度を計測してWebアプリ上から確認できるようにした

f:id:tansantktk:20220113205000j:plain

こんばんは!年末年始はいかがお過ごしでしたでしょうか?

今年は何をしていたかというと年末に秋月電子様で購入した温湿度センサーで遊んでました。測るだけでなくせっかくなら計測値を今使っているWebアプリ上で表示できたらなと思い、実践してみました!

実現したこと

  • Raspberry Piと温湿度センサーを繋ぎ、定期計測
  • 計測値をFirebase Realtime Databaseに保存
  • Webアプリ上からRealtime Databaseを参照し、現在の温湿度がどこでも見れるようにした

システム構成図

f:id:tansantktk:20220110202943p:plain

動作環境

ハードウェア

  • Raspberry Pi 3B+
  • AM2320
    • 温湿度センサー
  • ブレッドボード
  • ジャンパワイヤ(オス~メス)×4

センサーは秋月電子通商様で購入可能です。ラズパイは後継機でも問題ないと思われます。

秋月電子通商 トップページ - 電子部品・半導体 【通販・販売】

ソフトウェア

$ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description:    Raspbian GNU/Linux 10 (buster)
Release:    10
Codename:   buster

$ uname -a
Linux raspberrypi 5.10.63-v7+ #1459 SMP Wed Oct 6 16:41:10 BST 2021 armv7l GNU/Linux

$ python3 --version
Python 3.7.3

$ pip3 --version
pip 18.1 from /usr/lib/python3/dist-packages/pip (python 3.7)

ソースコードサンプル(バックエンド)

ディレクトリ構成などはGitHubより確認ください。

github.com

完成品サンプル

f:id:tansantktk:20220111214349p:plain

↑のスクリーンショットは私が私用で使っているエアコン操作のWebアプリですが、今回計測した温湿度も表示するようにしてみました。 今回の仕様では約1分毎に最新の自宅の温湿度を表示してくれます。

↓エアコン操作周りの機能は以前の記事にまとめていますので、ご興味があればどうぞ!

tm-progapp.hatenablog.com

アプリ構築

前提条件

  • 温湿度を格納する先であるFirebase Realtime Database、および認証用のサービスアカウントは既に作成されている
  • 温湿度を表示する為のフロントエンドアプリは作成済みで既にFirebase Hostingでホスティングされている

なお今回フロントエンド側の解説はせず、バックエンド側のみとなります。

温湿度センサーをラズパイと接続する

下記図のようにブレッドボードにAM2320を挿し、ジャンパワイヤでラズパイと接続します。

※作図ツールの都合上、AM2320の穴が付いた部分が手前を向いていますが、実際には逆向きになります。

f:id:tansantktk:20220113202856p:plain

各ジャンパワイヤの色別のAM2320からラズパイへの接続先は下記の通りです。

  • 緑色:AM2320 SCL→GPIO3(SCL1)
  • 青色:AM2320 GND→GND
  • 赤色:AM2320 SDA→GPIO2(SDA1)
  • 黄色:AM2320 VDD→5V

SCL, SDAは接続するラズパイ側のピンが決まっているのでよく確認しましょう。

Pythonから温湿度センサーを操作する

AM2320ではI2Cという通信インターフェースを利用するのでまず有効化します。

$ sudo raspi-config

3.Interface Options → I2Cを選択し、有効化します。

I2Cでセンサーを認識できているか確認します。

$ i2cdetect -y -r 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- -- 
$ i2cdetect -y -r 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- 5c -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --   

出力値に5cが表示されていればセンサーを認識できています。

i2cdetectを2回実行しているのはAM2320が初期状態がスリープ状態になっており、1回目のコマンド実行だけでは正しく認識されない為です。

接続確認ができたら、次にFirebase Realtime Databaseを参照する為の認証ファイル(json)を用意し、credentialsディレクトリの中に配置します。(認証ファイルのDL方法はFirebase公式等を参照ください) 今回のサンプルではcredentials/air-controller2-firebase-adminsdk-d3sed-4a407aafd0.jsonが認証ファイルです。

認証ファイルが準備できたら、main.pyを作成して計測処理をガリガリ書きます。

main.py

import sys
import time
import smbus
import firebase_admin
from firebase_admin import credentials
from firebase_admin import db

i2c = smbus.SMBus(1)
sensor_address = 0x5c
cred = credentials.Certificate('./credentials/air-controller2-firebase-adminsdk-d3sed-4a407aafd0.json')
firebase_admin.initialize_app(cred, {
    'databaseURL': 'https://air-controller2.firebaseio.com'
})
database = db.reference('')

def wake_up_sensor(address):
    try:
        i2c.write_i2c_block_data(address, 0x00, [])
    except:
        pass

def read_temperature_humidity(address):
    i2c.write_i2c_block_data(address, 0x03, [0x00, 0x04])
    time.sleep(0.015)
    return i2c.read_i2c_block_data(address, 0, 6)

if __name__ == '__main__':
    try:
        while True:
            # センサーは無操作により自動スリープするので起動させる処理を挟む
            wake_up_sensor(sensor_address)
            time.sleep(0.003)

            block = read_temperature_humidity(sensor_address)
            # 人間が理解しやすいよう数値変換
            humidity = float(block[2] << 8 | block[3]) / 10
            temperature = float(block[4] << 8 | block[5]) / 10

            print(f'humidity: {humidity}%  temperature: {temperature}%')
            # RealtimeDatabaseに保存
            database.update({
                'humidity': humidity,
                'temperature': temperature
            })

            time.sleep(60)
    except KeyboardInterrupt:
        sys.exit(0)

センサーの仕様上、スリープ解除の処理やビット演算を行っていますが、メインの処理は温湿度を取得して、Realtime Databaseに保存です。

コードが書けたらデバッグして、標準出力に温湿度が出ることを確認します。

$ python3 ./main.py

温湿度の計測処理をSystemdのサービス化して永続動作させる

以前Node.jsのスクリプトではforeverを使って永続動作を実現しましたが、今回はPythonなのでSystemdのサービスを自作して永続動作を実現しました。

まずはサービスから呼び出す際に実行するシェルスクリプトを作成します。 プロジェクトのルートにservice.shを作成し、下記のように記述します。

service.sh

#!/bin/bash

cd `dirname $0`
python3 ./main.py

次にユニットファイルを作成して編集します。 自作のユニットファイルを追加する時は/etc/systemd/system/*************.serviceというファイル名で作成します。

$ touch /etc/systemd/system/thermo-pi.service
$ vim /etc/systemd/system/thermo-pi.service

/etc/systemd/system/thermo-pi.service

[Unit]
Description = execute thermo-pi.

[Service]
# ExecStartに実行するシェルスクリプトのパスを記述
ExecStart = /home/pi/thermo-pi/service.sh
Restart = always
Type = simple

[Install]
WantedBy = multi-user.target

ExecStartに永続化したいシェルスクリプトのパスを指定しています。 ユニットファイルを作成したら、ターミナルからServiceを稼働させます。

# 一旦systemctlをリロードして追加したユニットファイルを認識させる
$ sudo systemctl daemon-reload

# Serviceの開始
$ sudo systemctl start thermo-pi

# Serviceが"active (running)"であることを確認する
$ sudo systemctl status thermo-pi
● thermo-pi.service - execute thermo-pi.
   Loaded: loaded (/etc/systemd/system/thermo-pi.service; enabled; vendor preset
   Active: active (running) since Mon 2022-01-10 19:32:08 JST; 1 day 2h ago
 Main PID: 21217 (service.sh)
    Tasks: 2 (limit: 1935)
   CGroup: /system.slice/thermo-pi.service
           ├─21217 /bin/bash /home/pi/thermo-pi/service.sh
           └─21219 python3 ./main.py

 111 18:26:11 raspberrypi service.sh[21217]: humidity: 39.9%  temperature: 16
...

これでPythonコードはサービス化され、永続動作するようになりました。ラズパイを再起動しても自動的にOS起動後にユニットファイルで指定したシェルスクリプトを実行してくれます。

あとはFirebase Hostingでホスティングしたフロントエンド側からRealtime Databaseを監視させ、温湿度をレンダリングするようにすればWebアプリ経由で自宅の温湿度が確認できるアプリの出来上がりです😆

フロントエンドは当記事では割愛しますが、特に難しいことはしていません。npmパッケージfirebaseでRealtime Databaseに常時接続するようにし、温湿度の値を画面にレンダリングするだけです。

あとがき

今回の実装のおかげで、Webアプリ経由でのエアコン操作、温湿度確認まで実現することができました。これで温度の情報が保持できたので、閾値を下回ったら自動的にエアコンをONにする…といった処理も実現できそうです。やはり自宅のIoT化はやっていて楽しいですね!

この時期には大活躍の加湿器もIoT化したいとは思っているのですが、起動・ストップには物理スイッチを押す必要がある点、定期的な水の補給が必要な点が課題です。

市販で販売されているSwitchBot等を活用すればラズパイから物理スイッチを押させることはできそうですが、水の補給は自分でポリタンクから給水するようなハードウェア上の機構を組む必要がありそうで、なかなかすぐには実現できなさそうです…🤔 ハードウェア方面は専門外なのでじっくりやっていきたいですね。

参考

www.fabshop.jp