エンジニアのはしがき

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

RaspberryPiをCO2測定マシンにしてみた

夏ですね😎

連休に何をしていたかというと個人的な夏休みの自由研究としてラズパイでCO2測定マシンを作っておりました。

↓こんな感じです。無骨ですが個人用なのでいいのです。 f:id:tansantktk:20210731213155j:plain

今回はこのマシンについて紹介していきたいと思います。

参考

CO2量の測定処理の部分については下記の記事を参考に組ませていただき、当記事ではプラスアルファの要素としてブラウザに表示するWebページを加えてみました。

dev.classmethod.jp

ソースコード

github.com

動作環境

python3, node, npmを利用しますのでまずはインストールしておいてください。

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

$ python3 --version
Python 3.7.3

$ node -v
v14.17.2

$ npm -v
6.14.13

ハード類

  • Raspberry Pi 3B+
  • CO2センサーモジュール MH-Z19C
  • コネクタ付ケーブル 20cm 40P メスメス
  • ラズパイと接続する小さめのモニタ, HDMIケーブル, USBケーブル等

CO2の測定用のセンサーモジュール、ケーブルはおなじみ秋月電子通商さんから購入しました。少量からでも販売してくれる強い味方です。 ラズパイは本体と一緒にケースも買っておくと良いでしょう。

ラズパイと接続するモニタは家にあったいつ買ったのかも分からない余りものを使いました。ラズパイは3B+程度のスペックの方が動作が安定するのでおすすめです。Zeroでも試しましたがChromiumレンダリングがカクカクで運用はかなり厳しいです。モニタは画面が映れば何でも良いと思います。

akizukidenshi.com

システム構成図

今回はセキュリティうんぬんを考えるのが面倒だった為、全てローカルで動作させています。

f:id:tansantktk:20210731215337p:plain

定期的にpythonで測定させたCO2量をMariaDBで保持し、定期的にChromiumからNode.js経由でCO2量を取得して画面表示をするという処理になります。

データベースの作成

MariaDBをインストールし、データベースとテーブルを作成します。また別プログラムからSQLを実行する為のユーザーも新たに作成します。

# MariaDBのインストール
$ sudo apt install mariadb-server
$ systemctl start mariadb.service
# MariaDBへrootユーザーでログイン
$ sudo mysql -u root
# データベースの作成
MariaDB[(none)] > create database co2_observe;
# テーブルの作成
MariaDB[(none)] > USE co2_observe;
MariaDB[(co2_observe)] > CREATE TABLE co2 (
    id  INT AUTO_INCREMENT  PRIMARY KEY
    , co2_val FLOAT
    , created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    , updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
# プログラムからアクセスする為のユーザーを作成。パスワードは例ですので任意のものを指定ください。
MariaDB[(none)] > create user raspberrypi@localhost identified by 'raspberrypi';
MariaDB[(none)] > GRANT ALL ON co2_observe.* TO raspberrypi@localhost IDENTIFIED BY 'raspberrypi';

作成手順

ラズパイとmh-z19を接続する

下記サイトに回路図を載せてくれていますので、図に従ってジャンパワイヤで接続しましょう。 ラズパイの電源がONになっているとGPIOピンに電気が流れているので、接続の際は電源を落とした状態でやります。

pypi.org

CO2監視アプリの作成

まずはPythonライブラリのmh-z19, mysqlclientをインストールしておきます。mh-z19はセンサーモジュールから簡単に測定結果を取得するAPIを提供してくれています。

$ sudo pip3 install mh-z19
$ sudo pip3 install mysqlclient

あとはpythonで定期的にmh-z19の測定結果を取得するコードを書きます。

co2_observe.py

import subprocess
import re
import json
import MySQLdb
import os
import time

# MariaDBにCO2量をINSERT
def insert_co2(co2):
    conn = MySQLdb.connect(
        user='raspberrypi',
        passwd='raspberrypi',
        host='localhost',
        db='co2_observe'
    )
    cur = conn.cursor()

    try:
        sql = """
            INSERT INTO co2 (
                co2_val
            )     
            VALUES (
                %(co2_val)s
            )
        """
        bind_params = {
            'co2_val': co2
        }
        cur.execute(sql, bind_params)
        conn.commit()
        return True
    except BaseException as e:
        raise e
    finally:
        cur.close
        conn.close

def get_co2():
    # mh_z19の実行にはsudoが必要なので、subprocess経由で実行している
    out = subprocess.check_output(['sudo', 'python3', '-m', 'mh_z19'])
    result_json = out.decode("utf-8")
    # 結果はJSON文字列で返るのでdictにキャスト
    co2_dict = json.loads(result_json)
    # 起動直後はたまにmh_z19がNoneを返す為、条件分岐で例外を回避しておく
    if co2_dict is None:
        print('mh_z19 returned None')
        return False
    result = insert_co2(co2_dict['co2'])
    return True

# 10秒毎にCO2量を取得するループを実行
while True:
    get_co2()
    time.sleep(10)

python3 co2_observe.pyで実行すれば勝手にデータベースにCO2の測定量が溜まっていきます。 もし画面表示する必要がないなら、ここまでのソースコードでも十分でしょう。SQLでCO2の測定結果を集計してグラフ表示したりもできそうですね。

データ取得用APIの作成

次はCO2量を表示する画面となるWebページを作っていきますが、その前にMariaDBからレコードを取得する為のAPIサーバをexpressでざくざくと作ります。

# 必要なnpmパッケージをインストール
$ npm install express cors mysql2

app.js

const express = require('express')
const cors = require('cors')
const app = express()
const port = 3000
const mysql = require('mysql2')
const conn = mysql.createConnection({
    host: 'localhost',
    user: 'raspberrypi',
    password: 'raspberrypi',
    database: 'co2_observer'
});

conn.connect((err) => {
    if (err) {
        console.error(err.stack);
        return;
    }
    console.log('mysql connection success');
});

app.use(cors());

app.get('/LatestCo2', (req, res) => {
    conn.query(
        `
            SELECT *
            FROM co2
            ORDER BY created_at DESC
            LIMIT 1
        `
        , (err, result) => {
            res.send(result);
        }
    );
});

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
});

CO2表示ページの作成

ただのhtmlを作成し、jsのsetInterval()で定期的に先ほど作ったAPIを呼び出すようにし、CO2量を画面表示するようにします。

index.html

<!DOCTYPE html>
<html lang="ja" class="no-js">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width">
  <title>CO2 Display</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
  <style  type="text/css">
    body {
        font-size: 6vw;
        color: #ffffff;
        background-color: #2f2f2f;
    }
    .label {
        font-size: 5vw;
    }
    .co2-text {
        font-size: 30vw;
    }
  </style>
  <meta property="og:locale" content="ja_JP">
  <meta property="og:type" content="website">
</head>
<body>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
       async function getLatestCo2() {
           const res = await axios.get('http://localhost:3000/LatestCo2');
           if (res && res.data && res.data.length >= 1) {
               const co2_val = res.data[0].co2_val;
               var co2Text = document.getElementById('co2-text');
               co2Text.textContent = co2_val;
           }
       }
       // 定期的にCO2量を取得するAPIを呼び出す
       setInterval(() => {
           getLatestCo2();
       }, 5000);
   </script>

    <main class="container vw-100 vh-100">
        <div class="row align-items-center justify-content-center h-100">
            <div class="col-12">
                <div class="label text-center">CO2 status</div>
                <div class="text-center">
                    <span id="co2-text" class="co2-text">-</span>
                    <span>ppm</span>
                </div>
            </div>
        </div>
    </main>
</body>
</html>

Chromiumのインストール

Webページを表示する際に使うChromiumをインストールします。

$ sudo apt install chromium-browser

シェルスクリプトで全てのサーバを一斉起動させる

あとはそれぞれのサーバを立ち上げるコマンドをシェルスクリプトから実行するだけです。

co2-server.sh

#!/bin/bash

# CO2監視用のプログラムをバックグランド実行
python3 co2_observe.py &
# CO2取得用のexpressサーバをバックグランド実行
node app.js &
# CO2表示用のWebページをChromiumで全画面表示
chromium-browser index.html --kiosk

↓のような感じでブラウザに画面が出てくれればOKです。 f:id:tansantktk:20210731211023p:plain

時間経過で勝手にppmの値が変わっていくはず。デザインはお好みで変えていった方がいいかも。

ちなみに大気中の通常の濃度は360ppmとなっており、厚生労働省によるとコロナウイルスの集団感染回避のための必要換気量を満たす基準として1000ppm以下であるかどうか確認することが有効だそうです。

https://www.mhlw.go.jp/content/000698868.pdf

ラズパイ起動時に自動的にサーバを立ち上げる

chromium等のGUI操作を実施する場合はautostartに記述します。 rc.localもよく使われますが、こちらに記述するとchromiumが動いてくれません。

~/.config/lxsession/LXDE-pi/autostart

@lxpanel --profile LXDE-pi
@pcmanfm --desktop --profile LXDE-pi
@xscreensaver -no-splash

/home/pi/co2/co2-serve.sh

autostart経由で実行するシェルスクリプトは、フルパスで記述する必要があるので修正します。

co2-server.sh

#!/bin/bash

/usr/bin/python3 /home/pi/co2/co2-observer/co2_observe.py &
/usr/bin/node /home/pi/co2/co2-api/app.js &
/usr/bin/chromium-browser /home/pi/co2/co2-display/index.html --kiosk

これで起動するだけでCO2を測定するマシンの完成となります!

ラズパイ起動時の自動処理については下記の記事が大変参考になりました

qiita.com

あとがき

本当はdocker-composeで各サーバを管理したかったのですが、ラズパイのCPUアーキテクチャの問題で様々なエラーにひっかかり、時間的に作り切れませんでした😫

いずれARM64のラズパイOSもGAされるといいですね!