mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-10-23 05:32:18 +00:00
feat(config): complete SD working configuration
This commit is contained in:
1 parent
a58304b3d1
commit
fd093d60bc
45 files changed
+1490
-2917
No files matched your search
@@ -5,7 +5,7 @@
|
||||
* The codec deliberately does not own a file, an AppConfig snapshot, or a
|
||||
* dynamically sized string. A platform adapter provides one reusable line
|
||||
* scratch buffer and streams it to or from durable storage. This keeps both
|
||||
* normal configuration projection and portable backup/restore out of ESP task
|
||||
* normal configuration projection and platform-owned extensions out of ESP task
|
||||
* stacks and avoids a whole-document JSON representation.
|
||||
*/
|
||||
|
||||
@@ -20,8 +20,11 @@ namespace app::tms
|
||||
{
|
||||
|
||||
constexpr std::size_t kMaxLineBytes = 384U;
|
||||
constexpr std::size_t kMaxDocumentBytes = 16U * 1024U;
|
||||
constexpr uint16_t kSchemaVersion = 2U;
|
||||
// The document is streamed a line at a time. This limit bounds SD-card
|
||||
// input without reserving document-sized RAM; it also leaves room for all ten
|
||||
// Wi-Fi profiles and optional cellular credentials alongside AppConfig.
|
||||
constexpr std::size_t kMaxDocumentBytes = 32U * 1024U;
|
||||
constexpr uint16_t kSchemaVersion = 3U;
|
||||
|
||||
struct LineScratch
|
||||
{
|
||||
@@ -63,7 +66,7 @@ struct Output
|
||||
bool (*write)(void* context, const char* data, std::size_t length) = nullptr;
|
||||
};
|
||||
|
||||
// A non-owning record emitter used by platform-specific backup extensions.
|
||||
// A non-owning record emitter used by platform-specific TMS extensions.
|
||||
// It writes through the same bounded scratch as the core document writer.
|
||||
class RecordWriter
|
||||
{
|
||||
@@ -89,8 +92,47 @@ class RecordWriter
|
||||
|
||||
using RecordExtension = bool (*)(void* context, RecordWriter& writer);
|
||||
|
||||
// A non-owning decoded record exposed to platform extensions. It deliberately
|
||||
// reuses the core codec's strict scalar, percent-escaped text, and base64
|
||||
// parsers so an extension cannot accidentally create a second TMS grammar.
|
||||
class RecordReader
|
||||
{
|
||||
public:
|
||||
const char* key() const { return key_; }
|
||||
bool boolean(bool* target = nullptr) const;
|
||||
bool u8(uint8_t* target = nullptr, uint8_t maximum = UINT8_MAX) const;
|
||||
bool u16(uint16_t* target = nullptr, uint16_t maximum = UINT16_MAX) const;
|
||||
bool u32(uint32_t* target = nullptr) const;
|
||||
bool i32(int32_t* target = nullptr) const;
|
||||
bool text(char* target, std::size_t capacity) const;
|
||||
bool blob(uint8_t* target,
|
||||
std::size_t capacity,
|
||||
std::size_t* decoded_length = nullptr) const;
|
||||
|
||||
private:
|
||||
friend class Decoder;
|
||||
RecordReader(const char* key, const char* type, const char* value)
|
||||
: key_(key), type_(type), value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
const char* key_ = nullptr;
|
||||
const char* type_ = nullptr;
|
||||
const char* value_ = nullptr;
|
||||
};
|
||||
|
||||
enum class RecordConsumeResult : uint8_t
|
||||
{
|
||||
Unhandled,
|
||||
Accepted,
|
||||
Invalid,
|
||||
};
|
||||
|
||||
using RecordConsumer = RecordConsumeResult (*)(void* context, const RecordReader& reader);
|
||||
using DocumentFinalizer = bool (*)(void* context, bool applying, uint16_t schema_version);
|
||||
|
||||
/**
|
||||
* Emits a complete canonical TMSET2 document. `scratch` is caller-owned and
|
||||
* Emits a complete canonical document for the current TMS schema. `scratch` is caller-owned and
|
||||
* must remain valid only for the call; it is never retained. Values that can
|
||||
* contain arbitrary bytes are base64 encoded, while text values use percent
|
||||
* escapes, so every emitted record is one bounded physical line.
|
||||
@@ -112,7 +154,11 @@ bool writeDocument(const AppConfig& config,
|
||||
class Decoder
|
||||
{
|
||||
public:
|
||||
explicit Decoder(AppConfig* target, DocumentKind expected_kind);
|
||||
explicit Decoder(AppConfig* target,
|
||||
DocumentKind expected_kind,
|
||||
RecordConsumer consumer = nullptr,
|
||||
void* extension_context = nullptr,
|
||||
DocumentFinalizer finalizer = nullptr);
|
||||
|
||||
// `line` must be NUL-terminated, contain no trailing newline, and is
|
||||
// mutable because the parser splits key/type/value in place.
|
||||
@@ -120,13 +166,19 @@ class Decoder
|
||||
bool finish();
|
||||
|
||||
const DocumentInfo& info() const { return info_; }
|
||||
uint16_t schemaVersion() const { return schema_version_; }
|
||||
|
||||
private:
|
||||
bool consumeRecord(char* key, char* type, char* value);
|
||||
|
||||
AppConfig* target_ = nullptr;
|
||||
DocumentKind expected_kind_ = DocumentKind::Working;
|
||||
RecordConsumer consumer_ = nullptr;
|
||||
void* extension_context_ = nullptr;
|
||||
DocumentFinalizer finalizer_ = nullptr;
|
||||
DocumentInfo info_{};
|
||||
uint16_t schema_version_ = 0U;
|
||||
uint16_t magic_version_ = 0U;
|
||||
bool saw_magic_ = false;
|
||||
bool saw_schema_ = false;
|
||||
bool saw_kind_ = false;
|
||||
|
||||
@@ -64,9 +64,6 @@ They must not depend on concrete implementations under `platform/esp/*`,
|
||||
policy instead of defining their own brightness tables or arithmetic.
|
||||
- `settings_store.h`
|
||||
Persistent key/value and blob storage for shared UI/runtime settings.
|
||||
- `settings_backup_runtime.h`
|
||||
SD-card Trail-Mate settings backup/restore contract for shared Settings UI
|
||||
while keeping filesystem and platform persistence details out of shared code.
|
||||
- `sstv_runtime.h`
|
||||
SSTV support and session lifecycle hooks.
|
||||
- `team_ui_store_runtime.h`
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace platform::ui::settings_backup
|
||||
{
|
||||
|
||||
struct Status
|
||||
{
|
||||
bool supported = false;
|
||||
bool sd_present = false;
|
||||
bool has_backup = false;
|
||||
bool busy = false;
|
||||
char message[96] = {};
|
||||
char detail[128] = {};
|
||||
};
|
||||
|
||||
bool is_supported();
|
||||
Status status();
|
||||
bool backup();
|
||||
bool restore();
|
||||
bool remove();
|
||||
|
||||
} // namespace platform::ui::settings_backup
|
||||
@@ -8,6 +8,25 @@
|
||||
namespace platform::ui::settings_store
|
||||
{
|
||||
|
||||
using ChangeObserver = void (*)(void* context, const char* ns, const char* key);
|
||||
|
||||
// One platform may subscribe to durable settings changes (for example, to
|
||||
// refresh an SD working projection). A batch coalesces multi-key owners such
|
||||
// as the Wi-Fi profile set into one notification without retaining values.
|
||||
void set_change_observer(ChangeObserver observer, void* context);
|
||||
void begin_change_batch();
|
||||
void end_change_batch();
|
||||
|
||||
class ScopedChangeBatch
|
||||
{
|
||||
public:
|
||||
ScopedChangeBatch() { begin_change_batch(); }
|
||||
~ScopedChangeBatch() { end_change_batch(); }
|
||||
|
||||
ScopedChangeBatch(const ScopedChangeBatch&) = delete;
|
||||
ScopedChangeBatch& operator=(const ScopedChangeBatch&) = delete;
|
||||
};
|
||||
|
||||
void put_int(const char* ns, const char* key, int value);
|
||||
void put_bool(const char* ns, const char* key, bool value);
|
||||
void put_uint(const char* ns, const char* key, uint32_t value);
|
||||
@@ -18,7 +37,8 @@ bool get_bool(const char* ns, const char* key, bool default_value);
|
||||
uint32_t get_uint(const char* ns, const char* key, uint32_t default_value);
|
||||
bool get_string(const char* ns, const char* key, std::string& out);
|
||||
// Bounded counterpart to get_string. ESP persistence paths must prefer this
|
||||
// overload so a settings backup never allocates a string sized by NVS input.
|
||||
// overload so working-configuration sync never allocates a string sized by
|
||||
// NVS input.
|
||||
bool get_string_into(const char* ns,
|
||||
const char* key,
|
||||
char* out,
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace platform::ui::wifi
|
||||
|
||||
constexpr std::size_t kMaxSsidLength = 32;
|
||||
constexpr std::size_t kMaxPasswordLength = 64;
|
||||
constexpr std::size_t kMaxSavedProfileCount = 10;
|
||||
constexpr std::size_t kMaxIpLength = 47;
|
||||
constexpr std::size_t kMaxStatusMessageLength = 95;
|
||||
|
||||
@@ -51,10 +52,20 @@ struct ScanResult
|
||||
bool requires_password = true;
|
||||
};
|
||||
|
||||
// Iteration keeps callers from materialising a ten-profile automatic object
|
||||
// merely to serialize the persistent network set. `index == 0` is the highest
|
||||
// connection priority.
|
||||
using SavedProfileVisitor = bool (*)(void* context, std::size_t index, const Config& profile);
|
||||
|
||||
bool is_supported();
|
||||
bool load_config(Config& out);
|
||||
bool save_config(const Config& config);
|
||||
bool find_saved_config(const char* ssid, Config& out);
|
||||
bool visit_saved_profiles(bool* enabled,
|
||||
std::size_t* count,
|
||||
SavedProfileVisitor visitor,
|
||||
void* context);
|
||||
bool replace_saved_profiles(bool enabled, const Config* profiles, std::size_t count);
|
||||
bool apply_enabled(bool enabled);
|
||||
bool connect(const Config* override_config = nullptr);
|
||||
void disconnect();
|
||||
|
||||
Reference in new issue
Block a user