diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6985cc7 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,61 @@ +set(headers + src/configconstraints_base.h + src/configconstraints_espchrono.h + src/configmanager.h + src/configmanager_priv.h + src/configutils_base.h + src/configutils_cpputils.h + src/configutils_espchrono.h + src/configutils_espcpputils.h + src/configutils_espwifistack.h + src/configutils_optional.h + src/configutils_priv_enum.h + src/configutils_sntp.h + src/configwrapper.h + src/configwrapperinterface.h + src/configwrapper_priv.h +) + +set(sources + src/configwrapper_base.cpp + src/configwrapper_cpputils.cpp + src/configwrapper_espchrono.cpp + src/configwrapper_espcpputils.cpp + src/configwrapper_espwifistack.cpp + src/configwrapper_optional.cpp + src/configwrapper_sntp.cpp + src/configwrapperinterface.cpp +) + +set(dependencies + ArduinoJson + cpputils + espchrono + espcpputils + esphttpdutils + espwifistack + date + espchrono + expected + fmt +) + +idf_component_register( + INCLUDE_DIRS + src + SRCS + ${headers} + ${sources} + REQUIRES + ${dependencies} +) + +target_compile_options(${COMPONENT_TARGET} + PRIVATE + -fstack-reuse=all + -fstack-protector-all + -Wno-unused-function + -Wno-deprecated-declarations + -Wno-missing-field-initializers + -Wno-parentheses +) diff --git a/Kconfig.projbuild b/Kconfig.projbuild new file mode 100644 index 0000000..233b707 --- /dev/null +++ b/Kconfig.projbuild @@ -0,0 +1,44 @@ +menu "ESP Config lib settings" + +choice LOG_LOCAL_LEVEL_CONFIG + bool "go-e CONFIG log verbosity" + default LOG_LOCAL_LEVEL_CONFIG_INFO + help + Specify how much output to compile into the binary. + You can set lower verbosity level at runtime using + esp_log_level_set function. + + Note that this setting limits which log statements + are compiled into the program in go-e sources. So + setting this to, say, "Warning" would mean that + changing log level to "Debug" at runtime will not + be possible. + + config LOG_LOCAL_LEVEL_CONFIG_NONE + bool "No output" + config LOG_LOCAL_LEVEL_CONFIG_ERROR + bool "Error" + config LOG_LOCAL_LEVEL_CONFIG_WARN + bool "Warning" + config LOG_LOCAL_LEVEL_CONFIG_INFO + bool "Info" + config LOG_LOCAL_LEVEL_CONFIG_DEBUG + bool "Debug" + config LOG_LOCAL_LEVEL_CONFIG_VERBOSE + bool "Verbose" +endchoice + +config LOG_LOCAL_LEVEL_CONFIG + int + default 0 if LOG_LOCAL_LEVEL_CONFIG_NONE + default 1 if LOG_LOCAL_LEVEL_CONFIG_ERROR + default 2 if LOG_LOCAL_LEVEL_CONFIG_WARN + default 3 if LOG_LOCAL_LEVEL_CONFIG_INFO + default 4 if LOG_LOCAL_LEVEL_CONFIG_DEBUG + default 5 if LOG_LOCAL_LEVEL_CONFIG_VERBOSE + +config SEPARATE_FACTORY_NVS_PARTITION + bool "Separate factory nvs partition (for easy erasure of user settings)" + default y + +endmenu diff --git a/src/configconstraints_base.h b/src/configconstraints_base.h new file mode 100644 index 0000000..e71ffd4 --- /dev/null +++ b/src/configconstraints_base.h @@ -0,0 +1,104 @@ +#pragma once + +// system includes +#include + +// 3rdparty lib includes +#include +#include + +// local includes +#include "configwrapper.h" +#include "esphttpdutils.h" + +namespace espconfig { +template +ConfigConstraintReturnType StringMaxSize(const std::string &str) +{ + if (str.size() > MAX_LENGTH) + return tl::make_unexpected(fmt::format("String length {} exceeds maximum {}", str.size(), MAX_LENGTH)); + return {}; +} + +template +ConfigConstraintReturnType StringMinMaxSize(const std::string &str) +{ + if (str.size() < MIN_LENGTH || str.size() > MAX_LENGTH) + return tl::make_unexpected(fmt::format("String length {} exceeds range {} to {}", str.size(), MIN_LENGTH, MAX_LENGTH)); + return {}; +} + +inline ConfigConstraintReturnType StringEmpty(const std::string &str) +{ + if (!str.empty()) + return tl::make_unexpected("String has to be empty"); + return {}; +} + +inline ConfigConstraintReturnType StringValidUrl(const std::string &str) +{ + return esphttpdutils::urlverify(str); +} + +template::ConstraintCallback callback0, ConfigWrapper::ConstraintCallback callback1> +ConfigConstraintReturnType StringOr(const std::string &str) +{ + const auto result0 = callback0(str); + if (result0) + return {}; + const auto result1 = callback1(str); + if (result1) + return {}; + return tl::make_unexpected(fmt::format("None of the following 2 constraints succeded: {} | {}", result0.error(), result1.error())); +} + +template::ConstraintCallback callback0, ConfigWrapper::ConstraintCallback callback1> +ConfigConstraintReturnType StringAnd(const std::string &str) +{ + if (const auto result = callback0(str); !result) + return tl::make_unexpected(result.error()); + if (const auto result = callback1(str); !result) + return tl::make_unexpected(result.error()); + return {}; +} + +template +ConfigConstraintReturnType OneOf(typename ConfigWrapper::value_t val) +{ + if (!((ALLOWED_VALUES == val) || ...)) + tl::make_unexpected("Value not one of the allowed ones"); + return {}; +} + +template +ConfigConstraintReturnType MinValue(typename ConfigWrapper::value_t val) +{ + if (val < MIN_VALUE) + return tl::make_unexpected(fmt::format("Value {} exceeds minimum {}", val, MIN_VALUE)); + return {}; +} + +template +ConfigConstraintReturnType MaxValue(typename ConfigWrapper::value_t val) +{ + if (val > MAX_VALUE) + return tl::make_unexpected(fmt::format("Value {} exceeds maximum {}", val, MAX_VALUE)); + return {}; +} + +template +ConfigConstraintReturnType MinMaxValue(typename ConfigWrapper::value_t val) +{ + if (val < MIN_VALUE || val > MAX_VALUE) + return tl::make_unexpected(fmt::format("Value {} exceeds range {} to {}", val, MIN_VALUE, MAX_VALUE)); + return {}; +} + +template +ConfigConstraintReturnType MinMaxOrZeroValue(typename ConfigWrapper::value_t val) +{ + if (val != 0 && (val < MIN_VALUE || val > MAX_VALUE)) + return tl::make_unexpected(fmt::format("Value {} exceeds constraint 0 or range {} to {}", val, MIN_VALUE, MAX_VALUE)); + return {}; +} +} // namespace espconfig diff --git a/src/configconstraints_espchrono.h b/src/configconstraints_espchrono.h new file mode 100644 index 0000000..98747ad --- /dev/null +++ b/src/configconstraints_espchrono.h @@ -0,0 +1,22 @@ +#pragma once + +// system includes +#include + +// 3rdparty lib includes +#include +#include + +// local includes +#include "configwrapper.h" +#include "espchrono.h" + +namespace espconfig { +inline ConfigConstraintReturnType MinTimeSyncInterval(espchrono::milliseconds32 val) +{ + using namespace std::chrono_literals; + if (val < 15s) + return tl::make_unexpected("SNTPv4 RFC 4330 enforces a minimum update time of 15 seconds"); + return {}; +} +} // namespace espconfig diff --git a/src/configmanager.h b/src/configmanager.h new file mode 100644 index 0000000..2f0943e --- /dev/null +++ b/src/configmanager.h @@ -0,0 +1,56 @@ +#pragma once + +#include "sdkconfig.h" + +// esp-idf includes +#include + +// local includes +#include "configwrapper.h" + +namespace espconfig { + +template +class ConfigManager : public ConfigContainer +{ +public: + using ConfigContainer::ConfigContainer; + + nvs_handle_t nvs_handle_user{}; +#ifdef CONFIG_SEPARATE_FACTORY_NVS_PARTITION + nvs_handle_t nvs_handle_factory{}; +#endif + + esp_err_t init(); + //bool erase(); + ConfigStatusReturnType reset(); + + ConfigWrapperInterface *findConfigByKey(std::string_view key); + + template + ConfigStatusReturnType write_config(ConfigWrapper &config, typename ConfigWrapper::value_t value); + + inline ConfigStatusReturnType reset_config(ConfigWrapperInterface &config); + + inline ConfigStatusReturnType force_reset_config(ConfigWrapperInterface &config); +}; + +template template +ConfigStatusReturnType ConfigManager::write_config(ConfigWrapper &config, typename ConfigWrapper::value_t value) +{ + return config.write(nvs_handle_user, value); +} + +template +inline ConfigStatusReturnType ConfigManager::reset_config(ConfigWrapperInterface &config) +{ + return config.reset(nvs_handle_user); +} + +template +inline ConfigStatusReturnType ConfigManager::force_reset_config(ConfigWrapperInterface &config) +{ + return config.forceReset(nvs_handle_user); +} + +} // namespace espconfig diff --git a/src/configmanager_priv.h b/src/configmanager_priv.h new file mode 100644 index 0000000..b279fd0 --- /dev/null +++ b/src/configmanager_priv.h @@ -0,0 +1,173 @@ +#pragma once + +#include "configmanager.h" + +#include "sdkconfig.h" +#define LOG_LOCAL_LEVEL CONFIG_LOG_LOCAL_LEVEL_CONFIG + +// esp-idf includes +#include +#include + +// local includes +#include "espchrono.h" + +#define INSTANTIATE_CONFIGMANAGER_TEMPLATES(Type) \ + template esp_err_t ConfigManager::init(); \ + /* template bool ConfigManager::erase(); */ \ + template ConfigStatusReturnType ConfigManager::reset(); \ + template ConfigWrapperInterface *ConfigManager::findConfigByKey(std::string_view key); + +namespace espconfig { +namespace { +constexpr const char * const TAG = "CONFIG"; +} // namespace + +template +esp_err_t ConfigManager::init() +{ + ESP_LOGD(TAG, "called"); + + auto before = espchrono::millis_clock::now(); +#ifndef CONFIG_NVS_ENCRYPTION + { + const auto result = nvs_flash_init_partition("nvs"); + ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "nvs_flash_init() for user returned: %s", esp_err_to_name(result)); + if (result != ESP_OK) + return result; + } + +#ifdef CONFIG_SEPARATE_FACTORY_NVS_PARTITION + { + const auto result = nvs_flash_init_partition("nvsFac"); + ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "nvs_flash_init() for factory returned: %s", esp_err_to_name(result)); + if (result != ESP_OK) + return result; + } +#endif +#else + const esp_partition_t *key_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS_KEYS, NULL); + if (!key_part) + { + ESP_LOGE(TAG, "nvs_keys partition not found!"); + return ESP_ERR_NVS_PART_NOT_FOUND; + } + + nvs_sec_cfg_t cfg; + + { + const auto result = nvs_flash_read_security_cfg(key_part, &cfg); + ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "nvs_flash_read_security_cfg() returned: %s", esp_err_to_name(result)); + if (result != ESP_OK) + { + if (result == ESP_ERR_NVS_KEYS_NOT_INITIALIZED) + { + const auto result = nvs_flash_generate_keys(key_part, &cfg); + ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "nvs_flash_generate_keys() returned: %s", esp_err_to_name(result)); + if (result != ESP_OK) + return result; + } + else + return result; + } + } + + { + const auto result = nvs_flash_secure_init_partition("nvs", &cfg); + ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "nvs_flash_secure_init() for user returned: %s", esp_err_to_name(result)); + if (result != ESP_OK) + return result; + } + +#ifdef CONFIG_SEPARATE_FACTORY_NVS_PARTITION + { + const auto result = nvs_flash_secure_init_partition("nvsFac", &cfg); + ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "nvs_flash_secure_init() for user returned: %s", esp_err_to_name(result)); + if (result != ESP_OK) + return result; + } +#endif +#endif + + { + const auto result = nvs_open_from_partition("nvs", "recharge", NVS_READWRITE, &nvs_handle_user); + ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "nvs_open() for user partition returned: %s", esp_err_to_name(result)); + if (result != ESP_OK) + return result; + } + +#ifdef CONFIG_SEPARATE_FACTORY_NVS_PARTITION + { + const auto result = nvs_open_from_partition("nvsFac", "recharge", NVS_READWRITE, &nvs_handle_factory); + ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "nvs_open() for factory partition returned: %s", esp_err_to_name(result)); + if (result != ESP_OK) + return result; + } +#endif + + auto after = espchrono::millis_clock::now(); + + ESP_LOGI(TAG, "initializing NVS took %lldms", std::chrono::milliseconds{after-before}.count()); + + before = espchrono::millis_clock::now(); + + bool success = true; + for (ConfigWrapperInterface &config : ConfigContainer::getAllConfigParams()) + if (const auto result = config.loadFromFlash(nvs_handle_user); !result) + { + ESP_LOGE(TAG, "config parameter %s failed to load: %.*s", config.nvsName(), result.error().size(), result.error().data()); + success = false; + } + + after = espchrono::millis_clock::now(); + + ESP_LOGI(TAG, "loading all config params took %lldms", std::chrono::milliseconds{after-before}.count()); + + return success ? ESP_OK : ESP_FAIL; +} + +//template +//bool ConfigManager::erase() +//{ +// ESP_LOGW(TAG, "called"); + +// nvs_close(nvs_handle_user); +// const auto err = nvs_flash_erase(); +// ESP_LOG_LEVEL_LOCAL((err == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "nvs_flash_erase() returned: %s", esp_err_to_name(err)); +// if (err != ESP_OK) +// return false; + +// return init(); +//} + +template +ConfigStatusReturnType ConfigManager::reset() +{ + ESP_LOGW(TAG, "called"); + + std::string message; + + for (ConfigWrapperInterface &config : ConfigContainer::getAllConfigParams()) + if (const auto result = reset_config(config); !result) + { + if (!message.empty()) + message.append(", "); + message.append(fmt::format("reset of {} failed: {}", config.nvsName(), result.error())); + } + + if (!message.empty()) + return tl::make_unexpected(std::move(message)); + + return {}; +} + +template +ConfigWrapperInterface *ConfigManager::findConfigByKey(std::string_view key) +{ + const auto configParams = ConfigContainer::getAllConfigParams(); + const auto iter = std::find_if(std::cbegin(configParams), std::cend(configParams), + [&key](const ConfigWrapperInterface &config){ return key == config.nvsName(); }); + return iter != std::cend(configParams) ? &iter->get() : nullptr; +} + +} // namespace espconfig diff --git a/src/configutils_base.h b/src/configutils_base.h new file mode 100644 index 0000000..a8a20d0 --- /dev/null +++ b/src/configutils_base.h @@ -0,0 +1,77 @@ +#pragma once + +// system includes +#include +#include + +// esp-idf includes +#include + +// local includes +#include "futurecpp.h" + +namespace espconfig { + +inline esp_err_t nvs_get(nvs_handle handle, const char* key, int8_t* out_value) { return nvs_get_i8 (handle, key, out_value); } +inline esp_err_t nvs_get(nvs_handle handle, const char* key, uint8_t* out_value) { return nvs_get_u8 (handle, key, out_value); } +inline esp_err_t nvs_get(nvs_handle handle, const char* key, int16_t* out_value) { return nvs_get_i16(handle, key, out_value); } +inline esp_err_t nvs_get(nvs_handle handle, const char* key, uint16_t* out_value){ return nvs_get_u16(handle, key, out_value); } +inline esp_err_t nvs_get(nvs_handle handle, const char* key, int32_t* out_value) { return nvs_get_i32(handle, key, out_value); } +inline esp_err_t nvs_get(nvs_handle handle, const char* key, uint32_t* out_value){ return nvs_get_u32(handle, key, out_value); } +inline esp_err_t nvs_get(nvs_handle handle, const char* key, int64_t* out_value) { return nvs_get_i64(handle, key, out_value); } +inline esp_err_t nvs_get(nvs_handle handle, const char* key, uint64_t* out_value){ return nvs_get_u64(handle, key, out_value); } +inline esp_err_t nvs_get(nvs_handle handle, const char* key, bool* out_value) +{ + uint8_t temp; + const auto result = nvs_get(handle, key, &temp); + if (result == ESP_OK && out_value) + *out_value = temp; + return result; +} +inline esp_err_t nvs_get(nvs_handle handle, const char* key, float* out_value) +{ + uint32_t temp; + const auto result = nvs_get(handle, key, &temp); + if (result == ESP_OK) + *out_value = std::bit_cast(temp); + return result; +} +inline esp_err_t nvs_get(nvs_handle handle, const char* key, double* out_value) +{ + uint64_t temp; + const auto result = nvs_get(handle, key, &temp); + if (result == ESP_OK) + *out_value = std::bit_cast(temp); + return result; +} +inline esp_err_t nvs_get(nvs_handle handle, const char* key, std::string* out_value) +{ + size_t length; + if (const esp_err_t result = nvs_get_str(handle, key, nullptr, &length); result != ESP_OK) + return result; + + char buf[length]; + if (const esp_err_t result = nvs_get_str(handle, key, buf, &length); result != ESP_OK) + return result; + + *out_value = buf; + + return ESP_OK; +} + + + +inline esp_err_t nvs_set(nvs_handle handle, const char* key, int8_t value) { return nvs_set_i8 (handle, key, value); } +inline esp_err_t nvs_set(nvs_handle handle, const char* key, uint8_t value) { return nvs_set_u8 (handle, key, value); } +inline esp_err_t nvs_set(nvs_handle handle, const char* key, int16_t value) { return nvs_set_i16(handle, key, value); } +inline esp_err_t nvs_set(nvs_handle handle, const char* key, uint16_t value) { return nvs_set_u16(handle, key, value); } +inline esp_err_t nvs_set(nvs_handle handle, const char* key, int32_t value) { return nvs_set_i32(handle, key, value); } +inline esp_err_t nvs_set(nvs_handle handle, const char* key, uint32_t value) { return nvs_set_u32(handle, key, value); } +inline esp_err_t nvs_set(nvs_handle handle, const char* key, int64_t value) { return nvs_set_i64(handle, key, value); } +inline esp_err_t nvs_set(nvs_handle handle, const char* key, uint64_t value) { return nvs_set_u64(handle, key, value); } +inline esp_err_t nvs_set(nvs_handle handle, const char* key, bool value) { return nvs_set_u8 (handle, key, value); } +inline esp_err_t nvs_set(nvs_handle handle, const char* key, float value) { return nvs_set(handle, key, std::bit_cast(value)); } +inline esp_err_t nvs_set(nvs_handle handle, const char* key, double value) { return nvs_set(handle, key, std::bit_cast(value)); } +inline esp_err_t nvs_set(nvs_handle handle, const char* key, const std::string &value) { return nvs_set_str(handle, key, value.c_str()); } + +} // namespace espconfig diff --git a/src/configutils_cpputils.h b/src/configutils_cpputils.h new file mode 100644 index 0000000..e14c07f --- /dev/null +++ b/src/configutils_cpputils.h @@ -0,0 +1,28 @@ +#pragma once + +// esp-idf includes +#include + +// local includes +#include "configutils_base.h" +#include "color_utils.h" + +namespace espconfig { + +inline esp_err_t nvs_get(nvs_handle handle, const char* key, cpputils::ColorHelper* out_value) +{ + uint32_t temp; + const auto result = nvs_get(handle, key, &temp); + if (result == ESP_OK) + *out_value = cpputils::numberToColor(temp); + return result; +} + + + +inline esp_err_t nvs_set(nvs_handle handle, const char* key, cpputils::ColorHelper value) +{ + return nvs_set(handle, key, cpputils::colorToNumber(value)); +} + +} // namespace espconfig diff --git a/src/configutils_espchrono.h b/src/configutils_espchrono.h new file mode 100644 index 0000000..68d994d --- /dev/null +++ b/src/configutils_espchrono.h @@ -0,0 +1,32 @@ +#pragma once + +// local includes +#include "configutils_priv_enum.h" +#include "espchrono.h" + +namespace espconfig { + +IMPLEMENT_NVS_GET_SET_ENUM(espchrono::DayLightSavingMode) + +#define IMPLEMENT_NVS_GET_SET_CHRONO(Name) \ + inline esp_err_t nvs_get(nvs_handle handle, const char* key, Name* out_value) \ + { \ + Name::rep temp; \ + const auto result = nvs_get(handle, key, &temp); \ + if (result == ESP_OK && out_value) \ + *out_value = Name{temp}; \ + return result; \ + } \ + \ + inline esp_err_t nvs_set(nvs_handle handle, const char* key, Name value) \ + { \ + return nvs_set(handle, key, Name::rep(value.count())); \ + } + +IMPLEMENT_NVS_GET_SET_CHRONO(espchrono::milliseconds32) +IMPLEMENT_NVS_GET_SET_CHRONO(espchrono::seconds32) +IMPLEMENT_NVS_GET_SET_CHRONO(espchrono::minutes32) +IMPLEMENT_NVS_GET_SET_CHRONO(espchrono::hours32) +#undef IMPLEMENT_NVS_GET_SET_CHRONO + +} // namespace espconfig diff --git a/src/configutils_espcpputils.h b/src/configutils_espcpputils.h new file mode 100644 index 0000000..f093fdc --- /dev/null +++ b/src/configutils_espcpputils.h @@ -0,0 +1,11 @@ +#pragma once + +// local includes +#include "configutils_priv_enum.h" +#include "taskutils.h" + +namespace espconfig { + +IMPLEMENT_NVS_GET_SET_ENUM(espcpputils::CoreAffinity) + +} // namespace espconfig diff --git a/src/configutils_espwifistack.h b/src/configutils_espwifistack.h new file mode 100644 index 0000000..a45c9b9 --- /dev/null +++ b/src/configutils_espwifistack.h @@ -0,0 +1,28 @@ +#pragma once + +// esp-idf includes +#include + +// local includes +#include "configutils_base.h" +#include "espwifiutils.h" + +namespace espconfig { + +inline esp_err_t nvs_get(nvs_handle handle, const char* key, wifi_stack::ip_address_t* out_value) +{ + wifi_stack::ip_address_t::value_t temp; + const auto result = nvs_get(handle, key, &temp); + if (result == ESP_OK) + *out_value = wifi_stack::ip_address_t{temp}; + return result; +} + + + +inline esp_err_t nvs_set(nvs_handle handle, const char* key, wifi_stack::ip_address_t value) +{ + return nvs_set(handle, key, value.value()); +} + +} // namespace espconfig diff --git a/src/configutils_optional.h b/src/configutils_optional.h new file mode 100644 index 0000000..839d198 --- /dev/null +++ b/src/configutils_optional.h @@ -0,0 +1,83 @@ +#pragma once + +// system includes +#include +#include + +// esp-idf includes +#include + +// local includes +#include "configutils_base.h" + +namespace espconfig { + +template +esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional* out_value) +{ + size_t length; + if (const esp_err_t result = nvs_get_blob(handle, key, nullptr, &length); result != ESP_OK) + return result; + + struct Pair { T value; bool valid; }; + union { Pair pair; char buf[sizeof(Pair)]; }; + + if (length != sizeof(Pair)) + { + //ESP_LOGW(TAG, "invalid size of optional, expected=%u actual=&zd", sizeof(Pair), length); + return ESP_ERR_INVALID_SIZE; + } + + if (const esp_err_t result = nvs_get_blob(handle, key, buf, &length); result != ESP_OK) + return result; + + if (pair.valid) + *out_value = pair.value; + else + *out_value = std::nullopt; + + return ESP_OK; +} + +template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional* out_value); +template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional* out_value); +template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional* out_value); +template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional* out_value); +template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional* out_value); +template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional* out_value); +template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional* out_value); +template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional* out_value); +template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional* out_value); +template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional* out_value); + + + +template +esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional value) +{ + struct Pair { T value; bool valid; }; + union { Pair pair; char buf[sizeof(Pair)]; }; + + if (value) + { + pair.valid = true; + pair.value = *value; + } + else + pair.valid = false; + + return nvs_set_blob(handle, key, buf, sizeof(buf)); +} + +template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional value); +template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional value); +template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional value); +template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional value); +template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional value); +template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional value); +template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional value); +template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional value); +template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional value); +template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional value); + +} // namespace espconfig diff --git a/src/configutils_priv_enum.h b/src/configutils_priv_enum.h new file mode 100644 index 0000000..9f35c34 --- /dev/null +++ b/src/configutils_priv_enum.h @@ -0,0 +1,25 @@ +#pragma once + +// system includes +#include + +// esp-idf includes +#include + +// local includes +#include "configutils_base.h" + +#define IMPLEMENT_NVS_GET_SET_ENUM(Name) \ + inline esp_err_t nvs_get(nvs_handle handle, const char* key, Name* out_value) \ + { \ + std::underlying_type_t temp; \ + const auto result = nvs_get(handle, key, &temp); \ + if (result == ESP_OK) \ + *out_value = Name(temp); \ + return result; \ + } \ + \ + inline esp_err_t nvs_set(nvs_handle handle, const char* key, Name value) \ + { \ + return nvs_set(handle, key, std::underlying_type_t(value)); \ + } diff --git a/src/configutils_sntp.h b/src/configutils_sntp.h new file mode 100644 index 0000000..dbbce72 --- /dev/null +++ b/src/configutils_sntp.h @@ -0,0 +1,13 @@ +#pragma once + +// esp-idf includes +#include + +// local includes +#include "configutils_priv_enum.h" + +namespace espconfig { + +IMPLEMENT_NVS_GET_SET_ENUM(sntp_sync_mode_t) + +} // namespace espconfig diff --git a/src/configwrapper.h b/src/configwrapper.h new file mode 100644 index 0000000..c88318c --- /dev/null +++ b/src/configwrapper.h @@ -0,0 +1,70 @@ +#pragma once + +// system includes +#include +#include + +// 3rdparty lib includes +#include + +// local includes +#include "configwrapperinterface.h" +#include "cppmacros.h" + +namespace espconfig { + +using ConfigConstraintReturnType = ConfigStatusReturnType; + +template +class ConfigWrapper final : public ConfigWrapperInterface +{ + CPP_DISABLE_COPY_MOVE(ConfigWrapper) + + using DefaultValueCallbackRef = T(&)(); + using DefaultValueCallbackPtr = T(*)(); + +public: + using value_t = typename std::conditional::value, const T &, T>::type; + using ConstraintCallback = ConfigConstraintReturnType(*)(value_t); + + ConfigWrapper(const T &defaultValue, AllowReset allowReset, ConstraintCallback constraintCallback, const char *nvsName); + ConfigWrapper(T &&defaultValue, AllowReset allowReset, ConstraintCallback constraintCallback, const char *nvsName); + ConfigWrapper(const ConfigWrapper &factoryConfig, ConstraintCallback constraintCallback, const char *nvsName); + ConfigWrapper(const DefaultValueCallbackRef &defaultCallback, AllowReset allowReset, ConstraintCallback constraintCallback, const char *nvsName); + ~ConfigWrapper() override; + + ConfigStatusReturnType write(nvs_handle_t nvsHandle, value_t value); + + const char *type() const override final; + + std::string valueAsString() const override final; + std::string defaultAsString() const override final; + + T defaultValue() const; + + ConfigStatusReturnType loadFromFlash(nvs_handle_t nvsHandle) override final; + ConfigStatusReturnType reset(nvs_handle_t nvsHandle) override final; + ConfigStatusReturnType forceReset(nvs_handle_t nvsHandle) override final; + + ConfigConstraintReturnType checkValue(value_t value) const; + + const T &value{m_value}; + +private: + ConfigStatusReturnType writeToFlash(nvs_handle_t nvsHandle, value_t value); + + T m_value; + + const enum : uint8_t { DefaultByValue, DefaultByFactoryConfig, DefaultByCallback } m_defaultType; + + union + { + const T m_defaultValue; + const ConfigWrapper * const m_factoryConfig; + const DefaultValueCallbackPtr m_defaultCallback; + }; + + const ConstraintCallback m_constraintCallback; +}; + +} // namespace espconfig diff --git a/src/configwrapper_base.cpp b/src/configwrapper_base.cpp new file mode 100644 index 0000000..bb59175 --- /dev/null +++ b/src/configwrapper_base.cpp @@ -0,0 +1,21 @@ +#include "configutils_base.h" +#include "strutils.h" +#define CONFIGWRAPPER_TOSTRING_USINGS using ::cpputils::toString; +#include "configwrapper_priv.h" + +namespace espconfig { + +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(bool) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(uint8_t) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(int8_t) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(uint16_t) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(int16_t) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(uint32_t) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(int32_t) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(uint64_t) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(int64_t) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(float) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(double) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::string) + +} // namespace espconfig diff --git a/src/configwrapper_cpputils.cpp b/src/configwrapper_cpputils.cpp new file mode 100644 index 0000000..35eaf24 --- /dev/null +++ b/src/configwrapper_cpputils.cpp @@ -0,0 +1,8 @@ +#include "configutils_cpputils.h" +#define CONFIGWRAPPER_TOSTRING_USINGS using ::cpputils::toString; +#include "configwrapper_priv.h" + +namespace espconfig { +using ColorHelper = cpputils::ColorHelper; +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(ColorHelper) +} // namespace espconfig diff --git a/src/configwrapper_espchrono.cpp b/src/configwrapper_espchrono.cpp new file mode 100644 index 0000000..027222f --- /dev/null +++ b/src/configwrapper_espchrono.cpp @@ -0,0 +1,19 @@ +#include "configutils_espchrono.h" +#define CONFIGWRAPPER_TOSTRING_USINGS using ::espchrono::toString; +#include "configwrapper_priv.h" + +namespace espconfig { + +using DayLightSavingMode = espchrono::DayLightSavingMode; +using milliseconds32 = espchrono::milliseconds32; +using seconds32 = espchrono::seconds32; +using minutes32 = espchrono::minutes32; +using hours32 = espchrono::hours32; + +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(espchrono::DayLightSavingMode) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(espchrono::milliseconds32) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(espchrono::seconds32) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(espchrono::minutes32) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(espchrono::hours32) + +} // namespace espconfig diff --git a/src/configwrapper_espcpputils.cpp b/src/configwrapper_espcpputils.cpp new file mode 100644 index 0000000..890c8d9 --- /dev/null +++ b/src/configwrapper_espcpputils.cpp @@ -0,0 +1,8 @@ +#include "configutils_espcpputils.h" +#define CONFIGWRAPPER_TOSTRING_USINGS using ::espcpputils::toString; +#include "configwrapper_priv.h" + +namespace espconfig { +using CoreAffinity = espcpputils::CoreAffinity; +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(CoreAffinity) +} // namespace espconfig diff --git a/src/configwrapper_espwifistack.cpp b/src/configwrapper_espwifistack.cpp new file mode 100644 index 0000000..25e2e23 --- /dev/null +++ b/src/configwrapper_espwifistack.cpp @@ -0,0 +1,8 @@ +#include "configutils_espwifistack.h" +#define CONFIGWRAPPER_TOSTRING_USINGS using ::wifi_stack::toString; +#include "configwrapper_priv.h" + +namespace espconfig { +using ip_address_t = wifi_stack::ip_address_t; +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(ip_address_t) +} // namespace espconfig diff --git a/src/configwrapper_optional.cpp b/src/configwrapper_optional.cpp new file mode 100644 index 0000000..56b5768 --- /dev/null +++ b/src/configwrapper_optional.cpp @@ -0,0 +1,19 @@ +#include "configutils_optional.h" +#include "strutils.h" +#define CONFIGWRAPPER_TOSTRING_USINGS using ::cpputils::toString; +#include "configwrapper_priv.h" + +namespace espconfig { + +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional) +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional) + +} // namespace espconfig diff --git a/src/configwrapper_priv.h b/src/configwrapper_priv.h new file mode 100644 index 0000000..253b411 --- /dev/null +++ b/src/configwrapper_priv.h @@ -0,0 +1,248 @@ +#pragma once + +#include "sdkconfig.h" +#define LOG_LOCAL_LEVEL CONFIG_LOG_LOCAL_LEVEL_CONFIG + +// system includes +#include +#include + +// esp-idf includes +#include + +// 3rdparty lib includes +#include + +// local includes +#include "configwrapper.h" +#include "cpputils.h" + +#define INSTANTIATE_CONFIGWRAPPER_TEMPLATES(TYPE) \ + template ConfigWrapper::ConfigWrapper(const TYPE &defaultValue, AllowReset allowReset, ConstraintCallback constraintCallback, const char *nvsName); \ + template ConfigWrapper::ConfigWrapper(TYPE &&defaultValue, AllowReset allowReset, ConstraintCallback constraintCallback, const char *nvsName); \ + template ConfigWrapper::ConfigWrapper(const ConfigWrapper &factoryConfig, ConstraintCallback constraintCallback, const char *nvsName); \ + template ConfigWrapper::ConfigWrapper(DefaultValueCallbackRef &defaultCallback, AllowReset allowReset, ConstraintCallback constraintCallback, const char *nvsName); \ + template ConfigStatusReturnType ConfigWrapper::write(nvs_handle_t nvsHandle, value_t value); \ + template<> const char *ConfigWrapper::type() const { return #TYPE; } \ + template std::string ConfigWrapper::valueAsString() const; \ + template std::string ConfigWrapper::defaultAsString() const; \ + template TYPE ConfigWrapper::defaultValue() const; \ + template ConfigStatusReturnType ConfigWrapper::loadFromFlash(nvs_handle_t nvsHandle); \ + template ConfigStatusReturnType ConfigWrapper::reset(nvs_handle_t nvsHandle); \ + template ConfigStatusReturnType ConfigWrapper::forceReset(nvs_handle_t nvsHandle); \ + template ConfigConstraintReturnType ConfigWrapper::checkValue(value_t value) const; \ + template ConfigStatusReturnType ConfigWrapper::writeToFlash(nvs_handle_t nvsHandle, value_t value); + +namespace espconfig { +namespace { +constexpr const char * const TAG = "CONFIG"; +} // namespace + +template +ConfigWrapper::ConfigWrapper(const T &defaultValue, AllowReset allowReset, ConstraintCallback constraintCallback, const char *nvsName) : + ConfigWrapperInterface{allowReset, nvsName}, + m_defaultType{DefaultByValue}, + m_defaultValue{defaultValue}, + m_constraintCallback{constraintCallback} +{ +} + +template +ConfigWrapper::ConfigWrapper(T &&defaultValue, AllowReset allowReset, ConstraintCallback constraintCallback, const char *nvsName) : + ConfigWrapperInterface{allowReset, nvsName}, + m_defaultType{DefaultByValue}, + m_defaultValue{std::move(defaultValue)}, + m_constraintCallback{constraintCallback} +{ +} + +template +ConfigWrapper::ConfigWrapper(const ConfigWrapper &factoryConfig, ConstraintCallback constraintCallback, const char *nvsName) : + ConfigWrapperInterface{AllowReset::DoReset, nvsName}, + m_defaultType{DefaultByFactoryConfig}, + m_factoryConfig{&factoryConfig}, + m_constraintCallback{constraintCallback} +{ +} + +template +ConfigWrapper::ConfigWrapper(const DefaultValueCallbackRef &defaultCallback, AllowReset allowReset, ConstraintCallback constraintCallback, const char *nvsName) : + ConfigWrapperInterface{allowReset, nvsName}, + m_defaultType{DefaultByCallback}, + m_defaultCallback{&defaultCallback}, + m_constraintCallback{constraintCallback} +{ +} + +template +ConfigWrapper::~ConfigWrapper() +{ + switch (m_defaultType) + { + case DefaultByValue: m_defaultValue.~T(); break; + case DefaultByFactoryConfig: /*m_factoryConfig.~typeof(m_factoryConfig)();*/ break; + case DefaultByCallback: /*m_defaultCallback.~typeof(m_defaultCallback)();*/ break; + } +} + +template +ConfigStatusReturnType ConfigWrapper::write(nvs_handle_t nvsHandle, value_t value) +{ + CONFIGWRAPPER_TOSTRING_USINGS + + ESP_LOGD(TAG, "%s %s", m_nvsName, toString(value).c_str()); + + if (!m_loaded) + ESP_LOGE(TAG, "%s has not been loaded yet!", m_nvsName); + + if (const auto result = checkValue(value); !result) + { + ESP_LOGW(TAG, "%s cannot be set to %s: constraint not met: %s", m_nvsName, toString(value).c_str(), result.error().c_str()); + return result; + } + + if (m_value == value) + return {}; + + return writeToFlash(nvsHandle, value); +} + +template +std::string ConfigWrapper::valueAsString() const +{ + CONFIGWRAPPER_TOSTRING_USINGS + + if (m_touched) + return toString(m_value); + else + return "--not-touched-- " + toString(m_value); +} + +template +std::string ConfigWrapper::defaultAsString() const +{ + CONFIGWRAPPER_TOSTRING_USINGS + + return toString(defaultValue()); +} + +template +T ConfigWrapper::defaultValue() const +{ + switch (m_defaultType) + { + case DefaultByValue: return m_defaultValue; + case DefaultByFactoryConfig: assert(m_factoryConfig->m_loaded); return m_factoryConfig->value; + case DefaultByCallback: return m_defaultCallback(); + } + + __builtin_unreachable(); +} + +template +ConfigStatusReturnType ConfigWrapper::loadFromFlash(nvs_handle_t nvsHandle) +{ + //ESP_LOGD(TAG, "%s", m_nvsName); + + const auto result = nvs_get(nvsHandle, m_nvsName, &m_value); + + ESP_LOG_LEVEL_LOCAL((cpputils::is_in(result, ESP_OK, ESP_ERR_NVS_NOT_FOUND) ? ESP_LOG_DEBUG : ESP_LOG_ERROR), TAG, "%s nvs_get() returned: %s", m_nvsName, esp_err_to_name(result)); + + if (result == ESP_OK) + { + if (const auto result = checkValue(m_value); !result) + { + ESP_LOGE(TAG, "%s constraint not met for value in flash: %s", m_nvsName, result.error().c_str()); + return forceReset(nvsHandle); + } + + m_loaded = true; + m_touched = true; + return {}; + } + else if (result == ESP_ERR_NVS_NOT_FOUND) + { + m_loaded = true; + m_touched = false; + m_value = defaultValue(); + if (const auto result = checkValue(m_value); !result) + ESP_LOGE(TAG, "%s constraint not met for value from default: %s", m_nvsName, result.error().c_str()); + + return {}; + } + else + { + return forceReset(nvsHandle); + } +} + +template +ConfigStatusReturnType ConfigWrapper::reset(nvs_handle_t nvsHandle) +{ + ESP_LOGD(TAG, "%s", m_nvsName); + + if (!m_allowReset) + return {}; + + return forceReset(nvsHandle); +} + +template +ConfigStatusReturnType ConfigWrapper::forceReset(nvs_handle_t nvsHandle) +{ + ESP_LOGD(TAG, "%s", m_nvsName); + + auto result = nvs_erase_key(nvsHandle, m_nvsName); + + ESP_LOG_LEVEL_LOCAL((cpputils::is_in(result, ESP_OK, ESP_ERR_NVS_NOT_FOUND) ? ESP_LOG_DEBUG : ESP_LOG_ERROR), TAG, "%s nvs_erase_key() returned: %s", m_nvsName, esp_err_to_name(result)); + + if (result == ESP_ERR_NVS_NOT_FOUND) + { + if (m_touched) + ESP_LOGE(TAG, "%s for touched and not found?!", m_nvsName); + result = ESP_OK; + } + + if (result == ESP_OK || !m_loaded) + { + if (!m_loaded /* && result == ESP_OK */) + m_loaded = true; + m_touched = false; + m_value = defaultValue(); + } + + if (result != ESP_OK) + return tl::make_unexpected(std::string{"nvs_erase_key() failed with "} + esp_err_to_name(result)); + + return {}; +} + +template +ConfigConstraintReturnType ConfigWrapper::checkValue(value_t value) const +{ + if (!m_constraintCallback) + return {}; + + return m_constraintCallback(value); +} + +template +ConfigStatusReturnType ConfigWrapper::writeToFlash(nvs_handle_t nvsHandle, value_t value) +{ + CONFIGWRAPPER_TOSTRING_USINGS + + ESP_LOGD(TAG, "%s %s", m_nvsName, toString(value).c_str()); + + const auto result = nvs_set(nvsHandle, m_nvsName, value); + + ESP_LOG_LEVEL_LOCAL((result == ESP_OK ? ESP_LOG_INFO : ESP_LOG_ERROR), TAG, "%s %s nvs_set() returned %s", m_nvsName, toString(value).c_str(), esp_err_to_name(result)); + + m_value = value; + m_touched = true; + + if (result != ESP_OK) + return tl::make_unexpected(std::string{"nvs_set() failed with "} + esp_err_to_name(result)); + return {}; +} + +} // namespace espconfig diff --git a/src/configwrapper_sntp.cpp b/src/configwrapper_sntp.cpp new file mode 100644 index 0000000..0baf106 --- /dev/null +++ b/src/configwrapper_sntp.cpp @@ -0,0 +1,8 @@ +#include "configutils_sntp.h" +#include "espstrutils.h" +#define CONFIGWRAPPER_TOSTRING_USINGS using ::espcpputils::toString; +#include "configwrapper_priv.h" + +namespace espconfig { +INSTANTIATE_CONFIGWRAPPER_TEMPLATES(sntp_sync_mode_t) +} // namespace espconfig diff --git a/src/configwrapperinterface.cpp b/src/configwrapperinterface.cpp new file mode 100644 index 0000000..e1bcec0 --- /dev/null +++ b/src/configwrapperinterface.cpp @@ -0,0 +1,31 @@ +#include "configwrapperinterface.h" + +#include "sdkconfig.h" +#define LOG_LOCAL_LEVEL CONFIG_LOG_LOCAL_LEVEL_CONFIG + +// system includes +#include +#include + +// esp-idf includes +#include +#include + +namespace espconfig { +namespace { +constexpr const char * const TAG = "CONFIG"; +} // namespace + +ConfigWrapperInterface::ConfigWrapperInterface(AllowReset allowReset, const char *nvsName) : + m_allowReset{allowReset == AllowReset::DoReset}, + m_nvsName{nvsName} +{ + { + const auto length = std::strlen(nvsName); + if (length >= NVS_KEY_NAME_MAX_SIZE) + ESP_LOGE(TAG, "invalid nvs key %s (too long %zd)", nvsName, length); + assert(length < NVS_KEY_NAME_MAX_SIZE); + } +} + +} // namespace espconfig diff --git a/src/configwrapperinterface.h b/src/configwrapperinterface.h new file mode 100644 index 0000000..50b36bf --- /dev/null +++ b/src/configwrapperinterface.h @@ -0,0 +1,51 @@ +#pragma once + +// system includes +#include + +// esp-idf includes +#include + +// 3rdparty lib includes +#include + +// local includes +#include "cppmacros.h" + +namespace espconfig { +enum class AllowReset { NoReset, DoReset }; +constexpr auto NoReset = AllowReset::NoReset; +constexpr auto DoReset = AllowReset::DoReset; + +using ConfigStatusReturnType = tl::expected; + +class ConfigWrapperInterface +{ + CPP_DISABLE_COPY_MOVE(ConfigWrapperInterface) + +public: + ConfigWrapperInterface(AllowReset allowReset, const char *nvsName); + virtual ~ConfigWrapperInterface() = default; + + virtual const char *type() const = 0; + + virtual std::string valueAsString() const = 0; + virtual std::string defaultAsString() const = 0; + + virtual ConfigStatusReturnType loadFromFlash(nvs_handle_t nvsHandle) = 0; + virtual ConfigStatusReturnType reset(nvs_handle_t nvsHandle) = 0; + virtual ConfigStatusReturnType forceReset(nvs_handle_t nvsHandle) = 0; + + bool allowReset() const { return m_allowReset; } + const char *nvsName() const { return m_nvsName; } + bool touched() const { return m_touched; } + bool loaded() const { return m_loaded; } + +protected: + const bool m_allowReset; + const char * const m_nvsName; + bool m_touched{}; + bool m_loaded{}; +}; + +} // namespace espconfig