Raspberry PiでDHT20とMH-Z19Cを使って温湿度・CO2を記録し、Discordへ送る方法
Raspberry Pi 4B に DHT20 と MH-Z19C を接続し、温度・湿度・CO2 を自動で記録して、日ごとのグラフと CSV を Discord に送るまでの流れをまとめる。DHT20 は I2C 接続、MH-Z19C は UART 接続で使うため、センサーごとに有効化するインターフェイスが異なる。
今回の構成では、センサーデータを Python の仮想環境で扱い、CSV に蓄積したあと、matplotlib で日次グラフを作成し、Discord Webhook へ画像と CSV をまとめて送信する。
DHT20の接続方法とインストール
DHT20 は I2C 接続の温湿度センサーで、Raspberry Pi 側では 3.3V、GND、SDA、SCL の4本を接続する。配線は VDD を 1 番ピン、GND を 6 番ピン、SDA を 3 番ピン、SCL を 5 番ピンに接続する形が基本になる。(私はファンを 1 番ピンに接続してしまっているので 17 番ピンに接続している)
| DHT20側 | Raspberry Pi 4B 物理ピン | 役割 |
|---|---|---|
| VDD | 1 , 17 | 3.3V |
| GND | 6 , 9 , 14 , 20 , 25 , 30 , 34 , 39 | GND |
| SDA | 3 , 27 | GPIO2 / SDA |
| SCL | 5 | GPIO3 / SCL |
接続後は sudo raspi-config を開き、Interface Options から I2C を有効化する。Raspberry Pi OS では system Python に直接 sudo pip install する方法が制限されるため、Python の仮想環境を作ってライブラリを入れる方が安全である。
sudo apt update
sudo apt install -y python3-venv python3-full
python3 -m venv ~/aht20-venv
source ~/aht20-venv/bin/activate
pip install --upgrade pip
pip install adafruit-circuitpython-ahtx0 adafruit-blinka pandas matplotlib requests
DHT20 の動作確認は、I2C オブジェクトを作って adafruit_ahtx0.AHTx0(i2c) を初期化し、温度と湿度を読み出すだけでよい。
import board
import adafruit_ahtx0
i2c = board.I2C()
sensor = adafruit_ahtx0.AHTx0(i2c)
print(f"温度: {sensor.temperature:.2f}°C")
print(f"湿度: {sensor.relative_humidity:.2f}%")
MH-Z19Cの接続方法とインストール
MH-Z19C は UART 接続の CO2 センサーで、Raspberry Pi 側では 5V 電源とシリアル通信ピンを使う。センサーの TX を Raspberry Pi の RX へ、センサーの RX を Raspberry Pi の TX へ交差接続するのがポイントである。
| MH-Z19C側 | Raspberry Pi 4B 物理ピン | 役割 |
|---|---|---|
| Vin | 2 , 4 | 5V |
| GND | 6 , 9 , 14 , 20 , 25 , 30 , 34 , 39 | GND |
| TXD | 10 | GPIO15 / RXD0 |
| RXD | 8 | GPIO14 / TXD0 |
UART を使う前に sudo raspi-config を開き、Interface Options から Serial Port を設定する。その際は「ログインシェルを使うか」に No、「シリアルポートハードウェアを有効にするか」に Yes を選ぶと、UART をセンサー通信用に使えるようになる。
MH-Z19C の Python ライブラリとして mh-z19 を使う場合、環境によって gpiozero や lgpio が必要になることがあるため、関連パッケージもまとめて入れておくと安定しやすい。
source ~/aht20-venv/bin/activate
sudo apt install -y swig
pip install mh-z19 gpiozero lgpio
インストール後は、次のコマンドで CO2 が読めるか確認できる。
sudo ~/aht20-venv/bin/python -c "import mh_z19; print(mh_z19.read_all())"
co2 に ppm 値が返ってくれば、UART の配線と設定は正常に動作していると判断できる。
5分ごとの計測と日付ごとのCSV保存
温湿度と CO2 をまとめて記録するには、DHT20 と MH-Z19C を同じ Python スクリプトから読み出し、日付ごとの CSV に追記していく構成が扱いやすい。日付単位でファイルを分けると、当日分だけグラフを作りたい場合や、前日分をそのまま Discord に送る場合に管理しやすい。
以下の log_dht20.py は、5分ごとに /home/miyamoto/sensor_logs/YYYY-MM-DD.csv を作成・追記し、温度、湿度、CO2 を保存するコードである。
import time
import csv
import os
from datetime import datetime
import board
import adafruit_ahtx0
import mh_z19
LOG_DIR = "/home/ユーザー名/sensor_logs"
os.makedirs(LOG_DIR, exist_ok=True)
i2c = board.I2C()
sensor = adafruit_ahtx0.AHTx0(i2c)
while True:
now = datetime.now()
csv_file = f"{LOG_DIR}/{now.strftime('%Y-%m-%d')}.csv"
file_exists = os.path.isfile(csv_file)
with open(csv_file, "a", newline="") as f:
writer = csv.writer(f)
if not file_exists:
writer.writerow(["timestamp", "temperature_c", "humidity_percent", "co2_ppm"])
temp = round(sensor.temperature, 2)
hum = round(sensor.relative_humidity, 2)
co2_data = mh_z19.read_all()
co2 = co2_data.get("co2", None) if co2_data else None
writer.writerow([now.strftime("%Y-%m-%d %H:%M:%S"), temp, hum, co2])
f.flush()
print(f"{now.strftime('%Y-%m-%d %H:%M:%S')} 温度: {temp}°C 湿度: {hum}% CO2: {co2}ppm")
time.sleep(300)
実行時は、MH-Z19C 側の都合で sudo を付けて仮想環境内の Python を明示する方が確実である。
sudo ~/aht20-venv/bin/python ~/log_dht20.py
ターミナルを閉じても計測を継続したい場合は nohup を使うとよい。
sudo nohup ~/aht20-venv/bin/python ~/log_dht20.py &
tail -f ~/nohup.out
グラフ化とDiscordへの送信
日ごとのグラフを作るには、対象日の CSV を pandas で読み込み、matplotlib で温度・湿度・CO2 の3段グラフを作成する。Discord Webhook は requests.post() でファイルを添付して送れるため、PNG と CSV を同時にアップロードできる。
matplotlib で日本語が文字化けする場合は、日本語フォントを OS 側に入れたうえで、font.family に Noto Sans CJK JP を指定することで改善できる。
sudo apt install -y fonts-noto-cjk
以下の plot_dht20.py は、引数なしなら当日分、日付を指定すればその日の CSV からグラフを作り、Discord に画像と CSV を送るコードである。
import matplotlib
matplotlib.rcParams['font.family'] = 'Noto Sans CJK JP'
import sys
import os
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import requests
from datetime import datetime
WEBHOOK_URL = "ここにURLを貼る"
LOG_DIR = "/home/ユーザー名/sensor_logs"
if len(sys.argv) > 1:
target_date = sys.argv[1]
else:
target_date = datetime.now().strftime("%Y-%m-%d")
csv_file = f"{LOG_DIR}/{target_date}.csv"
if not os.path.isfile(csv_file):
print(f"{target_date} のデータが見つかりません: {csv_file}")
raise SystemExit(1)
df = pd.read_csv(csv_file)
df["timestamp"] = pd.to_datetime(df["timestamp"])
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(12, 8), sharex=True)
ax1.plot(df["timestamp"], df["temperature_c"], color="tomato", label="温度")
ax1.set_ylabel("温度 (°C)")
ax1.legend()
ax1.grid(True, alpha=0.3)
ax2.plot(df["timestamp"], df["humidity_percent"], color="steelblue", label="湿度")
ax2.set_ylabel("湿度 (%)")
ax2.legend()
ax2.grid(True, alpha=0.3)
ax3.plot(df["timestamp"], df["co2_ppm"], color="seagreen", label="CO2")
ax3.set_ylabel("CO2 (ppm)")
ax3.axhline(y=1000, color="orange", linestyle="--", label="注意: 1000ppm")
ax3.axhline(y=1500, color="red", linestyle="--", label="換気推奨: 1500ppm")
ax3.legend()
ax3.grid(True, alpha=0.3)
ax3.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
plt.xticks(rotation=45)
plt.suptitle(f"DHT20 + MH-Z19C ログ({target_date})")
plt.tight_layout()
graph_path = f"{LOG_DIR}/{target_date}_graph.png"
plt.savefig(graph_path, dpi=150)
plt.close()
with open(graph_path, "rb") as gf, open(csv_file, "rb") as cf:
files = {
"file1": (f"{target_date}_graph.png", gf, "image/png"),
"file2": (f"{target_date}.csv", cf, "text/csv"),
}
requests.post(WEBHOOK_URL, files=files)
print("DiscordにグラフとCSVを送信しました")
当日分を送る場合は次のように実行する。
source ~/aht20-venv/bin/activate
python ~/plot_dht20.py
特定の日付を指定して送りたい場合は、引数に YYYY-MM-DD 形式の日付を付ければよい。
python ~/plot_dht20.py 2026-05-08
日付更新時に自動でDiscordへ送る
前日分のグラフと CSV を自動送信したい場合は、cron で毎日 0:01 に plot_dht20.py を前日の日付付きで実行する方法が分かりやすい。
crontab -e
末尾に次を追加する。
1 0 * * * /home/ユーザー名/aht20-venv/bin/python /home/ユーザー名/plot_dht20.py $(date -d "yesterday" +\%Y-\%m-\%d)
この設定により、日付が変わった直後に前日分のグラフ画像と CSV ファイルが自動で Discord へ送られる。
トラブルシュート
ModuleNotFoundError: No module named 'matplotlib' が出る場合は、仮想環境を有効にしていないことが多い。 source ~/aht20-venv/bin/activate のあとに python ~/plot_dht20.py を実行するか、~/aht20-venv/bin/python ~/plot_dht20.py のように Python をフルパスで指定すると確実である。
sensor_logs への保存時に PermissionError が出る場合は、sudo 実行でディレクトリの所有者が root になっている可能性がある。その場合は次のコマンドで所有権を戻せる。
sudo chown -R ユーザー名:ユーザー名 /home/miyamoto/sensor_logs
また、MH-Z19C で No module named 'mh_z19' や gpiozero 関連のエラーが出る場合は、仮想環境内のライブラリ不足か、sudo python によって仮想環境外の Python が呼ばれていることが原因になりやすい。sudo ~/aht20-venv/bin/python を使う形に統一すると切り分けしやすい。
iPhoneショートカットについての追加案
iPhone のショートカットに SSH 実行を組み込んだことで、家の中であればスマートフォンから手軽に Raspberry Pi 上のスクリプトを動かせるようになった。測定用の端末を毎回開かなくても、必要なタイミングで最新のグラフを Discord に送れるため、室内環境を確認するハードルがかなり下がった。
まとめ
今回の計測を通して、窓を閉めたまま就寝した場合には CO2 濃度が 2000 ppm を超えることがあり、室内の空気環境が想像以上に悪化していることが分かった。あらためて、換気の重要性を定量的に確認できた点は大きな収穫だった。
やはり換気は重要...
現時点では、窓を開けてもまだ比較的快適に過ごせる気温のため、換気による改善を取り入れやすい。しかし、夏場になると窓を開け続けることが温熱環境の悪化につながる可能性もあるため、今後は蓄積したログを見ながら、温度・湿度・CO2 のバランスが取れた最適な室内環境を探っていきたい。
また、現状では Discord 経由、あるいは Raspberry Pi にアクセスしたときにしかログを確認できず、日常的に使うにはやや手間がかかることも分かった。今後はディスプレイを接続し、室内環境が常時ひと目で分かるような構成にすることで、より実用的なモニタリングシステムに発展させたい。

