Raspberry Pi Zero Wで二酸化炭素濃度を測定してMacのメニューバーに表示する

公開

冬になると窓を閉め切ってしまうことが多いので、二酸化炭素濃度が高くなることが気になっていた。そこで、二酸化炭素濃度を計測してmacOSのメニューバーに表示できるようにした。

二酸化炭素濃度を計測

Raspberry Pi Zero WとMH-Z19Bを使って二酸化炭素濃度を計測している。かなり昔に購入したので、今はMH-Z19BではなくMH-Z19Eぐらいになっているらしい。

接続はググればわかるはずなので省略。pip などを使って mh-z19 をインストールして、python3 -m mh_z19 のようにすると、{'co2': 540.0} のような値が返ってくる。

このあたりはかなり前にやっていたので思い出せないが、PWMで繋いでいたので、python3 -m mh_z19 --pwm としていた。

Webサーバを立てる

ちょっとしたWebサーバなので、Flaskを使うことにした。pip などを使って Flask をインストールし、以下のように実装した。よくわからないが、GPIO.cleanup() を呼ばないと数回計測を実行するだけでタイムアウトしてしまうので追加した。このあたりは生成AIに書いてもらったのであまり参考にならないかもしれない。

from flask import Flask, jsonify
import mh_z19
import RPi.GPIO as GPIO

app = Flask(__name__)

@app.route('/co2')
def co2():
    try:
        GPIO.cleanup()
        result = mh_z19.read_from_pwm()
    except mh_z19.GPIO_Edge_Timeout as e:
        try:
            GPIO.cleanup()
        except Exception:
            pass
        return jsonify({'error': 'timeout reading CO2 sensor'}), 500

    if result and 'co2' in result:
        # 正常
        return jsonify({'co2': result['co2']})
    else:
        try:
            GPIO.cleanup()
        except Exception:
            pass
        return jsonify({'error': 'could not read CO2'}), 500

if __name__ == '__main__':
    try:
        app.run(host='0.0.0.0', port=5000)
    finally:
        GPIO.cleanup()

上記のPythonファイルを指定して実行し、問題なくデータが取得できそうだったら自動で起動できるようにする。

sudo nano /etc/systemd/system/co2-api.service
[Unit]
Description=CO2 API Service
After=network.target

[Service]
ExecStart=/usr/bin/python3 /home/pi/app.py
WorkingDirectory=/home/pi
StandardOutput=inherit
StandardError=inherit
Restart=always
User=pi

[Install]
WantedBy=multi-user.target

登録

sudo systemctl daemon-reload
sudo systemctl enable co2-api
sudo systemctl start co2-api

サービスの状態を確認

sudo systemctl status co2-api

macOSのメニューバーに表示

macOSのメニューバーの表示にはSwiftBarというアプリを使うことにした。このアプリはシェルスクリプトを定期的に実行して標準出力の内容をメニューバーに表示してくれるようだ。

シェルスクリプトはプラグインという扱いで、特定のフォルダに保存する。今回はRaspberry PiのFlaskで実装したサーバーにアクセスするシェルスクリプトを書いた。Rubyで書いたのは私がRuby好きなだけで、好きな言語で書くとよい。

#!/usr/bin/env ruby

require "json"
require "net/http"
require "uri"

SERVER_URL = "http://YOUR_SERVER:5000/"

response, body = Net::HTTP.get(URI.parse(SERVER_URL))
if response.nil?
  puts "error"
  return
end

puts "#{JSON.parse(response)["co2"]}ppm"