【WiFi温度計】複数ポイントで室内温度を測定・記録する

エアコンの効果を最適化するために、サーキュレーターの配置によって室内温度がどう変わるか、複数ポイントで測定・記録するシステムを考えてみました。


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

測定にM5Stackを使う

測定には、オールインワンタイプのマイコンユニット「M5Stack Basic」を使いました。

M5Stack Basic

M5Stackシリーズは、ディスプレイやボタン、バッテリーなどをコンパクトな筐体に収めたマイコンユニットです。ネットワーク機能として、WiFiやBluetoothを搭載しています。開発環境は、C/C++ベースのArduinoや、Python 3ベースのMicroPythonの他、ビジュアルプログラミングも可能です。

IoTでよく使われるRaspberry PiやArduinoなどが、剥き出しのボードで販売されているのに対し、M5Stackシリーズは、動作に必要なものがすべて筐体に含まれており、すぐに利用できます。

今回は、気温や湿度、気圧などを測定できるENV Unitという拡張モジュールをつなげて使用しました。本体(コア)と拡張モジュールを4台ずつ用意しました。

M5Stack BasicにEnv Unitを接続

用意したもの
品名 実売価格
M5Stack Basic(本体コア) 3,600円程度
M5Stack ENV Unit(気温などの環境センサーモジュール) 450円程度

集計にMachinistを使う

集計には、IIJの「Machinist」というWebサービスを選択しました。AWS CloudWatchのようなメトリクスの集計・可視化サービスで、10メトリクス(今回の場合は10箇所の温度)まで無料で利用できます。以前、「スマートスピーカーを遊びたおす会」に参加した際、会場スポンサーだったIIJが紹介していました。IoT向けで、JSON形式のデータをhttpでpostするだけで、メトリクスが記録されるため、とても簡単です。

Machinist

Machinistにより簡単に可視化ができる

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

APIキーはアカウント設定で管理

なお、machinistへのデータ送信時の仕様は、下記リンクにまとめられています。

送信データについて

Arduinoでの開発

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

Arduino IDEを使って開発する

NVSへ値を格納

M5Stackは、不揮発性ストレージ(NVS)を持っています。WiFiのSSID/暗号化キーや、MachinistのAPIキーなどの秘匿情報は、NVSに格納しておき、実行時に読み出すようにすれば、コード上に明記せずに済みます。
また、機器名のように、機器ごとに異なるパラメーターも、NVSに格納しておけば、コードを全機器で共通化することができます。

今回は、次の情報をNVSに格納することにしました。

NVSに格納する情報
  • WiFiのSSID
  • WiFiの暗号化キー
  • MachinistのAPIキー
  • M5Stackコアの名前
  • M5Stackセンサーの補正値

M5Stackコアの名前は、Machinist上で、温度の測定ポイント名として表示されます。当初は m5stack01 などとしていましたが、最終的には B01B04 と設定しました。
環境センサーモジュールの温度センサーは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に送信するのは温度のみです。

測定値を画面に表示し、Machinistに送信する

機器ごとの誤差を確認・補正

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

【補正前】各機器のばらつきは1℃以上あった

当初、各機器の測定値のばらつきは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倍程度、異なっています。ご注意ください。

【補正後】各機器のばらつきは0.5℃以下に

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