エアコンの効果を最適化するために、サーキュレーターの配置によって室内温度がどう変わるか、複数ポイントで測定・記録するシステムを考えてみました。
温かい空気は上に向かうため、天井に近い位置のエアコンの暖房では、なかなか部屋全体が暖まらないことがあります。サーキュレーターを併用していますが、設置場所・向きなど、どうすれば最適化できるかが、今一つわかりません。
そこで、部屋の複数ポイントでの室内温度の変化を記録するシステムを作ってみることにしました。
測定にM5Stackを使う
測定には、オールインワンタイプのマイコンユニット「M5Stack Basic」を使いました。

M5Stackシリーズは、ディスプレイやボタン、バッテリーなどをコンパクトな筐体に収めたマイコンユニットです。ネットワーク機能として、WiFiやBluetoothを搭載しています。開発環境は、C/C++ベースのArduinoや、Python 3ベースのMicroPythonの他、ビジュアルプログラミングも可能です。
IoTでよく使われるRaspberry PiやArduinoなどが、剥き出しのボードで販売されているのに対し、M5Stackシリーズは、動作に必要なものがすべて筐体に含まれており、すぐに利用できます。
今回は、気温や湿度、気圧などを測定できるENV Unitという拡張モジュールをつなげて使用しました。本体(コア)と拡張モジュールを4台ずつ用意しました。

用意したもの
品名 |
実売価格 |
M5Stack Basic(本体コア) |
3,600円程度 |
M5Stack ENV Unit(気温などの環境センサーモジュール) |
450円程度 |
集計にMachinistを使う
集計には、IIJの「Machinist」というWebサービスを選択しました。AWS CloudWatchのようなメトリクスの集計・可視化サービスで、10メトリクス(今回の場合は10箇所の温度)まで無料で利用できます。以前、「スマートスピーカーを遊びたおす会」に参加した際、会場スポンサーだったIIJが紹介していました。IoT向けで、JSON形式のデータをhttpでpostするだけで、メトリクスが記録されるため、とても簡単です。
Machinist

フリープランでアカウントを新規登録し、APIキーを作成します。APIキーは、httpリクエストの際に認証情報として利用します。

なお、machinistへのデータ送信時の仕様は、下記リンクにまとめられています。
送信データについて
Arduinoでの開発
今回、M5Stackの開発環境としては、使用経験があったArduinoを使いました。
Arduinoは、C/C++をベースにした環境のため、文字列を扱うのに、char型の配列やポインタを用います。一部、Stringクラスも利用できますが、ライブラリ関数によって、char型の配列やポインタでないと動作しないものもあり、少し面倒なところがありました。今後、MicroPythonで書き直したいと思っています。

NVSへ値を格納
M5Stackは、不揮発性ストレージ(NVS)を持っています。WiFiのSSID/暗号化キーや、MachinistのAPIキーなどの秘匿情報は、NVSに格納しておき、実行時に読み出すようにすれば、コード上に明記せずに済みます。
また、機器名のように、機器ごとに異なるパラメーターも、NVSに格納しておけば、コードを全機器で共通化することができます。
今回は、次の情報をNVSに格納することにしました。
NVSに格納する情報
- WiFiのSSID
- WiFiの暗号化キー
- MachinistのAPIキー
- M5Stackコアの名前
- M5Stackセンサーの補正値
M5Stackコアの名前は、Machinist上で、温度の測定ポイント名として表示されます。当初は m5stack01
などとしていましたが、最終的には B01
~ B04
と設定しました。
環境センサーモジュールの温度センサーはDHT12が使用されており、誤差は±0.5℃です。しかし、後述するように、それ以上の誤差があったため、測定値を補正することにしました。最初は、M5Stackセンサーの補正値に 0
を設定しておきます。
これらをNVSに格納するために、下記のコードをM5Stackに書き込みます。
※ {ssid}
{key}
{apikey}
{name}
{offset}
は、環境に合わせて設定します。
事前準備コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <Preferences.h> Preferences prefs;
void setup() { // WiFi設定 prefs.begin("wifi"); prefs.putString("ssid", "{ssid}"); prefs.putString("password", "{key}"); prefs.end(); // Machinist設定 prefs.begin("machinist"); prefs.putString("apikey", "{apikey}"); prefs.end(); // M5Stackコア設定 prefs.begin("core"); prefs.putString("name", "{name}"); prefs.end(); // M5Stackセンサー設定 prefs.begin("sensor"); prefs.putFloat("offset1", {offset}); prefs.end(); }
void loop() { }
|
なお、NVSへ値を格納するだけのコードのため、実行しても画面には何も表示されません。
メインコードを書き込み
事前準備コードを書き込み終わったら、メインコードを書き込みます。
なお、NVSの内容は消えませんので、今後、メインコードを修正する場合は、メインコードだけ再書き込みします。
メインコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| #include <M5Stack.h> #include <Preferences.h> #include <WiFi.h> #include <HTTPClient.h> #include <Wire.h> // for DHT12+BMP280 #include "DHT12.h" // for DHT12 #include "Adafruit_Sensor.h" // for DHT12 #include <Adafruit_BMP280.h> // for BMP280
DHT12 dht12; Adafruit_BMP280 bme; Preferences prefs; HTTPClient http;
char wifi_ssid[128]; // 127文字まで char wifi_pass[128]; // 127文字まで String apiKey; const String endPoint = "https://gw.machinist.iij.jp/endpoint"; const char* dataFormat = "{\"agent\": \"%s\", \"metrics\": [{\"namespace\": \"%s\", \"name\": \"%s\", " "\"data_point\": {\"value\": %4.1f}, \"tags\": {\"sensor\": \"%s\"}}]}"; char core_name[16]; // 15文字まで float sensor_offset1; unsigned long nextUpdate;
void load_preferences() { // WiFi設定 prefs.begin("wifi", true); prefs.getString("ssid", wifi_ssid, sizeof(wifi_ssid)); prefs.getString("password", wifi_pass, sizeof(wifi_pass)); prefs.end(); // Machinist設定 prefs.begin("machinist", true); apiKey = prefs.getString("apikey"); prefs.end(); // M5Stackコア設定 prefs.begin("core", true); prefs.getString("name", core_name, sizeof(core_name)); prefs.end(); // M5Stackセンサー設定 prefs.begin("sensor", true); sensor_offset1 = prefs.getFloat("offset1"); prefs.end(); }
void wifi_connect() { M5.Lcd.print("WiFi Connetcing..."); WiFi.begin(wifi_ssid, wifi_pass); // 接続完了するまでループ while (WiFi.status() != WL_CONNECTED) { delay(1000); M5.Lcd.print("."); } M5.Lcd.printf("Connected!\n"); }
void sendData(char *nameSpace, char *metricName, double metricValue, char *sensor_name) { // 送信データを用意 char dataBuffer[1024]; // 1023文字まで sprintf(dataBuffer, dataFormat, core_name, nameSpace, metricName, metricValue, sensor_name); // データを送信 http.begin(endPoint); http.addHeader("Content-Type", "application/json"); http.addHeader("Authorization", "Bearer " + apiKey); http.POST(dataBuffer); }
void setup() { // 初期設定 M5.begin(); M5.Power.begin(); // 画面設定 M5.Lcd.setBrightness(3); M5.Lcd.setTextSize(3); M5.Lcd.setTextColor(WHITE, BLACK); // センサー設定 Wire.begin(); while (!bme.begin(0x76)){ M5.Lcd.println("Could not find a valid BMP280 sensor, check wiring!"); } // NVSの読み出し load_preferences(); // WiFi接続 wifi_connect(); // 機器固有情報を表示 M5.Lcd.fillScreen(BLACK); M5.Lcd.setCursor(0, 0); M5.Lcd.printf("Name:%s\n", core_name); M5.Lcd.printf("Offset:%+4.2f\n", sensor_offset1); M5.Lcd.print("IP:"); M5.Lcd.println(WiFi.localIP()); // 初回データ送信時刻を設定 nextUpdate = millis() + 1000; }
void loop() { // 最新状態の取得 float temp = dht12.readTemperature() + sensor_offset1; float humi = dht12.readHumidity(); float pres = bme.readPressure(); // 画面更新 M5.Lcd.setCursor(0, 120); M5.Lcd.printf("Temp:%4.1fC\nHumi:%2.0f%%\n", temp, humi); M5.Lcd.printf("Pressure:%6.0fPa\n", pres); // Machinistへ送信 if ( nextUpdate <= millis() ) { sendData("Env", "Temp", temp, "1"); // 次回送信は1分=60000ミリ秒後 nextUpdate = nextUpdate + 60000; } // 画面更新は1秒=1000ミリ秒後 delay(1000); }
|
書き込みが完了すると、測定が始まります。温度を画面に表示し、Machinistに送信します。
※画面には、温度・湿度・気圧が表示されますが、Machinistに送信するのは温度のみです。

機器ごとの誤差を確認・補正
機器ごとの誤差を確認するために、まずは、4台を同じ場所に並べて、温度を測定してみました。

当初、各機器の測定値のばらつきは1℃以内に収まるかなと考えていましたが、実際には、最大で1.5℃ほどありました。並べる順番を変更しても、3号機の温度が高く、2号機の温度が低いのは変わりません。
各機器のグラフは、平行に近い感じで推移していますので、単純な方法で補正することにしました。全機器の測定値の平均を基準値とし、測定値と基準値との差を算出。機器ごとの「基準値との差」の平均値を、その機器の補正すべき値とすることにしました。

今回の補正値
機器名 |
B01 |
B02 |
B03 |
B04 |
補正値 |
0.16 |
0.51 |
-0.59 |
-0.08 |
NVSは頻繁には書き換えない方がよいため、次のコードでセンサー補正値のみ書き換えました。 {offset}
のところに補正値を指定します。
センサー補正値の修正コード
1 2 3 4 5 6 7 8 9 10 11 12
| #include <Preferences.h> Preferences prefs;
void setup() { // M5Stackセンサー設定 prefs.begin("sensor"); prefs.putFloat("offset1", {offset}); prefs.end(); }
void loop() { }
|
すると、補正前は1℃以上あったバラツキが、0.5℃以下に収まりました。
※Machinistでは、Y軸は自動スケールします。このため、補正前と補正後のグラフでは、Y軸のスケールが4倍程度、異なっています。ご注意ください。

この程度の誤差であれば、問題なさそうです。部屋のさまざまな場所に設置して、サーキュレーターの効果について、検証してみたいと考えています。