mametarou963 が 2021年02月21日22時55分18秒 に編集
コメント無し
本文の変更
# 背景 私は(コロナ)10人前後の開発メンバーと20畳程度の部屋で仕事をしています。 現在コロナ禍であり、適切な換気が期待されています。 一方で、現在は冬で寒く、空気が悪いから換気をしたいという人と、寒いから換気はしてほしくない人で分かれて、なかなか最適な環境にできないことがあり苦労しています。 部屋小さくないため、部屋の反対隅にいる人の空気の状態や寒暖が把握しにくいという難点がありました。 部屋の4隅などの4点の環境情報を取得し、リアルタイム表示すれば、ある程度環境情報が共有できますし、意思決定に寄与できると考えました。 そこで、部屋の四隅の温度、湿度、二酸化炭素量を計測し、一か所で表示するプロダクトを開発しました。 # 部品 * M5Stack x 1 * M5StickC x 4 * HUB Unit x 4 * ENV2 Unit x 4 * TVOC/eCO2 Unit x 4 # 構成 * モニターユニット * M5Stack ![モニターユニット](https://camo.elchika.com/c4d10715ee80ac6da98d01900fdb09a1413968a9/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66643265623836652d623336622d343438652d383961342d6164366436373031303335312f61393837396233392d623031342d346432632d613738372d393731366465393934366338/) * センサーユニット x 4 * M5StickC * HUB Unit * ENV2 Unit * TVOC/eCO2 Unit ![センサーユニット(横)](https://camo.elchika.com/cda7b33e5edd3b4b0104f69c2838fd76cdc43662/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66643265623836652d623336622d343438652d383961342d6164366436373031303335312f64633461373637372d663737642d343234352d396263632d366332666562373032326439/) ![センサーユニット(縦)](https://camo.elchika.com/edde9930127997ee1d4a118e9670295d6604e45e/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66643265623836652d623336622d343438652d383961342d6164366436373031303335312f32326566346166332d663036632d343665622d393563352d666332333239306530646637/) # 機能 4か所のセンサーユニットを取り付けた箇所の温度・湿度・TVOC・eCO2の量が1か所のモニターユニットで確認できます。 ![モニターユニット(4点での環境情報を表示)](https://camo.elchika.com/021ef26c2249ef840b8964a91107ef63d5afbd7a/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f66643265623836652d623336622d343438652d383961342d6164366436373031303335312f34343335313465612d623466352d343039662d616337382d623266353132386263346662/) 厚生労働省によると室内ではTVOCは400以下、eCO2は1000以下が望ましいとのことで、温湿度との兼ね合いと4つ角付近の人との状況を確認しながら、換気、暖気をメンバーで議論することができます。 # 工夫したところ * ワイヤレスかつwifi環境不要で部屋4点の環境情報を取得表示 私が所属している会社では、wifi環境を会社管理のPC以外は接続できないようになっています。 このような環境下でも気軽に使えるように、モニターユニットのM5Stackをwifiアクセスポイントにし、 センサーユニットはモニターユニットのアクセスポイントに接続するようにしています。 このような実装にすることで、wifi環境がなくても本プロダクトを使用可能です。 * 細かい設定不要で使用開始可能 M5StickCのEEPROMで、各センサーユニットのデバイス番号を覚えるようにしています。 ですから、一度設定してしまえば後は好きなところに持って行って、モニターユニットとセンサーユニットの電源を入れれば、部屋の4隅の環境情報のモニタリングを開始できます。
@[youtube](https://youtu.be/-oqRTlUOn24)
[本プロダクトの動作と工夫点](https://youtu.be/-oqRTlUOn24)
# プログラム ```arduino:モニターユニット #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"); } } } ``` ```arduino:センサーユニット #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(); } } ```