はじめに
こんにちは。田中みそです。
**「opniz」**というobnizっぽいことができるオープンソースIoTフレームワークを作って公開しています。
最近WebSocketに対応したv0.2を公開し、家でopnizにて実際に運用しているおうちハックデバイスのv0.2化対応を行いました。
そのうちのひとつをopnizの作例として紹介したいと思います。
CO2センサ(MH-Z19C)データのGoogle Spreadsheetsロギング
このおうちハックデバイスは、CO2センサ「MH-Z19C」にて取得したCO2濃度データを10分おきにGoogle Spreadsheetsへ書き込む、というデバイスです。
寝室に設置して就寝中のCO2濃度を計測しています。
今の時期ですと寝室のドアを締め、一晩中エアコンをつけて家族4人で寝ていますが、そうするとCO2濃度が危険域に入ってしまうことが分かりました。
このような環境では安眠は得られません。
対策としてドアを少し開けて換気するようにし、あわせてCO2濃度をロギングし効果計測を行っています。
構成図
構成はとてもシンプルです。
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」があります。
今回の例もそうですが、ひとつのマイコンデバイスを制御するだけであれば「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 |
私はユニバーサル基板で自作したハットを使っています。
材料費数十円で作れますが割と手間です。
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拡張のコードも含まれています。
╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╮
╏ ソースコードを表示(折りたたみ) ╏
╰╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╯
#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にて定義された関数を呼び出しているだけです。
// 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 install opniz@0.2.2 node-fetch dayjs
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
メソッドの値に相当します。
// 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化する、なんてことも可能です。
opnizはこういった無限の可能性をローコストで実現できますので、ぜひとも一度お試しいただけますと幸いです。