LoginSignup
7
1

More than 1 year has passed since last update.

opnizでつくる「CO2センサ(MH-Z19C)データのGoogle Spreadsheetsロギングシステム」

Last updated at Posted at 2021-12-09

image

はじめに

こんにちは。田中みそです。
opnizというobnizっぽいことができるオープンソースIoTフレームワークを作って公開しています。

image

最近WebSocketに対応したv0.2を公開し、家でopnizにて実際に運用しているおうちハックデバイスのv0.2化対応を行いました。
そのうちのひとつをopnizの作例として紹介したいと思います。

CO2センサ(MH-Z19C)データのGoogle Spreadsheetsロギング

このおうちハックデバイスは、CO2センサ「MH-Z19C」にて取得したCO2濃度データを10分おきにGoogle Spreadsheetsへ書き込む、というデバイスです。
寝室に設置して就寝中のCO2濃度を計測しています。

今の時期ですと寝室のドアを締め、一晩中エアコンをつけて家族4人で寝ていますが、そうするとCO2濃度が危険域に入ってしまうことが分かりました。
このような環境では安眠は得られません。
対策としてドアを少し開けて換気するようにし、あわせてCO2濃度をロギングし効果計測を行っています。

構成図

image

構成はとてもシンプルです。
opniz SDKを実装したプログラムにて、マイコンデバイスと接続したMH-Z19CのCO2濃度の値を取得し、Google SpreadSheetsへHTTP POSTで書き込んでいます。
マイコンデバイスにはopniz Arduino Libraryを書き込んだM5ATOMを使用しています(M5ATOM Lite、M5ATOM Matrixどちらでも大丈夫です)。

opnizのしくみ

詳細な実装説明へ入る前にopnizのしくみ・構成について簡単に説明します。
opnizはマイコンデバイスへ書き込むための「opniz Arduino Library」、Node.jsでマイコンデバイスを遠隔制御するための「opniz Node.js SDK」、そしてマイコンデバイスとプログラム間のメッセージを中継する「opniz Server」があります。

image

今回の例もそうですが、ひとつのマイコンデバイスを制御するだけであれば「opniz Server」は不要です。
「opniz Arduino Library」を書き込んだマイコンデバイスと「opniz Node.js SDK」を実装したプログラムとで直接通信を行えます。
複数のマイコンデバイスを複数のプログラムから制御したいケースでは「opniz Server」を導入すると管理が楽になります。

実装

それでは実装の説明に入っていきます。
以下の4点についてopnizの実装を中心とした説明とソースコードを掲載します。

MH-Z19CとM5ATOMの接続

MH-Z19Cの各ピンを以下のようにM5ATOMとつなげます。
M5ATOMの4つ穴側のピン列がぴったり使えます。

MH-Z19C M5ATOM
Vin 5V
GND GND
Tx 21
Rx 25

私はユニバーサル基板で自作したハットを使っています。
材料費数十円で作れますが割と手間です。

image

image

opniz Arduino Libraryの実装

opniz Arduino Libraryは基本的にはexampleのコードをWi-Fi情報等のみ書き換えてスケッチ書き込みすればそのまま使えます(opnizはあくまでNode.jsでマイコンデバイスをコントロールするのが主目的なので)。
外部のArduinoライブラリをopniz Node.js SDKから使いたいときはRPC定義を拡張して使うこともできます。

今回はMH-Z19Cを制御する「MHZ19_uart」というArduinoライブラリを使用しています。
以下のソースコードの大部分はexampleと同様のコードですが、MHZ19_uartを用いたopniz拡張のコードも含まれています。

╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╮
╏    ソースコードを表示(折りたたみ)   ╏
╰╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╯
main.ino
#include <OpnizM5Atom.h>
#include <lib/WiFiConnector.h>
#include <MHZ19_uart.h>



const char* ssid = "<SSID>";         // WiFiのSSIDに書き換え
const char* password = "<PASSWORD>"; // WiFiのパスワードに書き換え
WiFiConnector wifiConnector(ssid, password);

const char* address = "192.168.0.1"; // Node.js SDKを実行する端末のIPアドレスに書き換え
const uint16_t port = 3000;
Opniz::M5Atom* opniz = new Opniz::M5Atom(address, port);

MHZ19_uart mhz19;
#define TX_PIN 25
#define RX_PIN 21



// MHZ19_uart関数用ハンドラ定義
// CO2濃度取得関数
class getCO2PPMHandler : public BaseHandler {
public:
    String name() override { return "getCO2PPM"; };
    String procedure(JsonArray params) override {
        return (String)mhz19.getCO2PPM();
    }
};
// CO2センサ内温度取得関数
class getTemperatureHandler : public BaseHandler {
public:
    String name() override { return "getTemperature"; };
    String procedure(JsonArray params) override {
        return (String)mhz19.getTemperature();
    }
};



void setup() {
    initM5(); // M5ATOM初期化

    wifiConnector.connect(); // WiFi接続

    // MHZ19_uart関数用ハンドラを登録
    opniz->addHandler({
        new getCO2PPMHandler,
        new getTemperatureHandler,
    });
    opniz->connect(); // Node.js SDKへ接続

    // mhz19ライブラリ初期化
    mhz19.begin(RX_PIN, TX_PIN);
    mhz19.setAutoCalibration(false);
}

void loop() {
    opniz->loop();         // opnizメインループ
    wifiConnector.watch(); // WiFi接続監視
}

opniz Arduino Libraryの書き込み方法

opniz Arduino LibraryはまだArduino IDEのライブラリ等で公開していません。
そのためライブラリのインストールはGitHubからZIPをダウンロードし、Arduinoライブラリへ登録する必要があります。
画面キャプチャを交えたインストール手順をコチラに掲載してますので参照いただければと思います。

また上記の手順にもありますが「ArduinoJson」「WebSockets」「M5Atom」「FastLED」も依存ライブラリとして必要となり、加えて今回は「MHZ19_uart」も必要となります。
これらのライブラリもArduino IDEへインストールしてください。

opniz実装のポイント

opniz拡張としてやっていることは以下の2点です。

  • MHZ19_uartのインクルード、初期化
  • MHZ19_uartの関数をopniz Node.js SDKから呼び出せるようハンドラ定義し追加する

ポイントはclass getCO2PPMHandler : public BaseHandler {という記述から始まるコードブロック部です。
BaseHandlerクラスを継承し、opniz Node.js SDKからコールされるRPC名をnameメソッドで定義し、コールされた際の動きをprocedureメソッドで定義します。
今回は単純にMHZ19_uartにて定義された関数を呼び出しているだけです。

main.ino(opniz拡張部の抜粋)
// MHZ19_uart関数用ハンドラ定義
// CO2濃度取得関数
class getCO2PPMHandler : public BaseHandler {
public:
    String name() override { return "getCO2PPM"; };
    String procedure(JsonArray params) override {
        return (String)mhz19.getCO2PPM();
    }
};
// CO2センサ内温度取得関数
class getTemperatureHandler : public BaseHandler {
public:
    String name() override { return "getTemperature"; };
    String procedure(JsonArray params) override {
        return (String)mhz19.getTemperature();
    }
};

opniz Node.js SDKの実装

opniz Node.js SDKにてメインロジックを実装します。
main関数のforループがメイン処理のループとなっています。

このコードでは以下の処理を行っています。

  • 5秒おきにCO2濃度を取得し、値によってM5ATOMのLEDの点灯色を変更
  • 10分おきにCO2濃度をGoogle Spreadsheetsに書き込む

╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╮
╏    ソースコードを表示(折りたたみ)   ╏
╰╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╯
npmパッケージのインストール
npm install opniz@0.2.2 node-fetch dayjs
index.ts
import { Opniz } from "opniz"
import fetch from "node-fetch"
import dayjs from "dayjs"



const port = 3000

const spreadsheetUrl = "https://script.google.com/macros/s/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/exec" // Google SpreadsheetのApps Script公開アプリURLを指定

const intervalTime = 5000
const writeMinute = 10



// CO2センサ対応opniz拡張クラス定義
class MHZ19C extends Opniz.M5Atom {
    // CO2濃度取得
    public async getCO2PPM(): Promise<number> {
        return Number(await this.exec("getCO2PPM"))
    }
    // CO2センサ内温度取得
    public async getTemperature(): Promise<number> {
        return Number(await this.exec("getTemperature"))
    }
}
const opniz = new MHZ19C({ port })



// CO2濃度によりLED表示変更
const getCO2Color = (co2ppm) => {
    // MEMO: RGB形式ではなく、GRB形式で指定
    let color = "#000000"
    if (co2ppm >= 1000) color = "#00ff00" // 赤
    if (co2ppm < 1000) color = "#88ff00" // 橙
    if (co2ppm < 800) color = "#ff8800" // 黄緑
    if (co2ppm < 600) color = "#ff0000" // 緑
    if (co2ppm === -1) color = "#0000ff" // 青
    return color
}



// タイマー
const makeTimer = () => {
    let preMinute = -1
    return (func, time) => {
        const nowMinute = Number(dayjs().format("m"))
        if (nowMinute % time === 0 && nowMinute !== preMinute) {
            preMinute = nowMinute
            func()
        }
    }
}
const timer = makeTimer()



// Google Spreadsheetに書き込み
const writeSpreadsheet = async (...data) => {
    if (!spreadsheetUrl) throw Error("Url invalid!")
    await fetch(spreadsheetUrl, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify([dayjs().format("YYYY/MM/DD HH:mm:ss"), ...data]),
    })
}



// メインロジック
const main = async () => {
    while (!(await opniz.connect())) { console.log("connect...")}
    console.log("[connected]")

    try {
        for (;;) {
            console.log("[loop]")

            const co2ppm = await opniz.getCO2PPM() // CO2濃度取得
            console.log({ co2ppm })
            await opniz.dis.drawpix(0, getCO2Color(co2ppm)) // LED表示色変更
            timer(() => writeSpreadsheet(co2ppm), writeMinute) // 10分おきにGoogle Spreadsheetに書き込み

            await opniz.sleep(intervalTime)
        }
    } catch(e) {
        console.log("[error]", e.message)
        await main()
    }
}
main()

opniz実装のポイント

opniz実装としてのポイントはclass MHZ19C extends Opniz.M5Atom {から始まるコードブロック部です。
ここでOpniz.M5Atomクラスを継承しopniz Arduino Library側で定義したMHZ19_uartの関数を呼び出すハンドラをコールするメソッドを定義しています。
this.exec()にてパラメータに指定している文字列が前述のopniz Arduino Libraryのソースコードで定義したハンドラのnameメソッドの値に相当します。

index.ts(opniz拡張部の抜粋)
// CO2センサ対応opniz拡張クラス定義
class MHZ19C extends Opniz.M5Atom {
    // CO2濃度取得
    public async getCO2PPM(): Promise<number> {
        return Number(await this.exec("getCO2PPM"))
    }
    // CO2センサ内温度取得
    public async getTemperature(): Promise<number> {
        return Number(await this.exec("getTemperature"))
    }
}
const opniz = new MHZ19C({ port })

Google Apps Scriptの実装

GASのコードも掲載しておきます。
わりとよくあるコードだと思いますがHTTP POSTで受け取ったJSONデータをもとに、シートの最下行にレコードを追加していくコードです。

このApps Scriptを公開アプリとしてデプロイし、発行されたURLを前述のopniz Node.js SDKのソースコードに記述します。

function doPost(e) {
    const data = JSON.parse(e.postData.getDataAsString())

    const sheet = SpreadsheetApp.getActive().getActiveSheet()
    sheet.appendRow(data)

    const output = ContentService.createTextOutput()
    output.setMimeType(ContentService.MimeType.JSON)
    output.setContent(JSON.stringify({ message: "success!" }))
    return output
}

おわりに

opnizの作例としてCO2センサを制御する例を紹介させていただきました。
今回のCO2ロギングシステムは家で常時稼働しているRaspberry Piにデプロイして実行しています。

しかしこのopnizプログラムをHerokuといったクラウド環境にデプロイして実行することも可能です。
さらにExpress等を用いてセンサデータを取得するWeb API化する、なんてことも可能です。

image

opnizはこういった無限の可能性をローコストで実現できますので、ぜひとも一度お試しいただけますと幸いです。

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1