From e6fe489be7da26b9083dd2491caed887a0a808d5 Mon Sep 17 00:00:00 2001 From: Achim Date: Fri, 8 Mar 2024 13:29:17 +0700 Subject: [PATCH 1/6] Updated display texts --- examples/ONE/ONE.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/ONE/ONE.ino b/examples/ONE/ONE.ino index fde5c3c..3ce80cc 100644 --- a/examples/ONE/ONE.ino +++ b/examples/ONE/ONE.ino @@ -740,7 +740,7 @@ void setup() { /** Show boot display */ Serial.println("Firmware Version: " + ag.getVersion()); - displayShowText("One V9", "FW Ver: " + ag.getVersion(), ""); + displayShowText("AirGradient ONE", "FW Version: ", ag.getVersion()); delay(DISPLAY_DELAY_SHOW_CONTENT_MS); /** Init sensor */ @@ -1490,7 +1490,7 @@ static void connectToWifi() { ledSmState = APP_SM_WIFI_MANAGER_STA_CONNECTING; }); - displayShowText("Connecting to", "config WiFi", "..."); + displayShowText("Connecting to", "WiFi", "..."); wifiManager.autoConnect(wifiSSID.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT); xTaskCreate( [](void *obj) { From e76dcf07c8c79aa85d9bf204fe49cd95230f9c89 Mon Sep 17 00:00:00 2001 From: Achim Date: Fri, 8 Mar 2024 13:47:28 +0700 Subject: [PATCH 2/6] Updated log texts --- examples/ONE/ONE.ino | 10 +++++----- examples/Open_Air/Open_Air.ino | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/ONE/ONE.ino b/examples/ONE/ONE.ino index 3ce80cc..3370916 100644 --- a/examples/ONE/ONE.ino +++ b/examples/ONE/ONE.ino @@ -1768,18 +1768,18 @@ static void updateServerConfiguration(void) { if (agServer.getCo2AbcDaysConfig() > 0) { if (hasSensorS8) { int newHour = agServer.getCo2AbcDaysConfig() * 24; - Serial.printf("abcDays config: %d days(%d hours)\r\n", + Serial.printf("Requested abcDays setting: %d days (%d hours)\r\n", agServer.getCo2AbcDaysConfig(), newHour); int curHour = ag.s8.getAbcPeriod(); - Serial.printf("Current config: %d (hours)\r\n", curHour); + Serial.printf("Current S8 abcDays setting: %d (hours)\r\n", curHour); if (curHour == newHour) { - Serial.println("Set 'abcDays' ignored"); + Serial.println("'abcDays' unchanged"); } else { if (ag.s8.setAbcPeriod(agServer.getCo2AbcDaysConfig() * 24) == false) { - Serial.println("Set S8 abcDays period calibration failed"); + Serial.println("Set S8 abcDays period failed"); } else { - Serial.println("Set S8 abcDays period calibration success"); + Serial.println("Set S8 abcDays period success"); } } } else { diff --git a/examples/Open_Air/Open_Air.ino b/examples/Open_Air/Open_Air.ino index f614f90..e704857 100644 --- a/examples/Open_Air/Open_Air.ino +++ b/examples/Open_Air/Open_Air.ino @@ -1234,18 +1234,18 @@ static void updateServerConfiguration(void) { if (agServer.getCo2AbcDaysConfig() > 0) { if (hasSensorS8) { int newHour = agServer.getCo2AbcDaysConfig() * 24; - Serial.printf("abcDays config: %d days(%d hours)\r\n", + Serial.printf("Requested abcDays setting: %d days (%d hours)\r\n", agServer.getCo2AbcDaysConfig(), newHour); int curHour = ag.s8.getAbcPeriod(); - Serial.printf("Current config: %d (hours)\r\n", curHour); + Serial.printf("Current S8 abcDays setting: %d (hours)\r\n", curHour); if (curHour == newHour) { - Serial.println("Set 'abcDays' ignored"); + Serial.println("'abcDays' unchanged"); } else { if (ag.s8.setAbcPeriod(agServer.getCo2AbcDaysConfig() * 24) == false) { - Serial.println("Set S8 abcDays period calibration failed"); + Serial.println("Set S8 abcDays period failed"); } else { - Serial.println("Set S8 abcDays period calibration success"); + Serial.println("Set S8 abcDays period success"); } } } From 512509c2e26651d00b1677a77ada12e4428efc66 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Sun, 10 Mar 2024 08:44:49 +0700 Subject: [PATCH 3/6] Print HTTP Response in logs in case of error --- examples/BASIC/BASIC.ino | 2 ++ examples/ONE/ONE.ino | 2 ++ examples/Open_Air/Open_Air.ino | 2 ++ 3 files changed, 6 insertions(+) diff --git a/examples/BASIC/BASIC.ino b/examples/BASIC/BASIC.ino index 383f158..45c29d7 100644 --- a/examples/BASIC/BASIC.ino +++ b/examples/BASIC/BASIC.ino @@ -249,6 +249,8 @@ public: if ((retCode == 200) || (retCode == 429)) { serverFailed = false; return true; + } else { + Serial.printf("Post response failed code: %d\r\n", retCode); } serverFailed = true; return false; diff --git a/examples/ONE/ONE.ino b/examples/ONE/ONE.ino index 3370916..fce9869 100644 --- a/examples/ONE/ONE.ino +++ b/examples/ONE/ONE.ino @@ -338,6 +338,8 @@ public: if ((retCode == 200) || (retCode == 429)) { serverFailed = false; return true; + } else { + Serial.printf("Post response failed code: %d\r\n", retCode); } serverFailed = true; return false; diff --git a/examples/Open_Air/Open_Air.ino b/examples/Open_Air/Open_Air.ino index e704857..49cb36d 100644 --- a/examples/Open_Air/Open_Air.ino +++ b/examples/Open_Air/Open_Air.ino @@ -339,6 +339,8 @@ public: if ((retCode == 200) || (retCode == 429)) { serverFailed = false; return true; + } else { + Serial.printf("Post response failed code: %d\r\n", retCode); } serverFailed = true; return false; From 9ae8fb235577e8f1bedf241917699227525ded33 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Sun, 10 Mar 2024 09:34:04 +0700 Subject: [PATCH 4/6] fix: Completely turn off LEDbar --- examples/ONE/ONE.ino | 18 +++++++++++++++--- src/Main/LedBar.cpp | 20 ++++++++++++++++++-- src/Main/LedBar.h | 3 +++ 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/examples/ONE/ONE.ino b/examples/ONE/ONE.ino index fce9869..0de5a4c 100644 --- a/examples/ONE/ONE.ino +++ b/examples/ONE/ONE.ino @@ -485,6 +485,7 @@ private: if (EEPROM.readBytes(0, &config, sizeof(config)) != sizeof(config)) { config.inF = false; config.inUSAQI = false; + config.useRGBLedBar = UseLedBarCO2; // default use LED bar for CO2 memset(config.models, 0, sizeof(config.models)); memset(config.mqttBrokers, 0, sizeof(config.mqttBrokers)); @@ -743,13 +744,17 @@ void setup() { /** Show boot display */ Serial.println("Firmware Version: " + ag.getVersion()); displayShowText("AirGradient ONE", "FW Version: ", ag.getVersion()); + + boardInit(); delay(DISPLAY_DELAY_SHOW_CONTENT_MS); /** Init sensor */ - boardInit(); /** Init AirGradient server */ agServer.begin(); + if (agServer.getLedBarMode() == UseLedBarOff) { + ag.ledBar.setEnable(false); + } /** Run LED test on start up */ displayShowText("Press now for", "LED test &", "offline mode"); @@ -799,6 +804,8 @@ void setup() { dispSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED); ledSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED); delay(DISPLAY_DELAY_SHOW_CONTENT_MS); + } else { + ag.ledBar.setEnable(agServer.getLedBarMode() != UseLedBarOff); } } @@ -1767,6 +1774,9 @@ static void updateServerConfiguration(void) { } } + // Update LED bar + ag.ledBar.setEnable(agServer.getLedBarMode() != UseLedBarOff); + if (agServer.getCo2AbcDaysConfig() > 0) { if (hasSensorS8) { int newHour = agServer.getCo2AbcDaysConfig() * 24; @@ -2143,9 +2153,11 @@ static void sensorLedColorHandler(void) { case UseLedBarPM: setRGBledPMcolor(pm25); break; + case UseLedBarOff: + ag.ledBar.clear(); + break; default: - ag.ledBar.setColor(0, 0, 0, ag.ledBar.getNumberOfLeds() - 1); - ag.ledBar.setColor(0, 0, 0, ag.ledBar.getNumberOfLeds() - 2); + ag.ledBar.clear(); break; } } diff --git a/src/Main/LedBar.cpp b/src/Main/LedBar.cpp index 63b589d..2627d55 100644 --- a/src/Main/LedBar.cpp +++ b/src/Main/LedBar.cpp @@ -112,9 +112,13 @@ void LedBar::setColor(uint8_t red, uint8_t green, uint8_t blue) { /** * @brief Call to turn LED on/off base on the setting color - * + * */ void LedBar::show(void) { + // Ignore update the LED if LED bar disabled + if (enabled == false) { + return; + } if (pixel()->canShow()) { pixel()->show(); } @@ -122,6 +126,18 @@ void LedBar::show(void) { /** * @brief Set all LED to off color (r,g,b) = (0,0,0) - * + * */ void LedBar::clear(void) { pixel()->clear(); } + +void LedBar::setEnable(bool enable) { + if (this->enabled != enable) { + if (enable == false) { + pixel()->clear(); + pixel()->show(); + } + } + this->enabled = enable; +} + +bool LedBar::isEnabled(void) { return enabled; } diff --git a/src/Main/LedBar.h b/src/Main/LedBar.h index b2431e3..777ed24 100644 --- a/src/Main/LedBar.h +++ b/src/Main/LedBar.h @@ -23,8 +23,11 @@ public: int getNumberOfLeds(void); void show(void); void clear(void); + void setEnable(bool enable); + bool isEnabled(void); private: + bool enabled = true; const BoardDef *_bsp; bool _isBegin = false; uint8_t _ledState = 0; From 6837529096b2c6a79de65ea151878289aaa47923 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Sun, 10 Mar 2024 11:20:52 +0700 Subject: [PATCH 5/6] add compensation temperature and humidity for SGP41 --- examples/ONE/ONE.ino | 7 ++++++- examples/Open_Air/Open_Air.ino | 19 +++++++++++++++++++ src/Sgp41/Sgp41.cpp | 17 +++++++++++++++-- src/Sgp41/Sgp41.h | 1 + 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/examples/ONE/ONE.ino b/examples/ONE/ONE.ino index eff2c19..8768adc 100644 --- a/examples/ONE/ONE.ino +++ b/examples/ONE/ONE.ino @@ -744,7 +744,7 @@ void setup() { /** Show boot display */ Serial.println("Firmware Version: " + ag.getVersion()); displayShowText("AirGradient ONE", "FW Version: ", ag.getVersion()); - + boardInit(); delay(DISPLAY_DELAY_SHOW_CONTENT_MS); @@ -2288,6 +2288,11 @@ static void tempHumUpdate(void) { Serial.printf("Temperature in C: %0.2f\r\n", temp); Serial.printf("Relative Humidity: %d\r\n", hum); + + // Update compensation temperature and humidity for SGP41 + if (hasSensorSGP) { + ag.sgp41.setCompensationTemperatureHumidity(temp, hum); + } } else { Serial.println("SHT read failed"); } diff --git a/examples/Open_Air/Open_Air.ino b/examples/Open_Air/Open_Air.ino index 49cb36d..e63bac8 100644 --- a/examples/Open_Air/Open_Air.ino +++ b/examples/Open_Air/Open_Air.ino @@ -1204,6 +1204,25 @@ static void pmUpdate(void) { pm2hum = 0; } } + + if (hasSensorSGP) { + float temp; + float hum; + if (pmsResult_1 && pmsResult_2) { + temp = (temp_1 + temp_2) / 2.0f; + hum = (hum_1 + hum_2) / 2.0f; + } else { + if (pmsResult_1) { + temp = temp_1; + hum = hum_1; + } + if (pmsResult_2) { + temp = temp_2; + hum = hum_2; + } + } + ag.sgp41.setCompensationTemperatureHumidity(temp, hum); + } } static void co2Update(void) { diff --git a/src/Sgp41/Sgp41.cpp b/src/Sgp41/Sgp41.cpp index f477c94..471a986 100644 --- a/src/Sgp41/Sgp41.cpp +++ b/src/Sgp41/Sgp41.cpp @@ -253,7 +253,20 @@ int Sgp41::getTvocRaw(void) { return tvocRaw; } /** * @brief Get NOX raw value - * - * @return int + * + * @return int */ int Sgp41::getNoxRaw(void) { return noxRaw; } + +/** + * @brief Set compasation temperature and humidity to calculate TVOC and NOx + * index + * + * @param temp Temperature + * @param hum Humidity + */ +void Sgp41::setCompensationTemperatureHumidity(float temp, float hum) { + defaultT = static_cast((temp + 45) * 65535 / 175); + defaultRh = static_cast(hum * 65535 / 100); + AgLog("Update: defaultT: %d, defaultRh: %d", defaultT, defaultRh); +} diff --git a/src/Sgp41/Sgp41.h b/src/Sgp41/Sgp41.h index db0065e..3bbeda8 100644 --- a/src/Sgp41/Sgp41.h +++ b/src/Sgp41/Sgp41.h @@ -25,6 +25,7 @@ public: int getNoxIndex(void); int getTvocRaw(void); int getNoxRaw(void); + void setCompensationTemperatureHumidity(float temp, float hum); private: bool onConditioning = true; From d92d312b0c0e8a85f1cfb3bd453a4857280c10a1 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Sun, 10 Mar 2024 11:57:52 +0700 Subject: [PATCH 6/6] add `openmetrics` --- examples/Open_Air/Open_Air.ino | 182 +++++++++++++++++++++++++++++++-- 1 file changed, 176 insertions(+), 6 deletions(-) diff --git a/examples/Open_Air/Open_Air.ino b/examples/Open_Air/Open_Air.ino index e63bac8..d8fe616 100644 --- a/examples/Open_Air/Open_Air.ino +++ b/examples/Open_Air/Open_Air.ino @@ -1417,6 +1417,180 @@ void webServerMeasureCurrentGet(void) { webServer.send(200, "application/json", getServerSyncData(true)); } +/** + * Sends metrics in Prometheus/OpenMetrics format to the currently connected + * webServer client. + * + * For background, see: + * https://prometheus.io/docs/instrumenting/exposition_formats/ + */ +void webServerMetricsGet(void) { + String response; + String current_metric_name; + const auto add_metric = [&](const String &name, const String &help, + const String &type, const String &unit = "") { + current_metric_name = "airgradient_" + name; + if (!unit.isEmpty()) + current_metric_name += "_" + unit; + response += "# HELP " + current_metric_name + " " + help + "\n"; + response += "# TYPE " + current_metric_name + " " + type + "\n"; + if (!unit.isEmpty()) + response += "# UNIT " + current_metric_name + " " + unit + "\n"; + }; + const auto add_metric_point = [&](const String &labels, const String &value) { + response += current_metric_name + "{" + labels + "} " + value + "\n"; + }; + + add_metric("info", "AirGradient device information", "info"); + add_metric_point("airgradient_serial_number=\"" + getDevId() + + "\",airgradient_device_type=\"" + ag.getBoardName() + + "\",airgradient_library_version=\"" + ag.getVersion() + + "\"", + "1"); + + add_metric("config_ok", + "1 if the AirGradient device was able to successfully fetch its " + "configuration from the server", + "gauge"); + add_metric_point("", agServer.isConfigFailed() ? "0" : "1"); + + add_metric( + "post_ok", + "1 if the AirGradient device was able to successfully send to the server", + "gauge"); + add_metric_point("", agServer.isServerFailed() ? "0" : "1"); + + add_metric( + "wifi_rssi", + "WiFi signal strength from the AirGradient device perspective, in dBm", + "gauge", "dbm"); + add_metric_point("", String(WiFi.RSSI())); + + if (hasSensorS8 && co2Ppm >= 0) { + add_metric("co2", + "Carbon dioxide concentration as measured by the AirGradient S8 " + "sensor, in parts per million", + "gauge", "ppm"); + add_metric_point("", String(co2Ppm)); + } + + float temp = -1001; + float hum = -1; + int pm01 = -1; + int pm25 = -1; + int pm10 = -1; + int pm03PCount = -1; + if (hasSensorPMS1 && hasSensorPMS2) { + temp = (temp_1 + temp_2) / 2.0f; + hum = (hum_1 + hum_2) / 2.0f; + pm01 = (pm01_1 + pm01_2) / 2; + pm25 = (pm25_1 + pm25_2) / 2; + pm10 = (pm10_1 + pm10_2) / 2; + pm03PCount = (pm03PCount_1 + pm03PCount_2) / 2; + } else { + if (hasSensorPMS1) { + temp = temp_1; + hum = hum_1; + pm01 = pm01_1; + pm25 = pm25_1; + pm10 = pm10_1; + pm03PCount = pm03PCount_1; + } + if (hasSensorPMS2) { + temp = temp_2; + hum = hum_2; + pm01 = pm01_2; + pm25 = pm25_2; + pm10 = pm10_2; + pm03PCount = pm03PCount_2; + } + } + + if (hasSensorPMS1 || hasSensorPMS2) { + if (pm01 >= 0) { + add_metric("pm1", + "PM1.0 concentration as measured by the AirGradient PMS " + "sensor, in micrograms per cubic meter", + "gauge", "ugm3"); + add_metric_point("", String(pm01)); + } + if (pm25 >= 0) { + add_metric("pm2d5", + "PM2.5 concentration as measured by the AirGradient PMS " + "sensor, in micrograms per cubic meter", + "gauge", "ugm3"); + add_metric_point("", String(pm25)); + } + if (pm10 >= 0) { + add_metric("pm10", + "PM10 concentration as measured by the AirGradient PMS " + "sensor, in micrograms per cubic meter", + "gauge", "ugm3"); + add_metric_point("", String(pm10)); + } + if (pm03PCount >= 0) { + add_metric("pm0d3", + "PM0.3 concentration as measured by the AirGradient PMS " + "sensor, in number of particules per 100 milliliters", + "gauge", "p100ml"); + add_metric_point("", String(pm03PCount)); + } + } + + if (hasSensorSGP) { + if (tvocIndex >= 0) { + add_metric("tvoc_index", + "The processed Total Volatile Organic Compounds (TVOC) index " + "as measured by the AirGradient SGP sensor", + "gauge"); + add_metric_point("", String(tvocIndex)); + } + if (tvocRawIndex >= 0) { + add_metric("tvoc_raw", + "The raw input value to the Total Volatile Organic Compounds " + "(TVOC) index as measured by the AirGradient SGP sensor", + "gauge"); + add_metric_point("", String(tvocRawIndex)); + } + if (noxIndex >= 0) { + add_metric("nox_index", + "The processed Nitrous Oxide (NOx) index as measured by the " + "AirGradient SGP sensor", + "gauge"); + add_metric_point("", String(noxIndex)); + } + if (noxRawIndex >= 0) { + add_metric("nox_raw", + "The raw input value to the Nitrous Oxide (NOx) index as " + "measured by the AirGradient SGP sensor", + "gauge"); + add_metric_point("", String(noxRawIndex)); + } + } + + if (hasSensorPMS1 || hasSensorPMS2) { + if (temp > -1001) { + add_metric("temperature", + "The ambient temperature as measured by the AirGradient SHT " + "sensor, in degrees Celsius", + "gauge", "celcius"); + add_metric_point("", String(temp)); + } + if (hum >= 0) { + add_metric( + "humidity", + "The relative humidity as measured by the AirGradient SHT sensor", + "gauge", "percent"); + add_metric_point("", String(hum)); + } + } + + response += "# EOF\n"; + webServer.send(200, + "application/openmetrics-text; version=1.0.0; charset=utf-8", + response); +} + void webServerHandler(void *param) { for (;;) { webServer.handleClient(); @@ -1431,13 +1605,9 @@ static void webServerInit(void) { } webServer.on("/measures/current", HTTP_GET, webServerMeasureCurrentGet); + // Make it possible to query this device from Prometheus/OpenMetrics. + webServer.on("/metrics", HTTP_GET, webServerMetricsGet); webServer.begin(); - MDNS.addService("http", "tcp", 80); - MDNS.addServiceTxt("http", "_tcp", "model", mdnsModelName); - MDNS.addServiceTxt("http", "_tcp", "serialno", getDevId()); - MDNS.addServiceTxt("http", "_tcp", "fw_ver", ag.getVersion()); - MDNS.addServiceTxt("http", "_tcp", "vendor", "AirGradient"); - MDNS.addService("http", "tcp", 80); MDNS.addService("_airgradient", "tcp", 80); MDNS.addServiceTxt("airgradient", "_tcp", "model", mdnsModelName); MDNS.addServiceTxt("airgradient", "_tcp", "serialno", getDevId());