423 lines
14 KiB
C++
423 lines
14 KiB
C++
#include "config.h"
|
|
#include "esp_rom_uart.h" // for uart wait idle
|
|
|
|
// Logging
|
|
static const char* TAG = "CFG";
|
|
|
|
Preferences preferences; // persistent storage for configuration
|
|
Config config(preferences); // configuration object
|
|
|
|
Config::Config(Preferences& prefs)
|
|
: prefs(prefs) {
|
|
|
|
// init defs mapping
|
|
for (size_t i = 0; i < configdefsCount; ++i) {
|
|
defs[configdefs[i].key] = &configdefs[i];
|
|
}
|
|
|
|
}
|
|
|
|
[[noreturn]] void Config::error_abort() const {
|
|
LOGD(TAG, "Rebooting in about 2 seconds");
|
|
esp_rom_uart_tx_wait_idle(0);
|
|
delay(2000); // to have a chance to read
|
|
abort();
|
|
}
|
|
|
|
ConfigValue Config::getValue(const ConfigDef *def) {
|
|
switch (def->type) {
|
|
case ConfigType::BYTE:
|
|
return (uint8_t)prefs.getUChar(def->key, std::get<uint8_t>(def->defval));
|
|
case ConfigType::SHORT:
|
|
return (int16_t)prefs.getShort(def->key, std::get<int16_t>(def->defval));
|
|
case ConfigType::INT:
|
|
return prefs.getInt(def->key, std::get<int32_t>(def->defval));
|
|
case ConfigType::BOOL:
|
|
return prefs.getBool(def->key, std::get<bool>(def->defval));
|
|
case ConfigType::FLOAT:
|
|
return prefs.getFloat(def->key, std::get<float>(def->defval));
|
|
case ConfigType::CHAR:
|
|
return (char)prefs.getChar(def->key, std::get<char>(def->defval));
|
|
case ConfigType::STRING:
|
|
return prefs.getString(def->key, std::get<String>(def->defval));
|
|
}
|
|
return prefs.getString(def->key, std::get<String>(def->defval));
|
|
}
|
|
|
|
void Config::loadValue(const char* key) {
|
|
// Load single value, e.g. to receive loglevel early
|
|
prefs.begin(PREF_NAME, true);
|
|
values[key] = getValue(defs.at(key));
|
|
prefs.end();
|
|
}
|
|
|
|
void Config::load() {
|
|
LOGD(TAG, "Loading configuration");
|
|
prefs.begin(PREF_NAME, true);
|
|
for (const auto& def : configdefs) {
|
|
if (prefs.isKey(def.key)) {
|
|
LOGD(TAG, "Config option '%s' loaded from NVS", def.key);
|
|
switch (def.type) {
|
|
case ConfigType::BYTE:
|
|
values[def.key] = (uint8_t)prefs.getUChar(def.key, std::get<uint8_t>(def.defval));
|
|
break;
|
|
case ConfigType::SHORT:
|
|
values[def.key] = (int16_t)prefs.getShort(def.key, std::get<int16_t>(def.defval));
|
|
break;
|
|
case ConfigType::INT:
|
|
values[def.key] = prefs.getInt(def.key, std::get<int32_t>(def.defval));
|
|
break;
|
|
case ConfigType::BOOL:
|
|
values[def.key] = prefs.getBool(def.key, std::get<bool>(def.defval));
|
|
break;
|
|
case ConfigType::FLOAT:
|
|
values[def.key] = prefs.getFloat(def.key, std::get<float>(def.defval));
|
|
break;
|
|
case ConfigType::CHAR:
|
|
values[def.key] = (char)prefs.getChar(def.key, std::get<char>(def.defval));
|
|
break;
|
|
case ConfigType::STRING:
|
|
values[def.key] = prefs.getString(def.key, std::get<String>(def.defval));
|
|
break;
|
|
}
|
|
} else {
|
|
LOGD(TAG, "Using default for '%s'", def.key);
|
|
switch (def.type) {
|
|
case ConfigType::BYTE:
|
|
values[def.key] = std::get<uint8_t>(def.defval);
|
|
break;
|
|
case ConfigType::SHORT:
|
|
values[def.key] = std::get<int16_t>(def.defval);
|
|
break;
|
|
case ConfigType::INT:
|
|
values[def.key] = std::get<int32_t>(def.defval);
|
|
break;
|
|
case ConfigType::BOOL:
|
|
values[def.key] = std::get<bool>(def.defval);
|
|
break;
|
|
case ConfigType::FLOAT:
|
|
values[def.key] = std::get<float>(def.defval);
|
|
break;
|
|
case ConfigType::CHAR:
|
|
values[def.key] = std::get<char>(def.defval);
|
|
break;
|
|
case ConfigType::STRING:
|
|
values[def.key] = std::get<String>(def.defval);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
prefs.end();
|
|
}
|
|
|
|
void Config::updateValue(const char* key, ConfigType type, ConfigValue newvalue) {
|
|
switch (type) {
|
|
case ConfigType::BYTE:
|
|
prefs.putUChar(key, std::get<uint8_t>(newvalue));
|
|
break;
|
|
case ConfigType::SHORT:
|
|
prefs.putShort(key, std::get<int16_t>(newvalue));
|
|
break;
|
|
case ConfigType::INT:
|
|
prefs.putInt(key, std::get<int32_t>(newvalue));
|
|
break;
|
|
case ConfigType::BOOL:
|
|
prefs.putBool(key, std::get<bool>(newvalue));
|
|
break;
|
|
case ConfigType::FLOAT:
|
|
prefs.putFloat(key, std::get<float>(newvalue));
|
|
break;
|
|
case ConfigType::CHAR:
|
|
prefs.putChar(key, std::get<char>(newvalue));
|
|
break;
|
|
case ConfigType::STRING:
|
|
prefs.putString(key, std::get<String>(newvalue));
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Config::save() {
|
|
ConfigValue oldval;
|
|
prefs.begin(PREF_NAME, false);
|
|
for (const auto& def : configdefs) {
|
|
if (prefs.isKey(def.key)) {
|
|
switch (def.type) {
|
|
case ConfigType::BYTE:
|
|
oldval = (uint8_t)prefs.getUChar(def.key, std::get<uint8_t>(def.defval));
|
|
break;
|
|
case ConfigType::SHORT:
|
|
oldval = (int16_t)prefs.getShort(def.key, std::get<int16_t>(def.defval));
|
|
break;
|
|
case ConfigType::INT:
|
|
oldval = prefs.getInt(def.key, std::get<int32_t>(def.defval));
|
|
break;
|
|
case ConfigType::BOOL:
|
|
oldval = prefs.getBool(def.key, std::get<bool>(def.defval));
|
|
break;
|
|
case ConfigType::FLOAT:
|
|
oldval = prefs.getFloat(def.key, std::get<float>(def.defval));
|
|
break;
|
|
case ConfigType::CHAR:
|
|
oldval = (char)prefs.getChar(def.key, std::get<char>(def.defval));
|
|
break;
|
|
case ConfigType::STRING:
|
|
oldval = prefs.getString(def.key, std::get<String>(def.defval));
|
|
break;
|
|
}
|
|
if (values[def.key] != oldval) {
|
|
LOGD(TAG, "NVS update needed for %s", def.key);
|
|
updateValue(def.key, def.type, values[def.key]);
|
|
}
|
|
} else {
|
|
LOGD(TAG, "Key does not exist in NVS: %s, creating", def.key);
|
|
updateValue(def.key, def.type, values[def.key]);
|
|
}
|
|
}
|
|
prefs.end();
|
|
}
|
|
|
|
void Config::dump() {
|
|
// only for debugging purposes,
|
|
LOGI(TAG, "========== Config options ==========");
|
|
for (const auto& def : configdefs) {
|
|
auto it = values.find(def.key);
|
|
if (it == values.end()) {
|
|
LOGW(TAG, "%s = <missing>", def.key);
|
|
continue;
|
|
}
|
|
const ConfigValue& value = it->second;
|
|
switch (def.type) {
|
|
case ConfigType::BYTE:
|
|
if (auto v = std::get_if<uint8_t>(&value))
|
|
LOGI(TAG, "%s = %u", def.key, *v);
|
|
break;
|
|
case ConfigType::SHORT:
|
|
if (auto v = std::get_if<int16_t>(&value))
|
|
LOGI(TAG, "%s = %d", def.key, *v);
|
|
break;
|
|
case ConfigType::INT:
|
|
if (auto v = std::get_if<int32_t>(&value))
|
|
LOGI(TAG, "%s = %d", def.key, *v);
|
|
break;
|
|
case ConfigType::BOOL:
|
|
if (auto v = std::get_if<bool>(&value))
|
|
LOGI(TAG, "%s = %s", def.key, *v ? "true" : "false");
|
|
break;
|
|
case ConfigType::FLOAT:
|
|
if (auto v = std::get_if<float>(&value))
|
|
LOGI(TAG, "%s = %.3f", def.key, *v);
|
|
break;
|
|
case ConfigType::CHAR:
|
|
if (auto v = std::get_if<char>(&value))
|
|
LOGI(TAG, "%s = '%c'", def.key, *v);
|
|
break;
|
|
case ConfigType::STRING:
|
|
if (auto v = std::get_if<String>(&value))
|
|
LOGI(TAG, "%s = \"%s\"", def.key, v->c_str());
|
|
break;
|
|
}
|
|
}
|
|
LOGI(TAG, "====================================");
|
|
}
|
|
|
|
void Config::clear() {
|
|
LOGI(TAG, "Clearing NVS volume: %s", PREF_NAME);
|
|
prefs.begin(PREF_NAME, false);
|
|
prefs.clear();
|
|
prefs.end();
|
|
}
|
|
|
|
template<typename T>
|
|
T Config::get(const char* key) const {
|
|
return std::get<T>(values.at(key));
|
|
}
|
|
|
|
ConfigValue Config::get(const char* key) const {
|
|
auto it = values.find(key);
|
|
return it->second;
|
|
}
|
|
|
|
bool Config::hasKey(const char* key) {
|
|
auto it = values.find(key);
|
|
return ! (it == values.end());
|
|
}
|
|
|
|
uint8_t Config::getByte(const char* key) const {
|
|
auto it = values.find(key);
|
|
if (it == values.end()) {
|
|
LOGE(TAG, "Missing config key: %s", key);
|
|
error_abort();
|
|
}
|
|
if (auto v = std::get_if<uint8_t>(&it->second))
|
|
return *v;
|
|
LOGE(TAG, "Type mismatch for key: %s", key);
|
|
error_abort();
|
|
}
|
|
|
|
int16_t Config::getShort(const char* key) const {
|
|
auto it = values.find(key);
|
|
if (it == values.end()) {
|
|
ESP_LOGE(TAG, "Missing config key: %s", key);
|
|
error_abort();
|
|
}
|
|
if (auto v = std::get_if<int16_t>(&it->second))
|
|
return *v;
|
|
ESP_LOGE(TAG, "Type mismatch for key: %s", key);
|
|
error_abort();
|
|
}
|
|
|
|
int32_t Config::getInt(const char* key) const {
|
|
auto it = values.find(key);
|
|
if (it == values.end()) {
|
|
LOGE(TAG, "Missing config key: %s", key);
|
|
error_abort();
|
|
}
|
|
if (auto v = std::get_if<int32_t>(&it->second))
|
|
return *v;
|
|
LOGE(TAG, "Type mismatch for key: %s", key);
|
|
error_abort();
|
|
}
|
|
|
|
bool Config::getBool(const char* key) const {
|
|
auto it = values.find(key);
|
|
if (it == values.end()) {
|
|
LOGE(TAG, "Missing config key: %s", key);
|
|
error_abort();
|
|
}
|
|
if (auto v = std::get_if<bool>(&it->second))
|
|
return *v;
|
|
LOGE(TAG, "Type mismatch for key: %s", key);
|
|
error_abort();
|
|
}
|
|
|
|
float Config::getFloat(const char* key) const {
|
|
auto it = values.find(key);
|
|
if (it == values.end()) {
|
|
LOGE(TAG, "Missing config key: %s", key);
|
|
error_abort();
|
|
}
|
|
if (auto v = std::get_if<float>(&it->second))
|
|
return *v;
|
|
LOGE(TAG, "Type mismatch for key: %s", key);
|
|
error_abort();
|
|
}
|
|
|
|
char Config::getChar(const char* key) const {
|
|
auto it = values.find(key);
|
|
if (it == values.end()) {
|
|
LOGE(TAG, "Missing config key: %s", key);
|
|
error_abort();
|
|
}
|
|
if (auto v = std::get_if<char>(&it->second))
|
|
return *v;
|
|
LOGE(TAG, "Type mismatch for key: %s", key);
|
|
error_abort();
|
|
}
|
|
|
|
const String& Config::getString(const char* key) const {
|
|
auto it = values.find(key);
|
|
if (it == values.end()) {
|
|
LOGE(TAG, "Missing config key: %s", key);
|
|
error_abort();
|
|
}
|
|
if (auto v = std::get_if<String>(&it->second))
|
|
return *v;
|
|
LOGE(TAG, "Type mismatch for key: %s", key);
|
|
error_abort();
|
|
}
|
|
|
|
const char* Config::getCString(const char* key) const {
|
|
return getString(key).c_str();
|
|
}
|
|
|
|
bool Config::setValue(const char* key, const char* value) {
|
|
// sets data associated to a key to a new value
|
|
// does not write back to preferences, that has to be done
|
|
// separately
|
|
LOGD(TAG, "setting: %s to %s", key, value);
|
|
bool success = false;
|
|
char* end;
|
|
unsigned long byte_temp;
|
|
long int_temp;
|
|
bool bool_temp;
|
|
float float_temp;
|
|
switch (defs[key]->type) {
|
|
case ConfigType::BYTE:
|
|
byte_temp = strtoul(value, &end, 10);
|
|
if (*end != '\0') {
|
|
LOGW(TAG, "not a number");
|
|
break;
|
|
}
|
|
if (byte_temp > 255) {
|
|
LOGW(TAG, "byte out of range");
|
|
break;
|
|
}
|
|
values[key] = static_cast<uint8_t>(byte_temp);
|
|
LOGD(TAG, "converted to BYTE");
|
|
success = true;
|
|
break;
|
|
case ConfigType::SHORT:
|
|
int_temp = strtoul(value, &end, 10);
|
|
if (*end != '\0') {
|
|
LOGW(TAG, "not a number");
|
|
break;
|
|
}
|
|
if (int_temp < INT16_MIN || int_temp > INT16_MAX) {
|
|
LOGW(TAG, "short out of range");
|
|
break;
|
|
}
|
|
values[key] = static_cast<int16_t>(int_temp);
|
|
LOGD(TAG, "converted to SHORT");
|
|
success = true;
|
|
break;
|
|
case ConfigType::INT:
|
|
int_temp = strtoul(value, &end, 10);
|
|
if (*end != '\0') {
|
|
LOGW(TAG, "not a number");
|
|
break;
|
|
}
|
|
if (int_temp < INT32_MIN || int_temp > INT32_MAX) {
|
|
LOGW(TAG, "integer out of range");
|
|
break;
|
|
}
|
|
values[key] = static_cast<int16_t>(int_temp);
|
|
LOGD(TAG, "converted to INT");
|
|
success = true;
|
|
break;
|
|
case ConfigType::BOOL:
|
|
if ((value[0] == '1' && value[1] == '\0') || (strcasecmp(value, "true") == 0)) {
|
|
bool_temp = true;
|
|
} else if ((value[0] == '0' && value[1] == '\0') || (strcasecmp(value, "false") == 0)) {
|
|
bool_temp = false;
|
|
} else {
|
|
LOGW(TAG, "invalid value for boolean");
|
|
break;
|
|
}
|
|
values[key] = bool_temp;
|
|
LOGD(TAG, "converted to BOOL");
|
|
success = true;
|
|
break;
|
|
case ConfigType::FLOAT:
|
|
float_temp = strtof(value, &end);
|
|
if (*end != '\0') {
|
|
LOGW(TAG, "invalid float");
|
|
break;
|
|
}
|
|
values[key] = float_temp;
|
|
LOGD(TAG, "converted to FLOAT");
|
|
success = true;
|
|
break;
|
|
case ConfigType::CHAR:
|
|
values[key] = value[0];
|
|
LOGD(TAG, "converted to CHAR");
|
|
success = true;
|
|
break;
|
|
case ConfigType::STRING:
|
|
values[key] = String(value);
|
|
LOGD(TAG, "converted to STRING");
|
|
success = true;
|
|
break;
|
|
}
|
|
return success;
|
|
}
|