ワイヤレス・wifi環境不要で部屋4点の環境情報表示器をつくる
背景
私は(コロナ)10人前後の開発メンバーと20畳程度の部屋で仕事をしています。
現在コロナ禍であり、適切な換気が期待されています。
一方で、現在は冬で寒く、空気が悪いから換気をしたいという人と、寒いから換気はしてほしくない人で分かれて、なかなか最適な環境にできないことがあり苦労しています。
部屋小さくないため、部屋の反対隅にいる人の空気の状態や寒暖が把握しにくいという難点がありました。
部屋の4隅などの4点の環境情報を取得し、リアルタイム表示すれば、ある程度環境情報が共有できますし、意思決定に寄与できると考えました。
そこで、部屋の四隅の温度、湿度、二酸化炭素量を計測し、一か所で表示するプロダクトを開発しました。
部品
- M5Stack x 1
- M5StickC x 4
- HUB Unit x 4
- ENV2 Unit x 4
- TVOC/eCO2 Unit x 4
構成
- モニターユニット
- M5Stack
- センサーユニット x 4
- M5StickC
- HUB Unit
- ENV2 Unit
- TVOC/eCO2 Unit
機能
4か所のセンサーユニットを取り付けた箇所の温度・湿度・TVOC・eCO2の量が1か所のモニターユニットで確認できます。
厚生労働省によると室内ではTVOCは400以下、eCO2は1000以下が望ましいとのことで、温湿度との兼ね合いと4つ角付近の人との状況を確認しながら、換気、暖気をメンバーで議論することができます。
動作する様子
工夫したところ
- ワイヤレスかつwifi環境不要で部屋4点の環境情報を取得表示
私が所属している会社では、wifi環境を会社管理のPC以外は接続できないようになっています。
このような環境下でも気軽に使えるように、モニターユニットのM5Stackをwifiアクセスポイントにし、
センサーユニットはモニターユニットのアクセスポイントに接続するようにしています。
このような実装にすることで、wifi環境がなくても本プロダクトを使用可能です。
- 細かい設定不要で使用開始可能
M5StickCのEEPROMで、各センサーユニットのデバイス番号を覚えるようにしています。
ですから、一度設定してしまえば後は好きなところに持って行って、モニターユニットとセンサーユニットの電源を入れれば、部屋の4隅の環境情報のモニタリングを開始できます。
プログラム
モニターユニット
#include "WiFi.h"
#include "AsyncUDP.h"
#include "ArduinoJson.h"
#include "M5Stack.h"
#define N 1024
// wifi
const char * ssid = "ESP32-Access-Point";
const char * password = "123456789";
WiFiServer server(1234);
AsyncUDP udp;
//device
#define DEVICE_NUMBER_MIN 1
#define DEVICE_NUMBER_MAX 4
struct deviceInfo
{
double tmp;
double humi;
int tvoc;
int eco2;
};
struct deviceInfo devInfo[DEVICE_NUMBER_MAX] = {0};
void setup()
{
// serial
Serial.begin(115200);
// M5Stack
M5.begin();
M5.Lcd.setTextSize(2);
// Connect to Wi-Fi network with SSID and password
Serial.print("Setting AP (Access Point)…");
WiFi.softAP(ssid, password);
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(IP);
server.begin();
if(udp.listenMulticast(IPAddress(239,1,2,3), 1234)) {
Serial.print("UDP Listening on IP: ");
Serial.println(WiFi.localIP());
udp.onPacket([](AsyncUDPPacket packet) {
Serial.print("UDP Packet Type: ");
Serial.print(packet.isBroadcast()?"Broadcast":packet.isMulticast()?"Multicast":"Unicast");
Serial.print(", From: ");
Serial.print(packet.remoteIP());
Serial.print(":");
Serial.print(packet.remotePort());
Serial.print(", To: ");
Serial.print(packet.localIP());
Serial.print(":");
Serial.print(packet.localPort());
Serial.print(", Length: ");
Serial.print(packet.length());
Serial.print(", Data:=>");
{
DynamicJsonDocument doc(N);
deserializeJson(doc, packet.data());
JsonObject obj = doc.as<JsonObject>();
int deviceNumber = obj["deviceNumber"];
Serial.print(", deviceNumber: ");
Serial.print(deviceNumber);
double tempValue = obj["tempValue"];
Serial.print(", tempValue: ");
Serial.print(tempValue);
double humiValue = obj["humiValue"];
Serial.print(", humiValue: ");
Serial.print(humiValue);
int tvocValue = obj["tvocValue"];
Serial.print(", tvocValue: ");
Serial.print(tvocValue);
int eco2Value = obj["eco2Value"];
Serial.print(", eco2Value: ");
Serial.print(eco2Value);
if((deviceNumber >= DEVICE_NUMBER_MIN) && (deviceNumber <= DEVICE_NUMBER_MAX)){
devInfo[deviceNumber-1].tmp = tempValue;
devInfo[deviceNumber-1].humi = humiValue;
devInfo[deviceNumber-1].tvoc = tvocValue;
devInfo[deviceNumber-1].eco2 = eco2Value;
}
}
Serial.println();
//reply to the client
packet.printf("Got %u bytes of data", packet.length());
});
// Send multicast
}
}
void loop()
{
delay(5000);
M5.update();
M5.Lcd.setCursor(0, 0);
{
int i = 0;
for(i = 0;i < DEVICE_NUMBER_MAX;i=i+2)
{
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.printf("DvNo| %d | %d\n",i+1,i+2);
M5.Lcd.printf("----+---------+-----------\n");
M5.Lcd.printf("Temp| %02.2fC | %02.2fC\n",devInfo[i].tmp,devInfo[i+1].tmp);
M5.Lcd.printf("Humi| %02.2f%% | %02.2f%%\n",devInfo[i].humi,devInfo[i+1].humi);
M5.Lcd.printf("TVOC| %4dppb| %4dppb\n", devInfo[i].tvoc,devInfo[i+1].tvoc);
M5.Lcd.printf("eCO2| %4dppm| %4dppb\n", devInfo[i].eco2,devInfo[i+1].eco2);
M5.Lcd.printf("\n");
}
}
}
センサーユニット
#include "EEPROM.h"
#include "WiFi.h"
#include "AsyncUDP.h"
#include "ArduinoJson.h"
#include "M5StickC.h"
#include "Adafruit_BMP280.h"
#include "Adafruit_SHT31.h"
#include "Adafruit_SGP30.h"
// EEPROM
#define EEPROM_SIZE 64
#define DEVICE_NUMBER_ADDRESS 1
// ENV 2
Adafruit_SHT31 sht3x = Adafruit_SHT31(&Wire);
Adafruit_BMP280 bme = Adafruit_BMP280(&Wire);
float tmp = 0.0;
float hum = 0.0;
float pressure = 0.0;
// SGP30
Adafruit_SGP30 sgp;
int i = 15;
long last_millis = 0;
// wifi udp
const char * ssid = "ESP32-Access-Point";
const char * password = "123456789";
AsyncUDP udp;
// device
int deviceNumber = 1;
#define DEVICE_NUMBER_MIN 1
#define DEVICE_NUMBER_MAX 4
void readDeviceNumber()
{
deviceNumber = EEPROM.readInt(DEVICE_NUMBER_ADDRESS);
if((deviceNumber < DEVICE_NUMBER_MIN) || (deviceNumber > DEVICE_NUMBER_MAX))
{
deviceNumber = DEVICE_NUMBER_MIN;
}
}
void countUpDeviceNumber()
{
deviceNumber = deviceNumber + 1;
if(deviceNumber > DEVICE_NUMBER_MAX)
{
deviceNumber = DEVICE_NUMBER_MIN;
}
EEPROM.writeInt(DEVICE_NUMBER_ADDRESS, deviceNumber);
EEPROM.commit();
}
void setup()
{
// シリアルの初期設定
Serial.begin(115200);
// EEPROMの初期化
if (!EEPROM.begin(EEPROM_SIZE))
{
Serial.println("Failed to initialise EEPROM");
Serial.println("Restarting...");
delay(1000);
ESP.restart();
}
// m5stickcの初期設定
M5.begin();
M5.Lcd.setRotation(3);
M5.Lcd.setTextSize(2);
Wire.begin(32, 33);
// deviceNumberの読み出し
readDeviceNumber();
// ENV2 の初期設定
while (!bme.begin(0x76)) {
Serial.println("Could not find a valid BMP280 sensor, check wiring!");
M5.Lcd.println("Could not find a valid BMP280 sensor.");
}
while (!sht3x.begin(0x44)) {
Serial.println("Could not find a valid SHT3X sensor, check wiring!");
M5.Lcd.println("Could not find a valid SHT3X sensor.");
}
// SGP30の初期設定
while(! sgp.begin()){
Serial.println("Could not find a valid SGP30 sensor, check wiring!");
M5.Lcd.println("Could not find a valid SGP30 sensor.");
}
// wifiの初期設定
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("WiFi Failed");
M5.Lcd.println("WiFi Failed");
while(1) {
delay(1000);
}
}
// udpの初期設定
if(udp.connect(IPAddress(192,168,1,100), 1234)) {
Serial.println("UDP connected");
udp.onPacket([](AsyncUDPPacket packet) {
Serial.print("UDP Packet Type: ");
Serial.print(packet.isBroadcast()?"Broadcast":packet.isMulticast()?"Multicast":"Unicast");
Serial.print(", From: ");
Serial.print(packet.remoteIP());
Serial.print(":");
Serial.print(packet.remotePort());
Serial.print(", To: ");
Serial.print(packet.localIP());
Serial.print(":");
Serial.print(packet.localPort());
Serial.print(", Length: ");
Serial.print(packet.length());
Serial.print(", Data: ");
Serial.write(packet.data(), packet.length());
Serial.println();
//reply to the client
packet.printf("Got %u bytes of data", packet.length());
});
}
M5.Lcd.fillScreen(BLACK);
}
void loop()
{
while(i > 0) {
if(millis()- last_millis > 1000) {
last_millis = millis();
i--;
}
}
delay(1000);
M5.update();
// 各値の取得
pressure = bme.readPressure();
tmp = sht3x.readTemperature();
hum = sht3x.readHumidity();
sgp.IAQmeasure();
// 表示の更新
Serial.printf("Temperatura: %2.2f*C Humedad: %0.2f%% Pressure: %0.2fhPa Tvoc:%dppb eCo2:c%dppm \r\n", tmp, hum, pressure / 100,sgp.TVOC,sgp.eCO2);
M5.Lcd.setCursor(0, 0);
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.printf("DvNo:%d\n",deviceNumber);
M5.Lcd.printf("Temp:%2.2fC\n", tmp);
M5.Lcd.printf("Humi:%2.2f%%\n", hum);
M5.Lcd.printf("TVOC:%4dppb\n", sgp.TVOC);
M5.Lcd.printf("eCO2:%4dppm\n", sgp.eCO2);
// 値の通知
{
//Send broadcast on port 1234
char json[1024] = "";
sprintf(json,"{\"deviceNumber\":%d,\"tempValue\":%2.2f,\"humiValue\":%2.2f,\"tvocValue\":%d,\"eco2Value\":%d}",deviceNumber,tmp,hum,sgp.TVOC,sgp.eCO2);
udp.broadcastTo(json, 1234);
}
// ボタンを押された場合はDeviceNumberを変更
if(M5.BtnB.wasPressed()){
Serial.println("ButtonB pressed");
countUpDeviceNumber();
}
}
投稿者の人気記事
-
mametarou963
さんが
2021/02/09
に
編集
をしました。
(メッセージ: 初版)
-
mametarou963
さんが
2021/02/21
に
編集
をしました。
-
mametarou963
さんが
2021/02/24
に
編集
をしました。
-
mametarou963
さんが
2021/02/24
に
編集
をしました。
-
mametarou963
さんが
2021/12/07
に
編集
をしました。
ログインしてコメントを投稿する