Update BASIC.ino example

This commit is contained in:
Phat Nguyen
2024-06-24 18:34:24 +07:00
parent 57c33e4900
commit dbc63194e6
12 changed files with 1079 additions and 471 deletions

View File

@ -31,190 +31,376 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
#include "AgConfigure.h" #include "AgConfigure.h"
#include "AgSchedule.h" #include "AgSchedule.h"
#include "AgWiFiConnector.h" #include "AgWiFiConnector.h"
#include "LocalServer.h"
#include "OpenMetrics.h"
#include "MqttClient.h"
#include <AirGradient.h> #include <AirGradient.h>
#include <ESP8266HTTPClient.h> #include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiClient.h> #include <WiFiClient.h>
#define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */ #define LED_BAR_ANIMATION_PERIOD 100 /** ms */
#define WIFI_CONNECT_RETRY_MS 10000 /** ms */ #define DISP_UPDATE_INTERVAL 2500 /** ms */
#define LED_BAR_COUNT_INIT_VALUE (-1) /** */ #define SERVER_CONFIG_SYNC_INTERVAL 60000 /** ms */
#define LED_BAR_ANIMATION_PERIOD 100 /** ms */ #define SERVER_SYNC_INTERVAL 60000 /** ms */
#define DISP_UPDATE_INTERVAL 5000 /** ms */ #define MQTT_SYNC_INTERVAL 60000 /** ms */
#define SERVER_CONFIG_SYNC_INTERVAL 60000 /** ms */ #define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
#define SERVER_SYNC_INTERVAL 60000 /** ms */ #define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */ #define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */ #define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
#define SENSOR_CO2_UPDATE_INTERVAL 5000 /** ms */ #define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */
#define SENSOR_PM_UPDATE_INTERVAL 5000 /** ms */ #define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */ #define FIRMWARE_CHECK_FOR_UPDATE_MS (60 * 60 * 1000) /** ms */
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
#define WIFI_HOTSPOT_PASSWORD_DEFAULT \
"cleanair" /** default WiFi AP password \
*/
/** Create airgradient instance for 'DIY_BASIC' board */ static AirGradient ag(DIY_BASIC);
static AirGradient ag = AirGradient(DIY_BASIC);
static Configuration configuration(Serial); static Configuration configuration(Serial);
static AgApiClient apiClient(Serial, configuration); static AgApiClient apiClient(Serial, configuration);
static Measurements measurements; static Measurements measurements;
static OledDisplay oledDisp(configuration, measurements, Serial); static OledDisplay oledDisplay(configuration, measurements, Serial);
static StateMachine sm(oledDisp, Serial, measurements, configuration); static StateMachine stateMachine(oledDisplay, Serial, measurements,
static WifiConnector wifiConnector(oledDisp, Serial, sm, configuration); 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 pmFailCount = 0;
static int pm25 = -1; static int getCO2FailCount = 0;
static float temp = -1001; static AgFirmwareMode fwMode = FW_MODE_I_BASIC_40PS;
static int hum = -1;
static String fwNewVersion;
static void boardInit(void); static void boardInit(void);
static void failedHandler(String msg); static void failedHandler(String msg);
static void executeCo2Calibration(void); static void configurationUpdateSchedule(void);
static void updateServerConfiguration(void); static void appDispHandler(void);
static void co2Update(void); static void oledDisplaySchedule(void);
static void pmUpdate(void); static void updateTvoc(void);
static void tempHumUpdate(void); static void updatePm(void);
static void sendDataToServer(void); static void sendDataToServer(void);
static void dispHandler(void); static void tempHumUpdate(void);
static String getDevId(void); static void co2Update(void);
static void showNr(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; AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplaySchedule);
bool hasSensorPMS = true;
bool hasSensorSHT = true;
int pmFailCount = 0;
int getCO2FailCount = 0;
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL, AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
updateServerConfiguration); configurationUpdateSchedule);
AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer); AgSchedule agApiPostSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
AgSchedule dispSchedule(DISP_UPDATE_INTERVAL, dispHandler);
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update); 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 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() { void setup() {
/** Serial for print debug message */
Serial.begin(115200); 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 */ /** Init I2C */
Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin()); Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin());
Wire.endTransmission(1);
delay(1000); 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(); boardInit();
/** Init AirGradient server */ /** Connecting wifi */
apiClient.begin(); bool connectToWifi = false;
apiClient.setAirGradient(&ag);
configuration.setAirGradient(&ag);
wifiConnector.setAirGradient(&ag);
/** Show boot display */ connectToWifi = !configuration.isOfflineMode();
displayShowText("DIY basic", "Lib:" + ag.getVersion(), ""); if (connectToWifi) {
delay(2000); apiClient.begin();
/** WiFi connect */ if (wifiConnector.connect()) {
// connectToWifi(); if (wifiConnector.isConnected()) {
if (wifiConnector.connect()) { mdnsInit();
if (WiFi.status() == WL_CONNECTED) { localServer.begin();
sendDataToAg(); initMqtt();
sendDataToAg();
apiClient.fetchServerConfiguration(); apiClient.fetchServerConfiguration();
if (configuration.isCo2CalibrationRequested()) { configSchedule.update();
executeCo2Calibration(); 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 */ /** Set offline mode without saving, cause wifi is not configured */
ag.display.clear(); if (wifiConnector.hasConfigurated() == false) {
ag.display.setCursor(1, 1); Serial.println("Set offline mode cause wifi is not configurated");
ag.display.setText("Warm Up"); configuration.setOfflineModeWithoutSave(true);
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();
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() { void loop() {
/** Handle schedule */
dispLedSchedule.run();
configSchedule.run(); configSchedule.run();
serverSchedule.run(); agApiPostSchedule.run();
dispSchedule.run();
if (hasSensorS8) { if (configuration.hasSensorS8) {
co2Schedule.run(); co2Schedule.run();
} }
if (hasSensorPMS) { if (configuration.hasSensorPMS1) {
pmsSchedule.run(); pmsSchedule.run();
ag.pms5003.handle();
} }
if (hasSensorSHT) { if (configuration.hasSensorSHT) {
tempHumSchedule.run(); 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(); wifiConnector.handle();
/** Read PMS on loop */ /** factory reset handle */
ag.pms5003.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() { static void sendDataToAg() {
// delay(1500); /** Change oledDisplay and led state */
if (apiClient.sendPing(wifiConnector.RSSI(), 0)) { stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting);
// Ping Server succses
delay(1500);
if (apiClient.sendPing(wifiConnector.RSSI(), measurements.bootCount)) {
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected);
} else { } 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) { void dispSensorNotFound(String ss) {
char buf[9]; oledDisplay.setText("Sensor", ss.c_str(), "not found");
ag.display.clear(); delay(2000);
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);
} }
static void boardInit(void) { 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) { if (ag.sht.begin(Wire) == false) {
hasSensorSHT = false; Serial.println("SHTx sensor not found");
Serial.println("SHT sensor not found"); configuration.hasSensorSHT = false;
dispSensorNotFound("SHT");
} }
/** CO2 init */ /** Init S8 CO2 sensor */
if (ag.s8.begin(&Serial) == false) { if (ag.s8.begin(&Serial) == false) {
Serial.println("CO2 S8 snsor not found"); Serial.println("CO2 S8 sensor not found");
hasSensorS8 = false; configuration.hasSensorS8 = false;
dispSensorNotFound("S8");
} }
/** PMS init */ /** Init PMS5003 */
configuration.hasSensorPMS1 = true;
configuration.hasSensorPMS2 = false;
if (ag.pms5003.begin(&Serial) == false) { if (ag.pms5003.begin(&Serial) == false) {
Serial.println("PMS sensor not found"); Serial.println("PMS sensor not found");
hasSensorPMS = false; configuration.hasSensorPMS1 = false;
dispSensorNotFound("PMS");
} }
/** Display init */ /** Set S8 CO2 abc days period */
ag.display.begin(Wire); if (configuration.hasSensorS8) {
ag.display.setTextColor(1); if (ag.s8.setAbcPeriod(configuration.getCO2CalibrationAbcDays() * 24)) {
ag.display.clear(); Serial.println("Set S8 AbcDays successful");
ag.display.show(); } else {
delay(100); Serial.println("Set S8 AbcDays failure");
}
}
localServer.setFwMode(fwMode);
} }
static void failedHandler(String msg) { static void failedHandler(String msg) {
@ -224,181 +410,160 @@ static void failedHandler(String msg) {
} }
} }
static void executeCo2Calibration(void) { static void configurationUpdateSchedule(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) {
if (apiClient.fetchServerConfiguration()) { if (apiClient.fetchServerConfiguration()) {
if (configuration.isCo2CalibrationRequested()) { configUpdateHandle();
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");
}
}
} }
} }
static void co2Update() { static void configUpdateHandle() {
int value = ag.s8.getCo2(); if (configuration.isUpdated() == false) {
if (value >= 0) { return;
co2Ppm = value; }
getCO2FailCount = 0;
Serial.printf("CO2 index: %d\r\n", co2Ppm); stateMachine.executeCo2Calibration();
} else {
getCO2FailCount++; String mqttUri = configuration.getMqttBrokerUri();
Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount); if (mqttClient.isCurrentUri(mqttUri) == false) {
if (getCO2FailCount >= 3) { mqttClient.end();
co2Ppm = -1; 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) { if (ag.pms5003.isFailed() == false) {
pm25 = ag.pms5003.getPm25Ae(); measurements.pm01_1 = ag.pms5003.getPm01Ae();
Serial.printf("PMS2.5: %d\r\n", pm25); 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; pmFailCount = 0;
} else { } else {
Serial.printf("PM read failed, %d", pmFailCount);
pmFailCount++; pmFailCount++;
Serial.printf("PMS read failed: %d\r\n", pmFailCount);
if (pmFailCount >= 3) { 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()) { if (ag.sht.measure()) {
temp = ag.sht.getTemperature(); measurements.Temperature = ag.sht.getTemperature();
hum = ag.sht.getRelativeHumidity(); measurements.Humidity = ag.sht.getRelativeHumidity();
Serial.printf("Temperature: %0.2f\r\n", temp);
Serial.printf(" Humidity: %d\r\n", hum);
} else {
Serial.println("Meaure SHT failed");
}
}
static void sendDataToServer() { Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature);
String wifi = "\"wifi\":" + String(WiFi.RSSI()); Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity);
String rco2 = ""; Serial.printf("Temperature compensated in C: %0.2f\r\n",
if(co2Ppm >= 0){ measurements.Temperature);
rco2 = ",\"rco2\":" + String(co2Ppm); Serial.printf("Relative Humidity compensated: %d\r\n",
} measurements.Humidity);
String pm02 = "";
if(pm25) {
pm02 = ",\"pm02\":" + String(pm25);
}
String rhum = "";
if(hum >= 0){
rhum = ",\"rhum\":" + String(rhum);
}
String payload = "{" + wifi + rco2 + pm02 + rhum + "}";
if (apiClient.postToServer(payload) == false) { // Update compensation temperature and humidity for SGP41
Serial.println("Post to server failed"); if (configuration.hasSensorSGP) {
} ag.sgp41.setCompensationTemperatureHumidity(measurements.Temperature,
} measurements.Humidity);
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));
} }
} else { } else {
if (pm25 < 0) { Serial.println("SHT read failed");
ln1 = "PM :- ug";
} else {
ln1 = "PM :" + String(pm25) + " ug";
}
} }
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;
} }

View File

@ -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; }

View File

@ -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 <Arduino.h>
#include <ESP8266WebServer.h>
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_ */

View File

@ -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;
}

View File

@ -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_ */

View File

@ -58,7 +58,6 @@ void OledDisplay::setCentralText(int y, const char *text) {
DISP()->drawStr(x, y, text); DISP()->drawStr(x, y, text);
} }
/** /**
* @brief Construct a new Ag Oled Display:: Ag Oled Display object * @brief Construct a new Ag Oled Display:: Ag Oled Display object
* *
@ -66,7 +65,8 @@ void OledDisplay::setCentralText(int y, const char *text) {
* @param value Measurements * @param value Measurements
* @param log Serial Stream * @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) {} : PrintLog(log, "OledDisplay"), config(config), value(value) {}
/** /**
@ -90,23 +90,31 @@ bool OledDisplay::begin(void) {
return true; return true;
} }
/** Create u8g2 instance */ if (ag->isOne() || ag->isPro3_7() || ag->isPro4_2()) {
u8g2 = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, U8X8_PIN_NONE); /** Create u8g2 instance */
if (u8g2 == NULL) { u8g2 = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, U8X8_PIN_NONE);
logError("Create 'U8G2' failed"); if (u8g2 == NULL) {
return false; logError("Create 'U8G2' failed");
} return false;
}
/** Init u8g2 */ /** Init u8g2 */
if (DISP()->begin() == false) { if (DISP()->begin() == false) {
logError("U8G2 'begin' failed"); logError("U8G2 'begin' failed");
return false; 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 /** Show low brightness on startup. then it's completely turn off on main
* application */ * application */
int brightness = config.getDisplayBrightness(); int brightness = config.getDisplayBrightness();
if(brightness == 0) { if (brightness == 0) {
setBrightness(1); setBrightness(1);
} }
@ -125,9 +133,13 @@ void OledDisplay::end(void) {
return; return;
} }
/** Free u8g2 */ if (ag->isOne() || ag->isPro3_7() || ag->isPro4_2()) {
delete DISP(); /** Free u8g2 */
u8g2 = NULL; delete DISP();
u8g2 = NULL;
} else if (ag->isBasic()) {
ag->display.end();
}
isBegin = false; isBegin = false;
logInfo("end"); logInfo("end");
@ -152,18 +164,31 @@ void OledDisplay::setText(String &line1, String &line2, String &line3) {
* @param line3 * @param line3
*/ */
void OledDisplay::setText(const char *line1, const char *line2, void OledDisplay::setText(const char *line1, const char *line2,
const char *line3) { const char *line3) {
if (isDisplayOff) { if (isDisplayOff) {
return; return;
} }
DISP()->firstPage(); if (ag->isOne() || ag->isPro3_7() || ag->isPro4_2()) {
do { DISP()->firstPage();
DISP()->setFont(u8g2_font_t0_16_tf); do {
DISP()->drawStr(1, 10, line1); DISP()->setFont(u8g2_font_t0_16_tf);
DISP()->drawStr(1, 30, line2); DISP()->drawStr(1, 10, line1);
DISP()->drawStr(1, 50, line3); DISP()->drawStr(1, 30, line2);
} while (DISP()->nextPage()); 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();
}
} }
/** /**
@ -175,7 +200,7 @@ void OledDisplay::setText(const char *line1, const char *line2,
* @param line4 * @param line4
*/ */
void OledDisplay::setText(String &line1, String &line2, String &line3, 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()); setText(line1.c_str(), line2.c_str(), line3.c_str(), line4.c_str());
} }
@ -188,19 +213,30 @@ void OledDisplay::setText(String &line1, String &line2, String &line3,
* @param line4 * @param line4
*/ */
void OledDisplay::setText(const char *line1, const char *line2, void OledDisplay::setText(const char *line1, const char *line2,
const char *line3, const char *line4) { const char *line3, const char *line4) {
if (isDisplayOff) { if (isDisplayOff) {
return; return;
} }
DISP()->firstPage(); if (ag->isOne() || ag->isPro3_7() || ag->isPro4_2()) {
do { DISP()->firstPage();
DISP()->setFont(u8g2_font_t0_16_tf); do {
DISP()->drawStr(1, 10, line1); DISP()->setFont(u8g2_font_t0_16_tf);
DISP()->drawStr(1, 25, line2); DISP()->drawStr(1, 10, line1);
DISP()->drawStr(1, 40, line3); DISP()->drawStr(1, 25, line2);
DISP()->drawStr(1, 55, line4); DISP()->drawStr(1, 40, line3);
} while (DISP()->nextPage()); 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();
}
} }
/** /**
@ -218,118 +254,160 @@ void OledDisplay::showDashboard(const char *status) {
return; return;
} }
char strBuf[10]; char strBuf[16];
if (ag->isOne() || ag->isPro3_7() || ag->isPro4_2()) {
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.
DISP()->firstPage(); DISP()->firstPage();
do { 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 { int strWidth = DISP()->getStrWidth(status);
isDisplayOff = false; DISP()->drawStr((DISP()->getWidth() - strWidth) / 2, 10, status);
DISP()->setContrast((127 * percent) / 100);
/** 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) { void OledDisplay::showFirmwareUpdateVersion(String version) {
if (isDisplayOff) { if (isDisplayOff) {
return; return;
@ -410,13 +488,25 @@ void OledDisplay::showFirmwareUpdateUpToDate(void) {
setCentralText(40, "up to date"); setCentralText(40, "up to date");
} while (DISP()->nextPage()); } while (DISP()->nextPage());
} }
#else
#endif
void OledDisplay::showRebooting(void) { void OledDisplay::showRebooting(void) {
DISP()->firstPage(); if (ag->isOne() || ag->isPro3_7() || ag->isPro4_2()) {
do { DISP()->firstPage();
DISP()->setFont(u8g2_font_t0_16_tf); do {
// setCentralText(20, "Firmware Update"); DISP()->setFont(u8g2_font_t0_16_tf);
setCentralText(40, "Rebooting..."); // setCentralText(20, "Firmware Update");
// setCentralText(60, String("Retry after 24h")); setCentralText(40, "Reboot...");
} while (DISP()->nextPage()); // 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();
}
} }

View File

@ -36,12 +36,16 @@ public:
void showDashboard(void); void showDashboard(void);
void showDashboard(const char *status); void showDashboard(const char *status);
void setBrightness(int percent); void setBrightness(int percent);
#ifdef ESP32
void showFirmwareUpdateVersion(String version); void showFirmwareUpdateVersion(String version);
void showFirmwareUpdateProgress(int percent); void showFirmwareUpdateProgress(int percent);
void showFirmwareUpdateSuccess(int count); void showFirmwareUpdateSuccess(int count);
void showFirmwareUpdateFailed(void); void showFirmwareUpdateFailed(void);
void showFirmwareUpdateSkipped(void); void showFirmwareUpdateSkipped(void);
void showFirmwareUpdateUpToDate(void); void showFirmwareUpdateUpToDate(void);
#else
#endif
void showRebooting(void); void showRebooting(void);
}; };

View File

@ -7,11 +7,11 @@
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */ #define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
#define RGB_COLOR_R 255, 0, 0 /** Red */ #define RGB_COLOR_R 255, 0, 0 /** Red */
#define RGB_COLOR_G 0, 255, 0 /** Green */ #define RGB_COLOR_G 0, 255, 0 /** Green */
#define RGB_COLOR_Y 255, 255, 0 /** Yellow */ #define RGB_COLOR_Y 255, 255, 0 /** Yellow */
#define RGB_COLOR_O 255, 165, 0 /** Organge */ #define RGB_COLOR_O 255, 165, 0 /** Organge */
#define RGB_COLOR_P 160, 32, 240 /** Purple */ #define RGB_COLOR_P 160, 32, 240 /** Purple */
/** /**
* @brief Animation LED bar with color * @brief Animation LED bar with color
@ -228,6 +228,9 @@ void StateMachine::co2Calibration(void) {
String str = String str =
"after " + String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec"; "after " + String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec";
disp.setText("Start CO2 calib", str.c_str(), ""); 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 { } else {
logInfo("Start CO2 calib after " + logInfo("Start CO2 calib after " +
String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec"); String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec");
@ -238,6 +241,8 @@ void StateMachine::co2Calibration(void) {
if (ag->s8.setBaselineCalibration()) { if (ag->s8.setBaselineCalibration()) {
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7()) { if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7()) {
disp.setText("Calibration", "success", ""); disp.setText("Calibration", "success", "");
} else if (ag->isBasic()) {
disp.setText("CO2 Calib", "success", "");
} else { } else {
logInfo("CO2 Calibration: success"); logInfo("CO2 Calibration: success");
} }
@ -264,7 +269,10 @@ void StateMachine::co2Calibration(void) {
} else { } else {
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7()) { if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7()) {
disp.setText("Calibration", "failure!!!", ""); disp.setText("Calibration", "failure!!!", "");
} else { } else if(ag->isBasic()) {
disp.setText("CO2 calib", "failure!!!", "");
}
else {
logInfo("CO2 Calibration: failure!!!"); logInfo("CO2 Calibration: failure!!!");
} }
delay(2000); delay(2000);
@ -279,15 +287,16 @@ void StateMachine::co2Calibration(void) {
if (ag->s8.setAbcPeriod(config.getCO2CalibrationAbcDays() * 24)) { if (ag->s8.setAbcPeriod(config.getCO2CalibrationAbcDays() * 24)) {
resultStr = "successful"; resultStr = "successful";
} }
String fromStr = String(curHour/24) + " days"; String fromStr = String(curHour / 24) + " days";
if(curHour == 0){ if (curHour == 0) {
fromStr = "off"; fromStr = "off";
} }
String toStr = String(config.getCO2CalibrationAbcDays()) + " days"; String toStr = String(config.getCO2CalibrationAbcDays()) + " days";
if(config.getCO2CalibrationAbcDays() == 0) { if (config.getCO2CalibrationAbcDays() == 0) {
toStr = "off"; toStr = "off";
} }
String msg = "Setting S8 from " + fromStr + " to " + toStr + " " + resultStr; String msg =
"Setting S8 from " + fromStr + " to " + toStr + " " + resultStr;
logInfo(msg); logInfo(msg);
} }
} else { } else {
@ -314,9 +323,7 @@ void StateMachine::ledBarTest(void) {
} }
} }
void StateMachine::ledBarPowerUpTest(void) { void StateMachine::ledBarPowerUpTest(void) { ledBarRunTest(); }
ledBarRunTest();
}
void StateMachine::ledBarRunTest(void) { void StateMachine::ledBarRunTest(void) {
disp.setText("LED Test", "running", "....."); disp.setText("LED Test", "running", ".....");
@ -398,8 +405,8 @@ StateMachine::~StateMachine() {}
* @param state * @param state
*/ */
void StateMachine::displayHandle(AgStateMachineState state) { void StateMachine::displayHandle(AgStateMachineState state) {
// Ignore handle if not ONE_INDOOR board // Ignore handle if not support display
if (!(ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7())) { if (!(ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7() || ag->isBasic())) {
if (state == AgStateMachineCo2Calibration) { if (state == AgStateMachineCo2Calibration) {
co2Calibration(); co2Calibration();
} }
@ -417,11 +424,17 @@ void StateMachine::displayHandle(AgStateMachineState state) {
case AgStateMachineWiFiManagerMode: case AgStateMachineWiFiManagerMode:
case AgStateMachineWiFiManagerPortalActive: { case AgStateMachineWiFiManagerPortalActive: {
if (wifiConnectCountDown >= 0) { if (wifiConnectCountDown >= 0) {
String line1 = String(wifiConnectCountDown) + "s to connect"; if (ag->isBasic()) {
String line2 = "to WiFi hotspot:"; String ssid = "\"airgradient-" + ag->deviceId() + "\" " +
String line3 = "\"airgradient-"; String(wifiConnectCountDown) + String("s");
String line4 = ag->deviceId() + "\""; disp.setText("Connect tohotspot:", ssid.c_str(), "");
disp.setText(line1, line2, line3, line4); } 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--; wifiConnectCountDown--;
} }
break; break;
@ -435,7 +448,12 @@ void StateMachine::displayHandle(AgStateMachineState state) {
break; break;
} }
case AgStateMachineWiFiOkServerConnecting: { case AgStateMachineWiFiOkServerConnecting: {
disp.setText("Connecting to", "Server", "..."); if (ag->isBasic()) {
disp.setText("Connecting", "to", "Server...");
} else {
disp.setText("Connecting to", "Server", "...");
}
break; break;
} }
case AgStateMachineWiFiOkServerConnected: { case AgStateMachineWiFiOkServerConnected: {
@ -451,7 +469,11 @@ void StateMachine::displayHandle(AgStateMachineState state) {
break; break;
} }
case AgStateMachineWiFiOkServerOkSensorConfigFailed: { 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; break;
} }
case AgStateMachineWiFiLost: { case AgStateMachineWiFiLost: {
@ -502,7 +524,7 @@ void StateMachine::displayHandle(void) { displayHandle(dispState); }
* *
*/ */
void StateMachine::displaySetAddToDashBoard(void) { void StateMachine::displaySetAddToDashBoard(void) {
if(addToDashBoard == false) { if (addToDashBoard == false) {
addToDashboardTime = 0; addToDashboardTime = 0;
addToDashBoardToggle = true; addToDashBoardToggle = true;
} }
@ -527,7 +549,8 @@ void StateMachine::displayWiFiConnectCountDown(int count) {
void StateMachine::ledAnimationInit(void) { ledBarAnimationCount = -1; } 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 * @param state
*/ */

View File

@ -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 (config->hasSensorPMS1) {
if (this->pm01_1 >= 0) { if (this->pm01_1 >= 0) {
root["pm01"] = this->pm01_1; root["pm01"] = this->pm01_1;
@ -177,7 +177,9 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
root["bootCount"] = bootCount; root["bootCount"] = bootCount;
if (localServer) { if (localServer) {
root["ledMode"] = config->getLedBarModeName(); if (ag->isOne()) {
root["ledMode"] = config->getLedBarModeName();
}
root["firmware"] = ag->getVersion(); root["firmware"] = ag->getVersion();
root["model"] = AgFirmwareModeName(fwMode); root["model"] = AgFirmwareModeName(fwMode);
} }

View File

@ -49,8 +49,8 @@ bool WifiConnector::connect(void) {
WIFI()->setSaveConfigCallback([this]() { _wifiSaveConfig(); }); WIFI()->setSaveConfigCallback([this]() { _wifiSaveConfig(); });
WIFI()->setSaveParamsCallback([this]() { _wifiSaveParamCallback(); }); WIFI()->setSaveParamsCallback([this]() { _wifiSaveParamCallback(); });
WIFI()->setConfigPortalTimeoutCallback([this]() {_wifiTimeoutCallback();}); WIFI()->setConfigPortalTimeoutCallback([this]() {_wifiTimeoutCallback();});
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7()) { if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_7() || ag->isBasic()) {
disp.setText("Connecting to", "WiFi", "..."); disp.setText("Connect to", "WiFi", "...");
} else { } else {
logInfo("Connecting to WiFi..."); logInfo("Connecting to WiFi...");
} }
@ -81,6 +81,7 @@ bool WifiConnector::connect(void) {
WifiConnector *connector = (WifiConnector *)obj; WifiConnector *connector = (WifiConnector *)obj;
while (connector->_wifiConfigPortalActive()) { while (connector->_wifiConfigPortalActive()) {
connector->_wifiProcess(); connector->_wifiProcess();
vTaskDelay(1);
} }
vTaskDelete(NULL); vTaskDelete(NULL);
}, },
@ -142,7 +143,7 @@ bool WifiConnector::connect(void) {
/** Show display wifi connect result failed */ /** Show display wifi connect result failed */
if (WiFi.isConnected() == false) { if (WiFi.isConnected() == false) {
sm.handleLeds(AgStateMachineWiFiManagerConnectFailed); 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); sm.displayHandle(AgStateMachineWiFiManagerConnectFailed);
} }
delay(6000); delay(6000);
@ -247,7 +248,7 @@ void WifiConnector::_wifiProcess() {
if (WiFi.isConnected() == false) { if (WiFi.isConnected() == false) {
/** Display countdown */ /** Display countdown */
uint32_t ms; 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); ms = (uint32_t)(millis() - dispPeriod);
if (ms >= 1000) { if (ms >= 1000) {
dispPeriod = millis(); dispPeriod = millis();

View File

@ -66,6 +66,8 @@ bool AirGradient::isPro3_7(void) {
return boardType == BoardType::DIY_PRO_INDOOR_V3_7; return boardType == BoardType::DIY_PRO_INDOOR_V3_7;
} }
bool AirGradient::isBasic(void) { return boardType == BoardType::DIY_BASIC; }
String AirGradient::deviceId(void) { String AirGradient::deviceId(void) {
String mac = WiFi.macAddress(); String mac = WiFi.macAddress();
mac.replace(":", ""); mac.replace(":", "");

View File

@ -149,6 +149,14 @@ public:
*/ */
bool isPro3_7(void); 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 * @brief Get device Id
* *