From dbc63194e68eaa541d5a62f421ab60cde6fc4f2d Mon Sep 17 00:00:00 2001 From: Phat Nguyen Date: Mon, 24 Jun 2024 18:34:24 +0700 Subject: [PATCH] Update `BASIC.ino` example --- examples/BASIC/BASIC.ino | 705 ++++++++++++++++++++------------- examples/BASIC/LocalServer.cpp | 61 +++ examples/BASIC/LocalServer.h | 38 ++ examples/BASIC/OpenMetrics.cpp | 186 +++++++++ examples/BASIC/OpenMetrics.h | 28 ++ src/AgOledDisplay.cpp | 432 ++++++++++++-------- src/AgOledDisplay.h | 4 + src/AgStateMachine.cpp | 71 ++-- src/AgValue.cpp | 6 +- src/AgWiFiConnector.cpp | 9 +- src/AirGradient.cpp | 2 + src/AirGradient.h | 8 + 12 files changed, 1079 insertions(+), 471 deletions(-) create mode 100644 examples/BASIC/LocalServer.cpp create mode 100644 examples/BASIC/LocalServer.h create mode 100644 examples/BASIC/OpenMetrics.cpp create mode 100644 examples/BASIC/OpenMetrics.h diff --git a/examples/BASIC/BASIC.ino b/examples/BASIC/BASIC.ino index dca53ae..47259e2 100644 --- a/examples/BASIC/BASIC.ino +++ b/examples/BASIC/BASIC.ino @@ -31,190 +31,376 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License #include "AgConfigure.h" #include "AgSchedule.h" #include "AgWiFiConnector.h" +#include "LocalServer.h" +#include "OpenMetrics.h" +#include "MqttClient.h" #include #include #include +#include #include -#define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */ -#define WIFI_CONNECT_RETRY_MS 10000 /** ms */ -#define LED_BAR_COUNT_INIT_VALUE (-1) /** */ -#define LED_BAR_ANIMATION_PERIOD 100 /** ms */ -#define DISP_UPDATE_INTERVAL 5000 /** ms */ -#define SERVER_CONFIG_SYNC_INTERVAL 60000 /** ms */ -#define SERVER_SYNC_INTERVAL 60000 /** ms */ -#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */ -#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */ -#define SENSOR_CO2_UPDATE_INTERVAL 5000 /** ms */ -#define SENSOR_PM_UPDATE_INTERVAL 5000 /** ms */ -#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */ -#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */ -#define WIFI_HOTSPOT_PASSWORD_DEFAULT \ - "cleanair" /** default WiFi AP password \ - */ +#define LED_BAR_ANIMATION_PERIOD 100 /** ms */ +#define DISP_UPDATE_INTERVAL 2500 /** ms */ +#define SERVER_CONFIG_SYNC_INTERVAL 60000 /** ms */ +#define SERVER_SYNC_INTERVAL 60000 /** ms */ +#define MQTT_SYNC_INTERVAL 60000 /** ms */ +#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */ +#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */ +#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */ +#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */ +#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */ +#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */ +#define FIRMWARE_CHECK_FOR_UPDATE_MS (60 * 60 * 1000) /** ms */ -/** Create airgradient instance for 'DIY_BASIC' board */ -static AirGradient ag = AirGradient(DIY_BASIC); +static AirGradient ag(DIY_BASIC); static Configuration configuration(Serial); static AgApiClient apiClient(Serial, configuration); - static Measurements measurements; -static OledDisplay oledDisp(configuration, measurements, Serial); -static StateMachine sm(oledDisp, Serial, measurements, configuration); -static WifiConnector wifiConnector(oledDisp, Serial, sm, configuration); +static OledDisplay oledDisplay(configuration, measurements, Serial); +static StateMachine stateMachine(oledDisplay, Serial, measurements, + configuration); +static WifiConnector wifiConnector(oledDisplay, Serial, stateMachine, + configuration); +static OpenMetrics openMetrics(measurements, configuration, wifiConnector, + apiClient); +static LocalServer localServer(Serial, openMetrics, measurements, configuration, + wifiConnector); +static MqttClient mqttClient(Serial); -static int co2Ppm = -1; -static int pm25 = -1; -static float temp = -1001; -static int hum = -1; +static int pmFailCount = 0; +static int getCO2FailCount = 0; +static AgFirmwareMode fwMode = FW_MODE_I_BASIC_40PS; + +static String fwNewVersion; static void boardInit(void); static void failedHandler(String msg); -static void executeCo2Calibration(void); -static void updateServerConfiguration(void); -static void co2Update(void); -static void pmUpdate(void); -static void tempHumUpdate(void); +static void configurationUpdateSchedule(void); +static void appDispHandler(void); +static void oledDisplaySchedule(void); +static void updateTvoc(void); +static void updatePm(void); static void sendDataToServer(void); -static void dispHandler(void); -static String getDevId(void); -static void showNr(void); +static void tempHumUpdate(void); +static void co2Update(void); +static void mdnsInit(void); +static void initMqtt(void); +static void factoryConfigReset(void); +static void wdgFeedUpdate(void); +static bool sgp41Init(void); +static void wifiFactoryConfigure(void); +static void mqttHandle(void); -bool hasSensorS8 = true; -bool hasSensorPMS = true; -bool hasSensorSHT = true; -int pmFailCount = 0; -int getCO2FailCount = 0; +AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplaySchedule); AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL, - updateServerConfiguration); -AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer); -AgSchedule dispSchedule(DISP_UPDATE_INTERVAL, dispHandler); + 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, updateTvoc); +AgSchedule watchdogFeedSchedule(60000, wdgFeedUpdate); +AgSchedule mqttSchedule(MQTT_SYNC_INTERVAL, mqttHandle); void setup() { + /** Serial for print debug message */ Serial.begin(115200); - showNr(); + delay(100); /** For bester show log */ + + /** Print device ID into log */ + Serial.println("Serial nr: " + ag.deviceId()); + + /** Initialize local configure */ + configuration.begin(); /** Init I2C */ Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin()); + Wire.endTransmission(1); delay(1000); - /** Board init */ + configuration.setAirGradient(&ag); + oledDisplay.setAirGradient(&ag); + stateMachine.setAirGradient(&ag); + wifiConnector.setAirGradient(&ag); + apiClient.setAirGradient(&ag); + openMetrics.setAirGradient(&ag); + localServer.setAirGraident(&ag); + + /** Init sensor */ boardInit(); - /** Init AirGradient server */ - apiClient.begin(); - apiClient.setAirGradient(&ag); - configuration.setAirGradient(&ag); - wifiConnector.setAirGradient(&ag); + /** Connecting wifi */ + bool connectToWifi = false; - /** Show boot display */ - displayShowText("DIY basic", "Lib:" + ag.getVersion(), ""); - delay(2000); + connectToWifi = !configuration.isOfflineMode(); + if (connectToWifi) { + apiClient.begin(); - /** WiFi connect */ - // connectToWifi(); - if (wifiConnector.connect()) { - if (WiFi.status() == WL_CONNECTED) { - sendDataToAg(); + if (wifiConnector.connect()) { + if (wifiConnector.isConnected()) { + mdnsInit(); + localServer.begin(); + initMqtt(); + sendDataToAg(); - apiClient.fetchServerConfiguration(); - if (configuration.isCo2CalibrationRequested()) { - executeCo2Calibration(); + apiClient.fetchServerConfiguration(); + configSchedule.update(); + if (apiClient.isFetchConfigureFailed()) { + if (apiClient.isNotAvailableOnDashboard()) { + stateMachine.displaySetAddToDashBoard(); + stateMachine.displayHandle( + AgStateMachineWiFiOkServerOkSensorConfigFailed); + } else { + stateMachine.displayClearAddToDashBoard(); + } + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); + } + } else { + if (wifiConnector.isConfigurePorttalTimeout()) { + oledDisplay.showRebooting(); + delay(2500); + oledDisplay.setText("", "", ""); + ESP.restart(); + } } } } - /** Show serial number display */ - ag.display.clear(); - ag.display.setCursor(1, 1); - ag.display.setText("Warm Up"); - ag.display.setCursor(1, 15); - ag.display.setText("Serial#"); - ag.display.setCursor(1, 29); - String id = getNormalizedMac(); - Serial.println("Device id: " + id); - String id1 = id.substring(0, 9); - String id2 = id.substring(9, 12); - ag.display.setText("\'" + id1); - ag.display.setCursor(1, 40); - ag.display.setText(id2 + "\'"); - ag.display.show(); + /** Set offline mode without saving, cause wifi is not configured */ + if (wifiConnector.hasConfigurated() == false) { + Serial.println("Set offline mode cause wifi is not configurated"); + configuration.setOfflineModeWithoutSave(true); + } - delay(5000); + /** Show display Warning up */ + String sn = "SN:" + ag.deviceId(); + oledDisplay.setText("Warming Up", sn.c_str(), ""); + + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); + + Serial.println("Display brightness: " + + String(configuration.getDisplayBrightness())); + oledDisplay.setBrightness(configuration.getDisplayBrightness()); + + appDispHandler(); } void loop() { + /** Handle schedule */ + dispLedSchedule.run(); configSchedule.run(); - serverSchedule.run(); - dispSchedule.run(); - if (hasSensorS8) { + agApiPostSchedule.run(); + + if (configuration.hasSensorS8) { co2Schedule.run(); } - if (hasSensorPMS) { + if (configuration.hasSensorPMS1) { pmsSchedule.run(); + ag.pms5003.handle(); } - if (hasSensorSHT) { + if (configuration.hasSensorSHT) { tempHumSchedule.run(); } + if (configuration.hasSensorSGP) { + tvocSchedule.run(); + } + /** Auto reset watchdog timer if offline mode or postDataToAirGradient */ + if (configuration.isOfflineMode() || + (configuration.isPostDataToAirGradient() == false)) { + watchdogFeedSchedule.run(); + } + + /** Check for handle WiFi reconnect */ wifiConnector.handle(); - /** Read PMS on loop */ - ag.pms5003.handle(); + /** factory reset handle */ + // factoryConfigReset(); + + /** check that local configura changed then do some action */ + configUpdateHandle(); + + localServer._handle(); + + if (configuration.hasSensorSGP) { + ag.sgp41.handle(); + } + + MDNS.update(); + + mqttSchedule.run(); + mqttClient.handle(); +} + +static void co2Update(void) { + int value = ag.s8.getCo2(); + if (value >= 0) { + measurements.CO2 = value; + getCO2FailCount = 0; + Serial.printf("CO2 (ppm): %d\r\n", measurements.CO2); + } else { + getCO2FailCount++; + Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount); + if (getCO2FailCount >= 3) { + measurements.CO2 = -1; + } + } +} + +static void mdnsInit(void) { + Serial.println("mDNS init"); + if (!MDNS.begin(localServer.getHostname().c_str())) { + Serial.println("Init mDNS failed"); + return; + } + + 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"); + + MDNS.announce(); +} + +static void initMqtt(void) { + if (mqttClient.begin(configuration.getMqttBrokerUri())) { + Serial.println("Setup connect to MQTT broker successful"); + } else { + Serial.println("setup Connect to MQTT broker failed"); + } +} + +static void wdgFeedUpdate(void) { + ag.watchdog.reset(); + Serial.println(); + Serial.println("Offline mode or isPostToAirGradient = false: watchdog reset"); + Serial.println(); +} + +static bool sgp41Init(void) { + ag.sgp41.setNoxLearningOffset(configuration.getNoxLearningOffset()); + ag.sgp41.setTvocLearningOffset(configuration.getTvocLearningOffset()); + if (ag.sgp41.begin(Wire)) { + Serial.println("Init SGP41 success"); + configuration.hasSensorSGP = true; + return true; + } else { + Serial.println("Init SGP41 failuire"); + configuration.hasSensorSGP = false; + } + return false; +} + +static void wifiFactoryConfigure(void) { + WiFi.persistent(true); + WiFi.begin("airgradient", "cleanair"); + WiFi.persistent(false); + oledDisplay.setText("Configure WiFi", "connect to", "\'airgradient\'"); + delay(2500); + oledDisplay.setText("Rebooting...", "", ""); + delay(2500); + oledDisplay.setText("", "", ""); + ESP.restart(); +} + +static void mqttHandle(void) { + if(mqttClient.isConnected() == false) { + mqttClient.connect(String("airgradient-") + ag.deviceId()); + } + + if (mqttClient.isConnected()) { + String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(), + &ag, &configuration); + String topic = "airgradient/readings/" + ag.deviceId(); + if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) { + Serial.println("MQTT sync success"); + } else { + Serial.println("MQTT sync failure"); + } + } } static void sendDataToAg() { - // delay(1500); - if (apiClient.sendPing(wifiConnector.RSSI(), 0)) { - // Ping Server succses + /** Change oledDisplay and led state */ + stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting); + + delay(1500); + if (apiClient.sendPing(wifiConnector.RSSI(), measurements.bootCount)) { + stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected); } else { - // Ping server failed + stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed); } - // delay(DISPLAY_DELAY_SHOW_CONTENT_MS); + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); } -void 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); +void dispSensorNotFound(String ss) { + oledDisplay.setText("Sensor", ss.c_str(), "not found"); + delay(2000); } static void boardInit(void) { - /** Init SHT sensor */ + /** Display init */ + oledDisplay.begin(); + + /** Show boot display */ + Serial.println("Firmware Version: " + ag.getVersion()); + + if (ag.isBasic()) { + oledDisplay.setText("DIY Basic", ag.getVersion().c_str(), ""); + } else { + oledDisplay.setText("AirGradient ONE", + "FW Version: ", ag.getVersion().c_str()); + } + + delay(DISPLAY_DELAY_SHOW_CONTENT_MS); + + ag.watchdog.begin(); + + /** Show message init sensor */ + oledDisplay.setText("Sensor", "init...", ""); + + /** Init sensor SGP41 */ + configuration.hasSensorSGP = false; + // if (sgp41Init() == false) { + // dispSensorNotFound("SGP41"); + // } + + /** Init SHT */ if (ag.sht.begin(Wire) == false) { - hasSensorSHT = false; - Serial.println("SHT sensor not found"); + Serial.println("SHTx sensor not found"); + configuration.hasSensorSHT = false; + dispSensorNotFound("SHT"); } - /** CO2 init */ + /** Init S8 CO2 sensor */ if (ag.s8.begin(&Serial) == false) { - Serial.println("CO2 S8 snsor not found"); - hasSensorS8 = false; + Serial.println("CO2 S8 sensor not found"); + configuration.hasSensorS8 = false; + dispSensorNotFound("S8"); } - /** PMS init */ + /** Init PMS5003 */ + configuration.hasSensorPMS1 = true; + configuration.hasSensorPMS2 = false; if (ag.pms5003.begin(&Serial) == false) { Serial.println("PMS sensor not found"); - hasSensorPMS = false; + configuration.hasSensorPMS1 = false; + + dispSensorNotFound("PMS"); } - /** Display init */ - ag.display.begin(Wire); - ag.display.setTextColor(1); - ag.display.clear(); - ag.display.show(); - delay(100); + /** Set S8 CO2 abc days period */ + if (configuration.hasSensorS8) { + if (ag.s8.setAbcPeriod(configuration.getCO2CalibrationAbcDays() * 24)) { + Serial.println("Set S8 AbcDays successful"); + } else { + Serial.println("Set S8 AbcDays failure"); + } + } + + localServer.setFwMode(fwMode); } static void failedHandler(String msg) { @@ -224,181 +410,160 @@ static void failedHandler(String msg) { } } -static void executeCo2Calibration(void) { - /** Count down for co2CalibCountdown secs */ - for (int i = 0; i < SENSOR_CO2_CALIB_COUNTDOWN_MAX; i++) { - displayShowText("CO2 calib.", "after", - String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec"); - delay(1000); - } - - if (ag.s8.setBaselineCalibration()) { - displayShowText("Calib", "success", ""); - delay(1000); - displayShowText("Wait to", "complete", "..."); - int count = 0; - while (ag.s8.isBaseLineCalibrationDone() == false) { - delay(1000); - count++; - } - displayShowText("Finished", "after", String(count) + " sec"); - delay(DISPLAY_DELAY_SHOW_CONTENT_MS); - } else { - displayShowText("Calibration", "failure", ""); - delay(DISPLAY_DELAY_SHOW_CONTENT_MS); - } -} - -static void updateServerConfiguration(void) { +static void configurationUpdateSchedule(void) { if (apiClient.fetchServerConfiguration()) { - if (configuration.isCo2CalibrationRequested()) { - if (hasSensorS8) { - executeCo2Calibration(); - } else { - Serial.println("CO2 S8 not available, calib ignored"); - } - } - if (configuration.getCO2CalibrationAbcDays() > 0) { - if (hasSensorS8) { - int newHour = configuration.getCO2CalibrationAbcDays() * 24; - Serial.printf("abcDays config: %d days(%d hours)\r\n", - 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(configuration.getCO2CalibrationAbcDays() * - 24) == false) { - Serial.println("Set S8 abcDays period calib failed"); - } else { - Serial.println("Set S8 abcDays period calib success"); - } - } - } else { - Serial.println("CO2 S8 not available, set 'abcDays' ignored"); - } - } + configUpdateHandle(); } } -static void co2Update() { - int value = ag.s8.getCo2(); - if (value >= 0) { - co2Ppm = value; - getCO2FailCount = 0; - Serial.printf("CO2 index: %d\r\n", co2Ppm); - } else { - getCO2FailCount++; - Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount); - if (getCO2FailCount >= 3) { - co2Ppm = -1; +static void configUpdateHandle() { + if (configuration.isUpdated() == false) { + return; + } + + stateMachine.executeCo2Calibration(); + + String mqttUri = configuration.getMqttBrokerUri(); + if (mqttClient.isCurrentUri(mqttUri) == false) { + mqttClient.end(); + initMqtt(); + } + + if (configuration.hasSensorSGP) { + if (configuration.noxLearnOffsetChanged() || + configuration.tvocLearnOffsetChanged()) { + ag.sgp41.end(); + + int oldTvocOffset = ag.sgp41.getTvocLearningOffset(); + int oldNoxOffset = ag.sgp41.getNoxLearningOffset(); + bool result = sgp41Init(); + const char *resultStr = "successful"; + if (!result) { + resultStr = "failure"; + } + if (oldTvocOffset != configuration.getTvocLearningOffset()) { + Serial.printf("Setting tvocLearningOffset from %d to %d hours %s\r\n", + oldTvocOffset, configuration.getTvocLearningOffset(), + resultStr); + } + if (oldNoxOffset != configuration.getNoxLearningOffset()) { + Serial.printf("Setting noxLearningOffset from %d to %d hours %s\r\n", + oldNoxOffset, configuration.getNoxLearningOffset(), + resultStr); + } } } + + if (configuration.isDisplayBrightnessChanged()) { + oledDisplay.setBrightness(configuration.getDisplayBrightness()); + } + + appDispHandler(); } -void pmUpdate() { +static void appDispHandler(void) { + AgStateMachineState state = AgStateMachineNormal; + + /** Only show display status on online mode. */ + if (configuration.isOfflineMode() == false) { + if (wifiConnector.isConnected() == false) { + state = AgStateMachineWiFiLost; + } else if (apiClient.isFetchConfigureFailed()) { + state = AgStateMachineSensorConfigFailed; + if (apiClient.isNotAvailableOnDashboard()) { + stateMachine.displaySetAddToDashBoard(); + } else { + stateMachine.displayClearAddToDashBoard(); + } + } else if (apiClient.isPostToServerFailed()) { + state = AgStateMachineServerLost; + } + } + stateMachine.displayHandle(state); +} + +static void oledDisplaySchedule(void) { + + appDispHandler(); +} + +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", 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); +} + +static void updatePm(void) { if (ag.pms5003.isFailed() == false) { - pm25 = ag.pms5003.getPm25Ae(); - Serial.printf("PMS2.5: %d\r\n", pm25); + 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", 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 { - Serial.printf("PM read failed, %d", pmFailCount); pmFailCount++; + Serial.printf("PMS read failed: %d\r\n", pmFailCount); if (pmFailCount >= 3) { - pm25 = -1; + measurements.pm01_1 = -1; + measurements.pm25_1 = -1; + measurements.pm10_1 = -1; + measurements.pm03PCount_1 = -1; } } } -static void tempHumUpdate() { +static void sendDataToServer(void) { + /** Ignore send data to server if postToAirGradient disabled */ + if (configuration.isPostDataToAirGradient() == false || + configuration.isOfflineMode()) { + return; + } + + String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), + &ag, &configuration); + if (apiClient.postToServer(syncData)) { + ag.watchdog.reset(); + Serial.println(); + Serial.println( + "Online mode and isPostToAirGradient = true: watchdog reset"); + Serial.println(); + } + + measurements.bootCount++; +} + +static void tempHumUpdate(void) { + delay(100); if (ag.sht.measure()) { - temp = ag.sht.getTemperature(); - hum = ag.sht.getRelativeHumidity(); - Serial.printf("Temperature: %0.2f\r\n", temp); - Serial.printf(" Humidity: %d\r\n", hum); - } else { - Serial.println("Meaure SHT failed"); - } -} + measurements.Temperature = ag.sht.getTemperature(); + measurements.Humidity = ag.sht.getRelativeHumidity(); -static void sendDataToServer() { - String wifi = "\"wifi\":" + String(WiFi.RSSI()); - String rco2 = ""; - if(co2Ppm >= 0){ - rco2 = ",\"rco2\":" + String(co2Ppm); - } - String pm02 = ""; - if(pm25) { - pm02 = ",\"pm02\":" + String(pm25); - } - String rhum = ""; - if(hum >= 0){ - rhum = ",\"rhum\":" + String(rhum); - } - String payload = "{" + wifi + rco2 + pm02 + rhum + "}"; + Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature); + Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity); + Serial.printf("Temperature compensated in C: %0.2f\r\n", + measurements.Temperature); + Serial.printf("Relative Humidity compensated: %d\r\n", + measurements.Humidity); - if (apiClient.postToServer(payload) == false) { - Serial.println("Post to server failed"); - } -} - -static void dispHandler() { - String ln1 = ""; - String ln2 = ""; - String ln3 = ""; - - if (configuration.isPmStandardInUSAQI()) { - if (pm25 < 0) { - ln1 = "AQI: -"; - } else { - ln1 = "AQI:" + String(ag.pms5003.convertPm25ToUsAqi(pm25)); + // Update compensation temperature and humidity for SGP41 + if (configuration.hasSensorSGP) { + ag.sgp41.setCompensationTemperatureHumidity(measurements.Temperature, + measurements.Humidity); } } else { - if (pm25 < 0) { - ln1 = "PM :- ug"; - - } else { - ln1 = "PM :" + String(pm25) + " ug"; - } + Serial.println("SHT read failed"); } - if (co2Ppm > -1001) { - ln2 = "CO2:" + String(co2Ppm); - } else { - ln2 = "CO2: -"; - } - - String _hum = "-"; - if (hum > 0) { - _hum = String(hum); - } - - String _temp = "-"; - - if (configuration.isTemperatureUnitInF()) { - if (temp > -1001) { - _temp = String((temp * 9 / 5) + 32).substring(0, 4); - } - ln3 = _temp + " " + _hum + "%"; - } else { - if (temp > -1001) { - _temp = String(temp).substring(0, 4); - } - ln3 = _temp + " " + _hum + "%"; - } - displayShowText(ln1, ln2, ln3); -} - -static String getDevId(void) { return getNormalizedMac(); } - -static void showNr(void) { - Serial.println(); - Serial.println("Serial nr: " + getDevId()); -} - -String getNormalizedMac() { - String mac = WiFi.macAddress(); - mac.replace(":", ""); - mac.toLowerCase(); - return mac; } diff --git a/examples/BASIC/LocalServer.cpp b/examples/BASIC/LocalServer.cpp new file mode 100644 index 0000000..8970ece --- /dev/null +++ b/examples/BASIC/LocalServer.cpp @@ -0,0 +1,61 @@ +#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), server(80) {} + +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(); + 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) { + if(ag->isOne()) { + server.send(200, "application/json", config.toString()); + } else { + server.send(200, "application/json", config.toString(fwMode)); + } +} + +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 = config.getFailedMesage(); + } + 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/BASIC/LocalServer.h b/examples/BASIC/LocalServer.h new file mode 100644 index 0000000..1a943b8 --- /dev/null +++ b/examples/BASIC/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; + ESP8266WebServer 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/BASIC/OpenMetrics.cpp b/examples/BASIC/OpenMetrics.cpp new file mode 100644 index 0000000..5270768 --- /dev/null +++ b/examples/BASIC/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; + int atmpCompensated = -1; + int ahumCompensated = -1; + + if (config.hasSensorSHT) { + _temp = measure.Temperature; + _hum = measure.Humidity; + atmpCompensated = _temp; + ahumCompensated = _hum; + } + + if (config.hasSensorPMS1) { + pm01 = measure.pm01_1; + pm25 = measure.pm25_1; + pm10 = measure.pm10_1; + pm03PCount = measure.pm03PCount_1; + } + + if (config.hasSensorPMS1) { + 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 / PMS " + "sensor, in degrees Celsius", + "gauge", "celsius"); + add_metric_point("", String(_temp)); + } + if (atmpCompensated > -1001) { + add_metric("temperature_compensated", + "The compensated ambient temperature as measured by the " + "AirGradient SHT / PMS " + "sensor, in degrees Celsius", + "gauge", "celsius"); + add_metric_point("", String(atmpCompensated)); + } + if (_hum >= 0) { + add_metric( + "humidity", + "The relative humidity as measured by the AirGradient SHT sensor", + "gauge", "percent"); + add_metric_point("", String(_hum)); + } + if (ahumCompensated >= 0) { + add_metric("humidity_compensated", + "The compensated relative humidity as measured by the " + "AirGradient SHT / PMS sensor", + "gauge", "percent"); + add_metric_point("", String(ahumCompensated)); + } + + response += "# EOF\n"; + return response; +} diff --git a/examples/BASIC/OpenMetrics.h b/examples/BASIC/OpenMetrics.h new file mode 100644 index 0000000..ed890f5 --- /dev/null +++ b/examples/BASIC/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/AgOledDisplay.cpp b/src/AgOledDisplay.cpp index 329d56f..c9e23f1 100644 --- a/src/AgOledDisplay.cpp +++ b/src/AgOledDisplay.cpp @@ -6,8 +6,8 @@ /** * @brief Show dashboard temperature and humdity - * - * @param hasStatus + * + * @param hasStatus */ void OledDisplay::showTempHum(bool hasStatus) { char buf[10]; @@ -58,20 +58,20 @@ void OledDisplay::setCentralText(int y, const char *text) { DISP()->drawStr(x, y, text); } - /** * @brief Construct a new Ag Oled Display:: Ag Oled Display object - * + * * @param config AgConfiguration * @param value Measurements * @param log Serial Stream */ -OledDisplay::OledDisplay(Configuration &config, Measurements &value, Stream &log) +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 OledDisplay::setAirGradient(AirGradient *ag) { this->ag = ag; } @@ -90,23 +90,31 @@ bool OledDisplay::begin(void) { return true; } - /** Create u8g2 instance */ - u8g2 = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, U8X8_PIN_NONE); - if (u8g2 == NULL) { - logError("Create 'U8G2' failed"); - return false; - } + if (ag->isOne() || ag->isPro3_7() || ag->isPro4_2()) { + /** Create u8g2 instance */ + u8g2 = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, U8X8_PIN_NONE); + if (u8g2 == NULL) { + logError("Create 'U8G2' failed"); + return false; + } - /** Init u8g2 */ - if (DISP()->begin() == false) { - logError("U8G2 'begin' failed"); - return false; + /** Init u8g2 */ + if (DISP()->begin() == false) { + logError("U8G2 'begin' failed"); + return false; + } + } else if (ag->isBasic()) { + logInfo("DIY_BASIC init"); + ag->display.begin(Wire); + ag->display.setTextColor(1); + ag->display.clear(); + ag->display.show(); } /** Show low brightness on startup. then it's completely turn off on main * application */ int brightness = config.getDisplayBrightness(); - if(brightness == 0) { + if (brightness == 0) { setBrightness(1); } @@ -125,9 +133,13 @@ void OledDisplay::end(void) { return; } - /** Free u8g2 */ - delete DISP(); - u8g2 = NULL; + if (ag->isOne() || ag->isPro3_7() || ag->isPro4_2()) { + /** Free u8g2 */ + delete DISP(); + u8g2 = NULL; + } else if (ag->isBasic()) { + ag->display.end(); + } isBegin = false; logInfo("end"); @@ -135,10 +147,10 @@ void OledDisplay::end(void) { /** * @brief Show text on 3 line of display - * - * @param line1 - * @param line2 - * @param line3 + * + * @param line1 + * @param line2 + * @param line3 */ void OledDisplay::setText(String &line1, String &line2, String &line3) { setText(line1.c_str(), line2.c_str(), line3.c_str()); @@ -146,190 +158,256 @@ void OledDisplay::setText(String &line1, String &line2, String &line3) { /** * @brief Show text on 3 line of display - * - * @param line1 - * @param line2 - * @param line3 + * + * @param line1 + * @param line2 + * @param line3 */ void OledDisplay::setText(const char *line1, const char *line2, - const char *line3) { + const char *line3) { if (isDisplayOff) { return; } - DISP()->firstPage(); - do { - DISP()->setFont(u8g2_font_t0_16_tf); - DISP()->drawStr(1, 10, line1); - DISP()->drawStr(1, 30, line2); - DISP()->drawStr(1, 50, line3); - } while (DISP()->nextPage()); + if (ag->isOne() || ag->isPro3_7() || ag->isPro4_2()) { + DISP()->firstPage(); + do { + DISP()->setFont(u8g2_font_t0_16_tf); + DISP()->drawStr(1, 10, line1); + DISP()->drawStr(1, 30, line2); + DISP()->drawStr(1, 50, line3); + } while (DISP()->nextPage()); + } else if (ag->isBasic()) { + ag->display.clear(); + + ag->display.setCursor(1, 1); + ag->display.setText(line1); + ag->display.setCursor(1, 17); + ag->display.setText(line2); + ag->display.setCursor(1, 33); + ag->display.setText(line3); + + ag->display.show(); + } } /** * @brief Set Text on 4 line - * - * @param line1 - * @param line2 - * @param line3 - * @param line4 + * + * @param line1 + * @param line2 + * @param line3 + * @param line4 */ void OledDisplay::setText(String &line1, String &line2, String &line3, - String &line4) { + String &line4) { setText(line1.c_str(), line2.c_str(), line3.c_str(), line4.c_str()); } /** * @brief Set Text on 4 line - * - * @param line1 - * @param line2 - * @param line3 - * @param line4 + * + * @param line1 + * @param line2 + * @param line3 + * @param line4 */ void OledDisplay::setText(const char *line1, const char *line2, - const char *line3, const char *line4) { + const char *line3, const char *line4) { if (isDisplayOff) { return; } - DISP()->firstPage(); - do { - DISP()->setFont(u8g2_font_t0_16_tf); - DISP()->drawStr(1, 10, line1); - DISP()->drawStr(1, 25, line2); - DISP()->drawStr(1, 40, line3); - DISP()->drawStr(1, 55, line4); - } while (DISP()->nextPage()); + if (ag->isOne() || ag->isPro3_7() || ag->isPro4_2()) { + DISP()->firstPage(); + do { + DISP()->setFont(u8g2_font_t0_16_tf); + DISP()->drawStr(1, 10, line1); + DISP()->drawStr(1, 25, line2); + DISP()->drawStr(1, 40, line3); + DISP()->drawStr(1, 55, line4); + } while (DISP()->nextPage()); + } else if (ag->isBasic()) { + ag->display.clear(); + ag->display.setCursor(0, 0); + ag->display.setText(line1); + ag->display.setCursor(0, 10); + ag->display.setText(line2); + ag->display.setCursor(0, 20); + ag->display.setText(line3); + ag->display.show(); + } } /** * @brief Update dashboard content - * + * */ void OledDisplay::showDashboard(void) { showDashboard(NULL); } /** * @brief Update dashboard content and error status - * + * */ void OledDisplay::showDashboard(const char *status) { if (isDisplayOff) { return; } - char strBuf[10]; - - DISP()->firstPage(); - do { - DISP()->setFont(u8g2_font_t0_16_tf); - if ((status == NULL) || (strlen(status) == 0)) { - showTempHum(false); - } else { - String strStatus = "Show status: " + String(status); - logInfo(strStatus); - - int strWidth = DISP()->getStrWidth(status); - DISP()->drawStr((DISP()->getWidth() - strWidth) / 2, 10, status); - - /** Show WiFi NA*/ - if (strcmp(status, "WiFi N/A") == 0) { - DISP()->setFont(u8g2_font_t0_12_tf); - showTempHum(true); - } - } - - /** Draw horizonal line */ - DISP()->drawLine(1, 13, 128, 13); - - /** Show CO2 label */ - DISP()->setFont(u8g2_font_t0_12_tf); - DISP()->drawUTF8(1, 27, "CO2"); - - DISP()->setFont(u8g2_font_t0_22b_tf); - if (value.CO2 > 0) { - int val = 9999; - if (value.CO2 < 10000) { - val = value.CO2; - } - sprintf(strBuf, "%d", val); - } else { - sprintf(strBuf, "%s", "-"); - } - DISP()->drawStr(1, 48, strBuf); - - /** Show CO2 value index */ - DISP()->setFont(u8g2_font_t0_12_tf); - DISP()->drawStr(1, 61, "ppm"); - - /** Draw vertical line */ - DISP()->drawLine(45, 14, 45, 64); - DISP()->drawLine(82, 14, 82, 64); - - /** Draw PM2.5 label */ - DISP()->setFont(u8g2_font_t0_12_tf); - DISP()->drawStr(48, 27, "PM2.5"); - - /** Draw PM2.5 value */ - DISP()->setFont(u8g2_font_t0_22b_tf); - if (config.isPmStandardInUSAQI()) { - if (value.pm25_1 >= 0) { - sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(value.pm25_1)); - } else { - sprintf(strBuf, "%s", "-"); - } - DISP()->drawStr(48, 48, strBuf); - DISP()->setFont(u8g2_font_t0_12_tf); - DISP()->drawUTF8(48, 61, "AQI"); - } else { - if (value.pm25_1 >= 0) { - sprintf(strBuf, "%d", value.pm25_1); - } else { - sprintf(strBuf, "%s", "-"); - } - DISP()->drawStr(48, 48, strBuf); - DISP()->setFont(u8g2_font_t0_12_tf); - DISP()->drawUTF8(48, 61, "ug/m³"); - } - - /** Draw tvocIndexlabel */ - DISP()->setFont(u8g2_font_t0_12_tf); - DISP()->drawStr(85, 27, "tvoc:"); - - /** Draw tvocIndexvalue */ - if (value.TVOC >= 0) { - sprintf(strBuf, "%d", value.TVOC); - } else { - sprintf(strBuf, "%s", "-"); - } - DISP()->drawStr(85, 39, strBuf); - - /** Draw NOx label */ - DISP()->drawStr(85, 53, "NOx:"); - if (value.NOx >= 0) { - sprintf(strBuf, "%d", value.NOx); - } else { - sprintf(strBuf, "%s", "-"); - } - DISP()->drawStr(85, 63, strBuf); - } while (DISP()->nextPage()); -} - -void OledDisplay::setBrightness(int percent) { - if (percent == 0) { - isDisplayOff = true; - - // Clear display. + char strBuf[16]; + if (ag->isOne() || ag->isPro3_7() || ag->isPro4_2()) { DISP()->firstPage(); do { - } while (DISP()->nextPage()); + DISP()->setFont(u8g2_font_t0_16_tf); + if ((status == NULL) || (strlen(status) == 0)) { + showTempHum(false); + } else { + String strStatus = "Show status: " + String(status); + logInfo(strStatus); - } else { - isDisplayOff = false; - DISP()->setContrast((127 * percent) / 100); + int strWidth = DISP()->getStrWidth(status); + DISP()->drawStr((DISP()->getWidth() - strWidth) / 2, 10, status); + + /** Show WiFi NA*/ + if (strcmp(status, "WiFi N/A") == 0) { + DISP()->setFont(u8g2_font_t0_12_tf); + showTempHum(true); + } + } + + /** Draw horizonal line */ + DISP()->drawLine(1, 13, 128, 13); + + /** Show CO2 label */ + DISP()->setFont(u8g2_font_t0_12_tf); + DISP()->drawUTF8(1, 27, "CO2"); + + DISP()->setFont(u8g2_font_t0_22b_tf); + if (value.CO2 > 0) { + int val = 9999; + if (value.CO2 < 10000) { + val = value.CO2; + } + sprintf(strBuf, "%d", val); + } else { + sprintf(strBuf, "%s", "-"); + } + DISP()->drawStr(1, 48, strBuf); + + /** Show CO2 value index */ + DISP()->setFont(u8g2_font_t0_12_tf); + DISP()->drawStr(1, 61, "ppm"); + + /** Draw vertical line */ + DISP()->drawLine(45, 14, 45, 64); + DISP()->drawLine(82, 14, 82, 64); + + /** Draw PM2.5 label */ + DISP()->setFont(u8g2_font_t0_12_tf); + DISP()->drawStr(48, 27, "PM2.5"); + + /** Draw PM2.5 value */ + DISP()->setFont(u8g2_font_t0_22b_tf); + if (config.isPmStandardInUSAQI()) { + if (value.pm25_1 >= 0) { + sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(value.pm25_1)); + } else { + sprintf(strBuf, "%s", "-"); + } + DISP()->drawStr(48, 48, strBuf); + DISP()->setFont(u8g2_font_t0_12_tf); + DISP()->drawUTF8(48, 61, "AQI"); + } else { + if (value.pm25_1 >= 0) { + sprintf(strBuf, "%d", value.pm25_1); + } else { + sprintf(strBuf, "%s", "-"); + } + DISP()->drawStr(48, 48, strBuf); + DISP()->setFont(u8g2_font_t0_12_tf); + DISP()->drawUTF8(48, 61, "ug/m³"); + } + + /** Draw tvocIndexlabel */ + DISP()->setFont(u8g2_font_t0_12_tf); + DISP()->drawStr(85, 27, "tvoc:"); + + /** Draw tvocIndexvalue */ + if (value.TVOC >= 0) { + sprintf(strBuf, "%d", value.TVOC); + } else { + sprintf(strBuf, "%s", "-"); + } + DISP()->drawStr(85, 39, strBuf); + + /** Draw NOx label */ + DISP()->drawStr(85, 53, "NOx:"); + if (value.NOx >= 0) { + sprintf(strBuf, "%d", value.NOx); + } else { + sprintf(strBuf, "%s", "-"); + } + DISP()->drawStr(85, 63, strBuf); + } while (DISP()->nextPage()); + } else if (ag->isBasic()) { + ag->display.clear(); + + /** Set CO2 */ + snprintf(strBuf, sizeof(strBuf), "CO2:%d", value.CO2); + ag->display.setCursor(0, 0); + ag->display.setText(strBuf); + + /** Set PM */ + ag->display.setCursor(0, 12); + snprintf(strBuf, sizeof(strBuf), "PM2.5:%d", value.pm25_1); + ag->display.setText(strBuf); + + /** Set temperature and humidity */ + if (value.Temperature <= -1001.0f) { + if (config.isTemperatureUnitInF()) { + snprintf(strBuf, sizeof(strBuf), "T:-F"); + } else { + snprintf(strBuf, sizeof(strBuf), "T:-C"); + } + } else { + if (config.isTemperatureUnitInF()) { + float tempF = (value.Temperature * 9) / 5 + 32; + snprintf(strBuf, sizeof(strBuf), "T:%d F", (int)tempF); + } else { + snprintf(strBuf, sizeof(strBuf), "T:%d C", (int)value.Temperature); + } + } + ag->display.setCursor(0, 24); + ag->display.setText(strBuf); + + snprintf(strBuf, sizeof(strBuf), "H:%d %%", (int)value.Humidity); + ag->display.setCursor(0, 36); + ag->display.setText(strBuf); + + ag->display.show(); } } +void OledDisplay::setBrightness(int percent) { + if (ag->isOne() || ag->isPro3_7() || ag->isPro4_2()) { + if (percent == 0) { + isDisplayOff = true; + + // Clear display. + DISP()->firstPage(); + do { + } while (DISP()->nextPage()); + + } else { + isDisplayOff = false; + DISP()->setContrast((127 * percent) / 100); + } + } else if (ag->isBasic()) { + ag->display.setContrast((255 * percent) / 100); + } +} + +#ifdef ESP32 void OledDisplay::showFirmwareUpdateVersion(String version) { if (isDisplayOff) { return; @@ -410,13 +488,25 @@ void OledDisplay::showFirmwareUpdateUpToDate(void) { setCentralText(40, "up to date"); } while (DISP()->nextPage()); } +#else + +#endif void OledDisplay::showRebooting(void) { - DISP()->firstPage(); - do { - DISP()->setFont(u8g2_font_t0_16_tf); - // setCentralText(20, "Firmware Update"); - setCentralText(40, "Rebooting..."); - // setCentralText(60, String("Retry after 24h")); - } while (DISP()->nextPage()); + if (ag->isOne() || ag->isPro3_7() || ag->isPro4_2()) { + DISP()->firstPage(); + do { + DISP()->setFont(u8g2_font_t0_16_tf); + // setCentralText(20, "Firmware Update"); + setCentralText(40, "Reboot..."); + // setCentralText(60, String("Retry after 24h")); + } while (DISP()->nextPage()); + } else if (ag->isBasic()) { + ag->display.clear(); + + ag->display.setCursor(0, 20); + ag->display.setText("Rebooting..."); + + ag->display.show(); + } } diff --git a/src/AgOledDisplay.h b/src/AgOledDisplay.h index 85bd2e0..28a0cba 100644 --- a/src/AgOledDisplay.h +++ b/src/AgOledDisplay.h @@ -36,12 +36,16 @@ public: void showDashboard(void); void showDashboard(const char *status); void setBrightness(int percent); +#ifdef ESP32 void showFirmwareUpdateVersion(String version); void showFirmwareUpdateProgress(int percent); void showFirmwareUpdateSuccess(int count); void showFirmwareUpdateFailed(void); void showFirmwareUpdateSkipped(void); void showFirmwareUpdateUpToDate(void); +#else + +#endif void showRebooting(void); }; diff --git a/src/AgStateMachine.cpp b/src/AgStateMachine.cpp index 5cdc9e4..dc1cf8c 100644 --- a/src/AgStateMachine.cpp +++ b/src/AgStateMachine.cpp @@ -7,11 +7,11 @@ #define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */ -#define RGB_COLOR_R 255, 0, 0 /** Red */ -#define RGB_COLOR_G 0, 255, 0 /** Green */ -#define RGB_COLOR_Y 255, 255, 0 /** Yellow */ -#define RGB_COLOR_O 255, 165, 0 /** Organge */ -#define RGB_COLOR_P 160, 32, 240 /** Purple */ +#define RGB_COLOR_R 255, 0, 0 /** Red */ +#define RGB_COLOR_G 0, 255, 0 /** Green */ +#define RGB_COLOR_Y 255, 255, 0 /** Yellow */ +#define RGB_COLOR_O 255, 165, 0 /** Organge */ +#define RGB_COLOR_P 160, 32, 240 /** Purple */ /** * @brief Animation LED bar with color @@ -228,6 +228,9 @@ void StateMachine::co2Calibration(void) { String str = "after " + String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec"; disp.setText("Start CO2 calib", str.c_str(), ""); + } else if (ag->isBasic()) { + String str = String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec"; + disp.setText("CO2 Calib", "after", str.c_str()); } else { logInfo("Start CO2 calib after " + String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec"); @@ -238,6 +241,8 @@ void StateMachine::co2Calibration(void) { if (ag->s8.setBaselineCalibration()) { if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7()) { disp.setText("Calibration", "success", ""); + } else if (ag->isBasic()) { + disp.setText("CO2 Calib", "success", ""); } else { logInfo("CO2 Calibration: success"); } @@ -264,7 +269,10 @@ void StateMachine::co2Calibration(void) { } else { if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7()) { disp.setText("Calibration", "failure!!!", ""); - } else { + } else if(ag->isBasic()) { + disp.setText("CO2 calib", "failure!!!", ""); + } + else { logInfo("CO2 Calibration: failure!!!"); } delay(2000); @@ -279,15 +287,16 @@ void StateMachine::co2Calibration(void) { if (ag->s8.setAbcPeriod(config.getCO2CalibrationAbcDays() * 24)) { resultStr = "successful"; } - String fromStr = String(curHour/24) + " days"; - if(curHour == 0){ + String fromStr = String(curHour / 24) + " days"; + if (curHour == 0) { fromStr = "off"; } String toStr = String(config.getCO2CalibrationAbcDays()) + " days"; - if(config.getCO2CalibrationAbcDays() == 0) { + if (config.getCO2CalibrationAbcDays() == 0) { toStr = "off"; } - String msg = "Setting S8 from " + fromStr + " to " + toStr + " " + resultStr; + String msg = + "Setting S8 from " + fromStr + " to " + toStr + " " + resultStr; logInfo(msg); } } else { @@ -314,9 +323,7 @@ void StateMachine::ledBarTest(void) { } } -void StateMachine::ledBarPowerUpTest(void) { - ledBarRunTest(); -} +void StateMachine::ledBarPowerUpTest(void) { ledBarRunTest(); } void StateMachine::ledBarRunTest(void) { disp.setText("LED Test", "running", "....."); @@ -398,8 +405,8 @@ StateMachine::~StateMachine() {} * @param state */ void StateMachine::displayHandle(AgStateMachineState state) { - // Ignore handle if not ONE_INDOOR board - if (!(ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7())) { + // Ignore handle if not support display + if (!(ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7() || ag->isBasic())) { if (state == AgStateMachineCo2Calibration) { co2Calibration(); } @@ -417,11 +424,17 @@ void StateMachine::displayHandle(AgStateMachineState state) { case AgStateMachineWiFiManagerMode: case AgStateMachineWiFiManagerPortalActive: { if (wifiConnectCountDown >= 0) { - String line1 = String(wifiConnectCountDown) + "s to connect"; - String line2 = "to WiFi hotspot:"; - String line3 = "\"airgradient-"; - String line4 = ag->deviceId() + "\""; - disp.setText(line1, line2, line3, line4); + if (ag->isBasic()) { + String ssid = "\"airgradient-" + ag->deviceId() + "\" " + + String(wifiConnectCountDown) + String("s"); + disp.setText("Connect tohotspot:", ssid.c_str(), ""); + } else { + String line1 = String(wifiConnectCountDown) + "s to connect"; + String line2 = "to WiFi hotspot:"; + String line3 = "\"airgradient-"; + String line4 = ag->deviceId() + "\""; + disp.setText(line1, line2, line3, line4); + } wifiConnectCountDown--; } break; @@ -435,7 +448,12 @@ void StateMachine::displayHandle(AgStateMachineState state) { break; } case AgStateMachineWiFiOkServerConnecting: { - disp.setText("Connecting to", "Server", "..."); + if (ag->isBasic()) { + disp.setText("Connecting", "to", "Server..."); + } else { + disp.setText("Connecting to", "Server", "..."); + } + break; } case AgStateMachineWiFiOkServerConnected: { @@ -451,7 +469,11 @@ void StateMachine::displayHandle(AgStateMachineState state) { break; } case AgStateMachineWiFiOkServerOkSensorConfigFailed: { - disp.setText("Monitor not", "setup on", "dashboard"); + if (ag->isBasic()) { + disp.setText("Monitor", "not on", "dashboard"); + } else { + disp.setText("Monitor not", "setup on", "dashboard"); + } break; } case AgStateMachineWiFiLost: { @@ -502,7 +524,7 @@ void StateMachine::displayHandle(void) { displayHandle(dispState); } * */ void StateMachine::displaySetAddToDashBoard(void) { - if(addToDashBoard == false) { + if (addToDashBoard == false) { addToDashboardTime = 0; addToDashBoardToggle = true; } @@ -527,7 +549,8 @@ void StateMachine::displayWiFiConnectCountDown(int count) { void StateMachine::ledAnimationInit(void) { ledBarAnimationCount = -1; } /** - * @brief Handle LED from state, only handle LED if board type is: One Indoor or Open Air + * @brief Handle LED from state, only handle LED if board type is: One Indoor or + * Open Air * * @param state */ diff --git a/src/AgValue.cpp b/src/AgValue.cpp index 2069c57..5ae3dfe 100644 --- a/src/AgValue.cpp +++ b/src/AgValue.cpp @@ -19,7 +19,7 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi, } } - if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7()) { + if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7() || ag->isBasic()) { if (config->hasSensorPMS1) { if (this->pm01_1 >= 0) { root["pm01"] = this->pm01_1; @@ -177,7 +177,9 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi, root["bootCount"] = bootCount; if (localServer) { - root["ledMode"] = config->getLedBarModeName(); + if (ag->isOne()) { + root["ledMode"] = config->getLedBarModeName(); + } root["firmware"] = ag->getVersion(); root["model"] = AgFirmwareModeName(fwMode); } diff --git a/src/AgWiFiConnector.cpp b/src/AgWiFiConnector.cpp index 897d1a0..687ba71 100644 --- a/src/AgWiFiConnector.cpp +++ b/src/AgWiFiConnector.cpp @@ -49,8 +49,8 @@ bool WifiConnector::connect(void) { WIFI()->setSaveConfigCallback([this]() { _wifiSaveConfig(); }); WIFI()->setSaveParamsCallback([this]() { _wifiSaveParamCallback(); }); WIFI()->setConfigPortalTimeoutCallback([this]() {_wifiTimeoutCallback();}); - if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7()) { - disp.setText("Connecting to", "WiFi", "..."); + if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7() || ag->isBasic()) { + disp.setText("Connect to", "WiFi", "..."); } else { logInfo("Connecting to WiFi..."); } @@ -81,6 +81,7 @@ bool WifiConnector::connect(void) { WifiConnector *connector = (WifiConnector *)obj; while (connector->_wifiConfigPortalActive()) { connector->_wifiProcess(); + vTaskDelay(1); } vTaskDelete(NULL); }, @@ -142,7 +143,7 @@ bool WifiConnector::connect(void) { /** Show display wifi connect result failed */ if (WiFi.isConnected() == false) { sm.handleLeds(AgStateMachineWiFiManagerConnectFailed); - if (ag->isOne() || ag->isPro4_2() || ag->isPro3_7()) { + if (ag->isOne() || ag->isPro4_2() || ag->isPro3_7() || ag->isBasic()) { sm.displayHandle(AgStateMachineWiFiManagerConnectFailed); } delay(6000); @@ -247,7 +248,7 @@ void WifiConnector::_wifiProcess() { if (WiFi.isConnected() == false) { /** Display countdown */ uint32_t ms; - if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7()) { + if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7() || ag->isBasic()) { ms = (uint32_t)(millis() - dispPeriod); if (ms >= 1000) { dispPeriod = millis(); diff --git a/src/AirGradient.cpp b/src/AirGradient.cpp index 93ecdee..6ba3c16 100644 --- a/src/AirGradient.cpp +++ b/src/AirGradient.cpp @@ -66,6 +66,8 @@ bool AirGradient::isPro3_7(void) { return boardType == BoardType::DIY_PRO_INDOOR_V3_7; } +bool AirGradient::isBasic(void) { return boardType == BoardType::DIY_BASIC; } + String AirGradient::deviceId(void) { String mac = WiFi.macAddress(); mac.replace(":", ""); diff --git a/src/AirGradient.h b/src/AirGradient.h index cf875ef..e1ced70 100644 --- a/src/AirGradient.h +++ b/src/AirGradient.h @@ -149,6 +149,14 @@ public: */ bool isPro3_7(void); + /** + * @brief Check that Airgradient object is DIY_BASIC + * + * @return true Yes + * @return false No + */ + bool isBasic(void); + /** * @brief Get device Id *