- Date Created: 2025-12-09
- Last Modified: 2025-12-09
Progress Log: CommandQueue Class Design (Phase 2)¶
Task Description¶
Following successful v1.13.0 Command class implementation, design Phase 2: CommandQueue class for unified command reception, parsing, and queuing - replacing fragmented text_command.cpp functionality.
Current State Analysis:
- Text command reception and parsing scattered across
text_command.cpp(7 functions) - Serial reception:
text_receive_line(), timeout handling, overflow detection - Queueing: FreeRTOS Queue with 10-item capacity
- Parsing:
text_parse()with alias resolution, character validation - Dispatch:
text_dispatch()with command table lookup - No unified interface: main.cpp must call 3 separate functions per cycle
Design Goals:
- Unified Reception Pipeline: Single class handles serial I/O → parse → queue
- Simplified Interface: main.cpp calls only
command_queue_receive()andcommand_queue_execute() - Type-Safe Commands: Leverage Command class for argument validation
- Foundation for Handlers: Standardized command format for all handlers
- Integration Ready: Works seamlessly with Command class for configuration updates
Proposed Architecture¶
CommandQueue Class Structure¶
class CommandQueue {
private:
// Queue storage (FreeRTOS static allocation)
static QueueHandle_t g_queue;
static StaticQueue_t g_queue_storage;
static uint8_t g_queue_buffer[COMMAND_QUEUE_SIZE * sizeof(command_t)];
// Helper methods
static bool receive_line(char* buffer, size_t buffer_size);
static command_t parse(const char* line);
static void discard_input(void);
public:
// Initialization
static void init(void);
// Reception and queueing (main entry point)
static bool receive(void);
// Query and execution
static bool has_pending(void) const;
static bool execute(void);
};
Data Structure: command_t¶
Simplified and optimized compared to current text_command_t:
typedef struct {
char name[32]; // Command name (uppercase, null-terminated)
uint8_t arg_count; // Number of arguments (0-2)
char args[2][16]; // Argument strings (null-terminated)
} command_t;
Improvements over text_command_t:
- Removed raw_buffer[64] (error context not needed - errors handled at reception)
- Kept essential fields: command_name, arg_count, args
- More memory-efficient for queue storage
Handler Signature Standardization¶
Current (in text_command_manager.cpp):
typedef command_response_t (*command_handler_t)(text_command_t cmd);
// Handler validation logic duplicated in each handler
command_response_t handle_set_poll_count(text_command_t cmd) {
if (cmd.arg_count != 1) { return error; }
uint16_t count = atoi(cmd.args[0]);
if (count < 1 || count > 65535) { return error; }
// ... set and execute
}
Proposed (CommandQueue with type-safe parsing):
typedef command_response_t (*command_handler_t)(const command_t& cmd);
// Validation moved to CommandQueue argument parsing
// Handler receives pre-validated values
command_response_t handle_set_poll_count(const command_t& cmd) {
// args[0] guaranteed to be valid uint16_t [1, 65535]
uint16_t count = atoi(cmd.args[0]); // Safe - already validated
Command::getInstance().set_poll_count(count);
return device_response_ok(RESPONSE_TYPE_OK);
}
Integration with Existing Architecture¶
Current Flow (3 separate calls):
main.cpp loop():
├─ text_receive() // Serial → parse → queue
├─ text_execute() // Dequeue → dispatch → respond
└─ (repeat)
CommandQueue Flow (2 calls, unified):
main.cpp loop():
├─ CommandQueue::receive() // Serial → parse → queue
├─ CommandQueue::execute() // Dequeue → dispatch → respond
└─ (repeat)
Compilation Strategy¶
When ENABLE_DEVICE_RESPONSE=1 (new path):
#if ENABLE_DEVICE_RESPONSE
// Phase 2: CommandQueue + Command class
// New implementation in command_queue.h/cpp
// Handlers in text_command_manager.cpp (adapted)
#else
// Legacy path: text_command.cpp
// Original text_receive(), text_parse(), text_execute()
// Unchanged and fully backward compatible
#endif
Key Point: CommandQueue is conditional on ENABLE_DEVICE_RESPONSE=1, same as Command class. They form an integrated subsystem.
Implementation Details¶
Reception Flow¶
- Availability Check:
Serial.available() > 0 - Line Reception:
Serial.readBytesUntil('\n', buffer, size) - Validation:
- Overflow detection (buffer full without newline)
- Empty command check
- Trailing whitespace trimming
- Return: true if valid line received, false if timeout/error
bool CommandQueue::receive_line(char* buffer, size_t buffer_size) {
Serial.setTimeout(COMMAND_RECEPTION_TIMEOUT_MS); // 500ms
size_t bytes = Serial.readBytesUntil('\n', buffer, buffer_size - 1);
// Check for overflow (buffer full without newline)
if (bytes == (buffer_size - 1)) {
discard_input();
return false;
}
// Check for empty command
if (bytes == 0) return false;
// Trim trailing whitespace
while (bytes > 0 && isspace(buffer[bytes - 1])) bytes--;
// Null terminate
buffer[bytes] = '\0';
return bytes > 0;
}
Parsing Flow¶
- Command Name Extraction: First whitespace-delimited token
- Uppercase Conversion: Normalize input
- Alias Resolution: Map single-char shortcuts (T, G, C, etc.)
- Argument Splitting: Subsequent tokens up to MAX_ARGS
- Return: Populated
command_tstructure
command_t CommandQueue::parse(const char* line) {
command_t cmd = {0};
// Copy to working buffer (strtok modifies it)
char work[MAX_COMMAND_LENGTH];
strncpy(work, line, sizeof(work) - 1);
work[sizeof(work) - 1] = '\0';
// Extract command name
char* token = strtok(work, " ");
if (!token) return cmd;
strncpy(cmd.name, token, sizeof(cmd.name) - 1);
cmd.name[sizeof(cmd.name) - 1] = '\0';
to_uppercase(cmd.name);
// Resolve aliases (T → SET_THRESHOLD, etc.)
resolve_alias(cmd.name);
// Extract arguments
while ((token = strtok(NULL, " ")) && cmd.arg_count < 2) {
strncpy(cmd.args[cmd.arg_count], token, sizeof(cmd.args[0]) - 1);
cmd.args[cmd.arg_count][sizeof(cmd.args[0]) - 1] = '\0';
cmd.arg_count++;
}
return cmd;
}
Queuing with FreeRTOS¶
Uses static allocation (same as current implementation):
static QueueHandle_t g_queue = NULL;
static StaticQueue_t g_queue_storage;
static uint8_t g_queue_buffer[COMMAND_QUEUE_SIZE * sizeof(command_t)];
void CommandQueue::init(void) {
g_queue = xQueueCreateStatic(
COMMAND_QUEUE_SIZE,
sizeof(command_t),
g_queue_buffer,
&g_queue_storage
);
}
bool CommandQueue::receive(void) {
if (!Serial.available()) return false;
char buffer[MAX_COMMAND_LENGTH];
if (!receive_line(buffer, sizeof(buffer))) {
discard_input();
return true; // We processed the input (even if invalid)
}
command_t cmd = parse(buffer);
BaseType_t result = xQueueSend(g_queue, &cmd, 0);
if (result != pdTRUE) {
Serial.println("ERROR: Command queue full");
}
return true;
}
bool CommandQueue::has_pending(void) const {
return uxQueueMessagesWaiting(g_queue) > 0;
}
bool CommandQueue::execute(void) {
if (!has_pending()) return false;
command_t cmd = {0};
if (xQueueReceive(g_queue, &cmd, 0) != pdTRUE) return false;
// Dispatch to handler (via command_table)
command_response_t response = dispatch(cmd);
// Send response
if (strlen(response.message) > 0) {
Serial.print(response.message);
}
return true;
}
Implementation Phases¶
Phase 2A: CommandQueue Core (v1.14.0)¶
Files Created:
include/command_queue.h(200 lines) - Public interface, data structuressrc/command_queue.cpp(300 lines) - Implementation, FreeRTOS integration
Tasks:
- Define
command_tstructure (simplified vs currenttext_command_t) - Implement
receive_line()with timeout and overflow detection - Implement
parse()with alias resolution - Integrate FreeRTOS Queue (static allocation)
- Implement
receive()(unified reception pipeline) - Implement
execute()(dequeue + dispatch) - Test with serial monitor (simple commands)
- Verify memory usage (Q: stack vs heap?)
Compatibility: Conditional on ENABLE_DEVICE_RESPONSE=1
Phase 2B: Handler Refactoring (v1.14.1)¶
Tasks:
- Update handler signatures to use
command_tinstead oftext_command_t - Migrate key handlers (GET_STATUS, GET_VERSION, SET_POLL_COUNT, SET_THRESHOLD, SET_DEADTIME)
- Move handler implementations to use Command class (v1.13.0)
- Update dispatch table (command_entry_t) to reference new handlers
- Verify backward compatibility with text command protocol
- Test all commands with serial monitor
Handlers to Update (priority order):
- GET_VERSION (no args) → uses Command::get_version()
- GET_STATUS (no args) → uses Command::format_status()
- SET_POLL_COUNT (1 arg: count) → uses Command::set_poll_count()
- SET_THRESHOLD (2 args: ch, val) → uses Command::set_threshold()
- SET_DEADTIME (1 arg: ms) → uses Command::set_deadtime()
Phase 2C: Complete Migration (v1.14.2)¶
Tasks:
- Migrate remaining commands (GET_THRESHOLD, GET_UPTIME, TEST_LED, RESET, GET_HELP, GET_STREAM, SET_STREAM, etc.)
- Migrate conditional commands (RTC, GNSS, WiFi, BME280 when available)
- Move all handler implementations into text_command_manager.cpp
- Verify command_table dispatch works for all 20+ commands
- Remove legacy text_command.cpp (only when ENABLE_DEVICE_RESPONSE=0 not used)
- Performance verification: measure parse latency, queue overhead
- Create release notes for v1.14.2
Key Design Decisions¶
- Static
command_tStructure: No dynamic allocation, fixed-size args -
Rationale: Embedded system, FreeRTOS Queue requires fixed item size
-
Removed
raw_buffer: Error context not needed in queue - Rationale: Errors handled at reception, handler receives validated commands
-
Saves 64 bytes per queued command
-
Conditional on
ENABLE_DEVICE_RESPONSE=1: Integrated with Command class - Rationale: Both form unified configuration management subsystem
-
Legacy text_command.cpp remains available when flag disabled
-
FreeRTOS Static Allocation: No heap, no fragmentation
- Rationale: Matches current implementation, safe for embedded
-
Queue size remains 10 items (configurable)
-
No Type Conversion in Handlers: Move uint16_t validation to reception
- Rationale: Simplify handlers, centralize validation logic
-
Handler receives pre-validated arguments
-
Unified
command_response_t: Single response type for all commands - Rationale: Integrates with device_response_builder from v1.12.0
- JSONL format for all responses
Risks & Mitigations¶
| Risk | Impact | Mitigation |
|---|---|---|
| Handler migration errors | Broken commands | Incremental migration (Phase 2B), test each command before moving next |
| Queue overflow under load | Lost commands | Monitor with GET_QUEUE_STATS, increase COMMAND_QUEUE_SIZE if needed |
| Parser performance | Latency increase | Benchmark parse time vs current text_parse(), optimize if needed |
| FreeRTOS integration | Crashes on queue ops | Use static allocation (proven), test with task priority scenarios |
| Backward compatibility | Legacy code breaks | Keep conditional on ENABLE_DEVICE_RESPONSE=1, test both paths |
| Memory fragmentation | Heap issues | No dynamic allocation in CommandQueue, stack-based parsing |
Architecture Diagram¶
Serial Input (raw bytes)
↓
CommandQueue::receive()
├─ receive_line() ← Handles Serial I/O, timeouts, overflow
├─ parse() ← Tokenizes, alias resolution
├─ Queueing ← FreeRTOS static queue
└─ Return true (consumed input)
↓
[Main loop continues]
↓
CommandQueue::execute()
├─ has_pending() ← Check queue
├─ Dequeue ← Get next command_t
├─ dispatch() ← Table-driven lookup
│ └─ Handler ← Execute command logic
└─ Send response ← Serial output
↓
Response sent to serial
File Organization After Phase 2C¶
Complete Separation via ENABLE_DEVICE_RESPONSE flag:
Path 1: ENABLE_DEVICE_RESPONSE=1 (CommandQueue + Command class)¶
include/
├─ command_queue.h (NEW - 200 lines)
├─ command.h (v1.13.0 - Command class singleton)
src/
├─ command_queue.cpp (NEW - 300 lines, core queue + reception + parsing)
├─ command_manager.cpp (NEW - dispatch table + utilities only, ~100 lines)
├─ command.cpp (v1.13.0 - Command class implementation)
│
└─ command/ (NEW - subdirectory for individual command handlers)
├─ version.cpp (handle_get_version)
├─ status.cpp (handle_get_status)
├─ mac_address.cpp (handle_get_mac_address)
├─ poll_count.cpp (handle_set_poll_count)
├─ threshold.cpp (handle_set_threshold, handle_get_threshold)
├─ deadtime.cpp (handle_set_deadtime)
├─ stream.cpp (handle_set_stream, handle_get_stream)
├─ test_led.cpp (handle_test_led)
├─ uptime.cpp (handle_get_uptime)
├─ help.cpp (handle_get_help)
├─ bme280.cpp (handle_get_bme280)
├─ reset.cpp (handle_reset)
├─ rtc.cpp (handle_set_rtc_time, handle_get_rtc_time - conditional ENABLE_RTC)
├─ gnss.cpp (handle_sync_time, handle_get_gnss_* - conditional ENABLE_GNSS)
└─ wifi.cpp (handle_set_wifi_ssid, handle_set_wifi_enable, handle_get_wifi_status - conditional ENABLE_WIFI)
Key Design Points:
- core files at src/ root: command_queue.cpp (300 lines) and command_manager.cpp (~100 lines)
- handler files in src/command/: Each file 20-50 lines, focused on 1-2 related commands
- Direct filename-to-command mapping: version.cpp has GET_VERSION, threshold.cpp has SET_THRESHOLD + GET_THRESHOLD
- Conditional compilation: Files with #if ENABLE_RTC etc. guards included based on build flags
- Zero overhead at file level: Only needed handlers compiled in, others excluded by src_filter
Path 2: ENABLE_DEVICE_RESPONSE=0 (Legacy text_command)¶
include/
├─ text_command.h (LEGACY - unchanged)
├─ runtime_config.h (LEGACY - unchanged)
src/
├─ text_command.cpp (LEGACY - unchanged)
├─ text_command_manager.cpp (LEGACY - unchanged)
├─ runtime_config.cpp (LEGACY - unchanged)
Key Point: Complete mutual exclusivity via conditional compilation:
#if ENABLE_DEVICE_RESPONSE
// CommandQueue path (NEW)
#include "command_queue.h"
CommandQueue::receive();
CommandQueue::execute();
#else
// Legacy text_command path (UNCHANGED)
#include "text_command.h"
text_receive();
text_execute();
#endif
Integration with v1.13.0 (Command Class)¶
CommandQueue forms the reception and parsing layer above Command class:
┌─────────────────────────────────────────────┐
│ Application Layer (main.cpp) │
│ - Detection loop │
│ - Sensor reading │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Command Handling (CommandQueue + Handlers) │
│ - text_receive() ← CommandQueue │
│ - text_execute() ← CommandQueue │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Configuration Management (Command Class) │
│ - get_poll_count(), set_poll_count() │
│ - get_threshold(ch), set_threshold(ch, val) │
│ - get_deadtime(), set_deadtime() │
│ - And RTC, BME280, GNSS conditionals │
└─────────────────────────────────────────────┘
Complete Separation Strategy (ENABLE_DEVICE_RESPONSE)¶
Design Philosophy: Two Independent Worlds¶
When user sets ENABLE_DEVICE_RESPONSE=1 in build config:
- Legacy code NEVER compiles:
text_command.cpp,text_command_manager.cpp,runtime_config.*are completely excluded - New code ALWAYS compiles:
command_queue.*,command_queue_handlers.cpp,command.cpp - No conditional logic inside files: Use
#ifat file/directory level, not inside functions
File-Level Conditional Compilation¶
main.cpp (the only file with #if conditionals):
#if ENABLE_DEVICE_RESPONSE
#include "command_queue.h"
#include "command.h"
void setup() {
CommandQueue::init();
}
void loop() {
CommandQueue::receive(); // NEW: unified reception + parse + queue
CommandQueue::execute(); // NEW: dequeue + dispatch + respond
}
#else
#include "text_command.h"
#include "runtime_config.h"
void setup() {
text_command_init();
}
void loop() {
text_receive(); // LEGACY: unchanged
text_execute(); // LEGACY: unchanged
}
#endif
platformio.ini (build-time exclusion):
[env:esp32dev-dev]
build_flags = -DENABLE_DEVICE_RESPONSE=1
lib_ignore =
src_filter =
+<*>
-<text_command.cpp>
-<text_command_manager.cpp>
-<runtime_config.cpp>
[env:esp32dev-release]
build_flags = -DENABLE_DEVICE_RESPONSE=0
lib_ignore =
src_filter =
+<*>
-<command_queue.cpp>
-<command_manager.cpp>
-<command.cpp>
Next Steps¶
- Finalize CommandQueue header (
include/command_queue.h): - Define
command_tstructure - Declare public static methods
- Add configuration macros (COMMAND_QUEUE_SIZE, TIMEOUT_MS)
-
Guard with
#if ENABLE_DEVICE_RESPONSE -
Implement CommandQueue core (
src/command_queue.cpp): - FreeRTOS Queue initialization
- Reception, parsing, queuing logic
- Error handling
-
Guard entire file with
#if ENABLE_DEVICE_RESPONSE -
Create command manager (
src/command_manager.cpp): - Implement all 48 handlers using Command class
- Full command dispatch table
- Integration with DeviceResponseBuilder
-
Guard entire file with
#if ENABLE_DEVICE_RESPONSE -
Update main.cpp for conditional flow:
- Add
#if ENABLE_DEVICE_RESPONSEblock -
Keep legacy path unchanged
-
Create release notes (v1.14.0):
- Document new CommandQueue class
- Explain unified reception pipeline
- Show usage examples for developers
- Document
ENABLE_DEVICE_RESPONSE=1requirement
Detailed Integration: CommandQueue + Command Class¶
Layer Architecture¶
┌──────────────────────────────────────────────────────┐
│ Serial I/O (main.cpp) │
│ - CommandQueue::receive() ← read from serial │
│ - CommandQueue::execute() ← write to serial │
└──────────────────┬───────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────┐
│ CommandQueue (command_queue.cpp/h) │
│ - Receive line from serial │
│ - Parse → command_t {name, args[]} │
│ - Queue with FreeRTOS │
│ - Dispatch to handler │
└──────────────────┬───────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────┐
│ Command Handlers (command_queue_handlers.cpp) │
│ - 48 handler functions taking const command_t& │
│ - Access arguments: cmd.args[0], cmd.args[1], etc. │
│ - Call Command class methods │
│ - Generate responses via DeviceResponseBuilder │
└──────────────────┬───────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────┐
│ Command Class (command.cpp/h - v1.13.0) │
│ - get_poll_count(), set_poll_count() │
│ - get_threshold(ch), set_threshold(ch, val) │
│ - get_deadtime(), set_deadtime() │
│ - get_stream(), set_stream() │
│ - DAC synchronization (automatic in setters) │
│ - RTC, GNSS, BME280 (conditional) │
└──────────────────┬───────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────┐
│ Hardware (DAC, GPIO, sensors) │
└──────────────────────────────────────────────────────┘
Implementation Architecture Note¶
⚠️ Important: command_manager.cpp provides the dispatch table and utilities, while individual handlers are implemented in src/command/*.cpp files.
File Roles¶
| File | Role |
|---|---|
| command_queue.cpp | Core: FreeRTOS queue, reception, parsing |
| command_manager.cpp | Dispatch table, utility functions, handler forward declarations |
| command/*.cpp | Individual handler implementations (~15 files) |
Subdirectory Structure Rationale¶
The command-name-based subdirectory structure provides:
- Scalability: 48 handlers across 15 files (avg 3 handlers/file) is maintainable
- Discoverability: Filename directly maps to command name (version.cpp → GET_VERSION)
- Isolation: Each handler/command group is self-contained
- Modularity: Adding new commands = adding new files (no monolithic file growth)
- Build flexibility: Can selectively exclude files via src_filter (conditional compilation)
Implementation Pattern¶
// command_manager.cpp - provides dispatch table and utilities
// Forward declarations (implicit private)
static command_response_t handle_get_version(const command_t& cmd);
static command_response_t handle_get_status(const command_t& cmd);
// ... 48 handlers
// Static dispatch table (const, zero runtime cost)
extern const command_entry_t command_table[] = {
{"GET_VERSION", {"V", NULL}, handle_get_version, "System", "..."},
// ... 48 entries
{NULL, {NULL}, NULL, NULL, NULL}
};
// Handler implementations
static command_response_t handle_get_version(const command_t& cmd) { ... }
static command_response_t handle_get_status(const command_t& cmd) { ... }
// ... 48 implementations
File Naming Convention¶
The name command_manager reflects its role (managing command dispatch/execution), not its structure (C functions, not classes).
Handler Implementation Pattern¶
Example 1: Simple getter (GET_VERSION)
// command_manager.cpp
static command_response_t handle_get_version(const command_t& cmd) {
if (cmd.arg_count != 0) {
return error_response("GET_VERSION", "No arguments expected", 1);
}
// Use Command class (v1.13.0)
const char* version = Command::getInstance().get_version();
// Build response via DeviceResponseBuilder
JsonDocument doc = DeviceResponseBuilder::simple("version", version);
serializeJson(doc, Serial);
Serial.println();
return success_response();
}
Example 2: Setter with validation (SET_POLL_COUNT)
// command_manager.cpp
static command_response_t handle_set_poll_count(const command_t& cmd) {
if (cmd.arg_count != 1) {
return error_response("SET_POLL_COUNT", "Missing argument: <count>", 1);
}
// Parse argument (CommandQueue guarantees valid format)
uint16_t count = atoi(cmd.args[0]);
// Call Command class setter (includes validation)
if (!Command::getInstance().set_poll_count(count)) {
return error_response("SET_POLL_COUNT", "Out of range [1, 65535]", 2);
}
// Build response
JsonDocument doc = DeviceResponseBuilder::simple("poll_count", count);
serializeJson(doc, Serial);
Serial.println();
return success_response();
}
Example 3: Complex handler (SET_THRESHOLD with DAC sync)
// command_manager.cpp
static command_response_t handle_set_threshold(const command_t& cmd) {
if (cmd.arg_count != 2) {
return error_response("SET_THRESHOLD", "Missing arguments: <ch> <val>", 1);
}
// Parse arguments
uint8_t ch = atoi(cmd.args[0]);
uint16_t val = atoi(cmd.args[1]);
// Call Command class setter (automatically syncs DAC)
if (!Command::getInstance().set_threshold(ch, val)) {
return error_response("SET_THRESHOLD", "Invalid channel [1,3] or value [0,1023]", 2);
}
// Build response (DAC sync already happened in Command::set_threshold)
JsonDocument doc;
JsonObject threshold = doc.createNestedObject("threshold");
threshold["channel"] = ch;
threshold["value"] = val;
serializeJson(doc, Serial);
Serial.println();
return success_response();
}
Dispatch Table Pattern¶
// command_manager.cpp
typedef struct {
const char* name;
const char* aliases[4];
command_response_t (*handler)(const command_t&);
const char* category;
const char* description;
} command_entry_t;
extern const command_entry_t command_table[] = {
// System Info
{"GET_VERSION", {"V", NULL}, handle_get_version, "System", "Firmware version"},
{"GET_STATUS", {"S", "STATUS", NULL}, handle_get_status, "System", "System status"},
{"GET_MAC_ADDRESS", {NULL}, handle_get_mac_address, "System", "MAC address"},
// Detection
{"SET_POLL_COUNT", {"C", NULL}, handle_set_poll_count, "Detection", "Set poll count"},
{"SET_THRESHOLD", {"T", NULL}, handle_set_threshold, "Detection", "Set threshold"},
{"GET_THRESHOLD", {"G", NULL}, handle_get_threshold, "Detection", "Get threshold"},
{"SET_DEADTIME", {"D", NULL}, handle_set_deadtime, "Detection", "Set deadtime"},
// ... 41 more handlers ...
{NULL, {NULL}, NULL, NULL, NULL} // Terminator
};
Success Criteria¶
- ✅ CommandQueue and dependencies compile only when
ENABLE_DEVICE_RESPONSE=1 - ✅ Legacy text_command path compiles only when
ENABLE_DEVICE_RESPONSE=0 - ✅ No shared code between paths (complete independence)
- ✅ All 48 handlers migrated to new pattern
- ✅ Each handler uses Command class (v1.13.0) directly
- ✅ DAC synchronization automatic (no duplication)
- ✅ Threshold range unified to [0, 1023]
- ✅ All responses use DeviceResponseBuilder
- ✅ Serial protocol unchanged (backward compatible)
- ✅ Memory usage ≤ current (stack-based, no heap)
- ✅ Performance measured and acceptable
Learnings from Command Class (v1.13.0)¶
Apply these proven patterns to CommandQueue:
- Conditional Compilation: Use
#if ENABLE_DEVICE_RESPONSEconsistently - JSDoc Documentation: Comprehensive function/method documentation
- Static Allocation: No heap for embedded reliability
- Simple API: Few public methods, clear responsibility
- Type Safety: Minimize casting, use explicit types
- Integration: Works with existing modules (Command class, handlers, responses)