diff --git a/examples/OneOpenAir/LocalConfig.cpp b/examples/OneOpenAir/LocalConfig.cpp new file mode 100644 index 00000000..5e1e76e2 --- /dev/null +++ b/examples/OneOpenAir/LocalConfig.cpp @@ -0,0 +1,397 @@ +#include "LocalConfig.h" +#include "EEPROM.h" + +void LocalConfig::printLog(String log) { + debugLog.printf("[LocalConfig] %s\r\n", log.c_str()); +} + +String LocalConfig::getLedBarModeName(UseLedBar mode) { + UseLedBar ledBarMode = mode; + if (ledBarMode == UseLedBarOff) { + return String("off"); + } else if (ledBarMode == UseLedBarPM) { + return String("pm"); + } else if (ledBarMode == UseLedBarCO2) { + return String("co2"); + } else { + return String("off"); + } +} + +void LocalConfig::saveConfig(void) { + config._check = 0; + int len = sizeof(config) - sizeof(config._check); + uint8_t *data = (uint8_t *)&config; + for (int i = 0; i < len; i++) { + config._check += data[i]; + } + EEPROM.writeBytes(0, &config, sizeof(config)); + EEPROM.commit(); + printLog("Save Config"); +} + +void LocalConfig::loadConfig(void) { + if (EEPROM.readBytes(0, &config, sizeof(config)) != sizeof(config)) { + printLog("Load configure failed"); + defaultConfig(); + } else { + uint32_t sum = 0; + uint8_t *data = (uint8_t *)&config; + int len = sizeof(config) - sizeof(config._check); + for (int i = 0; i < len; i++) { + sum += data[i]; + } + + if (sum != config._check) { + printLog("Configure validate invalid"); + defaultConfig(); + } + } +} +void LocalConfig::defaultConfig(void) { + // Default country is null + memset(config.country, 0, sizeof(config.country)); + // Default MQTT broker is null. + memset(config.mqttBroker, 0, sizeof(config.mqttBroker)); + + config.inUSAQI = false; // pmStandard = ugm3 + config.inF = false; + config.postDataToAirGradient = false; + config.locallyControlled = true; + config.displayMode = true; + config.useRGBLedBar = UseLedBar::UseLedBarCO2; + config.abcDays = 1; + config.tvocLearningOffset = 720; + config.noxLearningOffset = 720; + config.temperatureUnit = 'c'; + + saveConfig(); +} + +void LocalConfig::printConfig(void) { printLog(toString()); } + +LocalConfig::LocalConfig(Stream &debugLog) : debugLog(debugLog) {} + +LocalConfig::~LocalConfig() {} + +bool LocalConfig::begin(void) { + EEPROM.begin(512); + loadConfig(); + printConfig(); + + return true; +} + +/** + * @brief Parse JSON configura string to local configure + * + * @param data JSON string data + * @param isLocal true of data got from local, otherwise get from Aigradient + * server + * @return true Success + * @return false Failure + */ +bool LocalConfig::parse(String data, bool isLocal) { + JSONVar root = JSON.parse(data); + if (JSON.typeof_(root) == "undefined") { + printLog("Configuration JSON invalid"); + return false; + } + printLog("Parse configure success"); + + /** Is configuration changed */ + bool changed = false; + + if (JSON.typeof_(root["country"]) == "string") { + String country = root["country"]; + if (country.length() == 2) { + if (country != String(config.country)) { + changed = true; + snprintf(config.country, sizeof(config.country), country.c_str()); + printLog("Set country: " + country); + } + + // Update temperature unit if get configuration from server + if (isLocal == false) { + if (country == "US") { + if (config.temperatureUnit == 'c') { + changed = true; + config.temperatureUnit = 'f'; + } + } else { + if (config.temperatureUnit == 'f') { + changed = true; + config.temperatureUnit = 'c'; + } + } + } + } else { + printLog("Country name " + country + + " invalid. Find details here (ALPHA-2): " + "https://www.iban.com/country-codes"); + } + } + + if (JSON.typeof_(root["pmStandard"]) == "string") { + String pmStandard = root["pmStandard"]; + bool inUSAQI = true; + if (pmStandard == "ugm3") { + inUSAQI = false; + } + + if (inUSAQI != config.inUSAQI) { + config.inUSAQI = inUSAQI; + changed = true; + printLog("Set PM standard: " + pmStandard); + } + } + + if (JSON.typeof_(root["co2CalibrationRequested"]) == "boolean") { + co2CalibrationRequested = root["co2CalibrationRequested"]; + printLog("Set co2CalibrationRequested: " + String(co2CalibrationRequested)); + } + + if (JSON.typeof_(root["ledBarTestRequested"]) == "boolean") { + ledBarTestRequested = root["ledBarTestRequested"]; + printLog("Set ledBarTestRequested: " + String(ledBarTestRequested)); + } + + if (JSON.typeof_(root["ledBarMode"]) == "string") { + String mode = root["ledBarMode"]; + uint8_t ledBarMode = config.useRGBLedBar; + if (mode == "co2") { + ledBarMode = UseLedBarCO2; + } else if (mode == "pm") { + ledBarMode = UseLedBarPM; + } else if (mode == "off") { + ledBarMode = UseLedBarOff; + } else { + ledBarMode = config.useRGBLedBar; + printLog("ledBarMode value '" + mode + "' invalid"); + } + + if (ledBarMode != config.useRGBLedBar) { + config.useRGBLedBar = ledBarMode; + changed = true; + printLog("Set ledBarMode: " + mode); + } + } + + if (JSON.typeof_(root["displayMode"]) == "string") { + String mode = root["displayMode"]; + bool displayMode = false; + if (mode == "on") { + displayMode = true; + } else if (mode == "off") { + displayMode = false; + } else { + displayMode = config.displayMode; + printLog("displayMode '" + mode + "' invalid"); + } + + if (displayMode != config.displayMode) { + changed = true; + config.displayMode = displayMode; + printLog("Set displayMode: " + mode); + } + } + + if (JSON.typeof_(root["abcDays"]) == "number") { + int abcDays = root["abcDays"]; + if (abcDays != config.abcDays) { + config.abcDays = abcDays; + changed = true; + printLog("Set abcDays: " + String(abcDays)); + } + } + + if (JSON.typeof_(root["tvocLearningOffset"]) == "number") { + int tvocLearningOffset = root["tvocLearningOffset"]; + if (tvocLearningOffset != config.tvocLearningOffset) { + changed = true; + config.tvocLearningOffset = tvocLearningOffset; + printLog("Set tvocLearningOffset: " + String(tvocLearningOffset)); + } + } + + if (JSON.typeof_(root["noxLearningOffset"]) == "number") { + int noxLearningOffset = root["noxLearningOffset"]; + if (noxLearningOffset != config.noxLearningOffset) { + changed = true; + config.noxLearningOffset = noxLearningOffset; + printLog("Set noxLearningOffset: " + String(noxLearningOffset)); + } + } + + if (JSON.typeof_(root["mqttBrokerUrl"]) == "string") { + String broker = root["mqttBrokerUrl"]; + if (broker.length() < sizeof(config.mqttBroker)) { + if (broker != String(config.mqttBroker)) { + changed = true; + snprintf(config.mqttBroker, sizeof(config.mqttBroker), broker.c_str()); + printLog("Set mqttBrokerUrl: " + broker); + } + } else { + printLog("Error: mqttBroker length invalid: " + String(broker.length())); + } + } + + char temperatureUnit = 0; + if (JSON.typeof_(root["temperatureUnit"]) == "string") { + String unit = root["temperatureUnit"]; + if (unit == "c" || unit == "C") { + temperatureUnit = 'c'; + } else if (unit == "f" || unit == "F") { + temperatureUnit = 'f'; + } else { + temperatureUnit = 0; + } + } + + if (temperatureUnit != config.temperatureUnit) { + changed = true; + config.temperatureUnit = temperatureUnit; + if (temperatureUnit == 0) { + printLog("set temperatureUnit: null"); + } else { + printLog("set temperatureUnit: " + String(temperatureUnit)); + } + } + + if (JSON.typeof_(root["postDataToAirGradient"]) == "boolean") { + bool post = root["postDataToAirGradient"]; + if (post != config.postDataToAirGradient) { + changed = true; + config.postDataToAirGradient = post; + printLog("Set postDataToAirGradient: " + String(post)); + } + } + + if (JSON.typeof_(root["locallyControlled"]) == "boolean") { + bool locallyControlled = root["locallyControlled"]; + if (locallyControlled != config.locallyControlled) { + changed = true; + config.locallyControlled = locallyControlled; + printLog("set locallyControlled: " + String(locallyControlled)); + } + } + + /** Parse data only got from AirGradient server */ + if (isLocal == false) { + if (JSON.typeof_(root["model"]) == "string") { + String model = root["model"]; + if (model.length() < sizeof(config.model)) { + if (model != String(config.model)) { + changed = true; + snprintf(config.model, sizeof(config.model), model.c_str()); + } + } else { + printLog("Error: modal name length invalid: " + String(model.length())); + } + } + } + + if (changed) { + saveConfig(); + } + printConfig(); + + return true; +} + +String LocalConfig::toString(void) { + JSONVar root; + + /** "country" */ + root["Country"] = String(config.country); + + /** "pmStandard" */ + if (config.inUSAQI) { + root["pmStandard"] = "USAQI"; + } else { + root["pmStandard"] = "ugm3"; + } + + /** co2CalibrationRequested */ + /** ledBarTestRequested */ + + /** "ledBarMode" */ + root["ledBarMode"] = getLedBarModeName(); + + /** "displayMode" */ + root["displayMode"] = config.displayMode; + + /** "abcDays" */ + root["abcDays"] = config.abcDays; + + /** "tvocLearningOffset" */ + root["tvocLearningOffset"] = config.tvocLearningOffset; + + /** "noxLearningOffset" */ + root["noxLearningOffset"] = config.noxLearningOffset; + + /** "mqttBrokerUrl" */ + root["mqttBrokerUrl"] = String(config.mqttBroker); + + /** "temperatureUnit" */ + root["temperatureUnit"] = String(config.temperatureUnit); + + /** "locallyControlled" */ + root["locallyControlled"] = config.locallyControlled; + + /** "postDataToAirGradient" */ + root["postDataToAirGradient"] = config.postDataToAirGradient; + + return JSON.stringify(root); +} + +bool LocalConfig::isTemperatureUnitInF(void) { + return (config.temperatureUnit == 'f'); +} + +String LocalConfig::getCountry(void) { return String(config.country); } + +bool LocalConfig::isPmStandardInUSAQI(void) { return config.inUSAQI; } + +int LocalConfig::getCO2CalirationAbcDays(void) { return config.abcDays; } + +UseLedBar LocalConfig::getLedBarMode(void) { + return (UseLedBar)config.useRGBLedBar; +} + +String LocalConfig::getLedBarModeName(void) { + return getLedBarModeName((UseLedBar)config.useRGBLedBar); +} + +bool LocalConfig::getDisplayMode(void) { return config.displayMode; } + +String LocalConfig::getMqttBrokerUri(void) { return String(config.mqttBroker); } + +bool LocalConfig::isPostDataToAirGradient(void) { + return config.postDataToAirGradient; +} + +bool LocalConfig::isLocallyControlled(void) { return config.locallyControlled; } + +bool LocalConfig::isCo2CalibrationRequested(void) { + bool requested = co2CalibrationRequested; + co2CalibrationRequested = false; // clear requested + return requested; +} + +bool LocalConfig::isLedBarTestRequested(void) { + bool requested = ledBarTestRequested; + ledBarTestRequested = false; + return requested; +} + +/** + * @brief Reset default configure + */ +void LocalConfig::reset(void) { + defaultConfig(); + printLog("Reset to default configure"); + printConfig(); +} + +String LocalConfig::getModel(void) { return String(config.model); } diff --git a/examples/OneOpenAir/LocalConfig.h b/examples/OneOpenAir/LocalConfig.h new file mode 100644 index 00000000..3e799117 --- /dev/null +++ b/examples/OneOpenAir/LocalConfig.h @@ -0,0 +1,66 @@ +#ifndef _LOCAL_CONFIG_H_ +#define _LOCAL_CONFIG_H_ + +#include +#include +#include + +class LocalConfig { +private: + struct Config { + char model[20]; + char country[3]; /** Country name has only 2 character, ex: TH = Thailand */ + char mqttBroker[256]; /** MQTT broker URI */ + bool inUSAQI; /** If PM standard "ugm3" inUSAQI = false, otherwise is true + */ + bool inF; /** Temperature unit F */ + bool postDataToAirGradient; /** If true, monitor will not POST data to + airgradient server. Make sure no error + message shown on monitor */ + bool locallyControlled; /** If true, configuration from airgradient server + will be ignored */ + bool displayMode; /** true if enable display */ + uint8_t useRGBLedBar; + uint8_t abcDays; + int tvocLearningOffset; + int noxLearningOffset; + char temperatureUnit; // 'f' or 'c' + + uint32_t _check; + }; + struct Config config; + bool co2CalibrationRequested; + bool ledBarTestRequested; + Stream &debugLog; + + void printLog(String log); + String getLedBarModeName(UseLedBar mode); + void saveConfig(void); + void loadConfig(void); + void defaultConfig(void); + void printConfig(void); + +public: + LocalConfig(Stream &debugLog); + ~LocalConfig(); + + bool begin(void); + bool parse(String data, bool isLocal); + String toString(void); + bool isTemperatureUnitInF(void); + String getCountry(void); + bool isPmStandardInUSAQI(void); + int getCO2CalirationAbcDays(void); + UseLedBar getLedBarMode(void); + String getLedBarModeName(void); + bool getDisplayMode(void); + String getMqttBrokerUri(void); + bool isPostDataToAirGradient(void); + bool isLocallyControlled(void); + bool isCo2CalibrationRequested(void); + bool isLedBarTestRequested(void); + void reset(void); + String getModel(void); +}; + +#endif /** _LOCAL_CONFIG_H_ */ diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 626aa9ba..8133a025 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -47,6 +47,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License #include #include "EEPROM.h" +#include "LocalConfig.h" #include #include #include @@ -116,15 +117,6 @@ enum { #define I2C_SCL_PIN 6 #define OLED_I2C_ADDR 0x3C -/** - * @brief Use use LED bar state - */ -typedef enum { - UseLedBarOff, /** Don't use LED bar */ - UseLedBarPM, /** Use LED bar for PMS */ - UseLedBarCO2, /** Use LED bar for CO2 */ -} UseLedBar; - enum { FW_MODE_I_9PSL, /** ONE_INDOOR */ FW_MODE_O_1PST, /** PMS5003T, S8 and SGP41 */ @@ -171,6 +163,8 @@ private: */ class AgServer { public: + AgServer(LocalConfig &localConfig) : config(localConfig) {} + /** * @brief Initialize airgradient server, it's load the server configuration if * failed load it to default. @@ -179,20 +173,6 @@ public: void begin(void) { configFailed = false; serverFailed = false; - loadConfig(); - } - - /** - * @brief Reset local config into default value. - * - */ - void defaultReset(void) { - config.inF = false; - config.inUSAQI = false; - memset(config.models, 0, sizeof(config.models)); - memset(config.mqttBrokers, 0, sizeof(config.mqttBrokers)); - config.useRGBLedBar = UseLedBarCO2; - saveConfig(); } /** @@ -204,6 +184,11 @@ public: * @return false Failure */ bool fetchServerConfiguration(String id) { + if (config.isLocallyControlled()) { + Serial.println("Ignore fetch server configuration"); + return false; + } + String uri = "http://hw.airgradient.com/sensors/airgradient:" + id + "/one/config"; @@ -230,108 +215,7 @@ public: client.end(); Serial.println("Get server config: " + respContent); - /** Parse JSON */ - JSONVar root = JSON.parse(respContent); - if (JSON.typeof(root) == "undefined") { - /** JSON invalid */ - return false; - } - - /** Get "country" */ - bool inF = false; - if (JSON.typeof_(root["country"]) == "string") { - String _country = root["country"]; - country = _country; - - if (country == "US") { - inF = true; - } else { - inF = false; - } - } - - /** Get "pmsStandard" */ - bool inUSAQI = false; - if (JSON.typeof_(root["pmStandard"]) == "string") { - String standard = root["pmStandard"]; - if (standard == "ugm3") { - inUSAQI = false; - } else { - inUSAQI = true; - } - } - - /** Get "co2CalibrationRequested" */ - if (JSON.typeof_(root["co2CalibrationRequested"]) == "boolean") { - co2Calib = root["co2CalibrationRequested"]; - } else { - co2Calib = false; - } - - /** Get "ledBarMode" */ - uint8_t ledBarMode = UseLedBarOff; - if (JSON.typeof_(root["ledBarMode"]) == "string") { - String mode = root["ledBarMode"]; - ledBarMode = parseLedBarMode(mode); - } - - /** Get model */ - bool _saveConfig = false; - if (JSON.typeof_(root["model"]) == "string") { - String model = root["model"]; - if (model.length()) { - int len = model.length() < sizeof(config.models) - ? model.length() - : sizeof(config.models); - if (model != String(config.models)) { - memset(config.models, 0, sizeof(config.models)); - memcpy(config.models, model.c_str(), len); - _saveConfig = true; - } - } - } - - /** Get "mqttBrokerUrl" */ - if (JSON.typeof_(root["mqttBrokerUrl"]) == "string") { - String mqtt = root["mqttBrokerUrl"]; - if (mqtt.length()) { - int len = mqtt.length() < sizeof(config.mqttBrokers) - ? mqtt.length() - : sizeof(config.mqttBrokers); - if (mqtt != String(config.mqttBrokers)) { - memset(config.mqttBrokers, 0, sizeof(config.mqttBrokers)); - memcpy(config.mqttBrokers, mqtt.c_str(), len); - _saveConfig = true; - } - } - } - - /** Get 'abcDays' */ - if (JSON.typeof_(root["abcDays"]) == "number") { - co2AbcCalib = root["abcDays"]; - } else { - co2AbcCalib = -1; - } - - /** Get "ledBarTestRequested" */ - if (JSON.typeof_(root["ledBarTestRequested"]) == "boolean") { - ledBarTestRequested = root["ledBarTestRequested"]; - } else { - ledBarTestRequested = false; - } - - /** Show configuration */ - showServerConfig(); - if (_saveConfig || (inF != config.inF) || (inUSAQI != config.inUSAQI) || - (ledBarMode != config.useRGBLedBar)) { - config.inF = inF; - config.inUSAQI = inUSAQI; - config.useRGBLedBar = ledBarMode; - - saveConfig(); - } - - return true; + return config.parse(respContent, false); } /** @@ -343,6 +227,11 @@ public: * @return false Failure */ bool postToServer(String id, String payload) { + if (config.isPostDataToAirGradient() == false) { + Serial.println("Ignore post to Airgrdient server"); + return true; + } + if (WiFi.isConnected() == false) { return false; } @@ -371,22 +260,6 @@ public: return false; } - /** - * @brief Get temperature configuration unit - * - * @return true F unit - * @return false C Unit - */ - bool isTemperatureUnitF(void) { return config.inF; } - - /** - * @brief Get PMS standard unit - * - * @return true USAQI - * @return false ugm3 - */ - bool isPMSinUSAQI(void) { return config.inUSAQI; } - /** * @brief Get status of get server configuration is failed * @@ -403,189 +276,10 @@ public: */ bool isServerFailed(void) { return serverFailed; } - /** - * @brief Get request calibration CO2 - * - * @return true Requested. If result = true, it's clear after function call - * @return false Not-requested - */ - bool isCo2Calib(void) { - bool ret = co2Calib; - if (ret) { - co2Calib = false; - } - return ret; - } - - /** - * @brief Get request LedBar test - * - * @return true Requested. If result = true, it's clear after function call - * @return false Not-requested - */ - bool isLedBarTestRequested(void) { - bool ret = ledBarTestRequested; - if (ret) { - ledBarTestRequested = false; - } - return ret; - } - - /** - * @brief Get the Co2 auto calib period - * - * @return int days, -1 if invalid. - */ - int getCo2AbcDaysConfig(void) { return co2AbcCalib; } - - /** - * @brief Get device configuration model name - * - * @return String Model name, empty string if server failed - */ - String getModelName(void) { return String(config.models); } - - /** - * @brief Get mqttBroker url - * - * @return String Broker url, empty if server failed - */ - String getMqttBroker(void) { return String(config.mqttBrokers); } - - /** - * @brief Show server configuration parameter - */ - void showServerConfig(void) { - Serial.println("Server configuration: "); - Serial.printf("inF: %s\r\n", config.inF ? "true" : "false"); - Serial.printf("inUSAQI: %s\r\n", config.inUSAQI ? "true" : "false"); - Serial.printf("useRGBLedBar: %d\r\n", (int)config.useRGBLedBar); - Serial.printf("Model: %s\r\n", config.models); - Serial.printf("MQTT Broker: %s\r\n", config.mqttBrokers); - Serial.printf("S8 calibration period: %d\r\n", co2AbcCalib); - } - - /** - * @brief Get server config led bar mode - * - * @return UseLedBar - */ - UseLedBar getLedBarMode(void) { return (UseLedBar)config.useRGBLedBar; } - - /** - * @brief Return the name of the led bare mode. - * - * @return String - */ - String getLedBarModeName(void) { - UseLedBar ledBarMode = getLedBarMode(); - if (ledBarMode == UseLedBarOff) { - return String("off"); - } else if (ledBarMode == UseLedBarPM) { - return String("pm"); - } else if (ledBarMode == UseLedBarCO2) { - return String("co2"); - } else { - return String("off"); - } - } - - /** - * @brief Get the Country - * - * @return String - */ - String getCountry(void) { return country; } - private: - bool configFailed; /** Flag indicate get server configuration failed */ - bool serverFailed; /** Flag indicate post data to server failed */ - bool co2Calib; /** Is co2Ppmcalibration requset */ - bool ledBarTestRequested; /** */ - int co2AbcCalib = -1; /** update auto calibration number of day */ - String country; /***/ - - struct config_s { - bool inF; - bool inUSAQI; - uint8_t useRGBLedBar; - char models[20]; - char mqttBrokers[256]; - uint32_t checksum; - }; - struct config_s config; - - /** - * @brief Set server configuration default - */ - void defaultConfig(void) { - config.inF = false; - config.inUSAQI = false; - config.useRGBLedBar = UseLedBarCO2; - memset(config.models, 0, sizeof(config.models)); - memset(config.mqttBrokers, 0, sizeof(config.mqttBrokers)); - - Serial.println("Load config default"); - saveConfig(); - } - - /** - * @brief Get local server configuration - */ - void loadConfig(void) { - if (EEPROM.readBytes(0, &config, sizeof(config)) != sizeof(config)) { - Serial.println("Load configure failed"); - defaultConfig(); - } else { - uint32_t sum = 0; - uint8_t *data = (uint8_t *)&config; - for (int i = 0; i < sizeof(config) - 4; i++) { - sum += data[i]; - } - if (sum != config.checksum) { - Serial.println("config checksum failed"); - defaultConfig(); - } - } - - showServerConfig(); - } - - /** - * @brief Save server configuration - */ - void saveConfig(void) { - config.checksum = 0; - uint8_t *data = (uint8_t *)&config; - for (int i = 0; i < sizeof(config) - 4; i++) { - config.checksum += data[i]; - } - - EEPROM.writeBytes(0, &config, sizeof(config)); - EEPROM.commit(); - Serial.println("Save config"); - } - - /** - * @brief Parse LED bar mode - * - * @param mode LED mode - * @return UseLedBar - */ - UseLedBar parseLedBarMode(String mode) { - UseLedBar ledBarMode = UseLedBarOff; - if (mode == "co2") { - ledBarMode = UseLedBarCO2; - } else if (mode == "pm") { - ledBarMode = UseLedBarPM; - } else if (mode == "off") { - ledBarMode = UseLedBarOff; - } else { - ledBarMode = UseLedBarOff; - } - - return ledBarMode; - } + bool configFailed; /** Flag indicate get server configuration failed */ + bool serverFailed; /** Flag indicate post data to server failed */ + LocalConfig &config; }; static void mqtt_event_handler(void *handler_args, esp_event_base_t base, @@ -724,7 +418,9 @@ public: static AgMqtt agMqtt; static TaskHandle_t mqttTask = NULL; -static AgServer agServer; +static LocalConfig localConfig(Serial); +static AgServer agServer(localConfig); + static AirGradient *ag; static U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE); @@ -791,6 +487,7 @@ static int countPosition; const int targetCount = 20; static bool ledBarButtonTest = false; +static bool localConfigUpdate = false; static void boardInit(void); static void failedHandler(String msg); @@ -828,13 +525,14 @@ AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, tvocUpdate); AgSchedule wdgFeedSchedule(60000, wdgFeedUpdate); void setup() { - EEPROM.begin(512); - /** Serial for print debug message */ Serial.begin(115200); delay(100); /** For bester show log */ showNr(); + /** Initialize local configure */ + localConfig.begin(); + /** Init I2C */ Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN); delay(1000); @@ -861,7 +559,7 @@ void setup() { ledTest(); } else { /** Check LED mode to disabled LED */ - if (agServer.getLedBarMode() == UseLedBarOff) { + if (localConfig.getLedBarMode() == UseLedBarOff) { ag->ledBar.setEnable(false); } connectToWifi(); @@ -877,8 +575,8 @@ void setup() { webServerInit(); /** MQTT init */ - if (agServer.getMqttBroker().isEmpty() == false) { - if (agMqtt.begin(agServer.getMqttBroker())) { + if (localConfig.getMqttBrokerUri().isEmpty() == false) { + if (agMqtt.begin(localConfig.getMqttBrokerUri())) { createMqttTask(); Serial.println("MQTT client init success"); } else { @@ -900,7 +598,7 @@ void setup() { ledSmHandler(APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED); delay(DISPLAY_DELAY_SHOW_CONTENT_MS); } else { - ag->ledBar.setEnable(agServer.getLedBarMode() != UseLedBarOff); + ag->ledBar.setEnable(localConfig.getLedBarMode() != UseLedBarOff); } } else { offlineMode = true; @@ -943,7 +641,9 @@ void loop() { tvocSchedule.run(); } - if (offlineMode) { + /** Auto reset external watchdog timer on offline mode and + * postDataToAirGradient disabled. */ + if (offlineMode || (localConfig.isPostDataToAirGradient() == false)) { wdgFeedSchedule.run(); } @@ -966,6 +666,11 @@ void loop() { ag->pms5003t_2.handle(); } } + + if (localConfigUpdate) { + localConfigUpdate = false; + configUpdateHandle(); + } } static void setTestColor(char color) { @@ -1259,6 +964,8 @@ static void webServerInit(void) { webServer.on("/measures/current", HTTP_GET, webServerMeasureCurrentGet); // Make it possible to query this device from Prometheus/OpenMetrics. webServer.on("/metrics", HTTP_GET, webServerMetricsGet); + webServer.on("/config", HTTP_GET, localConfigGet); + webServer.on("/config", HTTP_PUT, localConfigPut); webServer.begin(); MDNS.addService("_airgradient", "_tcp", 80); MDNS.addServiceTxt("_airgradient", "_tcp", "model", getFirmwareModeName()); @@ -1273,6 +980,21 @@ static void webServerInit(void) { Serial.printf("Webserver init: %s.local\r\n", host.c_str()); } +static void localConfigGet() { + webServer.send(200, "application/json", localConfig.toString()); +} +static void localConfigPut() { + String data = webServer.arg(0); + String response = "Failure"; + if (localConfig.parse(data, true)) { + localConfigUpdate = true; + response = "Success"; + } else { + Serial.println("PUT data invalid"); + } + webServer.send(200, "text/plain", response); +} + static String getServerSyncData(bool localServer) { JSONVar root; root["wifi"] = WiFi.RSSI(); @@ -1406,7 +1128,7 @@ static String getServerSyncData(bool localServer) { root["boot"] = bootCount; if (localServer) { - root["ledMode"] = agServer.getLedBarModeName(); + root["ledMode"] = localConfig.getLedBarModeName(); root["firmwareVersion"] = ag->getVersion(); root["fwMode"] = getFirmwareModeName(); } @@ -1438,7 +1160,7 @@ static void createMqttTask(void) { } } }, - "mqtt-task", 1024 * 3, NULL, 6, &mqttTask); + "mqtt-task", 1024 * 2, NULL, 6, &mqttTask); if (mqttTask == NULL) { Serial.println("Creat mqttTask failed"); @@ -1484,7 +1206,7 @@ static void factoryConfigReset(void) { wifiManager.resetSettings(); /** Reset local config */ - agServer.defaultReset(); + localConfig.reset(); if (isOneIndoor()) { displayShowText("Factory reset", "successful", ""); @@ -1597,7 +1319,7 @@ static void displayShowDashboard(String err) { if ((err == NULL) || err.isEmpty()) { /** Show temperature */ - if (agServer.isTemperatureUnitF()) { + if (localConfig.isTemperatureUnitInF()) { if (temp > -1001) { float tempF = (temp * 9 / 5) + 32; sprintf(strBuf, "%.1f°F", tempF); @@ -1632,7 +1354,7 @@ static void displayShowDashboard(String err) { if (err == "WiFi N/A") { u8g2.setFont(u8g2_font_t0_12_tf); - if (agServer.isTemperatureUnitF()) { + if (localConfig.isTemperatureUnitInF()) { if (temp > -1001) { float tempF = (temp * 9 / 5) + 32; sprintf(strBuf, "%.1f", tempF); @@ -1697,7 +1419,7 @@ static void displayShowDashboard(String err) { /** Draw PM2.5 value */ u8g2.setFont(u8g2_font_t0_22b_tf); - if (agServer.isPMSinUSAQI()) { + if (localConfig.isPmStandardInUSAQI()) { if (pm25_1 >= 0) { sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(pm25_1)); } else { @@ -2210,55 +1932,61 @@ static void failedHandler(String msg) { */ static void updateServerConfiguration(void) { if (agServer.fetchServerConfiguration(getDevId())) { - if (agServer.isCo2Calib()) { - if (hasSensorS8) { - co2Calibration(); - } else { - Serial.println("CO2 S8 not available, calibration ignored"); - } + configUpdateHandle(); + } +} + +static void configUpdateHandle() { + if (localConfig.isCo2CalibrationRequested()) { + if (hasSensorS8) { + co2Calibration(); + } else { + Serial.println("CO2 S8 not available, calibration ignored"); } + } - // Update LED bar - ag->ledBar.setEnable(agServer.getLedBarMode() != UseLedBarOff); + // Update LED bar + ag->ledBar.setEnable(localConfig.getLedBarMode() != UseLedBarOff); - if (agServer.getCo2AbcDaysConfig() > 0) { - if (hasSensorS8) { - int newHour = agServer.getCo2AbcDaysConfig() * 24; - Serial.printf("Requested abcDays setting: %d days (%d hours)\r\n", - agServer.getCo2AbcDaysConfig(), newHour); - int curHour = ag->s8.getAbcPeriod(); - Serial.printf("Current S8 abcDays setting: %d (hours)\r\n", curHour); - if (curHour == newHour) { - Serial.println("'abcDays' unchanged"); + if (localConfig.getCO2CalirationAbcDays() > 0) { + if (hasSensorS8) { + int newHour = localConfig.getCO2CalirationAbcDays() * 24; + Serial.printf("Requested abcDays setting: %d days (%d hours)\r\n", + localConfig.getCO2CalirationAbcDays(), newHour); + int curHour = ag->s8.getAbcPeriod(); + Serial.printf("Current S8 abcDays setting: %d (hours)\r\n", curHour); + if (curHour == newHour) { + Serial.println("'abcDays' unchanged"); + } else { + if (ag->s8.setAbcPeriod(localConfig.getCO2CalirationAbcDays() * 24) == + false) { + Serial.println("Set S8 abcDays period failed"); } else { - if (ag->s8.setAbcPeriod(agServer.getCo2AbcDaysConfig() * 24) == - false) { - Serial.println("Set S8 abcDays period failed"); - } else { - Serial.println("Set S8 abcDays period success"); - } + Serial.println("Set S8 abcDays period success"); } - } else { - Serial.println("CO2 S8 not available, set 'abcDays' ignored"); } + } else { + Serial.println("CO2 S8 not available, set 'abcDays' ignored"); } + } - if (agServer.isLedBarTestRequested()) { - if (agServer.getCountry() == "TH") { - ledTest2Min(); - } else { - ledTest(); - } + if (localConfig.isLedBarTestRequested()) { + if (localConfig.getCountry() == "TH") { + ledTest2Min(); + } else { + ledTest(); } + } - String mqttUri = agServer.getMqttBroker(); - if (mqttUri != agMqtt.getUri()) { - agMqtt.end(); + String mqttUri = localConfig.getMqttBrokerUri(); + if (mqttUri != agMqtt.getUri()) { + agMqtt.end(); - if (mqttTask != NULL) { - vTaskDelete(mqttTask); - mqttTask = NULL; - } + if (mqttTask != NULL) { + vTaskDelete(mqttTask); + mqttTask = NULL; + } + if (mqttUri.length() > 0) { if (agMqtt.begin(mqttUri)) { Serial.println("Connect to MQTT broker successful"); createMqttTask(); @@ -2703,7 +2431,7 @@ static void dispSmHandler(int sm) { * @brief Handle change LED color base on sensor value of CO2 and PMS */ static void sensorLedColorHandler(void) { - switch (agServer.getLedBarMode()) { + switch (localConfig.getLedBarMode()) { case UseLedBarCO2: setRGBledCO2color(co2Ppm); break; diff --git a/src/AirGradient.h b/src/AirGradient.h index 01a451d7..f592149b 100644 --- a/src/AirGradient.h +++ b/src/AirGradient.h @@ -13,6 +13,16 @@ #include "Sgp41/Sgp41.h" #include "Sht/Sht.h" +/** + * @brief RGB LED bar mode for ONE_INDOOR board + * + */ +enum UseLedBar { + UseLedBarOff, /** Don't use LED bar */ + UseLedBarPM, /** Use LED bar for PMS */ + UseLedBarCO2, /** Use LED bar for CO2 */ +}; + /** * @brief Class with define all the sensor has supported by Airgradient. Each * sensor usage must be init before use. @@ -109,16 +119,16 @@ class AirGradient { /** * @brief Get the Board Name object - * - * @return String + * + * @return String */ String getBoardName(void); /** * @brief Round double value with for 2 decimal - * + * * @param valuem Round value - * @return double + * @return double */ double round2(double value);