From 45770827319e60b7c71e1315ab0d641d62f2e835 Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Sun, 7 Apr 2024 16:39:01 +0700 Subject: [PATCH] clarifying method/variable/class names --- examples/BASIC/BASIC.ino | 114 +-- examples/OneOpenAir/LocalServer.cpp | 68 ++ examples/OneOpenAir/LocalServer.h | 38 + examples/OneOpenAir/OneOpenAir.ino | 1270 +++++++-------------------- examples/OneOpenAir/OpenMetrics.cpp | 186 ++++ examples/OneOpenAir/OpenMetrics.h | 28 + src/AgApiClient.cpp | 2 +- src/AgApiClient.h | 4 +- src/AgConfigure.cpp | 106 ++- src/AgConfigure.h | 16 +- src/AgOledDisplay.cpp | 38 +- src/AgOledDisplay.h | 10 +- src/AgStateMachine.cpp | 317 +++++-- src/AgStateMachine.h | 30 +- src/AgValue.cpp | 151 ++++ src/AgValue.h | 78 +- src/AgWiFiConnector.cpp | 150 +++- src/AgWiFiConnector.h | 27 +- src/AirGradient.cpp | 8 +- src/AirGradient.h | 2 +- src/App/AppDef.h | 8 + 21 files changed, 1372 insertions(+), 1279 deletions(-) create mode 100644 examples/OneOpenAir/LocalServer.cpp create mode 100644 examples/OneOpenAir/LocalServer.h create mode 100644 examples/OneOpenAir/OpenMetrics.cpp create mode 100644 examples/OneOpenAir/OpenMetrics.h create mode 100644 src/AgValue.cpp diff --git a/examples/BASIC/BASIC.ino b/examples/BASIC/BASIC.ino index 45cf66e..9cc8a7d 100644 --- a/examples/BASIC/BASIC.ino +++ b/examples/BASIC/BASIC.ino @@ -15,7 +15,6 @@ https://www.airgradient.com/documentation/diy-v4/ Following libraries need to be installed: “WifiManager by tzapu, tablatronix” tested with version 2.0.16-rc.2 "Arduino_JSON" by Arduino version 0.2.0 -"U8g2" by oliver version 2.34.22 Please make sure you have esp8266 board manager installed. Tested with version 3.1.2. @@ -32,15 +31,15 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License */ -#include -#include "AgSchedule.h" -#include "AgConfigure.h" #include "AgApiClient.h" +#include "AgConfigure.h" +#include "AgSchedule.h" +#include "AgWiFiConnector.h" +#include #include #include #include #include -#include #define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */ #define WIFI_CONNECT_RETRY_MS 10000 /** ms */ @@ -61,20 +60,19 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License /** Create airgradient instance for 'DIY_BASIC' board */ static AirGradient ag = AirGradient(DIY_BASIC); -static AgConfigure localConfig(Serial); -static AgApiClient apiClient(Serial, localConfig); +static Configuration configuration(Serial); +static AgApiClient apiClient(Serial, configuration); +static WifiConnector wifiConnector(Serial); static int co2Ppm = -1; static int pm25 = -1; static float temp = -1001; static int hum = -1; static long val; -static String wifiSSID = ""; -static bool wifiHasConfig = false; /** */ static void boardInit(void); static void failedHandler(String msg); -static void co2Calibration(void); +static void executeCo2Calibration(void); static void updateServerConfiguration(void); static void co2Update(void); static void pmUpdate(void); @@ -82,7 +80,6 @@ static void tempHumUpdate(void); static void sendDataToServer(void); static void dispHandler(void); static String getDevId(void); -static void updateWiFiConnect(void); static void showNr(void); bool hasSensorS8 = true; @@ -112,23 +109,24 @@ void setup() { /** Init AirGradient server */ apiClient.begin(); apiClient.setAirGradient(&ag); + wifiConnector.setAirGradient(&ag); /** Show boot display */ displayShowText("DIY basic", "Lib:" + ag.getVersion(), ""); delay(2000); /** WiFi connect */ - connectToWifi(); - if (WiFi.status() == WL_CONNECTED) { - wifiHasConfig = true; - sendPing(); + // connectToWifi(); + if (wifiConnector.connect()) { + if (WiFi.status() == WL_CONNECTED) { + sendDataToAg(); - apiClient.fetchServerConfiguration(); - if (localConfig.isCo2CalibrationRequested()) { - co2Calibration(); + apiClient.fetchServerConfiguration(); + if (configuration.isCo2CalibrationRequested()) { + executeCo2Calibration(); + } } } - /** Show serial number display */ ag.display.clear(); ag.display.setCursor(1, 1); @@ -162,13 +160,13 @@ void loop() { tempHumSchedule.run(); } - updateWiFiConnect(); + wifiConnector.handle(); /** Read PMS on loop */ ag.pms5003.handle(); } -static void sendPing() { +static void sendDataToAg() { JSONVar root; root["wifi"] = WiFi.RSSI(); root["boot"] = 0; @@ -197,39 +195,6 @@ void displayShowText(String ln1, String ln2, String ln3) { delay(100); } -// Wifi Manager -void connectToWifi() { - WiFiManager wifiManager; - wifiSSID = "AG-" + String(ESP.getChipId(), HEX); - wifiManager.setConfigPortalBlocking(false); - wifiManager.setConfigPortalTimeout(WIFI_CONNECT_COUNTDOWN_MAX); - wifiManager.autoConnect(wifiSSID.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT); - - uint32_t lastTime = millis(); - int count = WIFI_CONNECT_COUNTDOWN_MAX; - displayShowText(String(WIFI_CONNECT_COUNTDOWN_MAX) + " sec", - "SSID:", wifiSSID); - while (wifiManager.getConfigPortalActive()) { - wifiManager.process(); - uint32_t ms = (uint32_t)(millis() - lastTime); - if (ms >= 1000) { - lastTime = millis(); - displayShowText(String(count) + " sec", "SSID:", wifiSSID); - count--; - - // Timeout - if (count == 0) { - break; - } - } - } - if (!WiFi.isConnected()) { - displayShowText("Booting", "offline", "mode"); - Serial.println("failed to connect and hit timeout"); - delay(DISPLAY_DELAY_SHOW_CONTENT_MS); - } -} - static void boardInit(void) { /** Init SHT sensor */ if (ag.sht.begin(Wire) == false) { @@ -264,7 +229,7 @@ static void failedHandler(String msg) { } } -static void co2Calibration(void) { +static void executeCo2Calibration(void) { /** Count down for co2CalibCountdown secs */ for (int i = 0; i < SENSOR_CO2_CALIB_COUNTDOWN_MAX; i++) { displayShowText("CO2 calib", "after", @@ -291,25 +256,25 @@ static void co2Calibration(void) { static void updateServerConfiguration(void) { if (apiClient.fetchServerConfiguration()) { - if (localConfig.isCo2CalibrationRequested()) { + if (configuration.isCo2CalibrationRequested()) { if (hasSensorS8) { - co2Calibration(); + executeCo2Calibration(); } else { Serial.println("CO2 S8 not available, calib ignored"); } } - if (localConfig.getCO2CalirationAbcDays() > 0) { + if (configuration.getCO2CalibrationAbcDays() > 0) { if (hasSensorS8) { - int newHour = localConfig.getCO2CalirationAbcDays() * 24; + int newHour = configuration.getCO2CalibrationAbcDays() * 24; Serial.printf("abcDays config: %d days(%d hours)\r\n", - localConfig.getCO2CalirationAbcDays(), newHour); + configuration.getCO2CalibrationAbcDays(), newHour); int curHour = ag.s8.getAbcPeriod(); Serial.printf("Current config: %d (hours)\r\n", curHour); if (curHour == newHour) { Serial.println("set 'abcDays' ignored"); } else { - if (ag.s8.setAbcPeriod(localConfig.getCO2CalirationAbcDays() * 24) == - false) { + if (ag.s8.setAbcPeriod(configuration.getCO2CalibrationAbcDays() * + 24) == false) { Serial.println("Set S8 abcDays period calib failed"); } else { Serial.println("Set S8 abcDays period calib success"); @@ -388,7 +353,7 @@ static void dispHandler() { String ln2 = ""; String ln3 = ""; - if (localConfig.isPmStandardInUSAQI()) { + if (configuration.isPmStandardInUSAQI()) { if (pm25 < 0) { ln1 = "AQI: -"; } else { @@ -415,7 +380,7 @@ static void dispHandler() { String _temp = "-"; - if (localConfig.isTemperatureUnitInF()) { + if (configuration.isTemperatureUnitInF()) { if (temp > -1001) { _temp = String((temp * 9 / 5) + 32).substring(0, 4); } @@ -431,27 +396,6 @@ static void dispHandler() { static String getDevId(void) { return getNormalizedMac(); } -/** - * @brief WiFi reconnect handler - */ -static void updateWiFiConnect(void) { - static uint32_t lastRetry; - if (wifiHasConfig == false) { - return; - } - if (WiFi.isConnected()) { - lastRetry = millis(); - return; - } - uint32_t ms = (uint32_t)(millis() - lastRetry); - if (ms >= WIFI_CONNECT_RETRY_MS) { - lastRetry = millis(); - WiFi.reconnect(); - - Serial.printf("Re-Connect WiFi\r\n"); - } -} - static void showNr(void) { Serial.println(); Serial.println("Serial nr: " + getDevId()); diff --git a/examples/OneOpenAir/LocalServer.cpp b/examples/OneOpenAir/LocalServer.cpp new file mode 100644 index 0000000..7775176 --- /dev/null +++ b/examples/OneOpenAir/LocalServer.cpp @@ -0,0 +1,68 @@ +#include "LocalServer.h" + +LocalServer::LocalServer(Stream &log, OpenMetrics &openMetrics, + Measurements &measure, Configuration &config, + WifiConnector &wifiConnector) + : PrintLog(log, "LocalServer"), openMetrics(openMetrics), measure(measure), + config(config), wifiConnector(wifiConnector) {} + +LocalServer::~LocalServer() {} + +bool LocalServer::begin(void) { + server.on("/measures/current", HTTP_GET, [this]() { _GET_measure(); }); + server.on(openMetrics.getApi(), HTTP_GET, [this]() { _GET_metrics(); }); + server.on("/config", HTTP_GET, [this]() { _GET_config(); }); + server.on("/config", HTTP_PUT, [this]() { _PUT_config(); }); + server.begin(); + + if (xTaskCreate( + [](void *param) { + LocalServer *localServer = (LocalServer *)param; + for (;;) { + localServer->_handle(); + } + }, + "webserver", 1024 * 4, this, 5, NULL) != pdTRUE) { + Serial.println("Create task handle webserver failed"); + } + logInfo("Init: " + getHostname() + ".local"); + + return true; +} + +void LocalServer::setAirGraident(AirGradient *ag) { this->ag = ag; } + +String LocalServer::getHostname(void) { + return "airgradient_" + ag->deviceId(); +} + +void LocalServer::_handle(void) { server.handleClient(); } + +void LocalServer::_GET_config(void) { + server.send(200, "application/json", config.toString()); +} + +void LocalServer::_PUT_config(void) { + String data = server.arg(0); + String response = ""; + int statusCode = 400; // Status code for data invalid + if (config.parse(data, true)) { + statusCode = 200; + response = "Success"; + } else { + response = "Set for cloud configuration. Local configuration ignored"; + } + server.send(statusCode, "text/plain", response); +} + +void LocalServer::_GET_metrics(void) { + server.send(200, openMetrics.getApiContentType(), openMetrics.getPayload()); +} + +void LocalServer::_GET_measure(void) { + server.send( + 200, "application/json", + measure.toString(true, fwMode, wifiConnector.RSSI(), ag, &config)); +} + +void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; } diff --git a/examples/OneOpenAir/LocalServer.h b/examples/OneOpenAir/LocalServer.h new file mode 100644 index 0000000..74c0d61 --- /dev/null +++ b/examples/OneOpenAir/LocalServer.h @@ -0,0 +1,38 @@ +#ifndef _LOCAL_SERVER_H_ +#define _LOCAL_SERVER_H_ + +#include "AgConfigure.h" +#include "AgValue.h" +#include "AirGradient.h" +#include "OpenMetrics.h" +#include "AgWiFiConnector.h" +#include +#include + +class LocalServer : public PrintLog { +private: + AirGradient *ag; + OpenMetrics &openMetrics; + Measurements &measure; + Configuration &config; + WifiConnector &wifiConnector; + WebServer server; + AgFirmwareMode fwMode; + +public: + LocalServer(Stream &log, OpenMetrics &openMetrics, Measurements &measure, + Configuration &config, WifiConnector& wifiConnector); + ~LocalServer(); + + bool begin(void); + void setAirGraident(AirGradient *ag); + String getHostname(void); + void setFwMode(AgFirmwareMode fwMode); + void _handle(void); + void _GET_config(void); + void _PUT_config(void); + void _GET_metrics(void); + void _GET_measure(void); +}; + +#endif /** _LOCAL_SERVER_H_ */ diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 4f30dc5..6adcf95 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -40,7 +40,6 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License */ #include -// #include #include "AgApiClient.h" #include "AgConfigure.h" @@ -48,10 +47,13 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License #include "AgStateMachine.h" #include "AgWiFiConnector.h" #include "EEPROM.h" +#include "ESPmDNS.h" +#include "LocalServer.h" #include "MqttClient.h" +#include "OpenMetrics.h" +#include "WebServer.h" #include #include -#include #include #define LED_BAR_ANIMATION_PERIOD 100 /** ms */ @@ -66,9 +68,6 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License #define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */ #define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */ -/** Default WiFi AP password */ -#define WIFI_HOTSPOT_PASSWORD_DEFAULT "cleanair" - /** I2C define */ #define I2C_SDA_PIN 7 #define I2C_SCL_PIN 6 @@ -76,183 +75,127 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License static MqttClient mqttClient(Serial); static TaskHandle_t mqttTask = NULL; -static AgConfigure localConfig(Serial); // todo: rename class Configuration and variable to configuration -static AgApiClient apiClient(Serial, localConfig); -static AgValue agValue; // todo: rename class to Measurements and variable to measurements +static Configuration configuration(Serial); +static AgApiClient apiClient(Serial, configuration); +static Measurements measurements; static AirGradient *ag; -static AgOledDisplay disp(localConfig, agValue, Serial); // todo: rename class to OledDisplay and variable to oledDisplay -static AgStateMachine sm(disp, Serial, agValue, localConfig); // todo: rename class to StateMachine and variable to stateMachine -static AgWiFiConnector wifiConnector(disp, Serial, sm); // todo: rename class to WifiConnector and variable to wifiConnector -static WebServer webServer; +static OledDisplay oledDisplay(configuration, measurements, Serial); +static StateMachine stateMachine(oledDisplay, Serial, measurements, + configuration); +static WifiConnector wifiConnector(oledDisplay, Serial, stateMachine); +static OpenMetrics openMetrics(measurements, configuration, wifiConnector, + apiClient); +static LocalServer localServer(Serial, openMetrics, measurements, configuration, + wifiConnector); -/** Init schedule */ -static bool hasSensorS8 = true; -static bool hasSensorPMS1 = true; -static bool hasSensorPMS2 = true; -static bool hasSensorSGP = true; -static bool hasSensorSHT = true; static int pmFailCount = 0; static uint32_t factoryBtnPressTime = 0; static int getCO2FailCount = 0; -static uint32_t addToDashboardTime; -static bool isAddToDashboard = true; static bool offlineMode = false; static AgFirmwareMode fwMode = FW_MODE_I_9PSL; -static int bootCount; - -// todo: For below use class Measurements (before Values). -static int pm25_1 = -1; -static int pm01_1 = -1; -static int pm10_1 = -1; -static int pm03PCount_1 = -1; -static float temp_1 = -1001; -static int hum_1 = -1; - -static int pm25_2 = -1; -static int pm01_2 = -1; -static int pm10_2 = -1; -static int pm03PCount_2 = -1; -static float temp_2 = -1001; -static int hum_2 = -1; - -static int pm1Value01; -static int pm1Value25; -static int pm1Value10; -static int pm1PCount; -static int pm1temp; -static int pm1hum; -static int pm2Value01; -static int pm2Value25; -static int pm2Value10; -static int pm2PCount; -static int pm2temp; -static int pm2hum; -static int countPosition; -const int targetCount = 20; - static bool ledBarButtonTest = false; -static bool localConfigUpdate = false; - -// todo: see above comment static void boardInit(void); static void failedHandler(String msg); -static void updateServerConfiguration(void); -static void co2Calibration(void); +static void configurationUpdateSchedule(void); +static void executeCo2Calibration(void); static void appLedHandler(void); static void appDispHandler(void); static void updateWiFiConnect(void); -static void displayAndLedUpdate(void); -static void tvocUpdate(void); -static void pmUpdate(void); +static void oledDisplayLedBarSchedule(void); +static void updateTvoc(void); +static void updatePm(void); static void sendDataToServer(void); static void tempHumUpdate(void); static void co2Update(void); static void showNr(void); -static void webServerInit(void); -static String getServerSyncData(bool localServer); +static void mdnsInit(void); static void createMqttTask(void); +static void initMqtt(void); static void factoryConfigReset(void); static void wdgFeedUpdate(void); +static void ledBarEnabledUpdate(void); -AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, displayAndLedUpdate); // todo: rename to oledDisplayLedBarSchedule +AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplayLedBarSchedule); AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, - updateServerConfiguration); // todo: rename to configurationUpdateSchedule -AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer); // todo: rename to agApiPostSchedule + configurationUpdateSchedule); +AgSchedule agApiPostSchedule(SERVER_SYNC_INTERVAL, sendDataToServer); AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update); -AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, pmUpdate); +AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, updatePm); AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumUpdate); -AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, tvocUpdate); -AgSchedule wdgFeedSchedule(60000, wdgFeedUpdate); // todo: rename to watchdogFeedSchedule +AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, updateTvoc); +AgSchedule watchdogFeedSchedule(60000, wdgFeedUpdate); void setup() { /** Serial for print debug message */ Serial.begin(115200); delay(100); /** For bester show log */ - showNr(); // todo: can be inlined? + + /** Print device ID into log */ + Serial.println("Serial nr: " + ag->deviceId()); /** Initialize local configure */ - localConfig.begin(); + configuration.begin(); /** Init I2C */ Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN); delay(1000); - /** Detect board type */ + /** Detect board type: ONE_INDOOR has OLED display, Scan the I2C address to + * identify board type */ Wire.beginTransmission(OLED_I2C_ADDR); if (Wire.endTransmission() == 0x00) { - Serial.println("Detect ONE_INDOOR"); ag = new AirGradient(BoardType::ONE_INDOOR); } else { - Serial.println("Detect OPEN_AIR"); ag = new AirGradient(BoardType::OPEN_AIR_OUTDOOR); } + Serial.println("Detected " + ag->getBoardName()); /** Init sensor */ boardInit(); - - - disp.setAirGradient(ag); // todo: Can ag be passed in constructor in intitalisation above? - sm.setAirGradient(ag); // todo: Can ag be passed in constructor in intitalisation above? - wifiConnector.setAirGradient(ag); // todo: Can ag be passed in constructor in intitalisation above? + oledDisplay.setAirGradient(ag); + stateMachine.setAirGradient(ag); + wifiConnector.setAirGradient(ag); + apiClient.setAirGradient(ag); + openMetrics.setAirGradient(ag); + localServer.setAirGraident(ag); /** Connecting wifi */ - bool connectWifi = false; - if (ag->isOneIndoor()) { // todo: rename to isOne() + bool connectToWifi = false; + if (ag->isOne()) { if (ledBarButtonTest) { - ledBarTest(); + stateMachine.executeLedBarTest(); } else { - /** Check LED mode to disabled LED */ // todo: remove comment - if (localConfig.getLedBarMode() == LedBarModeOff) { - ag->ledBar.setEnable(false); - } - connectWifi = true; + ledBarEnabledUpdate(); + connectToWifi = true; } } else { - connectWifi = true; + connectToWifi = true; } - if (connectWifi) { - /** Init AirGradient server */ + if (connectToWifi) { apiClient.begin(); - apiClient.setAirGradient(ag); // todo: can be initiatlized obove? if (wifiConnector.connect()) { - Serial.println("Connect to wifi failed"); // todo: why failed of connect == true? - - /** - * Send first data to ping server and get server configuration - */ if (wifiConnector.isConnected()) { - webServerInit(); + mdnsInit(); + localServer.begin(); + initMqtt(); + sendDataToAg(); - /** MQTT init */ - if (localConfig.getMqttBrokerUri().isEmpty() == false) { - if (mqttClient.begin(localConfig.getMqttBrokerUri())) { - createMqttTask(); - Serial.println("MQTT client init success"); - } else { - Serial.println("MQTT client init failure"); - } - } - - sendPing(); // todo: rename to sendDataToAg(); - Serial.println(F("WiFi connected!")); - Serial.println("IP address: "); - Serial.println(wifiConnector.localIpStr()); - - /** Get first connected to wifi */ // todo: remove comment apiClient.fetchServerConfiguration(); if (apiClient.isFetchConfigureFailed()) { - if (ag->isOneIndoor()) { - sm.displayHandle(AgStateMachineWiFiOkServerOkSensorConfigFailed); + if (ag->isOne()) { + stateMachine.displayHandle( + AgStateMachineWiFiOkServerOkSensorConfigFailed); } - sm.ledHandle(AgStateMachineWiFiOkServerOkSensorConfigFailed); // todo: rename to "handleLeds" + stateMachine.handleLeds( + AgStateMachineWiFiOkServerOkSensorConfigFailed); delay(DISPLAY_DELAY_SHOW_CONTENT_MS); } else { - ag->ledBar.setEnable(localConfig.getLedBarMode() != LedBarModeOff); + ledBarEnabledUpdate(); } } else { offlineMode = true; @@ -261,46 +204,52 @@ void setup() { } /** Show display Warning up */ - if (ag->isOneIndoor()) { - disp.setText("Warming Up", "Serial Number:", ag->deviceId().c_str()); + if (ag->isOne()) { + oledDisplay.setText("Warming Up", "Serial Number:", ag->deviceId().c_str()); delay(DISPLAY_DELAY_SHOW_CONTENT_MS); } appLedHandler(); - if (ag->isOneIndoor()) { - appDispHandler(); - } + appDispHandler(); } void loop() { /** Handle schedule */ dispLedSchedule.run(); configSchedule.run(); - serverSchedule.run(); + agApiPostSchedule.run(); - if (hasSensorS8) { + if (configuration.hasSensorS8) { co2Schedule.run(); } - - if (hasSensorPMS1 || hasSensorPMS2) { + if (configuration.hasSensorPMS1 || configuration.hasSensorPMS2) { pmsSchedule.run(); } - - if (ag->isOneIndoor()) { - if (hasSensorSHT) { - delay(100); + if (ag->isOne()) { + if (configuration.hasSensorSHT) { tempHumSchedule.run(); } } - - if (hasSensorSGP) { + if (configuration.hasSensorSGP) { tvocSchedule.run(); } + if (ag->isOne()) { + if (configuration.hasSensorPMS1) { + ag->pms5003.handle(); + } + } else { + if (configuration.hasSensorPMS1) { + ag->pms5003t_1.handle(); + } + if (configuration.hasSensorPMS2) { + ag->pms5003t_2.handle(); + } + } /** Auto reset external watchdog timer on offline mode and * postDataToAirGradient disabled. */ - if (offlineMode || (localConfig.isPostDataToAirGradient() == false)) { - wdgFeedSchedule.run(); + if (offlineMode || (configuration.isPostDataToAirGradient() == false)) { + watchdogFeedSchedule.run(); } /** Check for handle WiFi reconnect */ @@ -309,501 +258,37 @@ void loop() { /** factory reset handle */ factoryConfigReset(); - /** Read PMS on loop */ - if (ag->isOneIndoor()) { - if (hasSensorPMS1) { - ag->pms5003.handle(); - } - } else { - if (hasSensorPMS1) { - ag->pms5003t_1.handle(); - } - if (hasSensorPMS2) { - ag->pms5003t_2.handle(); - } - } - /** check that local configura changed then do some action */ - if (localConfigUpdate) { - localConfigUpdate = false; - configUpdateHandle(); - } -} - - -// todo: Move into library -static void ledBarTestColor(char color) { // todo: rename to runLedTest() - int r = 0; - int g = 0; - int b = 0; - switch (color) { - case 'g': - g = 255; - break; - case 'y': - r = 255; - g = 255; - break; - case 'o': - r = 255; - g = 128; - break; - case 'r': - r = 255; - break; - case 'b': - b = 255; - break; - case 'w': - r = 255; - g = 255; - b = 255; - break; - case 'p': - r = 153; - b = 153; - break; - case 'z': - r = 102; - break; - case 'n': - default: - break; - } - ag->ledBar.setColor(r, g, b); -} - - -// todo: same as above -static void ledBarTest() { - disp.setText("LED Test", "running", "....."); - ledBarTestColor('r'); - ag->ledBar.show(); - delay(1000); - ledBarTestColor('g'); - ag->ledBar.show(); - delay(1000); - ledBarTestColor('b'); - ag->ledBar.show(); - delay(1000); - ledBarTestColor('w'); - ag->ledBar.show(); - delay(1000); - ledBarTestColor('n'); - ag->ledBar.show(); - delay(1000); -} - -// todo: same as above -static void ledBarTest2Min(void) { - uint32_t tstart = millis(); - - Serial.println("Start run LED test for 2 min"); - while (1) { - ledBarTest(); - uint32_t ms = (uint32_t)(millis() - tstart); - if (ms >= (60 * 1000 * 2)) { - Serial.println("LED test after 2 min finish"); - break; - } - } + configUpdateHandle(); } static void co2Update(void) { int value = ag->s8.getCo2(); if (value >= 0) { - agValue.CO2 = value; + measurements.CO2 = value; getCO2FailCount = 0; - Serial.printf("CO2 (ppm): %d\r\n", agValue.CO2); + Serial.printf("CO2 (ppm): %d\r\n", measurements.CO2); } else { getCO2FailCount++; Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount); if (getCO2FailCount >= 3) { - agValue.CO2 = -1; + measurements.CO2 = -1; } } } -static void showNr(void) { Serial.println("Serial nr: " + ag->deviceId()); } - -void webServerMeasureCurrentGet(void) { - webServer.send(200, "application/json", getServerSyncData(true)); -} - - -// todo: extract into seperate class called OpenMetrics - -/** - * 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) { // todo: rename to onWebServerMetricsGet - 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=\"" + ag->deviceId() + - "\",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("", apiClient.isFetchConfigureFailed() ? "0" : "1"); - - add_metric( - "post_ok", - "1 if the AirGradient device was able to successfully send to the server", - "gauge"); - add_metric_point("", apiClient.isPostToServerFailed() ? "0" : "1"); - - add_metric( - "wifi_rssi", - "WiFi signal strength from the AirGradient device perspective, in dBm", - "gauge", "dbm"); - add_metric_point("", String(wifiConnector.RSSI())); - - if (hasSensorS8 && agValue.CO2 >= 0) { - add_metric("co2", - "Carbon dioxide concentration as measured by the AirGradient S8 " - "sensor, in parts per million", - "gauge", "ppm"); - add_metric_point("", String(agValue.CO2)); - } - - 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 (ag->isOneIndoor()) { - if (hasSensorSHT) { - _temp = agValue.Temperature; - _hum = agValue.Humidity; - } - } 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 (agValue.TVOC >= 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(agValue.TVOC)); - } - if (agValue.TVOCRaw >= 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(agValue.TVOCRaw)); - } - if (agValue.NOx >= 0) { - add_metric("nox_index", - "The processed Nitrous Oxide (NOx) index as measured by the " - "AirGradient SGP sensor", - "gauge"); - add_metric_point("", String(agValue.NOx)); - } - if (agValue.NOxRaw >= 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(agValue.NOxRaw)); - } - } - - if (_temp > -1001) { - add_metric("temperature", - "The ambient temperature as measured by the AirGradient SHT " - "sensor, in degrees Celsius", - "gauge", "celsius"); - 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); -} - - -// todo: extract all webServer code into separate classes (but not into library) - -void webServerHandler(void *param) { - for (;;) { - webServer.handleClient(); - } -} - -static void webServerInit(void) { - String host = "airgradient_" + ag->deviceId(); - if (!MDNS.begin(host.c_str())) { +static void mdnsInit(void) { + if (!MDNS.begin(localServer.getHostname().c_str())) { Serial.println("Init mDNS failed"); return; } - webServer.on("/measures/current", HTTP_GET, webServerMeasureCurrentGet); - // Make it possible to query this device from Prometheus/OpenMetrics. - webServer.on("/metrics", HTTP_GET, webServerMetricsGet); - webServer.on("/config", HTTP_GET, localConfigGet); - webServer.on("/config", HTTP_PUT, localConfigPut); - webServer.begin(); MDNS.addService("_airgradient", "_tcp", 80); MDNS.addServiceTxt("_airgradient", "_tcp", "model", AgFirmwareModeName(fwMode)); MDNS.addServiceTxt("_airgradient", "_tcp", "serialno", ag->deviceId()); MDNS.addServiceTxt("_airgradient", "_tcp", "fw_ver", ag->getVersion()); MDNS.addServiceTxt("_airgradient", "_tcp", "vendor", "AirGradient"); - - if (xTaskCreate(webServerHandler, "webserver", 1024 * 4, NULL, 5, NULL) != - pdTRUE) { - Serial.println("Create task handle webserver failed"); - } - Serial.printf("Webserver init: %s.local\r\n", host.c_str()); -} - -static void localConfigGet() { - webServer.send(200, "application/json", localConfig.toString()); -} -static void localConfigPut() { - String data = webServer.arg(0); - String response = ""; - int statusCode = 400; // Status code for data invalid - if (localConfig.parse(data, true)) { - localConfigUpdate = true; - statusCode = 200; - response = "Success"; - } else { - response = "Set for cloud configuration. Local configuration ignored"; - } - webServer.send(statusCode, "text/plain", response); -} - -static String getServerSyncData(bool localServer) { - JSONVar root; - root["wifi"] = wifiConnector.RSSI(); - if (localServer) { - root["serialno"] = ag->deviceId(); - } - if (hasSensorS8) { - if (agValue.CO2 >= 0) { - root["rco2"] = agValue.CO2; - } - } - - if (ag->isOneIndoor()) { - if (hasSensorPMS1) { - if (pm01_1 >= 0) { - root["pm01"] = pm01_1; - } - if (pm25_1 >= 0) { - root["pm02"] = pm25_1; - } - if (pm10_1 >= 0) { - root["pm10"] = pm10_1; - } - if (pm03PCount_1 >= 0) { - if (localServer) { - root["pm003Count"] = pm03PCount_1; - } else { - root["pm003_count"] = pm03PCount_1; - } - } - } - - if (hasSensorSHT) { - if (agValue.Temperature > -1001) { - root["atmp"] = ag->round2(agValue.Temperature); - } - if (agValue.Humidity >= 0) { - root["rhum"] = agValue.Humidity; - } - } - - } else { - if (hasSensorPMS1 && hasSensorPMS2) { - root["pm01"] = ag->round2((pm01_1 + pm01_2) / 2.0); - root["pm02"] = ag->round2((pm25_1 + pm25_2) / 2.0); - root["pm10"] = ag->round2((pm10_1 + pm10_2) / 2.0); - if (localServer) { - root["pm003Count"] = ag->round2((pm03PCount_1 + pm03PCount_2) / 2.0); - } else { - root["pm003_count"] = ag->round2((pm03PCount_1 + pm03PCount_2) / 2.0); - } - root["atmp"] = ag->round2((temp_1 + temp_2) / 2.0); - root["rhum"] = ag->round2((hum_1 + hum_2) / 2.0); - } - - if (fwMode == FW_MDOE_O_1PS || fwMode == FW_MODE_O_1PST) { - if (hasSensorPMS1) { - root["pm01"] = pm01_1; - root["pm02"] = pm25_1; - root["pm10"] = pm10_1; - if (localServer) { - root["pm003Count"] = pm03PCount_1; - } else { - root["pm003_count"] = pm03PCount_1; - } - root["atmp"] = ag->round2(temp_1); - root["rhum"] = hum_1; - } - if (hasSensorPMS2) { - root["pm01"] = pm01_2; - root["pm02"] = pm25_2; - root["pm10"] = pm10_2; - if (localServer) { - root["pm003Count"] = pm03PCount_2; - } else { - root["pm003_count"] = pm03PCount_2; - } - root["atmp"] = ag->round2(temp_2); - root["rhum"] = hum_2; - } - } else { - if (hasSensorPMS1) { - root["channels"]["1"]["pm01"] = pm01_1; - root["channels"]["1"]["pm02"] = pm25_1; - root["channels"]["1"]["pm10"] = pm10_1; - if (localServer) { - root["channels"]["1"]["pm003Count"] = pm03PCount_1; - } else { - root["channels"]["1"]["pm003_count"] = pm03PCount_1; - } - root["channels"]["1"]["atmp"] = ag->round2(temp_1); - root["channels"]["1"]["rhum"] = hum_1; - } - if (hasSensorPMS2) { - root["channels"]["2"]["pm01"] = pm01_2; - root["channels"]["2"]["pm02"] = pm25_2; - root["channels"]["2"]["pm10"] = pm10_2; - if (localServer) { - root["channels"]["2"]["pm003Count"] = pm03PCount_2; - } else { - root["channels"]["2"]["pm003_count"] = pm03PCount_2; - } - root["channels"]["2"]["atmp"] = ag->round2(temp_2); - root["channels"]["2"]["rhum"] = hum_2; - } - } - } - - if (hasSensorSGP) { - if (agValue.TVOC >= 0) { - if (localServer) { - root["tvocIndex"] = agValue.TVOC; - } else { - root["tvoc_index"] = agValue.TVOC; - } - } - if (agValue.TVOCRaw >= 0) { - root["tvoc_raw"] = agValue.TVOCRaw; - } - if (agValue.NOx >= 0) { - if (localServer) { - root["noxIndex"] = agValue.NOx; - } else { - root["nox_index"] = agValue.NOx; - } - } - if (agValue.NOxRaw >= 0) { - root["nox_raw"] = agValue.NOxRaw; - } - } - root["boot"] = bootCount; - - if (localServer) { - root["ledMode"] = localConfig.getLedBarModeName(); - root["firmwareVersion"] = ag->getVersion(); - root["fwMode"] = AgFirmwareModeName(fwMode); - } - - return JSON.stringify(root); } static void createMqttTask(void) { @@ -821,7 +306,8 @@ static void createMqttTask(void) { /** Send data */ if (mqttClient.isConnected()) { - String payload = getServerSyncData(false); + String payload = measurements.toString( + false, fwMode, wifiConnector.RSSI(), ag, &configuration); String topic = "airgradient/readings/" + ag->deviceId(); if (mqttClient.publish(topic.c_str(), payload.c_str(), @@ -840,6 +326,15 @@ static void createMqttTask(void) { } } +static void initMqtt(void) { + if (mqttClient.begin(configuration.getMqttBrokerUri())) { + Serial.println("Connect to MQTT broker successful"); + createMqttTask(); + } else { + Serial.println("Connect to MQTT broker failed"); + } +} + static void factoryConfigReset(void) { if (ag->button.getState() == ag->button.BUTTON_PRESSED) { if (factoryBtnPressTime == 0) { @@ -848,9 +343,8 @@ static void factoryConfigReset(void) { uint32_t ms = (uint32_t)(millis() - factoryBtnPressTime); if (ms >= 2000) { // Show display message: For factory keep for x seconds - // Count display. - if (ag->isOneIndoor()) { - disp.setText("Factory reset", "keep pressed", "for 8 sec"); + if (ag->isOne()) { + oledDisplay.setText("Factory reset", "keep pressed", "for 8 sec"); } else { Serial.println("Factory reset, keep pressed for 8 sec"); } @@ -858,10 +352,10 @@ static void factoryConfigReset(void) { int count = 7; while (ag->button.getState() == ag->button.BUTTON_PRESSED) { delay(1000); - if (ag->isOneIndoor()) { + if (ag->isOne()) { String str = "for " + String(count) + " sec"; - disp.setText("Factory reset", "keep pressed", str.c_str()); + oledDisplay.setText("Factory reset", "keep pressed", str.c_str()); } else { Serial.printf("Factory reset, keep pressed for %d sec\r\n", count); } @@ -878,10 +372,10 @@ static void factoryConfigReset(void) { wifiConnector.reset(); /** Reset local config */ - localConfig.reset(); + configuration.reset(); - if (ag->isOneIndoor()) { - disp.setText("Factory reset", "successful", ""); + if (ag->isOne()) { + oledDisplay.setText("Factory reset", "successful", ""); } else { Serial.println("Factory reset successful"); } @@ -892,14 +386,14 @@ static void factoryConfigReset(void) { /** Show current content cause reset ignore */ factoryBtnPressTime = 0; - if (ag->isOneIndoor()) { + if (ag->isOne()) { appDispHandler(); } } } } else { if (factoryBtnPressTime != 0) { - if (ag->isOneIndoor()) { + if (ag->isOne()) { /** Restore last display content */ appDispHandler(); } @@ -915,24 +409,31 @@ static void wdgFeedUpdate(void) { Serial.println(); } -static void sendPing() { +static void ledBarEnabledUpdate(void) { + if (ag->isOne()) { + ag->ledBar.setEnable(configuration.getLedBarMode() != LedBarModeOff); + } +} + +static void sendDataToAg() { JSONVar root; root["wifi"] = wifiConnector.RSSI(); - root["boot"] = bootCount; + root["boot"] = measurements.bootCount; - /** Change disp and led state */ - if (ag->isOneIndoor()) { - sm.displayHandle(AgStateMachineWiFiOkServerConnecting); + /** Change oledDisplay and led state */ + if (ag->isOne()) { + stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting); } - sm.ledHandle(AgStateMachineWiFiOkServerConnecting); + stateMachine.handleLeds(AgStateMachineWiFiOkServerConnecting); /** Task handle led connecting animation */ xTaskCreate( [](void *obj) { for (;;) { // ledSmHandler(); - sm.ledHandle(); - if (sm.getLedState() != AgStateMachineWiFiOkServerConnecting) { + stateMachine.handleLeds(); + if (stateMachine.getLedState() != + AgStateMachineWiFiOkServerConnecting) { break; } delay(LED_BAR_ANIMATION_PERIOD); @@ -943,18 +444,18 @@ static void sendPing() { delay(1500); if (apiClient.postToServer(JSON.stringify(root))) { - if (ag->isOneIndoor()) { - sm.displayHandle(AgStateMachineWiFiOkServerConnected); + if (ag->isOne()) { + stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected); } - sm.ledHandle(AgStateMachineWiFiOkServerConnected); + stateMachine.handleLeds(AgStateMachineWiFiOkServerConnected); } else { - if (ag->isOneIndoor()) { - sm.displayHandle(AgStateMachineWiFiOkServerConnectFailed); + if (ag->isOne()) { + stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed); } - sm.ledHandle(AgStateMachineWiFiOkServerConnectFailed); + stateMachine.handleLeds(AgStateMachineWiFiOkServerConnectFailed); } delay(DISPLAY_DELAY_SHOW_CONTENT_MS); - sm.ledHandle(AgStateMachineNormal); + stateMachine.handleLeds(AgStateMachineNormal); } /** @@ -964,19 +465,20 @@ static void resetWatchdog() { ag->watchdog.reset(); } void dispSensorNotFound(String ss) { ss = ss + " not found"; - disp.setText("Sensor init", "Error:", ss.c_str()); + oledDisplay.setText("Sensor init", "Error:", ss.c_str()); delay(2000); } static void oneIndoorInit(void) { - hasSensorPMS2 = false; + configuration.hasSensorPMS2 = false; /** Display init */ - disp.begin(); + oledDisplay.begin(); /** Show boot display */ Serial.println("Firmware Version: " + ag->getVersion()); - disp.setText("AirGradient ONE", "FW Version: ", ag->getVersion().c_str()); + oledDisplay.setText("AirGradient ONE", + "FW Version: ", ag->getVersion().c_str()); delay(DISPLAY_DELAY_SHOW_CONTENT_MS); ag->ledBar.begin(); @@ -986,34 +488,34 @@ static void oneIndoorInit(void) { /** Init sensor SGP41 */ if (ag->sgp41.begin(Wire) == false) { Serial.println("SGP41 sensor not found"); - hasSensorSGP = false; + configuration.hasSensorSGP = false; dispSensorNotFound("SGP41"); } /** INit SHT */ if (ag->sht.begin(Wire) == false) { Serial.println("SHTx sensor not found"); - hasSensorSHT = false; + configuration.hasSensorSHT = false; dispSensorNotFound("SHT"); } /** Init S8 CO2 sensor */ if (ag->s8.begin(Serial1) == false) { Serial.println("CO2 S8 sensor not found"); - hasSensorS8 = false; + configuration.hasSensorS8 = false; dispSensorNotFound("S8"); } /** Init PMS5003 */ if (ag->pms5003.begin(Serial0) == false) { Serial.println("PMS sensor not found"); - hasSensorPMS1 = false; + configuration.hasSensorPMS1 = false; dispSensorNotFound("PMS"); } /** Run LED test on start up */ - disp.setText("Press now for", "LED test &", "offline mode"); + oledDisplay.setText("Press now for", "LED test &", "offline mode"); ledBarButtonTest = false; uint32_t stime = millis(); while (true) { @@ -1029,7 +531,7 @@ static void oneIndoorInit(void) { } } static void openAirInit(void) { - hasSensorSHT = false; + configuration.hasSensorSHT = false; fwMode = FW_MODE_O_1PST; Serial.println("Firmware Version: " + ag->getVersion()); @@ -1051,7 +553,7 @@ static void openAirInit(void) { Serial.println("Can not detect S8 on Serial1, try on Serial0"); /** Check on other port */ if (ag->s8.begin(Serial0) == false) { - hasSensorS8 = false; + configuration.hasSensorS8 = false; Serial.println("CO2 S8 sensor not found"); Serial.println("Can not detect S8 run mode 'PPT'"); @@ -1069,10 +571,10 @@ static void openAirInit(void) { } if (ag->sgp41.begin(Wire) == false) { - hasSensorSGP = false; + configuration.hasSensorSGP = false; Serial.println("SGP sensor not found"); - if (hasSensorS8 == false) { + if (configuration.hasSensorS8 == false) { Serial.println("Can not detect SGP run mode 'O-1PP'"); fwMode = FW_MODE_O_1PP; } else { @@ -1086,7 +588,7 @@ static void openAirInit(void) { bool pmInitSuccess = false; if (serial0Available) { if (ag->pms5003t_1.begin(Serial0) == false) { - hasSensorPMS1 = false; + configuration.hasSensorPMS1 = false; Serial.println("PMS1 sensor not found"); } else { serial0Available = false; @@ -1097,7 +599,7 @@ static void openAirInit(void) { if (pmInitSuccess == false) { if (serial1Available) { if (ag->pms5003t_1.begin(Serial1) == false) { - hasSensorPMS1 = false; + configuration.hasSensorPMS1 = false; Serial.println("PMS1 sensor not found"); } else { serial1Available = false; @@ -1105,16 +607,16 @@ static void openAirInit(void) { } } } - hasSensorPMS2 = false; // Disable PM2 + configuration.hasSensorPMS2 = false; // Disable PM2 } else { if (ag->pms5003t_1.begin(Serial0) == false) { - hasSensorPMS1 = false; + configuration.hasSensorPMS1 = false; Serial.println("PMS1 sensor not found"); } else { Serial.println("Found PMS 1 on Serial0"); } if (ag->pms5003t_2.begin(Serial1) == false) { - hasSensorPMS2 = false; + configuration.hasSensorPMS2 = false; Serial.println("PMS2 sensor not found"); } else { Serial.println("Found PMS 2 on Serial1"); @@ -1123,7 +625,7 @@ static void openAirInit(void) { /** update the PMS poll period base on fw mode and sensor available */ if (fwMode != FW_MODE_O_1PST) { - if (hasSensorPMS1 && hasSensorPMS2) { + if (configuration.hasSensorPMS1 && configuration.hasSensorPMS2) { pmsSchedule.setPeriod(2000); } } @@ -1131,24 +633,14 @@ static void openAirInit(void) { Serial.printf("Firmware Mode: %s\r\n", AgFirmwareModeName(fwMode)); } -// todo: remove below comment -/** - * @brief Initialize board - */ static void boardInit(void) { - if (ag->isOneIndoor()) { + if (ag->isOne()) { oneIndoorInit(); } else { openAirInit(); } } -// todo: remove below comment -/** - * @brief Failed handler - * - * @param msg Failure message - */ static void failedHandler(String msg) { while (true) { Serial.println(msg); @@ -1156,187 +648,62 @@ static void failedHandler(String msg) { } } -// todo: remove below comment -/** - * @brief Send data to server - */ -static void updateServerConfiguration(void) { +static void configurationUpdateSchedule(void) { if (apiClient.fetchServerConfiguration()) { configUpdateHandle(); } } static void configUpdateHandle() { - if (localConfig.isCo2CalibrationRequested()) { - if (hasSensorS8) { - co2Calibration(); - } else { - Serial.println("CO2 S8 not available, calibration ignored"); - } + if (configuration.isUpdated() == false) { + return; } - // Update LED bar - if (ag->isOneIndoor()) { - ag->ledBar.setEnable(localConfig.getLedBarMode() != LedBarModeOff); - } + ledBarEnabledUpdate(); + stateMachine.executeCo2Calibration(); + stateMachine.executeLedBarTest(); -// todo: rename to "getCO2CalibrationAbcDays" -// todo: move code into library - if (localConfig.getCO2CalirationAbcDays() > 0) { - if (hasSensorS8) { - int newHour = localConfig.getCO2CalirationAbcDays() * 24; - Serial.printf("Requested abcDays setting: %d days (%d hours)\r\n", - localConfig.getCO2CalirationAbcDays(), newHour); - int curHour = ag->s8.getAbcPeriod(); - Serial.printf("Current S8 abcDays setting: %d (hours)\r\n", curHour); - if (curHour == newHour) { - Serial.println("'abcDays' unchanged"); - } else { - if (ag->s8.setAbcPeriod(localConfig.getCO2CalirationAbcDays() * 24) == - false) { - Serial.println("Set S8 abcDays period failed"); - } else { - Serial.println("Set S8 abcDays period success"); - } - } - } else { - Serial.println("CO2 S8 not available, set 'abcDays' ignored"); - } - } - - if (localConfig.isLedBarTestRequested()) { - if (localConfig.getCountry() == "TH") { - ledBarTest2Min(); - } else { - ledBarTest(); - } - } - - String mqttUri = localConfig.getMqttBrokerUri(); + String mqttUri = configuration.getMqttBrokerUri(); if (mqttClient.isCurrentUri(mqttUri) == false) { mqttClient.end(); - - if (mqttTask != NULL) { - vTaskDelete(mqttTask); - mqttTask = NULL; - } - if (mqttUri.length() > 0) { - if (mqttClient.begin(mqttUri)) { - Serial.println("Connect to MQTT broker successful"); - createMqttTask(); - } else { - Serial.println("Connect to MQTT broker failed"); - } - } + initMqtt(); } + + appDispHandler(); + appLedHandler(); } - -// todo: remove comment - -/** - * @brief Calibration CO2 sensor, it's base calibration, after calib complete - * the value will be start at 400 if do calib on clean environment - */ - - // todo: move method into library - // todo: rename into executeCo2Calibration() -static void co2Calibration(void) { - Serial.println("co2Calibration: Start"); - /** Count down for co2CalibCountdown secs */ - for (int i = 0; i < SENSOR_CO2_CALIB_COUNTDOWN_MAX; i++) { - if (ag->isOneIndoor()) { - String str = - "after " + String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec"; - disp.setText("Start CO2 calib", str.c_str(), ""); - } else { - Serial.printf("Start CO2 calib after %d sec\r\n", - SENSOR_CO2_CALIB_COUNTDOWN_MAX - i); - } - delay(1000); - } - - if (ag->s8.setBaselineCalibration()) { - if (ag->isOneIndoor()) { - disp.setText("Calibration", "success", ""); - } else { - Serial.println("Calibration success"); - } - delay(1000); - if (ag->isOneIndoor()) { - disp.setText("Wait for", "calib finish", "..."); - } else { - Serial.println("Wait for calibration finish..."); - } - int count = 0; - while (ag->s8.isBaseLineCalibrationDone() == false) { - delay(1000); - count++; - } - if (ag->isOneIndoor()) { - String str = "after " + String(count); - disp.setText("Calib finish", str.c_str(), "sec"); - } else { - Serial.printf("Calibration finish after %d sec\r\n", count); - } - delay(2000); - } else { - if (ag->isOneIndoor()) { - disp.setText("Calibration", "failure!!!", ""); - } else { - Serial.println("Calibration failure!!!"); - } - delay(2000); - } - - /** Update display */ - if (ag->isOneIndoor()) { - appDispHandler(); - } -} - - -// todo: remove comment -/** - * @brief APP LED color handler - */ - - static void appLedHandler(void) { AgStateMachineState state = AgStateMachineNormal; if (wifiConnector.isConnected() == false) { state = AgStateMachineWiFiLost; } else if (apiClient.isFetchConfigureFailed()) { + stateMachine.displaySetAddToDashBoard(); state = AgStateMachineSensorConfigFailed; } else if (apiClient.isPostToServerFailed()) { state = AgStateMachineServerLost; } - sm.ledHandle(state); + stateMachine.handleLeds(state); } -/** - * @brief APP display content handler - */ static void appDispHandler(void) { - AgStateMachineState state = AgStateMachineNormal; - if (wifiConnector.isConnected() == false) { - state = AgStateMachineWiFiLost; - } else if (apiClient.isFetchConfigureFailed()) { - state = AgStateMachineSensorConfigFailed; - } else if (apiClient.isPostToServerFailed()) { - state = AgStateMachineServerLost; - } + if (ag->isOne()) { + AgStateMachineState state = AgStateMachineNormal; + if (wifiConnector.isConnected() == false) { + state = AgStateMachineWiFiLost; + } else if (apiClient.isFetchConfigureFailed()) { + state = AgStateMachineSensorConfigFailed; + } else if (apiClient.isPostToServerFailed()) { + state = AgStateMachineServerLost; + } - sm.displayHandle(state); + stateMachine.displayHandle(state); + } } -/** - * @brief APP display and LED handler - * - */ -static void displayAndLedUpdate(void) { - if (ag->isOneIndoor()) { +static void oledDisplayLedBarSchedule(void) { + if (ag->isOne()) { if (factoryBtnPressTime == 0) { appDispHandler(); } @@ -1344,193 +711,190 @@ static void displayAndLedUpdate(void) { appLedHandler(); } -// todo: remove comment -/** - * @brief Update tvocIndexindex - * - */ - - // todo: rename to "updateTvoc" -static void tvocUpdate(void) { - agValue.TVOC = ag->sgp41.getTvocIndex(); - agValue.TVOCRaw = ag->sgp41.getTvocRaw(); - agValue.NOx = ag->sgp41.getNoxIndex(); - agValue.NOxRaw = ag->sgp41.getNoxRaw(); +static void updateTvoc(void) { + measurements.TVOC = ag->sgp41.getTvocIndex(); + measurements.TVOCRaw = ag->sgp41.getTvocRaw(); + measurements.NOx = ag->sgp41.getNoxIndex(); + measurements.NOxRaw = ag->sgp41.getNoxRaw(); Serial.println(); - Serial.printf("TVOC index: %d\r\n", agValue.TVOC); - Serial.printf("TVOC raw: %d\r\n", agValue.TVOCRaw); - Serial.printf("NOx index: %d\r\n", agValue.NOx); - Serial.printf("NOx raw: %d\r\n", agValue.NOxRaw); + Serial.printf("TVOC index: %d\r\n", measurements.TVOC); + Serial.printf("TVOC raw: %d\r\n", measurements.TVOCRaw); + Serial.printf("NOx index: %d\r\n", measurements.NOx); + Serial.printf("NOx raw: %d\r\n", measurements.NOxRaw); } -// todo: remove comment -/** - * @brief Update PMS data - * - */ - - // todo: rename to "updatePm" -static void pmUpdate(void) { - if (ag->isOneIndoor()) { +static void updatePm(void) { + if (ag->isOne()) { if (ag->pms5003.isFailed() == false) { - pm01_1 = ag->pms5003.getPm01Ae(); - pm25_1 = ag->pms5003.getPm25Ae(); - pm10_1 = ag->pms5003.getPm10Ae(); - pm03PCount_1 = ag->pms5003.getPm03ParticleCount(); + measurements.pm01_1 = ag->pms5003.getPm01Ae(); + measurements.pm25_1 = ag->pms5003.getPm25Ae(); + measurements.pm10_1 = ag->pms5003.getPm10Ae(); + measurements.pm03PCount_1 = ag->pms5003.getPm03ParticleCount(); Serial.println(); - Serial.printf("PM1 ug/m3: %d\r\n", pm01_1); - Serial.printf("PM2.5 ug/m3: %d\r\n", pm25_1); - Serial.printf("PM10 ug/m3: %d\r\n", pm10_1); - Serial.printf("PM0.3 Count: %d\r\n", pm03PCount_1); + Serial.printf("PM1 ug/m3: %d\r\n", measurements.pm01_1); + Serial.printf("PM2.5 ug/m3: %d\r\n", measurements.pm25_1); + Serial.printf("PM10 ug/m3: %d\r\n", measurements.pm10_1); + Serial.printf("PM0.3 Count: %d\r\n", measurements.pm03PCount_1); pmFailCount = 0; } else { pmFailCount++; Serial.printf("PMS read failed: %d\r\n", pmFailCount); if (pmFailCount >= 3) { - pm01_1 = -1; - pm25_1 = -1; - pm10_1 = -1; - pm03PCount_1 = -1; + measurements.pm01_1 = -1; + measurements.pm25_1 = -1; + measurements.pm10_1 = -1; + measurements.pm03PCount_1 = -1; } } - agValue.PM25 = pm25_1; } else { bool pmsResult_1 = false; bool pmsResult_2 = false; - if (hasSensorPMS1 && (ag->pms5003t_1.isFailed() == false)) { - pm01_1 = ag->pms5003t_1.getPm01Ae(); - pm25_1 = ag->pms5003t_1.getPm25Ae(); - pm10_1 = ag->pms5003t_1.getPm10Ae(); - pm03PCount_1 = ag->pms5003t_1.getPm03ParticleCount(); - temp_1 = ag->pms5003t_1.getTemperature(); - hum_1 = ag->pms5003t_1.getRelativeHumidity(); + if (configuration.hasSensorPMS1 && (ag->pms5003t_1.isFailed() == false)) { + measurements.pm01_1 = ag->pms5003t_1.getPm01Ae(); + measurements.pm25_1 = ag->pms5003t_1.getPm25Ae(); + measurements.pm10_1 = ag->pms5003t_1.getPm10Ae(); + measurements.pm03PCount_1 = ag->pms5003t_1.getPm03ParticleCount(); + measurements.temp_1 = ag->pms5003t_1.getTemperature(); + measurements.hum_1 = ag->pms5003t_1.getRelativeHumidity(); pmsResult_1 = true; Serial.println(); - Serial.printf("[1] PM1 ug/m3: %d\r\n", pm01_1); - Serial.printf("[1] PM2.5 ug/m3: %d\r\n", pm25_1); - Serial.printf("[1] PM10 ug/m3: %d\r\n", pm10_1); - Serial.printf("[1] PM3.0 Count: %d\r\n", pm03PCount_1); - Serial.printf("[1] Temperature in C: %0.2f\r\n", temp_1); - Serial.printf("[1] Relative Humidity: %d\r\n", hum_1); + Serial.printf("[1] PM1 ug/m3: %d\r\n", measurements.pm01_1); + Serial.printf("[1] PM2.5 ug/m3: %d\r\n", measurements.pm25_1); + Serial.printf("[1] PM10 ug/m3: %d\r\n", measurements.pm10_1); + Serial.printf("[1] PM3.0 Count: %d\r\n", measurements.pm03PCount_1); + Serial.printf("[1] Temperature in C: %0.2f\r\n", measurements.temp_1); + Serial.printf("[1] Relative Humidity: %d\r\n", measurements.hum_1); } else { - pm01_1 = -1; - pm25_1 = -1; - pm10_1 = -1; - pm03PCount_1 = -1; - temp_1 = -1001; - hum_1 = -1; + measurements.pm01_1 = -1; + measurements.pm25_1 = -1; + measurements.pm10_1 = -1; + measurements.pm03PCount_1 = -1; + measurements.temp_1 = -1001; + measurements.hum_1 = -1; } - if (hasSensorPMS2 && (ag->pms5003t_2.isFailed() == false)) { - pm01_2 = ag->pms5003t_2.getPm01Ae(); - pm25_2 = ag->pms5003t_2.getPm25Ae(); - pm10_2 = ag->pms5003t_2.getPm10Ae(); - pm03PCount_2 = ag->pms5003t_2.getPm03ParticleCount(); - temp_2 = ag->pms5003t_2.getTemperature(); - hum_2 = ag->pms5003t_2.getRelativeHumidity(); + if (configuration.hasSensorPMS2 && (ag->pms5003t_2.isFailed() == false)) { + measurements.pm01_2 = ag->pms5003t_2.getPm01Ae(); + measurements.pm25_2 = ag->pms5003t_2.getPm25Ae(); + measurements.pm10_2 = ag->pms5003t_2.getPm10Ae(); + measurements.pm03PCount_2 = ag->pms5003t_2.getPm03ParticleCount(); + measurements.temp_2 = ag->pms5003t_2.getTemperature(); + measurements.hum_2 = ag->pms5003t_2.getRelativeHumidity(); pmsResult_2 = true; Serial.println(); - Serial.printf("[2] PM1 ug/m3: %d\r\n", pm01_2); - Serial.printf("[2] PM2.5 ug/m3: %d\r\n", pm25_2); - Serial.printf("[2] PM10 ug/m3: %d\r\n", pm10_2); - Serial.printf("[2] PM3.0 Count: %d\r\n", pm03PCount_2); - Serial.printf("[2] Temperature in C: %0.2f\r\n", temp_2); - Serial.printf("[2] Relative Humidity: %d\r\n", hum_2); + Serial.printf("[2] PM1 ug/m3: %d\r\n", measurements.pm01_2); + Serial.printf("[2] PM2.5 ug/m3: %d\r\n", measurements.pm25_2); + Serial.printf("[2] PM10 ug/m3: %d\r\n", measurements.pm10_2); + Serial.printf("[2] PM3.0 Count: %d\r\n", measurements.pm03PCount_2); + Serial.printf("[2] Temperature in C: %0.2f\r\n", measurements.temp_2); + Serial.printf("[2] Relative Humidity: %d\r\n", measurements.hum_2); } else { - pm01_2 = -1; - pm25_2 = -1; - pm10_2 = -1; - pm03PCount_2 = -1; - temp_2 = -1001; - hum_2 = -1; + measurements.pm01_2 = -1; + measurements.pm25_2 = -1; + measurements.pm10_2 = -1; + measurements.pm03PCount_2 = -1; + measurements.temp_2 = -1001; + measurements.hum_2 = -1; } - if (hasSensorPMS1 && hasSensorPMS2 && pmsResult_1 && pmsResult_2) { + if (configuration.hasSensorPMS1 && configuration.hasSensorPMS2 && + pmsResult_1 && pmsResult_2) { /** Get total of PMS1*/ - pm1Value01 = pm1Value01 + pm01_1; - pm1Value25 = pm1Value25 + pm25_1; - pm1Value10 = pm1Value10 + pm10_1; - pm1PCount = pm1PCount + pm03PCount_1; - pm1temp = pm1temp + temp_1; - pm1hum = pm1hum + hum_1; + measurements.pm1Value01 = measurements.pm1Value01 + measurements.pm01_1; + measurements.pm1Value25 = measurements.pm1Value25 + measurements.pm25_1; + measurements.pm1Value10 = measurements.pm1Value10 + measurements.pm10_1; + measurements.pm1PCount = + measurements.pm1PCount + measurements.pm03PCount_1; + measurements.pm1temp = measurements.pm1temp + measurements.temp_1; + measurements.pm1hum = measurements.pm1hum + measurements.hum_1; /** Get total of PMS2 */ - pm2Value01 = pm2Value01 + pm01_2; - pm2Value25 = pm2Value25 + pm25_2; - pm2Value10 = pm2Value10 + pm10_2; - pm2PCount = pm2PCount + pm03PCount_2; - pm2temp = pm2temp + temp_2; - pm2hum = pm2hum + hum_2; + measurements.pm2Value01 = measurements.pm2Value01 + measurements.pm01_2; + measurements.pm2Value25 = measurements.pm2Value25 + measurements.pm25_2; + measurements.pm2Value10 = measurements.pm2Value10 + measurements.pm10_2; + measurements.pm2PCount = + measurements.pm2PCount + measurements.pm03PCount_2; + measurements.pm2temp = measurements.pm2temp + measurements.temp_2; + measurements.pm2hum = measurements.pm2hum + measurements.hum_2; - countPosition++; + measurements.countPosition++; /** Get average */ - if (countPosition == targetCount) { - pm01_1 = pm1Value01 / targetCount; - pm25_1 = pm1Value25 / targetCount; - pm10_1 = pm1Value10 / targetCount; - pm03PCount_1 = pm1PCount / targetCount; - temp_1 = pm1temp / targetCount; - hum_1 = pm1hum / targetCount; + if (measurements.countPosition == measurements.targetCount) { + measurements.pm01_1 = + measurements.pm1Value01 / measurements.targetCount; + measurements.pm25_1 = + measurements.pm1Value25 / measurements.targetCount; + measurements.pm10_1 = + measurements.pm1Value10 / measurements.targetCount; + measurements.pm03PCount_1 = + measurements.pm1PCount / measurements.targetCount; + measurements.temp_1 = measurements.pm1temp / measurements.targetCount; + measurements.hum_1 = measurements.pm1hum / measurements.targetCount; - pm01_2 = pm2Value01 / targetCount; - pm25_2 = pm2Value25 / targetCount; - pm10_2 = pm2Value10 / targetCount; - pm03PCount_2 = pm2PCount / targetCount; - temp_2 = pm2temp / targetCount; - hum_2 = pm2hum / targetCount; + measurements.pm01_2 = + measurements.pm2Value01 / measurements.targetCount; + measurements.pm25_2 = + measurements.pm2Value25 / measurements.targetCount; + measurements.pm10_2 = + measurements.pm2Value10 / measurements.targetCount; + measurements.pm03PCount_2 = + measurements.pm2PCount / measurements.targetCount; + measurements.temp_2 = measurements.pm2temp / measurements.targetCount; + measurements.hum_2 = measurements.pm2hum / measurements.targetCount; - countPosition = 0; + measurements.countPosition = 0; - pm1Value01 = 0; - pm1Value25 = 0; - pm1Value10 = 0; - pm1PCount = 0; - pm1temp = 0; - pm1hum = 0; - pm2Value01 = 0; - pm2Value25 = 0; - pm2Value10 = 0; - pm2PCount = 0; - pm2temp = 0; - pm2hum = 0; + measurements.pm1Value01 = 0; + measurements.pm1Value25 = 0; + measurements.pm1Value10 = 0; + measurements.pm1PCount = 0; + measurements.pm1temp = 0; + measurements.pm1hum = 0; + measurements.pm2Value01 = 0; + measurements.pm2Value25 = 0; + measurements.pm2Value10 = 0; + measurements.pm2PCount = 0; + measurements.pm2temp = 0; + measurements.pm2hum = 0; } } if (pmsResult_1 && pmsResult_2) { - agValue.Temperature = (temp_1 + temp_2) / 2; - agValue.Humidity = (hum_1 + hum_2) / 2; + measurements.Temperature = + (measurements.temp_1 + measurements.temp_2) / 2; + measurements.Humidity = (measurements.hum_1 + measurements.hum_2) / 2; } else { if (pmsResult_1) { - agValue.Temperature = temp_1; - agValue.Humidity = hum_1; + measurements.Temperature = measurements.temp_1; + measurements.Humidity = measurements.hum_1; } if (pmsResult_2) { - agValue.Temperature = temp_2; - agValue.Humidity = hum_2; + measurements.Temperature = measurements.temp_2; + measurements.Humidity = measurements.hum_2; } } - if (hasSensorSGP) { + if (configuration.hasSensorSGP) { float temp; float hum; if (pmsResult_1 && pmsResult_2) { - temp = (temp_1 + temp_2) / 2.0f; - hum = (hum_1 + hum_2) / 2.0f; + temp = (measurements.temp_1 + measurements.temp_2) / 2.0f; + hum = (measurements.hum_1 + measurements.hum_2) / 2.0f; } else { if (pmsResult_1) { - temp = temp_1; - hum = hum_1; + temp = measurements.temp_1; + hum = measurements.hum_1; } if (pmsResult_2) { - temp = temp_2; - hum = hum_2; + temp = measurements.temp_2; + hum = measurements.hum_2; } } ag->sgp41.setCompensationTemperatureHumidity(temp, hum); @@ -1538,36 +902,28 @@ static void pmUpdate(void) { } } -// todo: remove comment -/** - * @brief Send data to server - * - */ static void sendDataToServer(void) { - String syncData = getServerSyncData(false); + String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), + ag, &configuration); if (apiClient.postToServer(syncData)) { resetWatchdog(); } - bootCount++; + measurements.bootCount++; } -// todo: remove comment -/** - * @brief Update temperature and humidity value - */ static void tempHumUpdate(void) { if (ag->sht.measure()) { - agValue.Temperature = ag->sht.getTemperature(); - agValue.Humidity = ag->sht.getRelativeHumidity(); + measurements.Temperature = ag->sht.getTemperature(); + measurements.Humidity = ag->sht.getRelativeHumidity(); - Serial.printf("Temperature in C: %0.2f\r\n", agValue.Temperature); - Serial.printf("Relative Humidity: %d\r\n", agValue.Humidity); + Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature); + Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity); // Update compensation temperature and humidity for SGP41 - if (hasSensorSGP) { - ag->sgp41.setCompensationTemperatureHumidity(agValue.Temperature, - agValue.Humidity); + if (configuration.hasSensorSGP) { + ag->sgp41.setCompensationTemperatureHumidity(measurements.Temperature, + measurements.Humidity); } } else { Serial.println("SHT read failed"); diff --git a/examples/OneOpenAir/OpenMetrics.cpp b/examples/OneOpenAir/OpenMetrics.cpp new file mode 100644 index 0000000..e5eaa8d --- /dev/null +++ b/examples/OneOpenAir/OpenMetrics.cpp @@ -0,0 +1,186 @@ +#include "OpenMetrics.h" + +OpenMetrics::OpenMetrics(Measurements &measure, Configuration &config, + WifiConnector &wifiConnector, AgApiClient &apiClient) + : measure(measure), config(config), wifiConnector(wifiConnector), + apiClient(apiClient) {} + +OpenMetrics::~OpenMetrics() {} + +void OpenMetrics::setAirGradient(AirGradient *ag) { this->ag = ag; } + +const char *OpenMetrics::getApiContentType(void) { + return "application/openmetrics-text; version=1.0.0; charset=utf-8"; +} + +const char *OpenMetrics::getApi(void) { return "/metrics"; } + +String OpenMetrics::getPayload(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=\"" + ag->deviceId() + + "\",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("", apiClient.isFetchConfigureFailed() ? "0" : "1"); + + add_metric( + "post_ok", + "1 if the AirGradient device was able to successfully send to the server", + "gauge"); + add_metric_point("", apiClient.isPostToServerFailed() ? "0" : "1"); + + add_metric( + "wifi_rssi", + "WiFi signal strength from the AirGradient device perspective, in dBm", + "gauge", "dbm"); + add_metric_point("", String(wifiConnector.RSSI())); + + if (config.hasSensorS8 && measure.CO2 >= 0) { + add_metric("co2", + "Carbon dioxide concentration as measured by the AirGradient S8 " + "sensor, in parts per million", + "gauge", "ppm"); + add_metric_point("", String(measure.CO2)); + } + + float _temp = -1001; + float _hum = -1; + int pm01 = -1; + int pm25 = -1; + int pm10 = -1; + int pm03PCount = -1; + if (config.hasSensorPMS1 && config.hasSensorPMS2) { + _temp = (measure.temp_1 + measure.temp_2) / 2.0f; + _hum = (measure.hum_1 + measure.hum_2) / 2.0f; + pm01 = (measure.pm01_1 + measure.pm01_2) / 2; + pm25 = (measure.pm25_1 + measure.pm25_2) / 2; + pm10 = (measure.pm10_1 + measure.pm10_2) / 2; + pm03PCount = (measure.pm03PCount_1 + measure.pm03PCount_2) / 2; + } else { + if (ag->isOne()) { + if (config.hasSensorSHT) { + _temp = measure.Temperature; + _hum = measure.Humidity; + } + } else { + if (config.hasSensorPMS1) { + _temp = measure.temp_1; + _hum = measure.hum_1; + pm01 = measure.pm01_1; + pm25 = measure.pm25_1; + pm10 = measure.pm10_1; + pm03PCount = measure.pm03PCount_1; + } + if (config.hasSensorPMS2) { + _temp = measure.temp_2; + _hum = measure.hum_2; + pm01 = measure.pm01_2; + pm25 = measure.pm25_2; + pm10 = measure.pm10_2; + pm03PCount = measure.pm03PCount_2; + } + } + } + + if (config.hasSensorPMS1 || config.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 (config.hasSensorSGP) { + if (measure.TVOC >= 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(measure.TVOC)); + } + if (measure.TVOCRaw >= 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(measure.TVOCRaw)); + } + if (measure.NOx >= 0) { + add_metric("nox_index", + "The processed Nitrous Oxide (NOx) index as measured by the " + "AirGradient SGP sensor", + "gauge"); + add_metric_point("", String(measure.NOx)); + } + if (measure.NOxRaw >= 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(measure.NOxRaw)); + } + } + + if (_temp > -1001) { + add_metric("temperature", + "The ambient temperature as measured by the AirGradient SHT " + "sensor, in degrees Celsius", + "gauge", "celsius"); + 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"; + return response; +} diff --git a/examples/OneOpenAir/OpenMetrics.h b/examples/OneOpenAir/OpenMetrics.h new file mode 100644 index 0000000..5df2349 --- /dev/null +++ b/examples/OneOpenAir/OpenMetrics.h @@ -0,0 +1,28 @@ +#ifndef _OPEN_METRICS_H_ +#define _OPEN_METRICS_H_ + +#include "AgConfigure.h" +#include "AgValue.h" +#include "AgWiFiConnector.h" +#include "AirGradient.h" +#include "AgApiClient.h" + +class OpenMetrics{ +private: + AirGradient *ag; + Measurements &measure; + Configuration &config; + WifiConnector &wifiConnector; + AgApiClient &apiClient; + +public: + OpenMetrics(Measurements &measure, Configuration &conig, + WifiConnector &wifiConnector, AgApiClient& apiClient); + ~OpenMetrics(); + void setAirGradient(AirGradient *ag); + const char *getApiContentType(void); + const char* getApi(void); + String getPayload(void); +}; + +#endif /** _OPEN_METRICS_H_ */ diff --git a/src/AgApiClient.cpp b/src/AgApiClient.cpp index 6ba54cc..f260b7e 100644 --- a/src/AgApiClient.cpp +++ b/src/AgApiClient.cpp @@ -9,7 +9,7 @@ #include #endif -AgApiClient::AgApiClient(Stream &debug, AgConfigure &config) +AgApiClient::AgApiClient(Stream &debug, Configuration &config) : PrintLog(debug, "ApiClient"), config(config) {} AgApiClient::~AgApiClient() {} diff --git a/src/AgApiClient.h b/src/AgApiClient.h index a9edd99..aafca0c 100644 --- a/src/AgApiClient.h +++ b/src/AgApiClient.h @@ -18,14 +18,14 @@ class AgApiClient : public PrintLog { private: - AgConfigure &config; + Configuration &config; AirGradient *ag; bool getConfigFailed; bool postToServerFailed; public: - AgApiClient(Stream &stream, AgConfigure &config); + AgApiClient(Stream &stream, Configuration &config); ~AgApiClient(); void begin(void); diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp index 5b0c762..117ccff 100644 --- a/src/AgConfigure.cpp +++ b/src/AgConfigure.cpp @@ -14,11 +14,11 @@ const char *LED_BAR_MODE_NAMES[] = { /** * @brief Get LedBarMode Name - * + * * @param mode LedBarMode value * @return String */ -String AgConfigure::getLedBarModeName(LedBarMode mode) { +String Configuration::getLedBarModeName(LedBarMode mode) { if (mode == LedBarModeOff) { return String(LED_BAR_MODE_NAMES[LedBarModeOff]); } else if (mode == LedBarModePm) { @@ -31,9 +31,9 @@ String AgConfigure::getLedBarModeName(LedBarMode mode) { /** * @brief Save configure to device storage (EEPROM) - * + * */ -void AgConfigure::saveConfig(void) { +void Configuration::saveConfig(void) { config._check = 0; int len = sizeof(config) - sizeof(config._check); uint8_t *data = (uint8_t *)&config; @@ -51,7 +51,7 @@ void AgConfigure::saveConfig(void) { logInfo("Save Config"); } -void AgConfigure::loadConfig(void) { +void Configuration::loadConfig(void) { bool readSuccess = false; #ifdef ESP8266 uint8_t *data = (uint8_t *)&config; @@ -85,9 +85,9 @@ void AgConfigure::loadConfig(void) { /** * @brief Set configuration default - * + * */ -void AgConfigure::defaultConfig(void) { +void Configuration::defaultConfig(void) { // Default country is null memset(config.country, 0, sizeof(config.country)); // Default MQTT broker is null. @@ -109,30 +109,31 @@ void AgConfigure::defaultConfig(void) { /** * @brief Show configuration as JSON string message over log - * + * */ -void AgConfigure::printConfig(void) { logInfo(toString().c_str()); } +void Configuration::printConfig(void) { logInfo(toString().c_str()); } /** * @brief Construct a new Ag Configure:: Ag Configure object - * + * * @param debugLog Serial Stream */ -AgConfigure::AgConfigure(Stream &debugLog) : PrintLog(debugLog, "Configure") {} +Configuration::Configuration(Stream &debugLog) + : PrintLog(debugLog, "Configure") {} /** * @brief Destroy the Ag Configure:: Ag Configure object - * + * */ -AgConfigure::~AgConfigure() {} +Configuration::~Configuration() {} /** * @brief Initialize configuration - * + * * @return true Success * @return false Failure */ -bool AgConfigure::begin(void) { +bool Configuration::begin(void) { EEPROM.begin(512); loadConfig(); printConfig(); @@ -149,7 +150,7 @@ bool AgConfigure::begin(void) { * @return true Success * @return false Failure */ -bool AgConfigure::parse(String data, bool isLocal) { +bool Configuration::parse(String data, bool isLocal) { JSONVar root = JSON.parse(data); if (JSON.typeof_(root) == "undefined") { logError("Configuration JSON invalid"); @@ -392,15 +393,16 @@ bool AgConfigure::parse(String data, bool isLocal) { } printConfig(); + udpated = true; return true; } /** * @brief Get current configuration value as JSON string - * - * @return String + * + * @return String */ -String AgConfigure::toString(void) { +String Configuration::toString(void) { JSONVar root; /** "country" */ @@ -449,85 +451,87 @@ String AgConfigure::toString(void) { /** * @brief Temperature unit (F or C) - * + * * @return true F * @return false C */ -bool AgConfigure::isTemperatureUnitInF(void) { +bool Configuration::isTemperatureUnitInF(void) { return (config.temperatureUnit == 'f'); } /** * @brief Country name, it's short name ex: TH = Thailand - * - * @return String + * + * @return String */ -String AgConfigure::getCountry(void) { return String(config.country); } +String Configuration::getCountry(void) { return String(config.country); } /** * @brief PM unit standard (USAQI, ugm3) - * + * * @return true USAQI * @return false ugm3 */ -bool AgConfigure::isPmStandardInUSAQI(void) { return config.inUSAQI; } +bool Configuration::isPmStandardInUSAQI(void) { return config.inUSAQI; } /** * @brief Get CO2 calibration ABC time - * + * * @return int Number of day */ -int AgConfigure::getCO2CalirationAbcDays(void) { return config.abcDays; } +int Configuration::getCO2CalibrationAbcDays(void) { return config.abcDays; } /** * @brief Get Led Bar Mode - * - * @return LedBarMode + * + * @return LedBarMode */ -LedBarMode AgConfigure::getLedBarMode(void) { +LedBarMode Configuration::getLedBarMode(void) { return (LedBarMode)config.useRGBLedBar; } /** * @brief Get LED bar mode name - * - * @return String + * + * @return String */ -String AgConfigure::getLedBarModeName(void) { +String Configuration::getLedBarModeName(void) { return getLedBarModeName((LedBarMode)config.useRGBLedBar); } /** * @brief Get display mode - * + * * @return true On * @return false Off */ -bool AgConfigure::getDisplayMode(void) { return config.displayMode; } +bool Configuration::getDisplayMode(void) { return config.displayMode; } /** * @brief Get MQTT uri - * - * @return String + * + * @return String */ -String AgConfigure::getMqttBrokerUri(void) { return String(config.mqttBroker); } +String Configuration::getMqttBrokerUri(void) { + return String(config.mqttBroker); +} /** * @brief Get configuratoin post data to AirGradient cloud - * + * * @return true Post * @return false No-Post */ -bool AgConfigure::isPostDataToAirGradient(void) { +bool Configuration::isPostDataToAirGradient(void) { return config.postDataToAirGradient; } /** * @brief Get current configuration control - * - * @return ConfigurationControl + * + * @return ConfigurationControl */ -ConfigurationControl AgConfigure::getConfigurationControl(void) { +ConfigurationControl Configuration::getConfigurationControl(void) { return (ConfigurationControl)config.configurationControl; } @@ -538,7 +542,7 @@ ConfigurationControl AgConfigure::getConfigurationControl(void) { * @return true Requested * @return false Not requested */ -bool AgConfigure::isCo2CalibrationRequested(void) { +bool Configuration::isCo2CalibrationRequested(void) { bool requested = co2CalibrationRequested; co2CalibrationRequested = false; // clear requested return requested; @@ -551,7 +555,7 @@ bool AgConfigure::isCo2CalibrationRequested(void) { * @return true Requested * @return false Not requested */ -bool AgConfigure::isLedBarTestRequested(void) { +bool Configuration::isLedBarTestRequested(void) { bool requested = ledBarTestRequested; ledBarTestRequested = false; return requested; @@ -560,7 +564,7 @@ bool AgConfigure::isLedBarTestRequested(void) { /** * @brief Reset default configure */ -void AgConfigure::reset(void) { +void Configuration::reset(void) { defaultConfig(); logInfo("Reset to default configure"); printConfig(); @@ -571,4 +575,10 @@ void AgConfigure::reset(void) { * * @return String */ -String AgConfigure::getModel(void) { return String(config.model); } +String Configuration::getModel(void) { return String(config.model); } + +bool Configuration::isUpdated(void) { + bool updated = this->udpated; + this->udpated = false; + return updated; +} diff --git a/src/AgConfigure.h b/src/AgConfigure.h index 04688c9..5ed4b4c 100644 --- a/src/AgConfigure.h +++ b/src/AgConfigure.h @@ -6,7 +6,7 @@ #include #include -class AgConfigure : public PrintLog { +class Configuration : public PrintLog { private: struct Config { char model[20]; @@ -32,6 +32,7 @@ private: struct Config config; bool co2CalibrationRequested; bool ledBarTestRequested; + bool udpated; String getLedBarModeName(LedBarMode mode); void saveConfig(void); @@ -40,8 +41,14 @@ private: void printConfig(void); public: - AgConfigure(Stream &debugLog); - ~AgConfigure(); + Configuration(Stream &debugLog); + ~Configuration(); + + bool hasSensorS8 = true; + bool hasSensorPMS1 = true; + bool hasSensorPMS2 = true; + bool hasSensorSGP = true; + bool hasSensorSHT = true; bool begin(void); bool parse(String data, bool isLocal); @@ -49,7 +56,7 @@ public: bool isTemperatureUnitInF(void); String getCountry(void); bool isPmStandardInUSAQI(void); - int getCO2CalirationAbcDays(void); + int getCO2CalibrationAbcDays(void); LedBarMode getLedBarMode(void); String getLedBarModeName(void); bool getDisplayMode(void); @@ -60,6 +67,7 @@ public: bool isLedBarTestRequested(void); void reset(void); String getModel(void); + bool isUpdated(void); }; #endif /** _AG_CONFIG_H_ */ diff --git a/src/AgOledDisplay.cpp b/src/AgOledDisplay.cpp index a123f13..db6c6fb 100644 --- a/src/AgOledDisplay.cpp +++ b/src/AgOledDisplay.cpp @@ -9,7 +9,7 @@ * * @param hasStatus */ -void AgOledDisplay::showTempHum(bool hasStatus) { +void OledDisplay::showTempHum(bool hasStatus) { char buf[10]; if (value.Temperature > -1001) { if (config.isTemperatureUnitInF()) { @@ -53,20 +53,20 @@ void AgOledDisplay::showTempHum(bool hasStatus) { * @brief Construct a new Ag Oled Display:: Ag Oled Display object * * @param config AgConfiguration - * @param value AgValue + * @param value Measurements * @param log Serial Stream */ -AgOledDisplay::AgOledDisplay(AgConfigure &config, AgValue &value, Stream &log) - : PrintLog(log, "AgOledDisplay"), config(config), value(value) {} +OledDisplay::OledDisplay(Configuration &config, Measurements &value, Stream &log) + : PrintLog(log, "OledDisplay"), config(config), value(value) {} /** * @brief Set AirGradient instance * * @param ag Point to AirGradient instance */ -void AgOledDisplay::setAirGradient(AirGradient *ag) { this->ag = ag; } +void OledDisplay::setAirGradient(AirGradient *ag) { this->ag = ag; } -AgOledDisplay::~AgOledDisplay() {} +OledDisplay::~OledDisplay() {} /** * @brief Initialize display @@ -74,7 +74,7 @@ AgOledDisplay::~AgOledDisplay() {} * @return true Success * @return false Failure */ -bool AgOledDisplay::begin(void) { +bool OledDisplay::begin(void) { if (isBegin) { logWarning("Already begin, call 'end' and try again"); return true; @@ -102,14 +102,14 @@ bool AgOledDisplay::begin(void) { * @brief De-Initialize display * */ -void AgOledDisplay::end(void) { +void OledDisplay::end(void) { if (!isBegin) { logWarning("Already end, call 'begin' and try again"); return; } /** Free u8g2 */ - delete u8g2; + delete DISP(); u8g2 = NULL; isBegin = false; @@ -123,7 +123,7 @@ void AgOledDisplay::end(void) { * @param line2 * @param line3 */ -void AgOledDisplay::setText(String &line1, String &line2, String &line3) { +void OledDisplay::setText(String &line1, String &line2, String &line3) { setText(line1.c_str(), line2.c_str(), line3.c_str()); } @@ -134,7 +134,7 @@ void AgOledDisplay::setText(String &line1, String &line2, String &line3) { * @param line2 * @param line3 */ -void AgOledDisplay::setText(const char *line1, const char *line2, +void OledDisplay::setText(const char *line1, const char *line2, const char *line3) { DISP()->firstPage(); do { @@ -153,7 +153,7 @@ void AgOledDisplay::setText(const char *line1, const char *line2, * @param line3 * @param line4 */ -void AgOledDisplay::setText(String &line1, String &line2, String &line3, +void OledDisplay::setText(String &line1, String &line2, String &line3, String &line4) { setText(line1.c_str(), line2.c_str(), line3.c_str(), line4.c_str()); } @@ -166,7 +166,7 @@ void AgOledDisplay::setText(String &line1, String &line2, String &line3, * @param line3 * @param line4 */ -void AgOledDisplay::setText(const char *line1, const char *line2, +void OledDisplay::setText(const char *line1, const char *line2, const char *line3, const char *line4) { DISP()->firstPage(); do { @@ -182,13 +182,13 @@ void AgOledDisplay::setText(const char *line1, const char *line2, * @brief Update dashboard content * */ -void AgOledDisplay::showDashboard(void) { showDashboard(NULL); } +void OledDisplay::showDashboard(void) { showDashboard(NULL); } /** * @brief Update dashboard content and error status * */ -void AgOledDisplay::showDashboard(const char *status) { +void OledDisplay::showDashboard(const char *status) { char strBuf[10]; DISP()->firstPage(); @@ -244,8 +244,8 @@ void AgOledDisplay::showDashboard(const char *status) { /** Draw PM2.5 value */ DISP()->setFont(u8g2_font_t0_22b_tf); if (config.isPmStandardInUSAQI()) { - if (value.PM25 >= 0) { - sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(value.PM25)); + if (value.pm25_1 >= 0) { + sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(value.pm25_1)); } else { sprintf(strBuf, "%s", "-"); } @@ -253,8 +253,8 @@ void AgOledDisplay::showDashboard(const char *status) { DISP()->setFont(u8g2_font_t0_12_tf); DISP()->drawUTF8(48, 61, "AQI"); } else { - if (value.PM25 >= 0) { - sprintf(strBuf, "%d", value.PM25); + if (value.pm25_1 >= 0) { + sprintf(strBuf, "%d", value.pm25_1); } else { sprintf(strBuf, "%s", "-"); } diff --git a/src/AgOledDisplay.h b/src/AgOledDisplay.h index a5bf54a..7def823 100644 --- a/src/AgOledDisplay.h +++ b/src/AgOledDisplay.h @@ -7,19 +7,19 @@ #include "Main/PrintLog.h" #include -class AgOledDisplay : public PrintLog { +class OledDisplay : public PrintLog { private: - AgConfigure &config; + Configuration &config; AirGradient *ag; bool isBegin = false; void *u8g2 = NULL; - AgValue &value; + Measurements &value; void showTempHum(bool hasStatus); public: - AgOledDisplay(AgConfigure &config, AgValue &value, + OledDisplay(Configuration &config, Measurements &value, Stream &log); - ~AgOledDisplay(); + ~OledDisplay(); void setAirGradient(AirGradient *ag); bool begin(void); diff --git a/src/AgStateMachine.cpp b/src/AgStateMachine.cpp index 65b3e48..b45c415 100644 --- a/src/AgStateMachine.cpp +++ b/src/AgStateMachine.cpp @@ -5,14 +5,16 @@ #define LED_SHORT_BLINK_DELAY 500 /** ms */ #define LED_LONG_BLINK_DELAY 2000 /** ms */ +#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */ + /** * @brief Animation LED bar with color - * - * @param r - * @param g - * @param b + * + * @param r + * @param g + * @param b */ -void AgStateMachine::ledBarSingleLedAnimation(uint8_t r, uint8_t g, uint8_t b) { +void StateMachine::ledBarSingleLedAnimation(uint8_t r, uint8_t g, uint8_t b) { if (ledBarAnimationCount < 0) { ledBarAnimationCount = 0; ag->ledBar.setColor(r, g, b, ledBarAnimationCount); @@ -27,10 +29,10 @@ void AgStateMachine::ledBarSingleLedAnimation(uint8_t r, uint8_t g, uint8_t b) { /** * @brief LED status blink with delay - * + * * @param ms Miliseconds */ -void AgStateMachine::ledStatusBlinkDelay(uint32_t ms) { +void StateMachine::ledStatusBlinkDelay(uint32_t ms) { ag->statusLed.setOn(); delay(ms); ag->statusLed.setOff(); @@ -39,15 +41,15 @@ void AgStateMachine::ledStatusBlinkDelay(uint32_t ms) { /** * @brief Led bar show led color status - * + * */ -void AgStateMachine::sensorLedHandle(void) { +void StateMachine::sensorhandleLeds(void) { switch (config.getLedBarMode()) { case LedBarMode::LedBarModeCO2: - co2LedHandle(); + co2handleLeds(); break; case LedBarMode::LedBarModePm: - pm25LedHandle(); + pm25handleLeds(); break; default: ag->ledBar.clear(); @@ -57,9 +59,9 @@ void AgStateMachine::sensorLedHandle(void) { /** * @brief Show CO2 LED status - * + * */ -void AgStateMachine::co2LedHandle(void) { +void StateMachine::co2handleLeds(void) { int co2Value = value.CO2; if (co2Value <= 400) { /** G; 1 */ @@ -140,10 +142,10 @@ void AgStateMachine::co2LedHandle(void) { /** * @brief Show PM2.5 LED status - * + * */ -void AgStateMachine::pm25LedHandle(void) { - int pm25Value = value.PM25; +void StateMachine::pm25handleLeds(void) { + int pm25Value = value.pm25_1; if (pm25Value <= 5) { /** G; 1 */ ag->ledBar.setColor(0, 255, 0, ag->ledBar.getNumberOfLeds() - 1); @@ -221,29 +223,182 @@ void AgStateMachine::pm25LedHandle(void) { } } +void StateMachine::co2Calibration(void) { + if (config.isCo2CalibrationRequested() && config.hasSensorS8) { + logInfo("CO2 Calibration"); + + /** Count down to 0 then start */ + for (int i = 0; i < SENSOR_CO2_CALIB_COUNTDOWN_MAX; i++) { + if (ag->isOne()) { + String str = + "after " + String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec"; + disp.setText("Start CO2 calib", str.c_str(), ""); + } else { + logInfo("Start CO2 calib after " + + String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec"); + } + delay(1000); + } + + if (ag->s8.setBaselineCalibration()) { + if (ag->isOne()) { + disp.setText("Calibration", "success", ""); + } else { + logInfo("CO2 Calibration: success"); + } + delay(1000); + if (ag->isOne()) { + disp.setText("Wait for", "calib finish", "..."); + } else { + logInfo("CO2 Calibration: Wait for calibration finish..."); + } + + /** Count down wait for finish */ + int count = 0; + while (ag->s8.isBaseLineCalibrationDone() == false) { + delay(1000); + count++; + } + if (ag->isOne()) { + String str = "after " + String(count); + disp.setText("Calib finish", str.c_str(), "sec"); + } else { + logInfo("CO2 Calibration: finish after " + String(count) + " sec"); + } + delay(2000); + } else { + if (ag->isOne()) { + disp.setText("Calibration", "failure!!!", ""); + } else { + logInfo("CO2 Calibration: failure!!!"); + } + delay(2000); + } + } + + if (config.getCO2CalibrationAbcDays() > 0 && config.hasSensorS8) { + int newHour = config.getCO2CalibrationAbcDays() * 24; + logInfo("Requested abcDays setting: " + + String(config.getCO2CalibrationAbcDays()) + "days (" + + String(newHour) + "hours)"); + int curHour = ag->s8.getAbcPeriod(); + logInfo("Current S8 abcDays setting: " + String(curHour) + "(hours)"); + if (curHour == newHour) { + logInfo("'abcDays' unchanged"); + } else { + if (ag->s8.setAbcPeriod(config.getCO2CalibrationAbcDays() * 24) == + false) { + logError("Set S8 abcDays period failed"); + } else { + logInfo("Set S8 abcDays period success"); + } + } + } else { + logWarning("CO2 S8 not available, set 'abcDays' ignored"); + } +} + +void StateMachine::ledBarTest(void) { + if (config.isLedBarTestRequested()) { + if (config.getCountry() == "TH") { + uint32_t tstart = millis(); + logInfo("Start run LED test for 2 min"); + while (1) { + ledBarRunTest(); + uint32_t ms = (uint32_t)(millis() - tstart); + if (ms >= (60 * 1000 * 2)) { + logInfo("LED test after 2 min finish"); + break; + } + } + } else { + ledBarRunTest(); + } + } +} + +void StateMachine::ledBarRunTest(void) { + disp.setText("LED Test", "running", "....."); + runLedTest('r'); + ag->ledBar.show(); + delay(1000); + runLedTest('g'); + ag->ledBar.show(); + delay(1000); + runLedTest('b'); + ag->ledBar.show(); + delay(1000); + runLedTest('w'); + ag->ledBar.show(); + delay(1000); + runLedTest('n'); + ag->ledBar.show(); + delay(1000); +} + +void StateMachine::runLedTest(char color) { + int r = 0; + int g = 0; + int b = 0; + switch (color) { + case 'g': + g = 255; + break; + case 'y': + r = 255; + g = 255; + break; + case 'o': + r = 255; + g = 128; + break; + case 'r': + r = 255; + break; + case 'b': + b = 255; + break; + case 'w': + r = 255; + g = 255; + b = 255; + break; + case 'p': + r = 153; + b = 153; + break; + case 'z': + r = 102; + break; + case 'n': + default: + break; + } + ag->ledBar.setColor(r, g, b); +} + /** * @brief Construct a new Ag State Machine:: Ag State Machine object - * - * @param disp AgOledDisplay + * + * @param disp OledDisplay * @param log Serial Stream - * @param value AgValue - * @param config AgConfigure + * @param value Measurements + * @param config Configuration */ -AgStateMachine::AgStateMachine(AgOledDisplay &disp, Stream &log, AgValue &value, - AgConfigure &config) - : PrintLog(log, "AgStateMachine"), disp(disp), value(value), - config(config) {} +StateMachine::StateMachine(OledDisplay &disp, Stream &log, Measurements &value, + Configuration &config) + : PrintLog(log, "StateMachine"), disp(disp), value(value), config(config) {} -AgStateMachine::~AgStateMachine() {} +StateMachine::~StateMachine() {} /** * @brief OLED display show content from state value - * - * @param state + * + * @param state */ -void AgStateMachine::displayHandle(AgStateMachineState state) { +void StateMachine::displayHandle(AgStateMachineState state) { // Ignore handle if not ONE_INDOOR board - if (!ag->isOneIndoor()) { + if (!ag->isOne()) { return; } @@ -320,6 +475,9 @@ void AgStateMachine::displayHandle(AgStateMachineState state) { disp.showDashboard(); break; } + case AgStateMachineCo2Calibration: + co2Calibration(); + break; default: break; } @@ -327,51 +485,49 @@ void AgStateMachine::displayHandle(AgStateMachineState state) { /** * @brief OLED display show content as previous state updated - * + * */ -void AgStateMachine::displayHandle(void) { displayHandle(dispState); } +void StateMachine::displayHandle(void) { displayHandle(dispState); } /** * @brief Update status add to dashboard - * + * */ -void AgStateMachine::displaySetAddToDashBoard(void) { +void StateMachine::displaySetAddToDashBoard(void) { addToDashBoard = true; addToDashboardTime = millis(); } -void AgStateMachine::displayClearAddToDashBoard(void) { - addToDashBoard = false; -} +void StateMachine::displayClearAddToDashBoard(void) { addToDashBoard = false; } /** * @brief Set WiFi connection coundown on dashboard - * + * * @param count Seconds */ -void AgStateMachine::displayWiFiConnectCountDown(int count) { +void StateMachine::displayWiFiConnectCountDown(int count) { wifiConnectCountDown = count; } /** * @brief Init before start LED bar animation - * + * */ -void AgStateMachine::ledAnimationInit(void) { ledBarAnimationCount = -1; } +void StateMachine::ledAnimationInit(void) { ledBarAnimationCount = -1; } /** * @brief Handle LED from state - * - * @param state + * + * @param state */ -void AgStateMachine::ledHandle(AgStateMachineState state) { +void StateMachine::handleLeds(AgStateMachineState state) { if (state > AgStateMachineNormal) { logError("ledHandle: state invalid"); return; } ledState = state; - if (ag->isOneIndoor()) { + if (ag->isOne()) { ag->ledBar.clear(); // Set all LED OFF } switch (state) { @@ -379,7 +535,7 @@ void AgStateMachine::ledHandle(AgStateMachineState state) { /** In WiFi Manager Mode */ /** Turn LED OFF */ /** Turn midle LED Color */ - if (ag->isOneIndoor()) { + if (ag->isOne()) { ag->ledBar.setColor(0, 0, 255, ag->ledBar.getNumberOfLeds() / 2); } else { ag->statusLed.setToggle(); @@ -388,7 +544,7 @@ void AgStateMachine::ledHandle(AgStateMachineState state) { } case AgStateMachineWiFiManagerPortalActive: { /** WiFi Manager has connected to mobile phone */ - if (ag->isOneIndoor()) { + if (ag->isOne()) { ag->ledBar.setColor(0, 0, 255); } else { ag->statusLed.setOn(); @@ -398,7 +554,7 @@ void AgStateMachine::ledHandle(AgStateMachineState state) { case AgStateMachineWiFiManagerStaConnecting: { /** after SSID and PW entered and OK clicked, connection to WiFI network is * attempted */ - if (ag->isOneIndoor()) { + if (ag->isOne()) { ledBarSingleLedAnimation(255, 255, 255); } else { ag->statusLed.setOff(); @@ -407,7 +563,7 @@ void AgStateMachine::ledHandle(AgStateMachineState state) { } case AgStateMachineWiFiManagerStaConnected: { /** Connecting to WiFi worked */ - if (ag->isOneIndoor()) { + if (ag->isOne()) { ag->ledBar.setColor(255, 255, 255); } else { ag->statusLed.setOff(); @@ -416,7 +572,7 @@ void AgStateMachine::ledHandle(AgStateMachineState state) { } case AgStateMachineWiFiOkServerConnecting: { /** once connected to WiFi an attempt to reach the server is performed */ - if (ag->isOneIndoor()) { + if (ag->isOne()) { ledBarSingleLedAnimation(0, 255, 0); } else { ag->statusLed.setOff(); @@ -425,7 +581,7 @@ void AgStateMachine::ledHandle(AgStateMachineState state) { } case AgStateMachineWiFiOkServerConnected: { /** Server is reachable, all fine */ - if (ag->isOneIndoor()) { + if (ag->isOne()) { ag->ledBar.setColor(0, 255, 0); } else { ag->statusLed.setOff(); @@ -441,7 +597,7 @@ void AgStateMachine::ledHandle(AgStateMachineState state) { } case AgStateMachineWiFiManagerConnectFailed: { /** Cannot connect to WiFi (e.g. wrong password, WPA Enterprise etc.) */ - if (ag->isOneIndoor()) { + if (ag->isOne()) { ag->ledBar.setColor(255, 0, 0); } else { ag->statusLed.setOff(); @@ -459,7 +615,7 @@ void AgStateMachine::ledHandle(AgStateMachineState state) { case AgStateMachineWiFiOkServerConnectFailed: { /** Connected to WiFi but server not reachable, e.g. firewall block/ * whitelisting needed etc. */ - if (ag->isOneIndoor()) { + if (ag->isOne()) { ag->ledBar.setColor(233, 183, 54); /** orange */ } else { ag->statusLed.setOff(); @@ -475,7 +631,7 @@ void AgStateMachine::ledHandle(AgStateMachineState state) { } case AgStateMachineWiFiOkServerOkSensorConfigFailed: { /** Server reachable but sensor not configured correctly */ - if (ag->isOneIndoor()) { + if (ag->isOne()) { ag->ledBar.setColor(139, 24, 248); /** violet */ } else { ag->statusLed.setOff(); @@ -492,12 +648,12 @@ void AgStateMachine::ledHandle(AgStateMachineState state) { case AgStateMachineWiFiLost: { /** Connection to WiFi network failed credentials incorrect encryption not * supported etc. */ - if (ag->isOneIndoor()) { + if (ag->isOne()) { /** WIFI failed status LED color */ ag->ledBar.setColor(255, 0, 0, 0); /** Show CO2 or PM color status */ // sensorLedColorHandler(); - sensorLedHandle(); + sensorhandleLeds(); } else { ag->statusLed.setOff(); } @@ -506,11 +662,11 @@ void AgStateMachine::ledHandle(AgStateMachineState state) { case AgStateMachineServerLost: { /** Connected to WiFi network but the server cannot be reached through the * internet, e.g. blocked by firewall */ - if (ag->isOneIndoor()) { + if (ag->isOne()) { ag->ledBar.setColor(233, 183, 54, 0); /** Show CO2 or PM color status */ - sensorLedHandle(); + sensorhandleLeds(); // sensorLedColorHandler(); } else { ag->statusLed.setOff(); @@ -520,66 +676,77 @@ void AgStateMachine::ledHandle(AgStateMachineState state) { case AgStateMachineSensorConfigFailed: { /** Server is reachable but there is some configuration issue to be fixed on * the server side */ - if (ag->isOneIndoor()) { + if (ag->isOne()) { ag->ledBar.setColor(139, 24, 248, 0); /** Show CO2 or PM color status */ - sensorLedHandle(); + sensorhandleLeds(); } else { ag->statusLed.setOff(); } break; } case AgStateMachineNormal: { - if (ag->isOneIndoor()) { - sensorLedHandle(); + if (ag->isOne()) { + sensorhandleLeds(); } else { ag->statusLed.setOff(); } break; } + case AgStateMachineLedBarTest: + ledBarTest(); + break; default: break; } // Show LED bar color - if (ag->isOneIndoor()) { + if (ag->isOne()) { ag->ledBar.show(); } } /** * @brief Handle LED as previous state updated - * + * */ -void AgStateMachine::ledHandle(void) { ledHandle(ledState); } +void StateMachine::handleLeds(void) { handleLeds(ledState); } /** * @brief Set display state - * - * @param state + * + * @param state */ -void AgStateMachine::setDisplayState(AgStateMachineState state) { +void StateMachine::setDisplayState(AgStateMachineState state) { dispState = state; } /** * @brief Get current display state - * - * @return AgStateMachineState + * + * @return AgStateMachineState */ -AgStateMachineState AgStateMachine::getDisplayState(void) { return dispState; } +AgStateMachineState StateMachine::getDisplayState(void) { return dispState; } /** * @brief Set AirGradient instance - * + * * @param ag Point to AirGradient instance */ -void AgStateMachine::setAirGradient(AirGradient *ag) { this->ag = ag; } +void StateMachine::setAirGradient(AirGradient *ag) { this->ag = ag; } /** * @brief Get current LED state - * - * @return AgStateMachineState + * + * @return AgStateMachineState */ -AgStateMachineState AgStateMachine::getLedState(void) { return ledState; } +AgStateMachineState StateMachine::getLedState(void) { return ledState; } + +void StateMachine::executeCo2Calibration(void) { + displayHandle(AgStateMachineCo2Calibration); +} + +void StateMachine::executeLedBarTest(void) { + handleLeds(AgStateMachineLedBarTest); +} diff --git a/src/AgStateMachine.h b/src/AgStateMachine.h index 78bc046..e0554f4 100644 --- a/src/AgStateMachine.h +++ b/src/AgStateMachine.h @@ -7,15 +7,15 @@ #include "Main/PrintLog.h" #include "App/AppDef.h" -class AgStateMachine : public PrintLog { +class StateMachine : public PrintLog { private: // AgStateMachineState state; AgStateMachineState ledState; AgStateMachineState dispState; AirGradient *ag; - AgOledDisplay &disp; - AgValue &value; - AgConfigure &config; + OledDisplay &disp; + Measurements &value; + Configuration &config; bool addToDashBoard = false; uint32_t addToDashboardTime; @@ -24,14 +24,18 @@ private: void ledBarSingleLedAnimation(uint8_t r, uint8_t g, uint8_t b); void ledStatusBlinkDelay(uint32_t delay); - void sensorLedHandle(void); - void co2LedHandle(void); - void pm25LedHandle(void); + void sensorhandleLeds(void); + void co2handleLeds(void); + void pm25handleLeds(void); + void co2Calibration(void); + void ledBarTest(void); + void ledBarRunTest(void); + void runLedTest(char color); public: - AgStateMachine(AgOledDisplay &disp, Stream &log, - AgValue &value, AgConfigure& config); - ~AgStateMachine(); + StateMachine(OledDisplay &disp, Stream &log, + Measurements &value, Configuration& config); + ~StateMachine(); void setAirGradient(AirGradient* ag); void displayHandle(AgStateMachineState state); void displayHandle(void); @@ -39,11 +43,13 @@ public: void displayClearAddToDashBoard(void); void displayWiFiConnectCountDown(int count); void ledAnimationInit(void); - void ledHandle(AgStateMachineState state); - void ledHandle(void); + void handleLeds(AgStateMachineState state); + void handleLeds(void); void setDisplayState(AgStateMachineState state); AgStateMachineState getDisplayState(void); AgStateMachineState getLedState(void); + void executeCo2Calibration(void); + void executeLedBarTest(void); }; #endif /** _AG_STATE_MACHINE_H_ */ diff --git a/src/AgValue.cpp b/src/AgValue.cpp new file mode 100644 index 0000000..fd32450 --- /dev/null +++ b/src/AgValue.cpp @@ -0,0 +1,151 @@ +#include "AgValue.h" +#include "AgConfigure.h" +#include "AirGradient.h" +#include + +String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi, + void *_ag, void *_config) { + AirGradient *ag = (AirGradient *)_ag; + Configuration *config = (Configuration *)_config; + + JSONVar root; + root["wifi"] = rssi; + if (localServer) { + root["serialno"] = ag->deviceId(); + } + if (config->hasSensorS8) { + if (this->CO2 >= 0) { + root["rco2"] = this->CO2; + } + } + + if (ag->isOne()) { + if (config->hasSensorPMS1) { + if (this->pm01_1 >= 0) { + root["pm01"] = this->pm01_1; + } + if (this->pm25_1 >= 0) { + root["pm02"] = this->pm25_1; + } + if (this->pm10_1 >= 0) { + root["pm10"] = this->pm10_1; + } + if (this->pm03PCount_1 >= 0) { + if (localServer) { + root["pm003Count"] = this->pm03PCount_1; + } else { + root["pm003_count"] = this->pm03PCount_1; + } + } + } + + if (config->hasSensorSHT) { + if (this->Temperature > -1001) { + root["atmp"] = ag->round2(this->Temperature); + } + if (this->Humidity >= 0) { + root["rhum"] = this->Humidity; + } + } + + } else { + if (config->hasSensorPMS1 && config->hasSensorPMS2) { + root["pm01"] = ag->round2((this->pm01_1 + this->pm01_2) / 2.0); + root["pm02"] = ag->round2((this->pm25_1 + this->pm25_2) / 2.0); + root["pm10"] = ag->round2((this->pm10_1 + this->pm10_2) / 2.0); + if (localServer) { + root["pm003Count"] = + ag->round2((this->pm03PCount_1 + this->pm03PCount_2) / 2.0); + } else { + root["pm003_count"] = + ag->round2((this->pm03PCount_1 + this->pm03PCount_2) / 2.0); + } + root["atmp"] = ag->round2((this->temp_1 + this->temp_2) / 2.0); + root["rhum"] = ag->round2((this->hum_1 + this->hum_2) / 2.0); + } + + if (fwMode == FW_MDOE_O_1PS || fwMode == FW_MODE_O_1PST) { + if (config->hasSensorPMS1) { + root["pm01"] = this->pm01_1; + root["pm02"] = this->pm25_1; + root["pm10"] = this->pm10_1; + if (localServer) { + root["pm003Count"] = this->pm03PCount_1; + } else { + root["pm003_count"] = this->pm03PCount_1; + } + root["atmp"] = ag->round2(this->temp_1); + root["rhum"] = this->hum_1; + } + if (config->hasSensorPMS2) { + root["pm01"] = this->pm01_2; + root["pm02"] = this->pm25_2; + root["pm10"] = this->pm10_2; + if (localServer) { + root["pm003Count"] = this->pm03PCount_2; + } else { + root["pm003_count"] = this->pm03PCount_2; + } + root["atmp"] = ag->round2(this->temp_2); + root["rhum"] = this->hum_2; + } + } else { + if (config->hasSensorPMS1) { + root["channels"]["1"]["pm01"] = this->pm01_1; + root["channels"]["1"]["pm02"] = this->pm25_1; + root["channels"]["1"]["pm10"] = this->pm10_1; + if (localServer) { + root["channels"]["1"]["pm003Count"] = this->pm03PCount_1; + } else { + root["channels"]["1"]["pm003_count"] = this->pm03PCount_1; + } + root["channels"]["1"]["atmp"] = ag->round2(this->temp_1); + root["channels"]["1"]["rhum"] = this->hum_1; + } + if (config->hasSensorPMS2) { + root["channels"]["2"]["pm01"] = this->pm01_2; + root["channels"]["2"]["pm02"] = this->pm25_2; + root["channels"]["2"]["pm10"] = this->pm10_2; + if (localServer) { + root["channels"]["2"]["pm003Count"] = this->pm03PCount_2; + } else { + root["channels"]["2"]["pm003_count"] = this->pm03PCount_2; + } + root["channels"]["2"]["atmp"] = ag->round2(this->temp_2); + root["channels"]["2"]["rhum"] = this->hum_2; + } + } + } + + if (config->hasSensorSGP) { + if (this->TVOC >= 0) { + if (localServer) { + root["tvocIndex"] = this->TVOC; + } else { + root["tvoc_index"] = this->TVOC; + } + } + if (this->TVOCRaw >= 0) { + root["tvoc_raw"] = this->TVOCRaw; + } + if (this->NOx >= 0) { + if (localServer) { + root["noxIndex"] = this->NOx; + } else { + root["nox_index"] = this->NOx; + } + } + if (this->NOxRaw >= 0) { + root["nox_raw"] = this->NOxRaw; + } + } + root["boot"] = bootCount; + + if (localServer) { + root["ledMode"] = config->getLedBarModeName(); + root["firmwareVersion"] = ag->getVersion(); + root["fwMode"] = AgFirmwareModeName(fwMode); + } + + return JSON.stringify(root); +} diff --git a/src/AgValue.h b/src/AgValue.h index 6ee8e7d..3176482 100644 --- a/src/AgValue.h +++ b/src/AgValue.h @@ -1,20 +1,76 @@ #ifndef _AG_VALUE_H_ #define _AG_VALUE_H_ -class AgValue { +#include +#include "App/AppDef.h" + +class Measurements { private: public: - AgValue() {} - ~AgValue() {} + Measurements() { + pm25_1 = -1; + pm01_1 = -1; + pm10_1 = -1; + pm03PCount_1 = -1; + temp_1 = -1001; + hum_1 = -1; - float Temperature = -1001; - int Humidity = -1; - int CO2 = -1; - int PM25 = -1; - int TVOC = -1; - int TVOCRaw = -1; - int NOx = -1; - int NOxRaw = -1; + pm25_2 = -1; + pm01_2 = -1; + pm10_2 = -1; + pm03PCount_2 = -1; + temp_2 = -1001; + hum_2 = -1; + + Temperature = -1001; + Humidity = -1; + CO2 = -1; + TVOC = -1; + TVOCRaw = -1; + NOx = -1; + NOxRaw = -1; + } + ~Measurements() {} + + float Temperature; + int Humidity; + int CO2; + int TVOC; + int TVOCRaw; + int NOx; + int NOxRaw; + + int pm25_1; + int pm01_1; + int pm10_1; + int pm03PCount_1; + float temp_1; + int hum_1; + + int pm25_2; + int pm01_2; + int pm10_2; + int pm03PCount_2; + float temp_2; + int hum_2; + + int pm1Value01; + int pm1Value25; + int pm1Value10; + int pm1PCount; + int pm1temp; + int pm1hum; + int pm2Value01; + int pm2Value25; + int pm2Value10; + int pm2PCount; + int pm2temp; + int pm2hum; + int countPosition; + const int targetCount = 20; + int bootCount; + + String toString(bool isLocal, AgFirmwareMode fwMode, int rssi, void* _ag, void* _config); }; #endif /** _AG_VALUE_H_ */ diff --git a/src/AgWiFiConnector.cpp b/src/AgWiFiConnector.cpp index e72ee12..aee707f 100644 --- a/src/AgWiFiConnector.cpp +++ b/src/AgWiFiConnector.cpp @@ -1,5 +1,3 @@ -#ifdef ESP32 - #include "AgWiFiConnector.h" #include "Libraries/WiFiManager/WiFiManager.h" @@ -8,25 +6,28 @@ #define WIFI() ((WiFiManager *)(this->wifi)) -/** - * @brief Construct a new Ag Wi Fi Connector:: Ag Wi Fi Connector object - * - * @param disp AgOledDisplay - * @param log Stream - * @param sm AgStateMachine - */ -AgWiFiConnector::AgWiFiConnector(AgOledDisplay &disp, Stream &log, - AgStateMachine &sm) - : PrintLog(log, "AgWiFiConnector"), disp(disp), sm(sm) {} - -AgWiFiConnector::~AgWiFiConnector() {} - /** * @brief Set reference AirGradient instance * * @param ag Point to AirGradient instance */ -void AgWiFiConnector::setAirGradient(AirGradient *ag) { this->ag = ag; } +void WifiConnector::setAirGradient(AirGradient *ag) { this->ag = ag; } + +#ifdef ESP32 +/** + * @brief Construct a new Ag Wi Fi Connector:: Ag Wi Fi Connector object + * + * @param disp OledDisplay + * @param log Stream + * @param sm StateMachine + */ +WifiConnector::WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm) + : PrintLog(log, "WifiConnector"), disp(disp), sm(sm) {} +#else +WifiConnector::WifiConnector(Stream &log) : PrintLog(log, "WiFiConnector") {} +#endif + +WifiConnector::~WifiConnector() {} /** * @brief Connection to WIFI AP process. Just call one times @@ -34,7 +35,7 @@ void AgWiFiConnector::setAirGradient(AirGradient *ag) { this->ag = ag; } * @return true Success * @return false Failure */ -bool AgWiFiConnector::connect(void) { +bool WifiConnector::connect(void) { if (wifi == NULL) { wifi = new WiFiManager(); if (wifi == NULL) { @@ -46,23 +47,27 @@ bool AgWiFiConnector::connect(void) { WIFI()->setConfigPortalBlocking(false); WIFI()->setTimeout(WIFI_CONNECT_COUNTDOWN_MAX); +#ifdef ESP32 WIFI()->setAPCallback([this](WiFiManager *obj) { _wifiApCallback(); }); WIFI()->setSaveConfigCallback([this]() { _wifiSaveConfig(); }); WIFI()->setSaveParamsCallback([this]() { _wifiSaveParamCallback(); }); - - if (ag->isOneIndoor()) { + if (ag->isOne()) { disp.setText("Connecting to", "WiFi", "..."); } else { logInfo("Connecting to WiFi..."); } - ssid = "airgradient-" + ag->deviceId(); +#else + ssid = "AG-" + String(ESP.getChipId(), HEX); +#endif + WIFI()->setConfigPortalTimeout(WIFI_CONNECT_COUNTDOWN_MAX); WIFI()->autoConnect(ssid.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT); +#ifdef ESP32 // Task handle WiFi connection. xTaskCreate( [](void *obj) { - AgWiFiConnector *connector = (AgWiFiConnector *)obj; + WifiConnector *connector = (WifiConnector *)obj; while (connector->_wifiConfigPortalActive()) { connector->_wifiProcess(); } @@ -81,7 +86,7 @@ bool AgWiFiConnector::connect(void) { if (WiFi.isConnected() == false) { /** Display countdown */ uint32_t ms; - if (ag->isOneIndoor()) { + if (ag->isOne()) { ms = (uint32_t)(millis() - dispPeriod); if (ms >= 1000) { dispPeriod = millis(); @@ -98,7 +103,7 @@ bool AgWiFiConnector::connect(void) { ms = (uint32_t)(millis() - ledPeriod); if (ms >= 100) { ledPeriod = millis(); - sm.ledHandle(); + sm.handleLeds(); } /** Check for client connect to change led color */ @@ -106,11 +111,11 @@ bool AgWiFiConnector::connect(void) { if (clientConnected != clientConnectChanged) { clientConnectChanged = clientConnected; if (clientConnectChanged) { - sm.ledHandle(AgStateMachineWiFiManagerPortalActive); + sm.handleLeds(AgStateMachineWiFiManagerPortalActive); } else { sm.ledAnimationInit(); - sm.ledHandle(AgStateMachineWiFiManagerMode); - if (ag->isOneIndoor()) { + sm.handleLeds(AgStateMachineWiFiManagerMode); + if (ag->isOne()) { sm.displayHandle(AgStateMachineWiFiManagerMode); } } @@ -121,15 +126,18 @@ bool AgWiFiConnector::connect(void) { } /** Show display wifi connect result failed */ if (WiFi.isConnected() == false) { - sm.ledHandle(AgStateMachineWiFiManagerConnectFailed); - if (ag->isOneIndoor()) { + sm.handleLeds(AgStateMachineWiFiManagerConnectFailed); + if (ag->isOne()) { sm.displayHandle(AgStateMachineWiFiManagerConnectFailed); } delay(6000); } else { hasConfig = true; + logInfo("WiFi Connected: " + WiFi.SSID() + " IP: " + localIpStr()); } - +#else + _wifiProcess(); +#endif return true; } @@ -137,50 +145,69 @@ bool AgWiFiConnector::connect(void) { * @brief Disconnect to current connected WiFi AP * */ -void AgWiFiConnector::disconnect(void) { +void WifiConnector::disconnect(void) { if (WiFi.isConnected()) { logInfo("Disconnect"); WiFi.disconnect(); } } +#ifdef ESP32 +#else +void WifiConnector::displayShowText(String ln1, String ln2, String ln3) { + char buf[9]; + ag->display.clear(); + + ag->display.setCursor(1, 1); + ag->display.setText(ln1); + ag->display.setCursor(1, 19); + ag->display.setText(ln2); + ag->display.setCursor(1, 37); + ag->display.setText(ln3); + + ag->display.show(); + delay(100); +} +#endif + /** * @brief Has wifi STA connected to WIFI softAP (this device) * * @return true Connected * @return false Not connected */ -bool AgWiFiConnector::wifiClientConnected(void) { +bool WifiConnector::wifiClientConnected(void) { return WiFi.softAPgetStationNum() ? true : false; } +#ifdef ESP32 /** * @brief Handle WiFiManage softAP setup completed callback * */ -void AgWiFiConnector::_wifiApCallback(void) { +void WifiConnector::_wifiApCallback(void) { sm.displayWiFiConnectCountDown(WIFI_CONNECT_COUNTDOWN_MAX); sm.setDisplayState(AgStateMachineWiFiManagerMode); sm.ledAnimationInit(); - sm.ledHandle(AgStateMachineWiFiManagerMode); + sm.handleLeds(AgStateMachineWiFiManagerMode); } /** * @brief Handle WiFiManager save configuration callback * */ -void AgWiFiConnector::_wifiSaveConfig(void) { +void WifiConnector::_wifiSaveConfig(void) { sm.setDisplayState(AgStateMachineWiFiManagerStaConnected); - sm.ledHandle(AgStateMachineWiFiManagerStaConnected); + sm.handleLeds(AgStateMachineWiFiManagerStaConnected); } /** * @brief Handle WiFiManager save parameter callback * */ -void AgWiFiConnector::_wifiSaveParamCallback(void) { +void WifiConnector::_wifiSaveParamCallback(void) { sm.ledAnimationInit(); - sm.ledHandle(AgStateMachineWiFiManagerStaConnecting); + sm.handleLeds(AgStateMachineWiFiManagerStaConnecting); sm.setDisplayState(AgStateMachineWiFiManagerStaConnecting); } @@ -190,21 +217,52 @@ void AgWiFiConnector::_wifiSaveParamCallback(void) { * @return true Active * @return false Not-Active */ -bool AgWiFiConnector::_wifiConfigPortalActive(void) { +bool WifiConnector::_wifiConfigPortalActive(void) { return WIFI()->getConfigPortalActive(); } - +#endif /** * @brief Process WiFiManager connection * */ -void AgWiFiConnector::_wifiProcess() { WIFI()->process(); } +void WifiConnector::_wifiProcess() { +#ifdef ESP32 + WIFI()->process(); +#else + int count = WIFI_CONNECT_COUNTDOWN_MAX; + displayShowText(String(WIFI_CONNECT_COUNTDOWN_MAX) + " sec", "SSID:", ssid); + while (WIFI()->getConfigPortalActive()) { + WIFI()->process(); + + uint32_t lastTime = millis(); + uint32_t ms = (uint32_t)(millis() - lastTime); + if (ms >= 1000) { + lastTime = millis(); + + displayShowText(String(count) + " sec", "SSID:", ssid); + + count--; + + // Timeout + if (count == 0) { + break; + } + } + } + + if (!WiFi.isConnected()) { + displayShowText("Booting", "offline", "mode"); + Serial.println("failed to connect and hit timeout"); + delay(2500); + } +#endif +} /** * @brief Handle and reconnect WiFi * */ -void AgWiFiConnector::handle(void) { +void WifiConnector::handle(void) { // Ignore if WiFi is not configured if (hasConfig == false) { return; @@ -232,27 +290,25 @@ void AgWiFiConnector::handle(void) { * @return true Connected * @return false Disconnected */ -bool AgWiFiConnector::isConnected(void) { return WiFi.isConnected(); } +bool WifiConnector::isConnected(void) { return WiFi.isConnected(); } /** * @brief Reset WiFi configuretion and connection, disconnect wifi before call * this method * */ -void AgWiFiConnector::reset(void) { WIFI()->resetSettings(); } +void WifiConnector::reset(void) { WIFI()->resetSettings(); } /** * @brief Get wifi RSSI * * @return int */ -int AgWiFiConnector::RSSI(void) { return WiFi.RSSI(); } +int WifiConnector::RSSI(void) { return WiFi.RSSI(); } /** * @brief Get WIFI IP as string format ex: 192.168.1.1 * * @return String */ -String AgWiFiConnector::localIpStr(void) { return WiFi.localIP().toString(); } - -#endif +String WifiConnector::localIpStr(void) { return WiFi.localIP().toString(); } diff --git a/src/AgWiFiConnector.h b/src/AgWiFiConnector.h index b61fe59..46cb53c 100644 --- a/src/AgWiFiConnector.h +++ b/src/AgWiFiConnector.h @@ -1,5 +1,3 @@ -#ifdef ESP32 - #ifndef _AG_WIFI_CONNECTOR_H_ #define _AG_WIFI_CONNECTOR_H_ @@ -10,11 +8,15 @@ #include -class AgWiFiConnector : public PrintLog { +class WifiConnector : public PrintLog { private: AirGradient *ag; - AgOledDisplay &disp; - AgStateMachine &sm; +#ifdef ESP32 + OledDisplay &disp; + StateMachine &sm; +#else + void displayShowText(String ln1, String ln2, String ln3); +#endif String ssid; void *wifi = NULL; bool hasConfig; @@ -23,17 +25,23 @@ private: bool wifiClientConnected(void); public: - AgWiFiConnector(AgOledDisplay &disp, Stream &log, AgStateMachine &sm); - ~AgWiFiConnector(); - void setAirGradient(AirGradient *ag); +#ifdef ESP32 + WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm); +#else + WifiConnector(Stream &log); +#endif + ~WifiConnector(); + bool connect(void); void disconnect(void); void handle(void); +#ifdef ESP32 void _wifiApCallback(void); void _wifiSaveConfig(void); void _wifiSaveParamCallback(void); bool _wifiConfigPortalActive(void); +#endif void _wifiProcess(); bool isConnected(void); void reset(void); @@ -42,6 +50,3 @@ public: }; #endif /** _AG_WIFI_CONNECTOR_H_ */ - - -#endif diff --git a/src/AirGradient.cpp b/src/AirGradient.cpp index 4957e74..783e0a0 100644 --- a/src/AirGradient.cpp +++ b/src/AirGradient.cpp @@ -50,7 +50,13 @@ String AirGradient::getBoardName(void) { return String(getBoardDefName(boardType)); } -bool AirGradient::isOneIndoor(void) { +/** + * @brief Board Type is ONE_INDOOR + * + * @return true ONE_INDOOR + * @return false Other + */ +bool AirGradient::isOne(void) { return boardType == BoardType::ONE_INDOOR; } diff --git a/src/AirGradient.h b/src/AirGradient.h index 7c15609..e056173 100644 --- a/src/AirGradient.h +++ b/src/AirGradient.h @@ -128,7 +128,7 @@ public: * @return true Yes * @return false No */ - bool isOneIndoor(void); + bool isOne(void); /** * @brief Get device Id diff --git a/src/App/AppDef.h b/src/App/AppDef.h index eb90600..4292c04 100644 --- a/src/App/AppDef.h +++ b/src/App/AppDef.h @@ -54,6 +54,14 @@ enum AgStateMachineState { the server side */ AgStateMachineSensorConfigFailed, + /** CO2 calibration */ + AgStateMachineCo2Calibration, + + /* LED bar testing */ + AgStateMachineLedBarTest, + + /** LED: Show working state. + * Display: Show dashboard */ AgStateMachineNormal, };