diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index 371edf6..28393d1 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -100,12 +100,12 @@ "state": { "type": "markdown", "state": { - "file": "99 Work/0 OneSec/OneSecNotes/10 Projects/OneSecServer/OneSec Server.md", + "file": "99 Work/0 OneSec/OneSecNotes/10 Projects/TeensyFlightcontroller/Parameter System Desing Document.md", "mode": "source", "source": false }, "icon": "lucide-file", - "title": "OneSec Server" + "title": "Parameter System Desing Document" } }, { @@ -300,7 +300,7 @@ } ], "direction": "horizontal", - "width": 200 + "width": 407.5 }, "right": { "id": "1017d1cf4a473028", @@ -495,6 +495,9 @@ }, "active": "8340cc400aabd495", "lastOpenFiles": [ + "99 Work/0 OneSec/OneSecNotes/10 Projects/OneSecServer/OneSec Server.md", + "99 Work/0 OneSec/OneSecNotes/10 Projects/TeensyFlightcontroller/Parameter System Desing Document.md", + "99 Work/0 OneSec/OneSecNotes/10 Projects/TeensyFlightcontroller", "Attachments/Pasted image 20250922115441.png", "Temporary/Untitled 15.md", "Temporary/Webapp - Chirp Log Review.md", @@ -520,7 +523,6 @@ "2 Personal/Home Lab/NAS/Jellyfin Installation.md", "2 Personal/Home Lab/iPhone/Maps.md", "Temporary/Drone Regulations Requirements.md", - "0 Journal/0 Daily/2025-09-10.md", "7 People/0_People.base", "Attachments/Belts, Suspenders.mp3", "Attachments/image 21.jpg", @@ -537,11 +539,9 @@ "Dashboard Canvas.canvas", "Attachments/Pasted image 20250627101456.png", "99 Work/0 OneSec/OneSecNotes/40 - User Manuals", - "Attachments/Pasted image 20250624161349.png", "2 Personal/1 Skills/IT", "Sync", "Shared_Folder", - "OneNote/BSM", "99 Work/0 OneSec/OneSecNotes/30 Engineering Skills/Computer Science/Untitled.canvas", "8 Work/OneSecNotes/Temporary/Untitled.canvas" ] diff --git a/99 Work/0 OneSec/OneSecNotes/10 Projects/TeensyFlightcontroller/Parameter System Desing Document.md b/99 Work/0 OneSec/OneSecNotes/10 Projects/TeensyFlightcontroller/Parameter System Desing Document.md new file mode 100644 index 0000000..cb3ae37 --- /dev/null +++ b/99 Work/0 OneSec/OneSecNotes/10 Projects/TeensyFlightcontroller/Parameter System Desing Document.md @@ -0,0 +1,596 @@ +--- +title: Parameter System Desing Document +created_date: 2025-09-23 +updated_date: 2025-09-23 +aliases: +tags: +--- +# Parameter System Desing Document + +> Teensy 4.0 • Teensyduino/Arduino framework • No RTOS • ETL containers • Chirp protocol for messaging + +> **Scope:** Flight Controller (FC) side only, with **name-as-canonical** parameters and **immediate apply** (no staging yet). + +> Enums such as TypeId, ParamStatus, ParamOp come from the Chirp side. Include the relevant Chirp headers; do not redefine them here. + +--- + +## 1) Current Rules / Constraints + +* **Platform:** Teensy 4.0, Arduino/Teensyduino framework, no RTOS. +* **Transport:** **Chirp** is the sole protocol for Get/Set requests and responses. +* **Design principles** + + 1. **Name is canonical.** We identify parameters by a unique, human-readable path. + 2. **Shallow ownership tree.** Each module owns a **single sub-tree**; keep names shallow: `module.param`. + 3. **Module-facing interface.** Modules expose parameters via light interfaces; the manager never “reaches in” to private variables. +* **Non-module params:** System-level, logging, build info, flight mode selectors, serial settings, etc. must also fit the same model. + +### Current Chirp Message Structure +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +--- + +## 2) Naming + +* **Hierarchical dot paths: (Either with `group.name` combination, or directly for `name`)** + Examples: + `attitudectrl.kp_roll`, `attitudectrl.torque_limit`, + `servo_left_flap.offset_angle`, `serial_pilot.baud_rate`, + `i2c_line1.clock`, + `logger.sink_interface`, `system.fw_version`. + +* **Uniqueness:** Names are **case-sensitive** and must be unique within the FC. + +* **Ownership:** The **root token** (e.g., `attitudectrl`, `logger`, `serial_pilot`) identifies which module/adapter owns the parameter. + +--- + +## 3) Core Components + +### 3.1 ParamRegistry (all descriptors) + +A single in-memory catalog describing **every configurable parameter** on the FC. +The registry answers **what** exists (metadata), **how to read** it, and **how to apply** a new value—**without** the manager touching module internals. + +#### Descriptor structure (Globally defined across every system in drone) + +* `participant` : which device owns this parameter set (here: `"flight_controller"`). + *Note:* other devices (autopilot, co-pilot) will have their own registries in their repos. + +* `group` : high-level bucket **within the FC**, typically the module or subsystem root (`"attitudectrl"`, `"logger"`, `"system"`, `"serial_copilot"`, `"rc"`, `"imu_leftwing"`). + +* `name` : either the **full name of the parameter**, e.g., `"attitudectrl.kp_roll"` , or partial name so that group + name results in the unique name. + +* `type` : `TypeId` (shared enum across Chirp, e.g., `Float`, `Uint16`, …). + +* `length` : size in bytes for validation and arrays. + +* `default_value` : bytes for default initialization at boot. + +* `flags` : bitmask. Possible flags: + + * `Writable` — if **unset**, parameter is read-only. + * `RebootRequired` — write accepted but **takes effect after reboot**. + * `ImmediateApply` — explicitly safe for immediate side-effects. + +* **Accessors (delegates):** + + * `get(name) : bytes ` — read current value from the owning module. + * `set(name, bytes) : status` — parse/validate module-local rules and update the module. + +> The registry owns **only metadata and function delegates**. Actual values remain inside modules. + +#### Example C++ sketch + +```cpp + +struct ParamDescriptor { + const char* participant; // "flight_controller" + const char* group; // "attitudectrl", "logger", ... + const char* name; // "attitudectrl.kp_roll" (canonical, unique) + TypeId type; + uint16_t length; // sizeof(float) etc. + uint8_t flags; // bitmask of ParamFlag + const uint8_t* default_bytes; // pointer to default value bytes + + // Delegates to module-owned code: + etl::delegate getter; + etl::delegate setter; +}; + +class ParamRegistry { +public: + // Fixed capacity + bool register_param(const ParamDescriptor& d); // returns false on duplicate name or full + const ParamDescriptor* find(const char* name) const; // or find(group, name) pair depending on full name implementation + +private: + etl::unordered_map, ParamDescriptor, NUM_PARAMS> m_by_name; // pre-sized buckets +}; +``` +**Note**: `TypeId` , `ParamStatus` definitions exist in relevant chirp header files. + + +> **Getter/Setter notes** +> +> * `getter` returns the current committed value from the module into `out`. +> * `setter` validates and updates the module, sets `reboot_required=true` if the new value won’t take effect until reboot. + +--- + +### 3.2 ParameterManager (global singleton) + +The single entry point for **Get/Set** operations and the **Chirp** callback endpoint. + +**Responsibilities** + +* Own the **ParamRegistry**. +* Expose a **simple API** used by both: + + * Internal code, and + * The **Chirp handler** (by registering a callback for `ParameterRequest`). +* Apply changes unless the descriptor is `RebootRequired`. + +**Minimal API sketch** + +```cpp +class ParameterManager { +public: + static ParameterManager& instance(); + + void init(ChirpHandler& chirp); // registers ParamRequest callback + ParamRegistry& registry(); + + // Internal helpers + ParamStatus get_by_name(const char* name, uint8_t* out, uint16_t& out_len, TypeId& out_type) const; + ParamStatus set_by_name(const char* name, const uint8_t* in, uint16_t in_len, TypeId type, bool& reboot_required); + + // Chirp callback (wired to opcode for ParameterRequest) + void chirp_param_request_callback(const chirp::ParameterRequest& req); + +private: + ParameterManager() = default; + ParamRegistry m_reg; +}; +``` + +**Key behaviors** + +* **Name resolution** via registry. +* **Immediate apply:** `set_by_name` calls the descriptor’s `setter`. If success: + * If `RebootRequired` flag OR setter sets `reboot_required=true` → reply `RebootRequired`. + * Else reply `Ok`. +* **Get** calls the descriptor’s `getter`. +* **Chirp bridge:** `chirp_param_request_callback` maps Chirp fields to the API above and produces a `ParameterResponse`. + +--- + +## 4) Module Integration via `IFlightModule` + +Grouping flight controller modules under a capability interface like `IFlightModule` can be useful: + +* Keeps a **clean boundary**: modules **declare** what they own and how to apply changes—no global manager poking internal data. +* Enables **testability**: I can unit-test a module’s param parsing/validation in PC native tests without wiring the entire FC. +* Simplifies **registration**: each module lists its parameters and provides delegates once, during init. + +**Example interface** + +```cpp +struct IFlightModule { + virtual const char* root_namespace() const = 0; // e.g., "attitudectrl" + virtual void enumerate_params(ParamRegistry& reg) = 0; // Optional. modules can expose dedicated getters/setters per param as how it is currently. + virtual ~IFlightModule() = default; +}; +``` + +**How it’s used** + +* On boot, each module calls `enumerate_params(reg)`. +* Inside `enumerate_params`, the module **registers its parameters** by building `ParamDescriptor`s with **delegates bound to specific setter/getter methods of `this`**. + +**Example — Attitude Controller registers `kp_roll`** + +```cpp +class AttitudeController : public IFlightModule { +public: + const char* root_namespace() const override { return "attitudectrl"; } + + void enumerate_params(ParamRegistry& reg) override { + static const float DEFAULT_KP_ROLL = 10.0f; // Either defined here or as a member variable + static const uint8_t* def = reinterpret_cast(&DEFAULT_KP_ROLL); + + ParamDescriptor d { + "flight_controller", + "attitudectrl", + "kp_roll", // or "attitudectrl.kp_roll" + TypeId::Float, + (uint16_t)(sizeof(float)), + (uint8_t)(ParamFlag::Writable | ParamFlag::ImmediateApply), + def, + // getter: copy current kp into out + etl::delegate::create(this), + // setter: parse float, validate, assign + etl::delegate::create(this) + }; + reg.register_param(d); + } + + // Example getter/setter implementations: + ParamStatus get_kp_roll(uint8_t* out, uint16_t& out_len) { + const uint16_t needed_length = static_cast(sizeof(float)); + if (out_len < needed_length) return ParamStatus::InvalidType; + memcpy(out, &m_kp_roll, needed_length); + out_len = needed_length; + return ParamStatus::Ok; + } + + ParamStatus set_kp_gains(const uint8_t* in, uint16_t in_len, bool& reboot_required) { + const uint16_t needed_length = static_cast(sizeof(float)); + if (in_len != needed_length) return ParamStatus::InvalidType; + float v; + memcpy(&v, in, needed_length); + + if (v[i] < min || v[i] > max) return ParamStatus::InvalidValue; + + reboot_required = false; // takes effect immediately + return ParamStatus::Ok; + } + +/* rest of the class definition ... */ + +private: + float m_kp_roll = 10.0f; + ... +}; +``` + +> This pattern can be replicated this pattern for other attitude controller params like `torque_limit`, indi related params etc. + +> Alternatively, we can alter the structure of `ParamDescriptor` in the example without redefining the current parameter structure of attitude controller, +keeping the array structure for kp, kd gains, torque limits etc. Design supports multiple value arrays as single parameter: + +```cpp + ParamDescriptor d { + "flight_controller", + "attitudectrl", + "kp_gains", // or "attitudectrl.kp_gains" + TypeId::Float, + (uint16_t)(3 * sizeof(float)), + (uint8_t)(ParamFlag::Writable | ParamFlag::ImmediateApply), + def, + // getter: copy current kp into out + etl::delegate::create(this), + // setter: parse float, validate, assign + etl::delegate::create(this) + }; +``` + +where `kp_gains` is defined as: +```cpp +private: + etl::array m_kp_gains; +``` + +--- + +## 5) Hardware Parameters via `IHardwareParamAdapter` + +Some parameters belong to **hardware drivers** (IMUs, serial baud, PWM ranges). I don’t want the ParameterManager to depend on driver APIs, and most drivers are used by multiple flight modules. + +**The adapter idea:** a **tiny object** owned by the module that uses the driver (or owned centrally) which exposes parameters for that hardware subtree, using the **same pattern** as a module: + +```cpp +struct IHardwareParamAdapter { + virtual const char* root_namespace() const = 0; // e.g., "serial" or "imu" + virtual void enumerate_params(ParamRegistry& reg) = 0; + virtual ~IHardwareParamAdapter() = default; +}; + +// Example: Serial adapter exposing baud rate +class SerialParamAdapter : public IHardwareParamAdapter { +public: + const char* root_namespace() const override { return m_root_namespace; } + + void set_root_namespace(char* root_namespace) { m_root_namespace = root_namespace; } // These setters can be needed for multiple instances of serial e.g `serial_pilot` , `serial_copilot` + + void enumerate_params(ParamRegistry& reg) override { + static const uint32_t DefaultBaud = 115200; + static const uint8_t* def = reinterpret_cast(&DefaultBaud); + + ParamDescriptor baud { + "flight_controller", + m_root_namespace, + "baud_rate", + TypeId::Uint32, + (uint16_t)sizeof(uint32_t), + (uint8_t)(ParamFlag::Writable | ParamFlag::RebootRequired), + def, + etl::delegate::create(this), + etl::delegate::create(this) + }; + reg.register_param(baud); + } + + ParamStatus get_baud(uint8_t* out, uint16_t& out_len) { + if (out_len < sizeof(uint32_t)) return ParamStatus::InvalidType; + memcpy(out, &m_baud, sizeof(uint32_t)); + out_len = sizeof(uint32_t); + return ParamStatus::Ok; + } + + ParamStatus set_baud(const uint8_t* in, uint16_t in_len, bool& reboot_required) { + if (in_len != sizeof(uint32_t)) return ParamStatus::InvalidType; + uint32_t v; memcpy(&v, in, sizeof(uint32_t)); + if (v < 9600 || v > 3000000) return ParamStatus::InvalidValue; + m_baud = v; + reboot_required = true; // takes effect after re-init + return ParamStatus::Ok; + } + +private: + char* m_root_namespace; + uint32_t m_baud = 115200; + Serial* m_serial; +}; +``` + +**Why it’s useful** + +* Keeps **hardware side-effects** localized (re-init serial, reconfigure I2C, etc.). +* Does not require any change to driver code +* Multiple modules can **share** one adapter’s namespace (`serial.*`, `imu.*`). +* Allows easy setup for *multiple hardware instances* (`serial_pilot`, `imu_leftwing`) +* Adapters register like modules; the manager treats them equivalently. + +--- + +## 6) Boot Sequence (step-by-step) + +1. **Construct** the global singleton: + + ```cpp + auto& paramManager = ParameterManager::instance(); + ``` + +2. **Initialize Chirp and register callback:** + + ```cpp + paramManager.init(chirp_handler); // internally: chirp_handler.register_callback(OPCODE_PARAM_REQUEST, &ParameterManager::chirp_param_request_callback); + ``` + +3. **Create modules and adapters using factory method** (AttitudeController, Navigator, Logger, SerialParamAdapter, etc.). + +4. **Register parameters**: + + Happens when modules are created, most likely in factory class. + ```cpp + attitudeController.enumerate_params(paramManager.registry()); + logger.enumerate_params(paramManager.registry()); + serialAdapter.enumerate_params(paramManager.registry()); + // ... others + ``` + +5. **Apply defaults** (optional): + + * Iterate all descriptors and call their `setter(default_bytes, length)` to seed modules with defaults **once** at boot. + +6. **Ready for runtime**: FC now answers Chirp Get/Set by **name**. + +--- + +## 7) Runtime Update Flow (example) + +**Scenario:** ground station sets `attitudectrl.kp_roll = 18.0f`. + +1. **Chirp RX:** `ChirpHandler` parses an incoming `ParameterRequest` with: + + * `op = Set` + * `name = "attitudectrl.kp_roll"` + * `type = Float` + * `length = 4` + * `value = { bytes of 18.0f }` + * `transaction_id` = 42 (example) + +2. **Callback dispatch:** `ChirpHandler` calls + `ParameterManager::chirp_param_request_callback(req)`. + +3. **Manager logic (simplified):** + + ```cpp + void ParameterManager::chirp_param_request_callback(const chirp::ParameterRequest& req) { + chirp::ParameterResponse resp{}; + resp.transaction_id = req.transaction_id; + resp.timestamp = micros64(); // or timeManager.time() etc. + + if (req.op == ParamOp::Set) { + bool reboot_required = false; + ParamStatus st = set_by_name((const char*)req.name, req.value, req.length, req.type, reboot_required); + + resp.status = to_wire_status(st, reboot_required); + resp.name_size = req.name_size; + memcpy(resp.name, req.name, req.name_size); + // Echo type/length; optionally include the applied value via a getter or copy directly from request + send_parameter_response(resp); + } + else if (req.op == ParamOp::Get) { + uint8_t buf[256]; uint16_t len = sizeof(buf); TypeId t; + ParamStatus st = get_by_name((const char*)req.name, buf, len, t); + + resp.status = to_wire_status(st, /*reboot_required*/false); + resp.type = t; resp.length = len; + memcpy(resp.value, buf, len); + send_parameter_response(resp); + } + } + ``` + +4. **Validation path inside `set_by_name`:** + + * **Lookup**: registry.find(name) → descriptor `d`; if not found → `NotFound`. + * **Type/length check**: `req.type == d.type` and `req.length == d.length`; else → `InvalidType`. + * **Access check**: + + * If `!(d.flags & Writable)` → `AccessDenied`. + * If `is_armed() && (d.flags & FlightLocked)` → `AccessDenied`. + * **Apply**: + + * Call `d.setter(req.value, req.length, reboot_required)`. + * If setter returns `InvalidValue` (e.g., out of bounds) → reply `InvalidValue`. + * If success and `(d.flags & RebootRequired) || reboot_required` → reply `RebootRequired`. + * Else → reply `Ok`. + +5. **Module effect:** + + * AttitudeController’s `m_kp_roll` is updated to 18.0f and will be used by the next control step. + +--- + +## 8) Validation Layers (where checks live) + +1. **Manager-level (generic)** + + * Name exists in registry. + * Type/length match the descriptor. + * Flags / access policy (Writable, FlightLocked). + +2. **Module-level (semantic)** + + * Bounds and relationships specific to the parameter. (Outside of min-max range, must be non-zero etc.) + +**Tiny helper for type/length check** + +```cpp +static bool check_wire_matches_desc(TypeId wire_type, uint16_t wire_len, const ParamDescriptor& d) { + return (wire_type == d.type) && (wire_len == d.length); +} +``` + +--- + +## 9) Example: Wiring it Together + +**Register the Chirp callback once** + +```cpp +void ParameterManager::init(ChirpHandler& chirp) { + chirp.register_callback(chirp::Opcode::ParameterRequest, + etl::delegate::create(this)); +} + +void ParameterManager::on_any_message(const chirp::MessageUnpacked& msg) { + // translate msg → ParameterRequest or ignore if not ParamRequest + chirp::ParameterRequest req; + if (!decode_parameter_request(msg, req)) return; + chirp_param_request_callback(req); +} +``` + +**Set/Get APIs** + +```cpp +ParamStatus ParameterManager::set_by_name(const char* name, const uint8_t* in, uint16_t in_len, TypeId type, bool& reboot_req) { + reboot_req = false; + const ParamDescriptor* d = m_reg.find(name); + if (!d) return ParamStatus::NotFound; + + if (!check_wire_matches_desc(type, in_len, *d)) return ParamStatus::InvalidType; + + if (!(d->flags & (uint8_t)ParamFlag::Writable)) return ParamStatus::AccessDenied; + if (is_armed() && (d->flags & (uint8_t)ParamFlag::FlightLocked)) return ParamStatus::AccessDenied; + + ParamStatus st = d->setter(in, in_len, reboot_req); + return st; +} + +ParamStatus ParameterManager::get_by_name(const char* name, uint8_t* out, uint16_t& out_len, TypeId& out_type) const { + const ParamDescriptor* d = m_reg.find(name); + if (!d) return ParamStatus::NotFound; + out_type = d->type; + return d->getter(out, out_len); +} +``` + +--- + +## 10) Non-Module/System Parameters + +Treat these as **their own roots** or grouped under `system.*` / `logger.*` / `build.*`, etc. + +* `logger.level` — writable, immediate. +* `system.flight_mode` — writable, may be `FlightLocked`. +* `build.git_sha` — read-only (no `Writable` flag). +* `serial_pilot.baud_rate` — `RebootRequired` (or requires port re-init), adapter-owned. + +They register exactly like module parameters (same `ParamDescriptor` pattern). + +--- + +## 11) Future Considerations + +* Parameter **Store** and **StagingBuffer** (two-phase commit). + → initial version applies immediately; I'd be a good idea to consider staging the changes and applying in a safe state for later versions. + +* Full-list enumeration & chunking (GetFullList) for sharing the parameter list with other devices. + +* Persistence to Flash/EEPROM + +--- + +## 12) Checklist to Implement + +1. Implement **ParamRegistry** with ETL containers. +2. Create **ParameterManager** singleton; wire to **ChirpHandler**. +3. Convert 1–2 modules (e.g., `attitudectrl`, `navigator`) to implement **enumerate\_params** and delegate setters/getters. +4. Add a **SerialParamAdapter** as a reference hardware example. +5. Bring up **Get/Set by name** end-to-end and test on PC and on Teensy. + +--- + +## TODOs + +Add the full list of current parameters in flight controller, to this document. + +Try to fit them in currently proposed structure with proper naming, flags. \ No newline at end of file