Initial code import

This commit is contained in:
2021-10-03 17:51:12 +02:00
parent c2deb42b10
commit 08e62500ea
25 changed files with 1248 additions and 0 deletions

61
CMakeLists.txt Normal file
View File

@@ -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
)

44
Kconfig.projbuild Normal file
View File

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

View File

@@ -0,0 +1,104 @@
#pragma once
// system includes
#include <string>
// 3rdparty lib includes
#include <tl/expected.hpp>
#include <fmt/core.h>
// local includes
#include "configwrapper.h"
#include "esphttpdutils.h"
namespace espconfig {
template<int MAX_LENGTH>
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<int MIN_LENGTH, int MAX_LENGTH>
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<ConfigWrapper<std::string>::ConstraintCallback callback0, ConfigWrapper<std::string>::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<ConfigWrapper<std::string>::ConstraintCallback callback0, ConfigWrapper<std::string>::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<typename T, T ... ALLOWED_VALUES>
ConfigConstraintReturnType OneOf(typename ConfigWrapper<T>::value_t val)
{
if (!((ALLOWED_VALUES == val) || ...))
tl::make_unexpected("Value not one of the allowed ones");
return {};
}
template<typename T, T MIN_VALUE>
ConfigConstraintReturnType MinValue(typename ConfigWrapper<T>::value_t val)
{
if (val < MIN_VALUE)
return tl::make_unexpected(fmt::format("Value {} exceeds minimum {}", val, MIN_VALUE));
return {};
}
template<typename T, T MAX_VALUE>
ConfigConstraintReturnType MaxValue(typename ConfigWrapper<T>::value_t val)
{
if (val > MAX_VALUE)
return tl::make_unexpected(fmt::format("Value {} exceeds maximum {}", val, MAX_VALUE));
return {};
}
template<typename T, T MIN_VALUE, T MAX_VALUE>
ConfigConstraintReturnType MinMaxValue(typename ConfigWrapper<T>::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<typename T, T MIN_VALUE, T MAX_VALUE>
ConfigConstraintReturnType MinMaxOrZeroValue(typename ConfigWrapper<T>::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

View File

@@ -0,0 +1,22 @@
#pragma once
// system includes
#include <string>
// 3rdparty lib includes
#include <tl/expected.hpp>
#include <fmt/core.h>
// 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

56
src/configmanager.h Normal file
View File

@@ -0,0 +1,56 @@
#pragma once
#include "sdkconfig.h"
// esp-idf includes
#include <nvs.h>
// local includes
#include "configwrapper.h"
namespace espconfig {
template<typename ConfigContainer>
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<typename T>
ConfigStatusReturnType write_config(ConfigWrapper<T> &config, typename ConfigWrapper<T>::value_t value);
inline ConfigStatusReturnType reset_config(ConfigWrapperInterface &config);
inline ConfigStatusReturnType force_reset_config(ConfigWrapperInterface &config);
};
template<typename ConfigContainer> template<typename T>
ConfigStatusReturnType ConfigManager<ConfigContainer>::write_config(ConfigWrapper<T> &config, typename ConfigWrapper<T>::value_t value)
{
return config.write(nvs_handle_user, value);
}
template<typename ConfigContainer>
inline ConfigStatusReturnType ConfigManager<ConfigContainer>::reset_config(ConfigWrapperInterface &config)
{
return config.reset(nvs_handle_user);
}
template<typename ConfigContainer>
inline ConfigStatusReturnType ConfigManager<ConfigContainer>::force_reset_config(ConfigWrapperInterface &config)
{
return config.forceReset(nvs_handle_user);
}
} // namespace espconfig

173
src/configmanager_priv.h Normal file
View File

@@ -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 <esp_log.h>
#include <nvs_flash.h>
// local includes
#include "espchrono.h"
#define INSTANTIATE_CONFIGMANAGER_TEMPLATES(Type) \
template esp_err_t ConfigManager<Type>::init(); \
/* template bool ConfigManager<Type>::erase(); */ \
template ConfigStatusReturnType ConfigManager<Type>::reset(); \
template ConfigWrapperInterface *ConfigManager<Type>::findConfigByKey(std::string_view key);
namespace espconfig {
namespace {
constexpr const char * const TAG = "CONFIG";
} // namespace
template<typename ConfigContainer>
esp_err_t ConfigManager<ConfigContainer>::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<typename ConfigContainer>
//bool ConfigManager<ConfigContainer>::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<typename ConfigContainer>
ConfigStatusReturnType ConfigManager<ConfigContainer>::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<typename ConfigContainer>
ConfigWrapperInterface *ConfigManager<ConfigContainer>::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

77
src/configutils_base.h Normal file
View File

@@ -0,0 +1,77 @@
#pragma once
// system includes
#include <cstdint>
#include <string>
// esp-idf includes
#include <nvs.h>
// 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<float>(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<double>(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<uint32_t>(value)); }
inline esp_err_t nvs_set(nvs_handle handle, const char* key, double value) { return nvs_set(handle, key, std::bit_cast<uint64_t>(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

View File

@@ -0,0 +1,28 @@
#pragma once
// esp-idf includes
#include <nvs.h>
// 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

View File

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

View File

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

View File

@@ -0,0 +1,28 @@
#pragma once
// esp-idf includes
#include <nvs.h>
// 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

View File

@@ -0,0 +1,83 @@
#pragma once
// system includes
#include <optional>
#include <cstdint>
// esp-idf includes
#include <nvs.h>
// local includes
#include "configutils_base.h"
namespace espconfig {
template<typename T>
esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional<T>* 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<int8_t>* out_value);
template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional<uint8_t>* out_value);
template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional<int16_t>* out_value);
template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional<uint16_t>* out_value);
template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional<int32_t>* out_value);
template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional<uint32_t>* out_value);
template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional<int64_t>* out_value);
template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional<uint64_t>* out_value);
template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional<float>* out_value);
template esp_err_t nvs_get(nvs_handle handle, const char* key, std::optional<double>* out_value);
template<typename T>
esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional<T> 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<int8_t> value);
template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional<uint8_t> value);
template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional<int16_t> value);
template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional<uint16_t> value);
template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional<int32_t> value);
template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional<uint32_t> value);
template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional<int64_t> value);
template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional<uint64_t> value);
template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional<float> value);
template esp_err_t nvs_set(nvs_handle handle, const char* key, std::optional<double> value);
} // namespace espconfig

View File

@@ -0,0 +1,25 @@
#pragma once
// system includes
#include <type_traits>
// esp-idf includes
#include <nvs.h>
// 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<Name> 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<Name>(value)); \
}

13
src/configutils_sntp.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
// esp-idf includes
#include <esp_sntp.h>
// local includes
#include "configutils_priv_enum.h"
namespace espconfig {
IMPLEMENT_NVS_GET_SET_ENUM(sntp_sync_mode_t)
} // namespace espconfig

70
src/configwrapper.h Normal file
View File

@@ -0,0 +1,70 @@
#pragma once
// system includes
#include <string>
#include <type_traits>
// 3rdparty lib includes
#include <tl/expected.hpp>
// local includes
#include "configwrapperinterface.h"
#include "cppmacros.h"
namespace espconfig {
using ConfigConstraintReturnType = ConfigStatusReturnType;
template<typename T>
class ConfigWrapper final : public ConfigWrapperInterface
{
CPP_DISABLE_COPY_MOVE(ConfigWrapper)
using DefaultValueCallbackRef = T(&)();
using DefaultValueCallbackPtr = T(*)();
public:
using value_t = typename std::conditional<std::is_same<T, std::string>::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<T> &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<T> * const m_factoryConfig;
const DefaultValueCallbackPtr m_defaultCallback;
};
const ConstraintCallback m_constraintCallback;
};
} // namespace espconfig

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<int8_t>)
INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional<uint8_t>)
INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional<int16_t>)
INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional<uint16_t>)
INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional<int32_t>)
INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional<uint32_t>)
INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional<int64_t>)
INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional<uint64_t>)
INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional<float>)
INSTANTIATE_CONFIGWRAPPER_TEMPLATES(std::optional<double>)
} // namespace espconfig

248
src/configwrapper_priv.h Normal file
View File

@@ -0,0 +1,248 @@
#pragma once
#include "sdkconfig.h"
#define LOG_LOCAL_LEVEL CONFIG_LOG_LOCAL_LEVEL_CONFIG
// system includes
#include <utility>
#include <cassert>
// esp-idf includes
#include <esp_log.h>
// 3rdparty lib includes
#include <fmt/core.h>
// local includes
#include "configwrapper.h"
#include "cpputils.h"
#define INSTANTIATE_CONFIGWRAPPER_TEMPLATES(TYPE) \
template ConfigWrapper<TYPE>::ConfigWrapper(const TYPE &defaultValue, AllowReset allowReset, ConstraintCallback constraintCallback, const char *nvsName); \
template ConfigWrapper<TYPE>::ConfigWrapper(TYPE &&defaultValue, AllowReset allowReset, ConstraintCallback constraintCallback, const char *nvsName); \
template ConfigWrapper<TYPE>::ConfigWrapper(const ConfigWrapper<TYPE> &factoryConfig, ConstraintCallback constraintCallback, const char *nvsName); \
template ConfigWrapper<TYPE>::ConfigWrapper(DefaultValueCallbackRef &defaultCallback, AllowReset allowReset, ConstraintCallback constraintCallback, const char *nvsName); \
template ConfigStatusReturnType ConfigWrapper<TYPE>::write(nvs_handle_t nvsHandle, value_t value); \
template<> const char *ConfigWrapper<TYPE>::type() const { return #TYPE; } \
template std::string ConfigWrapper<TYPE>::valueAsString() const; \
template std::string ConfigWrapper<TYPE>::defaultAsString() const; \
template TYPE ConfigWrapper<TYPE>::defaultValue() const; \
template ConfigStatusReturnType ConfigWrapper<TYPE>::loadFromFlash(nvs_handle_t nvsHandle); \
template ConfigStatusReturnType ConfigWrapper<TYPE>::reset(nvs_handle_t nvsHandle); \
template ConfigStatusReturnType ConfigWrapper<TYPE>::forceReset(nvs_handle_t nvsHandle); \
template ConfigConstraintReturnType ConfigWrapper<TYPE>::checkValue(value_t value) const; \
template ConfigStatusReturnType ConfigWrapper<TYPE>::writeToFlash(nvs_handle_t nvsHandle, value_t value);
namespace espconfig {
namespace {
constexpr const char * const TAG = "CONFIG";
} // namespace
template<typename T>
ConfigWrapper<T>::ConfigWrapper(const T &defaultValue, AllowReset allowReset, ConstraintCallback constraintCallback, const char *nvsName) :
ConfigWrapperInterface{allowReset, nvsName},
m_defaultType{DefaultByValue},
m_defaultValue{defaultValue},
m_constraintCallback{constraintCallback}
{
}
template<typename T>
ConfigWrapper<T>::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<typename T>
ConfigWrapper<T>::ConfigWrapper(const ConfigWrapper<T> &factoryConfig, ConstraintCallback constraintCallback, const char *nvsName) :
ConfigWrapperInterface{AllowReset::DoReset, nvsName},
m_defaultType{DefaultByFactoryConfig},
m_factoryConfig{&factoryConfig},
m_constraintCallback{constraintCallback}
{
}
template<typename T>
ConfigWrapper<T>::ConfigWrapper(const DefaultValueCallbackRef &defaultCallback, AllowReset allowReset, ConstraintCallback constraintCallback, const char *nvsName) :
ConfigWrapperInterface{allowReset, nvsName},
m_defaultType{DefaultByCallback},
m_defaultCallback{&defaultCallback},
m_constraintCallback{constraintCallback}
{
}
template<typename T>
ConfigWrapper<T>::~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<typename T>
ConfigStatusReturnType ConfigWrapper<T>::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<typename T>
std::string ConfigWrapper<T>::valueAsString() const
{
CONFIGWRAPPER_TOSTRING_USINGS
if (m_touched)
return toString(m_value);
else
return "--not-touched-- " + toString(m_value);
}
template<typename T>
std::string ConfigWrapper<T>::defaultAsString() const
{
CONFIGWRAPPER_TOSTRING_USINGS
return toString(defaultValue());
}
template<typename T>
T ConfigWrapper<T>::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<typename T>
ConfigStatusReturnType ConfigWrapper<T>::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<typename T>
ConfigStatusReturnType ConfigWrapper<T>::reset(nvs_handle_t nvsHandle)
{
ESP_LOGD(TAG, "%s", m_nvsName);
if (!m_allowReset)
return {};
return forceReset(nvsHandle);
}
template<typename T>
ConfigStatusReturnType ConfigWrapper<T>::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<typename T>
ConfigConstraintReturnType ConfigWrapper<T>::checkValue(value_t value) const
{
if (!m_constraintCallback)
return {};
return m_constraintCallback(value);
}
template<typename T>
ConfigStatusReturnType ConfigWrapper<T>::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

View File

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

View File

@@ -0,0 +1,31 @@
#include "configwrapperinterface.h"
#include "sdkconfig.h"
#define LOG_LOCAL_LEVEL CONFIG_LOG_LOCAL_LEVEL_CONFIG
// system includes
#include <cstring>
#include <cassert>
// esp-idf includes
#include <esp_log.h>
#include <nvs.h>
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

View File

@@ -0,0 +1,51 @@
#pragma once
// system includes
#include <string>
// esp-idf includes
#include <nvs.h>
// 3rdparty lib includes
#include <tl/expected.hpp>
// 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<void, std::string>;
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