Repository navigation
feat: keep the config in NVS and add the cfg partition migration #548
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
abd5273
feat: keep the config in NVS and add the cfg partition migration
hhvrc 857090d
Merge branch 'develop' into feat/config-nvs
hhvrc 8243e09
Merge branch 'develop' into feat/config-nvs
hhvrc 88eeff8
style(config): clang-format the config host tests
hhvrc 97ee7e1
fix(config): keep stored config intact on failed moves, loads and saves
hhvrc 8418f6c
fix(config): never erase a cfg partition written in a newer NVS format
hhvrc e84ff80
refactor(config): move NamespaceExists and KeyCount into the host tes…
hhvrc 2cb8c73
fix(estop): read the software trigger before the active flag in IsESt…
hhvrc 5630da5
refactor(config): constrain sections with a SectionValue concept
hhvrc 7838a40
refactor(estop): move the persisted E-Stop latch out of the config st…
hhvrc 02ed74c
revert(estop): move the IsEStopped read order fix to develop
hhvrc 961513a
chore: add the change file for the NVS config store
hhvrc 4cbc822
fix(config): keep stored networks and the first fill safe on partial …
hhvrc cb4ac5e
fix(config): tell a missing namespace from a failed open, and validat…
hhvrc 41beaef
refactor(config): one open-for-load, one saver and one partition init…
hhvrc 0e286cc
refactor(config): one JSON prologue and one strict boolean parser for…
hhvrc 568920f
refactor(config): plain-value accessors, shared credential helpers an…
hhvrc e61e9c8
docs(config): note the shared parse helpers and the plain-value acces…
hhvrc 5fc3209
fix(config): finish the two call sites the accessor change missed
hhvrc 8946838
style: format for the clang-format 21 the linter runs
hhvrc 44ca1c9
ci: key the ESP-IDF install cache on the runner image
hhvrc f6a8ead
fix(config): finish the host test's value accesses, write the E-Stop …
hhvrc f7e66bb
build: compile the firmware and the host tests as gnu++23, and let th…
hhvrc 2445ddc
test(config): decode \u escapes in the jsonconfig test again, and run…
hhvrc 4106bdb
test(config): the escaped fixture line itself, which the previous com…
hhvrc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| --- | ||
| kind: changed | ||
| --- | ||
| Keep the configuration in NVS instead of a littlefs file | ||
|
|
||
| ## Release Note | ||
| Settings are now stored in NVS | ||
|
|
||
| The hub keeps its settings in the chip's NVS key-value store instead of a single config file, and saves each setting on its own when it changes. Your WiFi networks, account link and other settings from 1.x are imported automatically on the first boot after updating. | ||
|
|
||
| - Each module owns its section of the config (`config/Section.h`); every section type meets the `SectionValue` concept, with its defaults defined in one place and its validation run on every value the section takes in | ||
| - On the 2.x partition table the store is the `cfg` partition (the 1.x littlefs `config` partition retyped in place); on the 1.x table it lives in the shared `nvs` partition until the layout is migrated, and then moves into `cfg` | ||
| - An empty store is filled once from the 1.x config file, which is never modified, otherwise from the defaults | ||
| - WiFi credentials are stored one per key; the WiFi driver's own NVS store is compiled out | ||
| - A section that can't be read runs on its defaults without erasing what is stored, and the save after a failed one rewrites every key; a single WiFi credential that doesn't read is dropped on its own, so the rest still load | ||
| - The store is marked filled only once every section reads back from flash as written; a first fill cut short is redone on the next boot | ||
| - A store written by newer firmware is loaded instead of reset, and a `cfg` partition in a newer NVS format is never erased | ||
| - A section whose NVS namespace fails to open is a failed load, not a load of the defaults: the defaults are only taken when the namespace was never written. The store likewise never fills or cleans up when its own marker cannot be read | ||
| - A boolean field of the wrong type in a `jsonconfig set` document rejects the document instead of storing the default, in every section | ||
| - The config accessors return the value itself (a section always has one); the WiFi section hands a network saved without an id the lowest free one on every path, so `networks` over serial no longer allocates ids itself | ||
| - An OTA check interval of zero and a hostname esp_netif would refuse become their defaults on every path (NVS, JSON and the setters), not only through the captive portal | ||
| - `Config::MigrateToCfgPartition` retypes the `config` partition as `cfg`; nothing calls it yet | ||
|
|
||
| ## Notices | ||
| - warning: **1.x firmware does not see settings changed after this update.** Downgrading to 1.x brings back the settings the hub had before updating, as long as the partition layout has not been migrated yet. Re-enter any WiFi networks or pairing you changed since. | ||
| - warning: Until the partition layout is migrated the settings live in the chip's shared NVS partition. If that partition has to be erased (it fills up, or a newer firmware wrote it in a format this one cannot read), the hub imports the 1.x settings again and anything changed since the update is lost. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # The C++ standard every OpenShock build compiles with. Included right after project() by the firmware's CMakeLists.txt | ||
| # and by every components/*/host_test/CMakeLists.txt, so the firmware and its host tests speak the same language. | ||
| # | ||
| # ESP-IDF appends -std=gnu++26 for chip targets and the newest -std the host compiler accepts for the linux target. | ||
| # GCC's C++26 mode is experimental and, from GCC 14 to 17, miscompiles a constexpr function that searches a string | ||
| # (string_view::find and anything on top of it) when the call is folded with literal arguments: the folded value | ||
| # disagrees with strict constant evaluation and with a runtime call (GCC bug 127126, -std=c++2c only). The device | ||
| # toolchain is GCC 15.2, so the firmware is as exposed as the common host test that first showed it. This flag comes | ||
| # after ESP-IDF's and the last -std on the command line wins. Drop it once the toolchain carries the fix. | ||
| idf_build_set_property(CXX_COMPILE_OPTIONS "-std=gnu++23" APPEND) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
38 changes: 38 additions & 0 deletions
38
components/captive_portal/include/captiveportal/CaptivePortalConfig.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| #pragma once | ||
|
|
||
| // The `captivePortal` config section. Owned by captive_portal and kept in NVS through the config framework | ||
| // (config/Section.h); namespace "portal", key always (u8). | ||
|
|
||
| #include "config/Section.h" | ||
| #include "serialization/_fbs/HubConfig_generated.h" | ||
|
|
||
| #include <optional> | ||
|
|
||
| namespace OpenShock::JSON { | ||
| class JsonView; | ||
| } | ||
|
|
||
| namespace OpenShock::Config { | ||
| struct CaptivePortalConfig { | ||
| CaptivePortalConfig(); // The defaults: the one place they are defined | ||
| CaptivePortalConfig(bool alwaysEnabled); | ||
|
|
||
| bool alwaysEnabled; | ||
|
|
||
| /// @brief No rules to apply yet. The section runs it on every value it takes in. | ||
| void Normalize() { } | ||
|
|
||
| [[nodiscard]] flatbuffers::Offset<Serialization::Configuration::CaptivePortalConfig> ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder, bool withSensitiveData) const; | ||
|
|
||
| /// @brief Reads this section from `partition`; a missing key is its default, a read error fails. | ||
| bool LoadNvs(const char* partition); | ||
| /// @brief Writes the keys that differ from `previous` (every key when null) to `partition`. | ||
| bool SaveNvs(const char* partition, const CaptivePortalConfig* previous) const; | ||
|
|
||
| bool FromJSON(JSON::JsonView json); | ||
| void ToJSON(json_gen_str_t* gen, const char* name, bool withSensitiveData) const; | ||
| }; | ||
|
|
||
| [[nodiscard]] CaptivePortalConfig GetCaptivePortalConfig(); | ||
| bool SetCaptivePortalConfig(const CaptivePortalConfig& config); | ||
| } // namespace OpenShock::Config |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
83 changes: 83 additions & 0 deletions
83
components/captive_portal/src/captiveportal/CaptivePortalConfig.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| #include <freertos/FreeRTOS.h> | ||
|
|
||
| #include "captiveportal/CaptivePortalConfig.h" | ||
|
|
||
| const char* const TAG = "Config::CaptivePortalConfig"; | ||
|
|
||
| #include "config/Nvs.h" | ||
| #include "config/Parse.h" | ||
| #include "Logging.h" | ||
|
|
||
| #include "json/Json.h" | ||
|
|
||
| using namespace OpenShock::Config; | ||
| namespace Fbs = OpenShock::Serialization::Configuration; | ||
|
|
||
| static constexpr const char* kNamespace = "portal"; | ||
| static constexpr const char* kKeyAlways = "always"; | ||
|
|
||
| static constexpr const char* kNamespaces[] = {kNamespace}; | ||
| static Section<CaptivePortalConfig> s_section({.name = "captivePortal", .order = 2, .hubConfigField = Fbs::HubConfig::VT_CAPTIVE_PORTAL, .namespaces = kNamespaces}); | ||
|
|
||
| CaptivePortalConfig::CaptivePortalConfig() | ||
| : alwaysEnabled(false) | ||
| { | ||
| } | ||
|
|
||
| CaptivePortalConfig::CaptivePortalConfig(bool alwaysEnabled) | ||
| : alwaysEnabled(alwaysEnabled) | ||
| { | ||
| } | ||
|
|
||
| flatbuffers::Offset<OpenShock::Serialization::Configuration::CaptivePortalConfig> CaptivePortalConfig::ToFlatbuffers(flatbuffers::FlatBufferBuilder& builder, bool withSensitiveData) const | ||
| { | ||
| return Serialization::Configuration::CreateCaptivePortalConfig(builder, alwaysEnabled); | ||
| } | ||
|
|
||
| bool CaptivePortalConfig::FromJSON(JSON::JsonView json) | ||
| { | ||
| if (auto done = Parse::ObjectOrDefault(*this, json, TAG)) { | ||
| return *done; | ||
| } | ||
|
|
||
| if (!Parse::JsonBool(alwaysEnabled, json, "alwaysEnabled", false, TAG)) { | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| void CaptivePortalConfig::ToJSON(json_gen_str_t* gen, const char* name, bool withSensitiveData) const | ||
| { | ||
| JSON::objBegin(gen, name); | ||
| json_gen_obj_set_bool(gen, "alwaysEnabled", alwaysEnabled); | ||
| JSON::objEnd(gen, name); | ||
| } | ||
|
|
||
| bool CaptivePortalConfig::LoadNvs(const char* partition) | ||
| { | ||
| auto handle = Nvs::Handle::OpenForLoad(partition, kNamespace); | ||
| if (!handle) { | ||
| return false; | ||
| } | ||
| return Nvs::Load(*handle, kKeyAlways, alwaysEnabled, false); | ||
| } | ||
|
|
||
| bool CaptivePortalConfig::SaveNvs(const char* partition, const CaptivePortalConfig* previous) const | ||
| { | ||
| auto handle = Nvs::Handle::Open(partition, kNamespace, true); | ||
| if (!handle) { | ||
| return false; | ||
| } | ||
| return Nvs::Saver(*handle, *this, previous).Set(kKeyAlways, &CaptivePortalConfig::alwaysEnabled).Commit(); | ||
| } | ||
|
|
||
| CaptivePortalConfig OpenShock::Config::GetCaptivePortalConfig() | ||
| { | ||
| return s_section.Get(); | ||
| } | ||
|
|
||
| bool OpenShock::Config::SetCaptivePortalConfig(const CaptivePortalConfig& config) | ||
| { | ||
| return s_section.Set(config); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,31 @@ | ||
| # OpenShock configuration model: typed config structs with JSON + flatbuffer | ||
| # (de)serialization, persisted to littlefs via the fs component. | ||
| # The config framework. It owns no settings: every module defines its own section (a value type plus a | ||
| # Config::Section<T>, config/Section.h) and its own accessors, and keeps it in NVS through what is here: | ||
| # | ||
| # Extracted out of `core` into a low-layer component: it depends only on the | ||
| # generated wire schema (serialization), JSON, the config filesystem and a few | ||
| # foundation utilities; nothing in core. This is what lets device_control (estop/ | ||
| # radio/visual/CommandHandler, which read Config::) leave core in a later step | ||
| # without a config <-> core cycle. | ||
| FILE(GLOB_RECURSE config_sources | ||
| ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) | ||
|
|
||
| # config/Nvs.h typed access to one NVS namespace, the primitive every section's LoadNvs/SaveNvs uses | ||
| # config/Section.h the section template: RAM cache, serialized writers, saving only what changed, the registry | ||
| # config/Store.h picking the store (`cfg`, or the shared `nvs` on the 1.x layout), the move between them, | ||
| # filling an empty store (the 1.x file, else defaults), factory reset, the whole-config JSON | ||
| # config/Wire.h the whole config as the FlatBuffers HubConfig (local protocol Ready message, rawconfig), and | ||
| # HubConfig -> JSON, which is also how the 1.x config file is imported | ||
| # config/Migration.h retyping the 1.x littlefs `config` partition as the `cfg` store (not wired up yet) | ||
| # config/Parse.h field parsing helpers for sections' FromJSON | ||
| # | ||
| # The 1.x import (Store.cpp's readLegacyConfig, littlefs through fs) is 1.x compatibility code: it goes away on | ||
| # master once the release/1.9 branch is cut. | ||
| idf_component_register( | ||
| SRCS ${config_sources} | ||
| SRCS "src/Migration.cpp" | ||
| "src/Nvs.cpp" | ||
| "src/Parse.cpp" | ||
| "src/Section.cpp" | ||
| "src/Store.cpp" | ||
| "src/Wire.cpp" | ||
| INCLUDE_DIRS "include" | ||
| REQUIRES common osjson serialization flatbuffers # in public headers (enums, json/Json.h, HubConfig fbs, TinyVec, flatbuffers::Offset) | ||
| esp_wifi esp_hal_gpio # WiFiCredentials.h -> esp_wifi_types.h (wifi_auth_mode_t); RFConfig.h -> hal/gpio_types.h (gpio_num_t) | ||
| PRIV_REQUIRES chipset fs logging # .cpp only (GPIO validation, ConfigFs, OS_LOG*/OS_PANIC; SimpleMutex via common) | ||
| REQUIRES common osjson # OpenShock.h, SimpleMutex.h, TinyVec.h, util/FunctionRef.h; json/Json.h and json_generator.h | ||
| nvs_flash # nvs.h in Nvs.h | ||
| serialization flatbuffers # HubConfig in Wire.h | ||
| esp_hal_gpio # hal/gpio_types.h in Parse.h | ||
| PRIV_REQUIRES fs logging esp_partition # LfsPartition (the 1.x import), OS_LOG*/OS_PANIC, the partition lookups | ||
| partition_layout esp_system # the config partition retype, esp_restart | ||
| ) | ||
|
|
||
| target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-error) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.