Files
arduino/src/AgConfigure.cpp

1239 lines
34 KiB
C++
Raw Normal View History

2024-04-03 07:04:55 +07:00
#include "AgConfigure.h"
#include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
#if ESP32
#include "FS.h"
#include "SPIFFS.h"
#else
#include "EEPROM.h"
#endif
#include <time.h>
2024-05-08 12:22:34 +07:00
#define EEPROM_CONFIG_SIZE 1024
#define CONFIG_FILE_NAME "/AgConfigure_Configuration.json"
2024-04-03 07:04:55 +07:00
const char *CONFIGURATION_CONTROL_NAME[] = {
[ConfigurationControlLocal] = "local",
2024-04-03 21:26:04 +07:00
[ConfigurationControlCloud] = "cloud",
2024-04-03 07:04:55 +07:00
[ConfigurationControlBoth] = "both"};
2024-04-05 06:39:59 +07:00
const char *LED_BAR_MODE_NAMES[] = {
[LedBarModeOff] = "off",
[LedBarModePm] = "pm",
[LedBarModeCO2] = "co2",
};
2024-04-03 07:04:55 +07:00
2024-05-08 12:22:34 +07:00
#define JSON_PROP_NAME(name) jprop_##name
#define JSON_PROP_DEF(name) const char *JSON_PROP_NAME(name) = #name
JSON_PROP_DEF(model);
JSON_PROP_DEF(country);
JSON_PROP_DEF(pmStandard);
JSON_PROP_DEF(ledBarMode);
JSON_PROP_DEF(displayMode);
JSON_PROP_DEF(abcDays);
JSON_PROP_DEF(tvocLearningOffset);
JSON_PROP_DEF(noxLearningOffset);
JSON_PROP_DEF(mqttBrokerUrl);
JSON_PROP_DEF(temperatureUnit);
JSON_PROP_DEF(configurationControl);
JSON_PROP_DEF(postDataToAirGradient);
JSON_PROP_DEF(ledbarBrightness);
JSON_PROP_DEF(displayBrightness);
JSON_PROP_DEF(co2CalibrationRequested);
JSON_PROP_DEF(ledBarTestRequested);
2024-05-09 14:56:40 +07:00
JSON_PROP_DEF(lastOta);
#define jprop_model_default ""
#define jprop_country_default ""
#define jprop_pmStandard_default getPMStandardString(false)
#define jprop_ledBarMode_default getLedBarModeName(LedBarMode::LedBarModeCO2)
#define jprop_displayMode_default getDisplayModeString(true)
#define jprop_abcDays_default 8
#define jprop_tvocLearningOffset_default 12
#define jprop_noxLearningOffset_default 12
#define jprop_mqttBrokerUrl_default ""
#define jprop_temperatureUnit_default "c"
#define jprop_configurationControl_default String(CONFIGURATION_CONTROL_NAME[ConfigurationControl::ConfigurationControlBoth])
#define jprop_postDataToAirGradient_default true
#define jprop_ledbarBrightness_default 100
#define jprop_displayBrightness_default 100
#define jprop_lastOta_default 0
2024-05-08 12:22:34 +07:00
JSONVar jconfig;
static bool jsonTypeInvalid(JSONVar root, String validType) {
String type = JSON.typeof_(root);
if (type == validType || type == "undefined" || type == "unknown" ||
type == "null") {
return false;
}
return true;
}
2024-04-05 06:39:59 +07:00
/**
* @brief Get LedBarMode Name
2024-04-07 16:39:01 +07:00
*
2024-04-05 06:39:59 +07:00
* @param mode LedBarMode value
* @return String
*/
2024-04-07 16:39:01 +07:00
String Configuration::getLedBarModeName(LedBarMode mode) {
if (mode == LedBarModeOff) {
return String(LED_BAR_MODE_NAMES[LedBarModeOff]);
} else if (mode == LedBarModePm) {
return String(LED_BAR_MODE_NAMES[LedBarModePm]);
} else if (mode == LedBarModeCO2) {
return String(LED_BAR_MODE_NAMES[LedBarModeCO2]);
2024-04-03 07:04:55 +07:00
}
return String("unknown");
2024-04-03 07:04:55 +07:00
}
2024-04-05 06:39:59 +07:00
/**
* @brief Save configure to device storage (EEPROM)
2024-04-07 16:39:01 +07:00
*
2024-04-05 06:39:59 +07:00
*/
2024-04-07 16:39:01 +07:00
void Configuration::saveConfig(void) {
2024-05-08 12:22:34 +07:00
String data = toString();
int len = data.length();
#ifdef ESP8266
2024-05-08 12:22:34 +07:00
for (int i = 0; i < len; i++) {
EEPROM.write(i, data[i]);
}
EEPROM.commit();
#else
File file = SPIFFS.open(CONFIG_FILE_NAME, "w", true);
if (file && !file.isDirectory()) {
2024-05-08 12:22:34 +07:00
if (file.write((const uint8_t *)data.c_str(), len) != len) {
logError("Write SPIFFS file failed");
}
file.close();
} else {
logError("Open SPIFFS file to write failed");
}
#endif
2024-04-03 21:26:04 +07:00
logInfo("Save Config");
2024-04-03 07:04:55 +07:00
}
2024-04-07 16:39:01 +07:00
void Configuration::loadConfig(void) {
2024-05-08 12:22:34 +07:00
char *buf = (char *)malloc(EEPROM_CONFIG_SIZE);
if (buf == NULL) {
logError("Malloc read file buffer failed");
return;
}
memset(buf, 0, EEPROM_CONFIG_SIZE);
#ifdef ESP8266
2024-05-08 12:22:34 +07:00
for (int i = 0; i < EEPROM_CONFIG_SIZE; i++) {
buf[i] = EEPROM.read(i);
}
#else
File file = SPIFFS.open(CONFIG_FILE_NAME);
if (file && !file.isDirectory()) {
logInfo("Reading file...");
if(file.readBytes(buf, file.size()) != file.size()) {
logError("Reading file: failed - size not match");
} else {
logInfo("Reading file: success");
}
file.close();
2024-04-03 07:04:55 +07:00
} else {
2024-05-08 12:22:34 +07:00
SPIFFS.format();
2024-04-03 07:04:55 +07:00
}
2024-05-08 12:22:34 +07:00
#endif
toConfig(buf);
free(buf);
2024-04-03 07:04:55 +07:00
}
2024-04-05 06:39:59 +07:00
/**
* @brief Set configuration default
2024-04-07 16:39:01 +07:00
*
2024-04-05 06:39:59 +07:00
*/
2024-04-07 16:39:01 +07:00
void Configuration::defaultConfig(void) {
2024-05-08 12:22:34 +07:00
jconfig = JSON.parse("{}");
jconfig[jprop_country] = jprop_country_default;
jconfig[jprop_mqttBrokerUrl] = jprop_mqttBrokerUrl_default;
jconfig[jprop_configurationControl] = jprop_configurationControl_default;
jconfig[jprop_pmStandard] = jprop_pmStandard_default;
jconfig[jprop_temperatureUnit] = jprop_temperatureUnit_default;
jconfig[jprop_postDataToAirGradient] = jprop_postDataToAirGradient_default;
jconfig[jprop_displayMode] = getDisplayModeString(true);
jconfig[jprop_ledbarBrightness] = jprop_ledbarBrightness_default;
jconfig[jprop_displayBrightness] = jprop_displayBrightness_default;
jconfig[jprop_ledBarMode] = jprop_ledbarBrightness_default;
jconfig[jprop_tvocLearningOffset] = jprop_tvocLearningOffset_default;
jconfig[jprop_noxLearningOffset] = jprop_noxLearningOffset_default;
jconfig[jprop_abcDays] = jprop_abcDays_default;
jconfig[jprop_model] = jprop_model_default;
2024-04-03 07:04:55 +07:00
saveConfig();
}
2024-04-05 06:39:59 +07:00
/**
* @brief Show configuration as JSON string message over log
2024-04-07 16:39:01 +07:00
*
2024-04-05 06:39:59 +07:00
*/
2024-05-08 12:22:34 +07:00
void Configuration::printConfig(void) {
String cfg = toString();
logInfo(cfg);
}
2024-04-03 07:04:55 +07:00
2024-04-05 06:39:59 +07:00
/**
* @brief Construct a new Ag Configure:: Ag Configure object
2024-04-07 16:39:01 +07:00
*
2024-04-05 06:39:59 +07:00
* @param debugLog Serial Stream
*/
2024-04-07 16:39:01 +07:00
Configuration::Configuration(Stream &debugLog)
: PrintLog(debugLog, "Configure") {}
2024-04-03 07:04:55 +07:00
2024-04-05 06:39:59 +07:00
/**
* @brief Destroy the Ag Configure:: Ag Configure object
2024-04-07 16:39:01 +07:00
*
2024-04-05 06:39:59 +07:00
*/
2024-04-07 16:39:01 +07:00
Configuration::~Configuration() {}
2024-04-03 07:04:55 +07:00
2024-04-05 06:39:59 +07:00
/**
* @brief Initialize configuration
2024-04-07 16:39:01 +07:00
*
2024-04-05 06:39:59 +07:00
* @return true Success
* @return false Failure
*/
2024-04-07 16:39:01 +07:00
bool Configuration::begin(void) {
#ifdef ESP32
if (!SPIFFS.begin(true)) {
logError("Init SPIFFS failed");
return false;
}
#else
EEPROM.begin(EEPROM_CONFIG_SIZE);
#endif
2024-04-03 07:04:55 +07:00
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
*/
2024-04-07 16:39:01 +07:00
bool Configuration::parse(String data, bool isLocal) {
logInfo("Parse configure: " + data);
2024-04-03 07:04:55 +07:00
JSONVar root = JSON.parse(data);
2024-04-08 10:15:45 +07:00
failedMessage = "";
2024-05-08 12:22:34 +07:00
if (root == undefined) {
2024-04-08 10:15:45 +07:00
failedMessage = "JSON invalid";
logError(failedMessage);
2024-04-03 07:04:55 +07:00
return false;
}
2024-04-03 21:26:04 +07:00
logInfo("Parse configure success");
2024-04-03 07:04:55 +07:00
/** Is configuration changed */
bool changed = false;
/** Get ConfigurationControl */
2024-05-08 12:22:34 +07:00
String lasCtrl = jconfig[jprop_configurationControl];
if (isLocal) {
2024-05-08 12:22:34 +07:00
if (JSON.typeof_(root[jprop_configurationControl]) == "string") {
String ctrl = root[jprop_configurationControl];
if (ctrl ==
String(CONFIGURATION_CONTROL_NAME
[ConfigurationControl::ConfigurationControlBoth]) ||
ctrl ==
String(CONFIGURATION_CONTROL_NAME
[ConfigurationControl::ConfigurationControlLocal]) ||
ctrl ==
String(CONFIGURATION_CONTROL_NAME
[ConfigurationControl::ConfigurationControlCloud])) {
if (ctrl != lasCtrl) {
jconfig[jprop_configurationControl] = ctrl;
changed = true;
}
2024-05-08 12:22:34 +07:00
} else {
failedMessage =
jsonValueInvalidMessage(String(jprop_configurationControl), ctrl);
jsonInvalid();
return false;
}
2024-04-03 07:04:55 +07:00
} else {
2024-05-08 12:22:34 +07:00
if (jsonTypeInvalid(root[jprop_configurationControl], "string")) {
failedMessage = jsonTypeInvalidMessage(
String(jprop_configurationControl), "string");
2024-04-08 10:15:45 +07:00
jsonInvalid();
return false;
}
2024-04-03 07:04:55 +07:00
}
if (changed) {
changed = false;
saveConfig();
2024-05-08 12:22:34 +07:00
configLogInfo(String(jprop_configurationControl), lasCtrl,
jconfig[jprop_configurationControl]);
}
2024-05-08 12:22:34 +07:00
if (jconfig[jprop_configurationControl] ==
String(CONFIGURATION_CONTROL_NAME
[ConfigurationControl::ConfigurationControlCloud])) {
2024-04-08 10:15:45 +07:00
failedMessage = "Local configure ignored";
jsonInvalid();
return false;
}
} else {
2024-05-08 12:22:34 +07:00
if (jconfig[jprop_configurationControl] ==
String(CONFIGURATION_CONTROL_NAME
[ConfigurationControl::ConfigurationControlLocal])) {
2024-04-08 10:15:45 +07:00
failedMessage = "Cloud configure ignored";
jsonInvalid();
return false;
}
2024-04-03 07:04:55 +07:00
}
2024-04-08 10:15:45 +07:00
char temperatureUnit = 0;
2024-05-08 12:22:34 +07:00
if (JSON.typeof_(root[jprop_country]) == "string") {
String country = root[jprop_country];
2024-04-03 07:04:55 +07:00
if (country.length() == 2) {
2024-05-08 12:22:34 +07:00
String oldCountry = jconfig[jprop_country];
if (country != oldCountry) {
2024-04-03 07:04:55 +07:00
changed = true;
2024-05-08 12:22:34 +07:00
configLogInfo(String(jprop_country), oldCountry, country);
jconfig[jprop_country] = country;
2024-04-03 07:04:55 +07:00
}
} else {
2024-04-08 10:15:45 +07:00
failedMessage = "Country name " + country +
" invalid. Find details here (ALPHA-2): "
"https://www.iban.com/country-codes";
jsonInvalid();
return false;
}
} else {
2024-05-08 12:22:34 +07:00
if (jsonTypeInvalid(root[jprop_country], "string")) {
failedMessage = jsonTypeInvalidMessage(String(jprop_country), "string");
2024-04-08 10:15:45 +07:00
jsonInvalid();
return false;
2024-04-03 07:04:55 +07:00
}
}
2024-05-08 12:22:34 +07:00
if (JSON.typeof_(root[jprop_pmStandard]) == "string") {
String standard = root[jprop_pmStandard];
if (standard == getPMStandardString(true) ||
standard == getPMStandardString(false)) {
String oldStandard = jconfig[jprop_pmStandard];
if (standard != oldStandard) {
configLogInfo(String(jprop_pmStandard), oldStandard, standard);
jconfig[jprop_pmStandard] = standard;
changed = true;
}
2024-04-08 10:15:45 +07:00
} else {
2024-05-08 12:22:34 +07:00
failedMessage =
jsonValueInvalidMessage(String(jprop_pmStandard), standard);
2024-04-08 10:15:45 +07:00
jsonInvalid();
return false;
2024-04-03 07:04:55 +07:00
}
2024-04-08 10:15:45 +07:00
} else {
2024-05-08 12:22:34 +07:00
if (jsonTypeInvalid(root[jprop_pmStandard], "string")) {
failedMessage =
jsonTypeInvalidMessage(String(jprop_pmStandard), "string");
2024-04-08 10:15:45 +07:00
jsonInvalid();
return false;
}
2024-04-03 07:04:55 +07:00
}
2024-05-08 12:22:34 +07:00
if (JSON.typeof_(root[jprop_co2CalibrationRequested]) == "boolean") {
co2CalibrationRequested = root[jprop_co2CalibrationRequested];
if (co2CalibrationRequested) {
2024-05-08 12:22:34 +07:00
logInfo(String(jprop_co2CalibrationRequested) + String(": ") +
String(co2CalibrationRequested ? "True" : "False"));
}
2024-04-08 10:15:45 +07:00
} else {
2024-05-08 12:22:34 +07:00
if (jsonTypeInvalid(root[jprop_co2CalibrationRequested], "boolean")) {
failedMessage = jsonTypeInvalidMessage(
String(jprop_co2CalibrationRequested), "boolean");
2024-04-08 10:15:45 +07:00
jsonInvalid();
return false;
}
2024-04-03 07:04:55 +07:00
}
2024-05-08 12:22:34 +07:00
if (JSON.typeof_(root[jprop_ledBarTestRequested]) == "boolean") {
ledBarTestRequested = root[jprop_ledBarTestRequested];
if (ledBarTestRequested) {
2024-05-08 12:22:34 +07:00
logInfo(String(jprop_ledBarTestRequested) + String(": ") +
String(ledBarTestRequested ? "True" : "False"));
}
2024-04-08 10:15:45 +07:00
} else {
2024-05-08 12:22:34 +07:00
if (jsonTypeInvalid(root[jprop_ledBarTestRequested], "boolean")) {
failedMessage =
jsonTypeInvalidMessage(String(jprop_ledBarTestRequested), "boolean");
2024-04-08 10:15:45 +07:00
jsonInvalid();
return false;
}
2024-04-03 07:04:55 +07:00
}
2024-05-08 12:22:34 +07:00
if (JSON.typeof_(root[jprop_ledBarMode]) == "string") {
String mode = root[jprop_ledBarMode];
if (mode == getLedBarModeName(LedBarMode::LedBarModeCO2) ||
mode == getLedBarModeName(LedBarMode::LedBarModeOff) ||
mode == getLedBarModeName(LedBarMode::LedBarModePm)) {
String oldMode = jconfig[jprop_ledBarMode];
if (mode != oldMode) {
jconfig[jprop_ledBarMode] = mode;
changed = true;
}
2024-04-03 07:04:55 +07:00
} else {
2024-05-08 12:22:34 +07:00
failedMessage = jsonValueInvalidMessage(String(jprop_ledBarMode), mode);
2024-04-08 10:15:45 +07:00
jsonInvalid();
return false;
2024-04-03 07:04:55 +07:00
}
2024-04-08 10:15:45 +07:00
} else {
2024-05-08 12:22:34 +07:00
if (jsonTypeInvalid(root[jprop_ledBarMode], "string")) {
failedMessage =
jsonTypeInvalidMessage(String(jprop_ledBarMode), "string");
2024-04-08 10:15:45 +07:00
jsonInvalid();
return false;
}
2024-04-03 07:04:55 +07:00
}
2024-05-08 12:22:34 +07:00
if (JSON.typeof_(root[jprop_displayMode]) == "string") {
String mode = root[jprop_displayMode];
if (mode == getDisplayModeString(true) ||
mode == getDisplayModeString(false)) {
String oldMode = jconfig[jprop_displayMode];
if (mode != oldMode) {
jconfig[jprop_displayMode] = mode;
changed = true;
}
2024-04-03 07:04:55 +07:00
} else {
2024-05-08 12:22:34 +07:00
jsonValueInvalidMessage(String(jprop_displayMode), mode);
2024-04-08 10:15:45 +07:00
jsonInvalid();
return false;
2024-04-03 07:04:55 +07:00
}
2024-04-08 10:15:45 +07:00
} else {
2024-05-08 12:22:34 +07:00
if (jsonTypeInvalid(root[jprop_displayMode], "string")) {
failedMessage =
jsonTypeInvalidMessage(String(jprop_displayMode), "string");
2024-04-08 10:15:45 +07:00
jsonInvalid();
return false;
}
2024-04-03 07:04:55 +07:00
}
2024-05-08 12:22:34 +07:00
if (JSON.typeof_(root[jprop_abcDays]) == "number") {
int value = root[jprop_abcDays];
if (value <= 0) {
value = 0;
2024-04-08 10:15:45 +07:00
}
2024-05-08 12:22:34 +07:00
int oldValue = jconfig[jprop_abcDays];
if (value != oldValue) {
logInfo(String("Set ") + String(jprop_abcDays) + String(": ") +
String(value));
configLogInfo(String(jprop_abcDays), getAbcDayString(oldValue),
String(getAbcDayString(value)));
jconfig[jprop_abcDays] = value;
2024-04-03 07:04:55 +07:00
changed = true;
}
2024-04-08 10:15:45 +07:00
} else {
2024-05-08 12:22:34 +07:00
if (jsonTypeInvalid(root[jprop_abcDays], "number")) {
failedMessage = jsonTypeInvalidMessage(String(jprop_abcDays), "number");
2024-04-08 10:15:45 +07:00
jsonInvalid();
return false;
}
2024-04-03 07:04:55 +07:00
}
_tvocLearningOffsetChanged = false;
2024-05-08 12:22:34 +07:00
if (JSON.typeof_(root[jprop_tvocLearningOffset]) == "number") {
int value = root[jprop_tvocLearningOffset];
int oldValue = jconfig[jprop_tvocLearningOffset];
if (value < 0) {
jsonValueInvalidMessage(String(jprop_tvocLearningOffset), String(value));
2024-04-08 10:15:45 +07:00
jsonInvalid();
return false;
2024-05-08 12:22:34 +07:00
} else {
if (value != oldValue) {
changed = true;
_tvocLearningOffsetChanged = true;
configLogInfo(String(jprop_tvocLearningOffset), String(oldValue),
String(value));
jconfig[jprop_tvocLearningOffset] = value;
}
2024-04-03 07:04:55 +07:00
}
2024-04-08 10:15:45 +07:00
} else {
2024-05-08 12:22:34 +07:00
if (jsonTypeInvalid(root[jprop_tvocLearningOffset], "number")) {
failedMessage =
jsonTypeInvalidMessage(String(jprop_tvocLearningOffset), "number");
2024-04-08 10:15:45 +07:00
jsonInvalid();
return false;
}
2024-04-03 07:04:55 +07:00
}
2024-05-08 12:22:34 +07:00
_noxLearnOffsetChanged = false;
if (JSON.typeof_(root[jprop_noxLearningOffset]) == "number") {
int value = root[jprop_noxLearningOffset];
int oldValue = jconfig[jprop_noxLearningOffset];
if (value > 0) {
if (value != oldValue) {
2024-04-03 07:04:55 +07:00
changed = true;
2024-05-08 12:22:34 +07:00
_noxLearnOffsetChanged = true;
configLogInfo(String(jprop_noxLearningOffset), String(oldValue),
String(value));
jconfig[jprop_noxLearningOffset] = value;
2024-04-03 07:04:55 +07:00
}
} else {
2024-05-08 12:22:34 +07:00
failedMessage = jsonValueInvalidMessage(String(jprop_noxLearningOffset),
String(value));
jsonInvalid();
return false;
}
} else {
if (jsonTypeInvalid(root[jprop_noxLearningOffset], "number")) {
2024-04-08 10:15:45 +07:00
failedMessage =
2024-05-08 12:22:34 +07:00
jsonTypeInvalidMessage(String(jprop_noxLearningOffset), "number");
2024-04-08 10:15:45 +07:00
jsonInvalid();
return false;
}
2024-05-08 12:22:34 +07:00
}
if (JSON.typeof_(root[jprop_mqttBrokerUrl]) == "string") {
String broker = root[jprop_mqttBrokerUrl];
String oldBroker = jconfig[jprop_mqttBrokerUrl];
if (broker != oldBroker) {
changed = true;
configLogInfo(String(jprop_mqttBrokerUrl), oldBroker, broker);
jconfig[jprop_mqttBrokerUrl] = broker;
}
2024-04-08 10:15:45 +07:00
} else {
2024-05-08 12:22:34 +07:00
if (jsonTypeInvalid(root[jprop_mqttBrokerUrl], "string")) {
failedMessage =
jsonTypeInvalidMessage(String(jprop_mqttBrokerUrl), "string");
2024-04-08 10:15:45 +07:00
jsonInvalid();
return false;
2024-04-03 07:04:55 +07:00
}
}
2024-05-08 12:22:34 +07:00
if (JSON.typeof_(root[jprop_temperatureUnit]) == "string") {
String unit = root[jprop_temperatureUnit];
String oldUnit = jconfig[jprop_temperatureUnit];
2024-04-08 10:15:45 +07:00
unit.toLowerCase();
2024-05-08 12:22:34 +07:00
if (unit == "c" || unit == "f") {
if (unit != oldUnit) {
changed = true;
jconfig[jprop_temperatureUnit] = unit;
configLogInfo(String(jprop_temperatureUnit), oldUnit, unit);
}
2024-04-03 07:04:55 +07:00
} else {
2024-05-08 12:22:34 +07:00
jsonValueInvalidMessage(String(jprop_temperatureUnit), unit);
jsonInvalid();
2024-04-08 10:15:45 +07:00
return false;
}
} else {
2024-05-08 12:22:34 +07:00
if (jsonTypeInvalid(root[jprop_temperatureUnit], "string")) {
failedMessage =
jsonTypeInvalidMessage(String(jprop_temperatureUnit), "string");
2024-04-08 10:15:45 +07:00
jsonInvalid();
return false;
2024-04-03 07:04:55 +07:00
}
}
if (isLocal) {
2024-05-08 12:22:34 +07:00
if (JSON.typeof_(root[jprop_postDataToAirGradient]) == "boolean") {
bool value = root[jprop_postDataToAirGradient];
bool oldValue = jconfig[jprop_postDataToAirGradient];
if (value != oldValue) {
changed = true;
2024-05-08 12:22:34 +07:00
configLogInfo(String(jprop_postDataToAirGradient),
String(oldValue ? "true" : "false"),
String(value ? "true" : "false"));
jconfig[jprop_postDataToAirGradient] = value;
}
} else {
2024-05-08 12:22:34 +07:00
if (jsonTypeInvalid(root[jprop_postDataToAirGradient], "boolean")) {
failedMessage = jsonTypeInvalidMessage(
String(jprop_postDataToAirGradient), "boolean");
jsonInvalid();
return false;
}
2024-04-08 10:15:45 +07:00
}
2024-04-03 07:04:55 +07:00
}
/** Parse data only got from AirGradient server */
if (isLocal == false) {
2024-05-08 12:22:34 +07:00
if (JSON.typeof_(root[jprop_model]) == "string") {
String model = root[jprop_model];
String oldModel = jconfig[jprop_model];
if (model != oldModel) {
changed = true;
configLogInfo(String(jprop_model), oldModel, model);
jconfig[jprop_model] = model;
2024-04-08 10:15:45 +07:00
}
} else {
2024-05-08 12:22:34 +07:00
if (jsonTypeInvalid(root[jprop_model], "string")) {
failedMessage = jsonTypeInvalidMessage(String(jprop_model), "string");
2024-04-08 10:15:45 +07:00
jsonInvalid();
return false;
2024-04-03 07:04:55 +07:00
}
}
}
2024-05-08 12:22:34 +07:00
if (JSON.typeof_(root[jprop_ledbarBrightness]) == "number") {
int value = root[jprop_ledbarBrightness];
int oldValue = jconfig[jprop_ledbarBrightness];
if (value >= 0 && value <= 100) {
if (value != oldValue) {
2024-05-01 21:25:35 +07:00
changed = true;
2024-05-08 12:22:34 +07:00
configLogInfo(String(jprop_ledbarBrightness), String(oldValue),
String(value));
2024-05-01 21:25:35 +07:00
ledBarBrightnessChanged = true;
2024-05-08 12:22:34 +07:00
jconfig[jprop_ledbarBrightness] = value;
2024-05-01 21:25:35 +07:00
}
2024-05-08 12:22:34 +07:00
} else {
failedMessage = jsonValueInvalidMessage(String(jprop_ledbarBrightness),
String(value));
jsonInvalid();
return false;
2024-05-01 21:25:35 +07:00
}
} else {
2024-05-08 12:22:34 +07:00
if (jsonTypeInvalid(root[jprop_ledbarBrightness], "number")) {
failedMessage =
jsonTypeInvalidMessage(String(jprop_ledbarBrightness), "number");
2024-05-01 21:25:35 +07:00
jsonInvalid();
return false;
}
}
2024-05-08 12:22:34 +07:00
if (JSON.typeof_(root[jprop_displayBrightness]) == "number") {
int value = root[jprop_displayBrightness];
int oldValue = jconfig[jprop_displayBrightness];
if (value >= 0 && value <= 100) {
if (value != oldValue) {
2024-05-01 21:25:35 +07:00
changed = true;
displayBrightnessChanged = true;
2024-05-08 12:22:34 +07:00
configLogInfo(String(jprop_displayBrightness), String(oldValue),
String(value));
jconfig[jprop_displayBrightness] = value;
2024-05-01 21:25:35 +07:00
}
2024-05-08 12:22:34 +07:00
} else {
failedMessage = jsonValueInvalidMessage(String(jprop_displayBrightness),
String(value));
jsonInvalid();
return false;
2024-05-01 21:25:35 +07:00
}
} else {
2024-05-08 12:22:34 +07:00
if (jsonTypeInvalid(root[jprop_displayBrightness], "number")) {
failedMessage =
jsonTypeInvalidMessage(String(jprop_displayBrightness), "number");
2024-05-01 21:25:35 +07:00
jsonInvalid();
return false;
}
}
if (JSON.typeof_(root["targetFirmware"]) == "string") {
String newVer = root["targetFirmware"];
String curVer = String(GIT_VERSION);
if (curVer != newVer) {
logInfo("Detected new firwmare version: " + newVer);
otaNewFirmwareVersion = newVer;
udpated = true;
} else {
otaNewFirmwareVersion = String("");
}
}
2024-04-03 07:04:55 +07:00
if (changed) {
udpated = true;
2024-04-03 07:04:55 +07:00
saveConfig();
printConfig();
} else {
logInfo("Nothing changed ignore udpate");
if (ledBarTestRequested || co2CalibrationRequested) {
udpated = true;
}
2024-04-03 07:04:55 +07:00
}
return true;
}
2024-04-05 06:39:59 +07:00
/**
* @brief Get current configuration value as JSON string
2024-04-07 16:39:01 +07:00
*
* @return String
2024-04-05 06:39:59 +07:00
*/
2024-05-08 12:22:34 +07:00
String Configuration::toString(void) { return JSON.stringify(jconfig); }
2024-04-03 07:04:55 +07:00
2024-04-05 06:39:59 +07:00
/**
* @brief Temperature unit (F or C)
2024-04-07 16:39:01 +07:00
*
2024-04-05 06:39:59 +07:00
* @return true F
* @return false C
*/
2024-04-07 16:39:01 +07:00
bool Configuration::isTemperatureUnitInF(void) {
2024-05-08 12:22:34 +07:00
String unit = jconfig[jprop_temperatureUnit];
return (unit == "f");
2024-04-03 07:04:55 +07:00
}
2024-04-05 06:39:59 +07:00
/**
* @brief Country name, it's short name ex: TH = Thailand
2024-04-07 16:39:01 +07:00
*
* @return String
2024-04-05 06:39:59 +07:00
*/
2024-05-08 12:22:34 +07:00
String Configuration::getCountry(void) {
String country = jconfig[jprop_country];
return country;
}
2024-04-03 07:04:55 +07:00
2024-04-05 06:39:59 +07:00
/**
* @brief PM unit standard (USAQI, ugm3)
2024-04-07 16:39:01 +07:00
*
2024-04-05 06:39:59 +07:00
* @return true USAQI
* @return false ugm3
*/
2024-05-08 12:22:34 +07:00
bool Configuration::isPmStandardInUSAQI(void) {
String standard = jconfig[jprop_pmStandard];
return (standard == getPMStandardString(true));
}
2024-04-03 07:04:55 +07:00
2024-04-05 06:39:59 +07:00
/**
* @brief Get CO2 calibration ABC time
2024-04-07 16:39:01 +07:00
*
2024-04-05 06:39:59 +07:00
* @return int Number of day
*/
2024-05-08 12:22:34 +07:00
int Configuration::getCO2CalibrationAbcDays(void) {
int value = jconfig[jprop_abcDays];
return value;
}
2024-04-03 07:04:55 +07:00
2024-04-05 06:39:59 +07:00
/**
* @brief Get Led Bar Mode
2024-04-07 16:39:01 +07:00
*
* @return LedBarMode
2024-04-05 06:39:59 +07:00
*/
2024-04-07 16:39:01 +07:00
LedBarMode Configuration::getLedBarMode(void) {
2024-05-08 12:22:34 +07:00
String mode = jconfig[jprop_ledBarMode];
if (mode == getLedBarModeName(LedBarModeCO2)) {
return LedBarModeCO2;
}
if (mode == getLedBarModeName(LedBarModeOff)) {
return LedBarModeOff;
}
if (mode == getLedBarModeName(LedBarModePm)) {
return LedBarModePm;
}
return LedBarModeOff;
2024-04-03 07:04:55 +07:00
}
2024-04-05 06:39:59 +07:00
/**
* @brief Get LED bar mode name
2024-04-07 16:39:01 +07:00
*
* @return String
2024-04-05 06:39:59 +07:00
*/
2024-04-07 16:39:01 +07:00
String Configuration::getLedBarModeName(void) {
2024-05-08 12:22:34 +07:00
String mode = jconfig[jprop_ledBarMode];
return mode;
2024-04-03 07:04:55 +07:00
}
2024-04-05 06:39:59 +07:00
/**
* @brief Get display mode
2024-04-07 16:39:01 +07:00
*
2024-04-05 06:39:59 +07:00
* @return true On
* @return false Off
*/
2024-05-08 12:22:34 +07:00
bool Configuration::getDisplayMode(void) {
String mode = jconfig[jprop_displayMode];
if (mode == getDisplayModeString(true)) {
return true;
}
return false;
}
2024-04-03 07:04:55 +07:00
2024-04-05 06:39:59 +07:00
/**
* @brief Get MQTT uri
2024-04-07 16:39:01 +07:00
*
* @return String
2024-04-05 06:39:59 +07:00
*/
2024-04-07 16:39:01 +07:00
String Configuration::getMqttBrokerUri(void) {
2024-05-08 12:22:34 +07:00
String broker = jconfig[jprop_mqttBrokerUrl];
return broker;
2024-04-07 16:39:01 +07:00
}
2024-04-03 07:04:55 +07:00
2024-04-05 06:39:59 +07:00
/**
* @brief Get configuratoin post data to AirGradient cloud
2024-04-07 16:39:01 +07:00
*
2024-04-05 06:39:59 +07:00
* @return true Post
* @return false No-Post
*/
2024-04-07 16:39:01 +07:00
bool Configuration::isPostDataToAirGradient(void) {
2024-05-08 12:22:34 +07:00
bool post = jconfig[jprop_postDataToAirGradient];
return post;
2024-04-03 07:04:55 +07:00
}
2024-04-05 06:39:59 +07:00
/**
* @brief Get current configuration control
2024-04-07 16:39:01 +07:00
*
* @return ConfigurationControl
2024-04-05 06:39:59 +07:00
*/
2024-04-07 16:39:01 +07:00
ConfigurationControl Configuration::getConfigurationControl(void) {
2024-05-08 12:22:34 +07:00
String ctrl = jconfig[jprop_configurationControl];
if (ctrl == String(CONFIGURATION_CONTROL_NAME
[ConfigurationControl::ConfigurationControlBoth])) {
return ConfigurationControl::ConfigurationControlBoth;
}
if (ctrl == String(CONFIGURATION_CONTROL_NAME
[ConfigurationControl::ConfigurationControlLocal])) {
return ConfigurationControl::ConfigurationControlLocal;
}
if (ctrl == String(CONFIGURATION_CONTROL_NAME
[ConfigurationControl::ConfigurationControlCloud])) {
return ConfigurationControl::ConfigurationControlCloud;
}
return ConfigurationControl::ConfigurationControlBoth;
2024-04-03 07:04:55 +07:00
}
/**
* @brief CO2 manual calib request, the request flag will clear after get. Must
* call this after parse success
*
* @return true Requested
* @return false Not requested
*/
2024-04-07 16:39:01 +07:00
bool Configuration::isCo2CalibrationRequested(void) {
2024-04-03 07:04:55 +07:00
bool requested = co2CalibrationRequested;
co2CalibrationRequested = false; // clear requested
return requested;
}
/**
* @brief LED bar test request, the request flag will clear after get. Must call
* this function after parse success
*
* @return true Requested
* @return false Not requested
*/
2024-04-07 16:39:01 +07:00
bool Configuration::isLedBarTestRequested(void) {
2024-04-03 07:04:55 +07:00
bool requested = ledBarTestRequested;
ledBarTestRequested = false;
return requested;
}
/**
* @brief Reset default configure
*/
2024-04-07 16:39:01 +07:00
void Configuration::reset(void) {
2024-04-03 07:04:55 +07:00
defaultConfig();
2024-04-03 21:26:04 +07:00
logInfo("Reset to default configure");
2024-04-03 07:04:55 +07:00
printConfig();
}
/**
* @brief Get model name, it's usage for offline mode
*
* @return String
*/
2024-05-08 12:22:34 +07:00
String Configuration::getModel(void) {
String model = jconfig[jprop_model];
return model;
}
2024-04-07 16:39:01 +07:00
bool Configuration::isUpdated(void) {
bool updated = this->udpated;
this->udpated = false;
return updated;
}
2024-04-08 10:15:45 +07:00
String Configuration::jsonTypeInvalidMessage(String name, String type) {
return "'" + name + "' type invalid, it's should '" + type + "'";
}
String Configuration::jsonValueInvalidMessage(String name, String value) {
return "'" + name + "' value '" + value + "' invalid";
}
void Configuration::jsonInvalid(void) {
loadConfig();
logError(failedMessage);
}
void Configuration::configLogInfo(String name, String fromValue,
String toValue) {
logInfo(String("Setting '") + name + String("' from '") + fromValue +
String("' to '") + toValue + String("'"));
}
String Configuration::getPMStandardString(bool usaqi) {
if (usaqi) {
return "us-aqi";
}
return "ugm3";
}
String Configuration::getDisplayModeString(bool dispMode) {
if (dispMode) {
return String("on");
}
return String("off");
}
String Configuration::getAbcDayString(int value) {
if (value <= 0) {
return String("off");
}
return String(value);
}
2024-05-08 12:22:34 +07:00
void Configuration::toConfig(const char *buf) {
logInfo("Parse file to JSON");
JSONVar root = JSON.parse(buf);
if (!(root == undefined)) {
jconfig = root;
}
bool changed = false;
bool isInvalid = false;
/** Validate country */
if (JSON.typeof_(jconfig[jprop_country]) != "string") {
isInvalid = true;
} else {
String country = jconfig[jprop_country];
if (country.length() != 2) {
isInvalid = true;
} else {
isInvalid = false;
}
}
if (isInvalid) {
jconfig[jprop_country] = jprop_country_default;
2024-05-08 12:22:34 +07:00
changed = true;
logInfo("toConfig: country changed");
}
/** validate: PM standard */
if (JSON.typeof_(jconfig[jprop_pmStandard]) != "string") {
isInvalid = true;
} else {
String standard = jconfig[jprop_pmStandard];
if (standard != getPMStandardString(true) &&
standard != getPMStandardString(false)) {
isInvalid = true;
} else {
isInvalid = false;
}
}
if (isInvalid) {
jconfig[jprop_pmStandard] = jprop_pmStandard_default;
2024-05-08 12:22:34 +07:00
changed = true;
logInfo("toConfig: pmStandard changed");
}
/** validate led bar mode */
if (JSON.typeof_(jconfig[jprop_ledBarMode]) != "string") {
isInvalid = true;
} else {
String mode = jconfig[jprop_ledBarMode];
if (mode != getLedBarModeName(LedBarMode::LedBarModeCO2) &&
mode != getLedBarModeName(LedBarMode::LedBarModeOff) &&
mode != getLedBarModeName(LedBarMode::LedBarModePm)) {
isInvalid = true;
} else {
isInvalid = false;
}
}
if (isInvalid) {
jconfig[jprop_ledBarMode] = jprop_ledBarMode_default;
2024-05-08 12:22:34 +07:00
changed = true;
logInfo("toConfig: ledBarMode changed");
}
/** validate display mode */
if (JSON.typeof_(jconfig[jprop_displayMode]) != "string") {
isInvalid = true;
} else {
String mode = jconfig[jprop_displayMode];
if (mode != getDisplayModeString(true) &&
mode != getDisplayModeString(false)) {
isInvalid = true;
} else {
isInvalid = false;
}
}
if (isInvalid) {
jconfig[jprop_displayMode] = jprop_displayMode_default;
2024-05-08 12:22:34 +07:00
changed = true;
logInfo("toConfig: displayMode changed");
}
/** validate abcday */
if (JSON.typeof_(jconfig[jprop_abcDays]) != "number") {
isInvalid = true;
} else {
isInvalid = false;
}
if (isInvalid) {
jconfig[jprop_abcDays] = jprop_abcDays_default;
2024-05-08 12:22:34 +07:00
changed = true;
logInfo("toConfig: abcDays changed");
}
/** validate tvoc learning offset */
if (JSON.typeof_(jconfig[jprop_tvocLearningOffset]) != "number") {
isInvalid = true;
} else {
int value = jconfig[jprop_tvocLearningOffset];
if (value < 0) {
isInvalid = true;
} else {
isInvalid = false;
}
}
if (isInvalid) {
jconfig[jprop_tvocLearningOffset] = jprop_tvocLearningOffset_default;
2024-05-08 12:22:34 +07:00
changed = true;
logInfo("toConfig: tvocLearningOffset changed");
}
/** validate nox learning offset */
if (JSON.typeof_(jconfig[jprop_noxLearningOffset]) != "number") {
isInvalid = true;
} else {
int value = jconfig[jprop_noxLearningOffset];
if (value < 0) {
isInvalid = true;
} else {
isInvalid = false;
}
}
if (isInvalid) {
jconfig[jprop_noxLearningOffset] = jprop_noxLearningOffset_default;
2024-05-08 12:22:34 +07:00
changed = true;
logInfo("toConfig: noxLearningOffset changed");
}
/** validate mqtt broker */
if (JSON.typeof_(jconfig[jprop_mqttBrokerUrl]) != "string") {
isInvalid = true;
} else {
isInvalid = false;
}
if (isInvalid) {
changed = true;
jconfig[jprop_mqttBrokerUrl] = jprop_mqttBrokerUrl_default;
2024-05-08 12:22:34 +07:00
logInfo("toConfig: mqttBroker changed");
}
/** Validate temperature unit */
if (JSON.typeof_(jconfig[jprop_temperatureUnit]) != "string") {
isInvalid = true;
} else {
String unit = jconfig[jprop_temperatureUnit];
if (unit != "c" && unit != "f") {
isInvalid = true;
} else {
isInvalid = false;
}
}
if (isInvalid) {
jconfig[jprop_temperatureUnit] = jprop_temperatureUnit_default;
2024-05-08 12:22:34 +07:00
changed = true;
logInfo("toConfig: temperatureUnit changed");
}
/** validate configuration control */
if (JSON.typeof_(jprop_configurationControl) != "string") {
isInvalid = true;
} else {
String ctrl = jconfig[jprop_configurationControl];
if (ctrl != String(CONFIGURATION_CONTROL_NAME
[ConfigurationControl::ConfigurationControlBoth]) &&
ctrl != String(CONFIGURATION_CONTROL_NAME
[ConfigurationControl::ConfigurationControlLocal]) &&
ctrl != String(CONFIGURATION_CONTROL_NAME
[ConfigurationControl::ConfigurationControlCloud])) {
isInvalid = true;
} else {
isInvalid = false;
}
}
if (isInvalid) {
jconfig[jprop_configurationControl] =jprop_configurationControl_default;
2024-05-08 12:22:34 +07:00
changed = true;
logInfo("toConfig: configurationControl changed");
}
/** Validate post to airgradient cloud */
if (JSON.typeof_(jconfig[jprop_postDataToAirGradient]) != "boolean") {
isInvalid = true;
} else {
isInvalid = false;
}
if (isInvalid) {
jconfig[jprop_postDataToAirGradient] = jprop_postDataToAirGradient_default;
2024-05-08 12:22:34 +07:00
changed = true;
logInfo("toConfig: postToAirGradient changed");
}
/** validate led bar brightness */
if (JSON.typeof_(jconfig[jprop_ledbarBrightness]) != "number") {
isInvalid = true;
} else {
int value = jconfig[jprop_ledbarBrightness];
if (value < 0 || value > 100) {
isInvalid = true;
} else {
isInvalid = false;
}
}
if (isInvalid) {
jconfig[jprop_ledbarBrightness] = jprop_ledbarBrightness_default;
2024-05-08 12:22:34 +07:00
changed = true;
logInfo("toConfig: ledBarBrightness changed");
}
/** Validate display brightness */
if (JSON.typeof_(jconfig[jprop_displayBrightness]) != "number") {
isInvalid = true;
} else {
int value = jconfig[jprop_displayBrightness];
if (value < 0 || value > 100) {
isInvalid = true;
} else {
isInvalid = false;
}
}
if (isInvalid) {
jconfig[jprop_displayBrightness] = jprop_displayBrightness_default;
2024-05-08 12:22:34 +07:00
changed = true;
logInfo("toConfig: displayBrightness changed");
}
2024-05-09 14:56:40 +07:00
/** Last OTA */
if(JSON.typeof_(jconfig[jprop_lastOta]) != "number") {
isInvalid = true;
} else {
isInvalid = false;
}
if(isInvalid) {
jconfig[jprop_lastOta] = jprop_lastOta_default;
2024-05-09 14:56:40 +07:00
changed = true;
}
2024-05-08 12:22:34 +07:00
if (changed) {
saveConfig();
}
}
2024-04-08 10:15:45 +07:00
String Configuration::getFailedMesage(void) { return failedMessage; }
void Configuration::setPostToAirGradient(bool enable) {
2024-05-08 12:22:34 +07:00
bool oldEnabled = jconfig[jprop_postDataToAirGradient];
if (enable != oldEnabled) {
jconfig[jprop_postDataToAirGradient] = enable;
logInfo("postDataToAirGradient set to: " + String(enable));
saveConfig();
} else {
logInfo("postDataToAirGradient: Ignored set to " + String(enable));
}
}
bool Configuration::noxLearnOffsetChanged(void) {
bool changed = _noxLearnOffsetChanged;
_noxLearnOffsetChanged = false;
return changed;
}
bool Configuration::tvocLearnOffsetChanged(void) {
bool changed = _tvocLearningOffsetChanged;
_tvocLearningOffsetChanged = false;
return changed;
}
int Configuration::getTvocLearningOffset(void) {
2024-05-08 12:22:34 +07:00
int value = jconfig[jprop_tvocLearningOffset];
return value;
}
int Configuration::getNoxLearningOffset(void) {
2024-05-08 12:22:34 +07:00
int value = jconfig[jprop_noxLearningOffset];
return value;
}
2024-04-25 06:49:14 +07:00
String Configuration::wifiSSID(void) { return "airgradient-" + ag->deviceId(); }
2024-04-25 06:49:14 +07:00
String Configuration::wifiPass(void) { return String("cleanair"); }
void Configuration::setAirGradient(AirGradient *ag) { this->ag = ag; }
2024-05-01 21:25:35 +07:00
2024-05-08 12:22:34 +07:00
int Configuration::getLedBarBrightness(void) {
int value = jconfig[jprop_ledbarBrightness];
return value;
}
2024-05-01 21:25:35 +07:00
bool Configuration::isLedBarBrightnessChanged(void) {
bool changed = ledBarBrightnessChanged;
ledBarBrightnessChanged = false;
return changed;
}
int Configuration::getDisplayBrightness(void) {
2024-05-08 12:22:34 +07:00
int value = jconfig[jprop_displayBrightness];
return value;
2024-05-01 21:25:35 +07:00
}
bool Configuration::isDisplayBrightnessChanged(void) {
bool changed = displayBrightnessChanged;
displayBrightnessChanged = false;
return changed;
}
/**
* @brief Get number of sec from last OTA
*
* @return int < 0 is invalid, 0 mean there is no OTA trigger.
*/
int Configuration::getLastOta(void) {
struct tm timeInfo;
if (getLocalTime(&timeInfo) == false) {
logError("Get localtime failed");
return -1;
}
int curYear = timeInfo.tm_year + 1900;
if (curYear < 2024) {
logError("Current year " + String(curYear) + String(" invalid"));
return -1;
}
2024-05-09 15:03:27 +07:00
double _t = jconfig[jprop_lastOta];
time_t lastOta = (time_t)_t;
time_t curTime = mktime(&timeInfo);
2024-05-09 14:56:40 +07:00
logInfo("Last ota time: " + String(lastOta));
if (lastOta == 0) {
return 0;
}
2024-05-09 14:56:40 +07:00
int sec = curTime - lastOta;
logInfo("Last ota secconds: " + String(sec));
return sec;
}
void Configuration::updateLastOta(void) {
struct tm timeInfo;
if (getLocalTime(&timeInfo) == false) {
logError("updateLastOta: Get localtime failed");
return;
}
int curYear = timeInfo.tm_year + 1900;
if (curYear < 2024) {
logError("updateLastOta: lolcal time invalid");
return;
}
2024-05-09 14:56:40 +07:00
time_t lastOta = mktime(&timeInfo);
2024-05-09 15:03:27 +07:00
jconfig[jprop_lastOta] = (unsigned long)lastOta;
2024-05-09 14:56:40 +07:00
logInfo("Last OTA: " + String(lastOta));
saveConfig();
}
String Configuration::newFirmwareVersion(void) {
String newFw = otaNewFirmwareVersion;
otaNewFirmwareVersion = String("");
return newFw;
}