v1.12.2 - Unified Device Response Phase 2B: Nested Handler Migration (2025-12-08)¶
What Changed?¶
This release completes Phase 2B of the Unified Device Response Protocol by introducing the DeviceResponseBuilder::nested() semantic factory method and migrating 7 complex handlers with nested JSON response structures. The enhancement improves API clarity, consolidates nested object creation patterns, and prepares the infrastructure for Phase 2C handlers while maintaining 100% backward compatibility.
What's New¶
Main Feature: Semantic Nested Response Builder¶
What it does:
The DeviceResponseBuilder::nested() method provides a semantic factory for creating responses with single named nested objects. Unlike the generic empty() method, nested() clearly communicates intent through its name and API design, making code more maintainable and reducing cognitive load.
How to use it:
Instead of manually creating nested objects:
// OLD (before v1.12.2)
JsonDocument doc;
doc["type"] = "response";
doc["status"] = "ok";
doc["sent_at"] = device_get_timestamp();
JsonObject threshold = doc.createNestedObject("threshold");
threshold["channel"] = 1;
threshold["value"] = 2048;
serializeJson(doc, Serial);
Serial.println();
Use the builder for cleaner, DRY code:
// NEW (v1.12.2+)
JsonDocument doc;
JsonObject threshold = DeviceResponseBuilder::nested("threshold", doc);
threshold["channel"] = 1;
threshold["value"] = 2048;
serializeJson(doc, Serial);
Serial.println();
Code example:
All Phase 2B handlers now follow this pattern:
static command_response_t handle_get_threshold(text_command_t cmd) {
if (cmd.arg_count != 1) {
text_response_error(F("GET_THRESHOLD"), F("Missing channel argument"), 1);
return response;
}
uint8_t ch = cmd.args[0];
if (ch < 1 || ch > 3) {
text_response_error(F("GET_THRESHOLD"), F("Invalid channel"), 1);
return response;
}
#if ENABLE_DEVICE_RESPONSE
// Clean: Use builder for nested response
uint16_t val = config_get_threshold(ch);
JsonDocument doc;
JsonObject threshold = DeviceResponseBuilder::nested("threshold", doc);
threshold["channel"] = ch;
threshold["value"] = val;
serializeJson(doc, Serial);
Serial.println();
response.is_ok = true;
#else
// Legacy fallback
uint16_t val = config_get_threshold(ch);
send_response(response_pairs("channel", ch, "threshold", val));
response.is_ok = true;
#endif
return response;
}
Builder API Enhancement:
DeviceResponseBuilder::simple(const char* field, T value)- String/integer/boolean fields (Phase 2A)DeviceResponseBuilder::nested(const char* object_name, JsonDocument& doc)- Semantic factory for single nested objects (Phase 2B) ✨ NEWDeviceResponseBuilder::empty()- Base response for complex structures (Phase 2C)
Installation¶
Quick Start¶
# Get the release
git checkout v1.12.2
# Build
task build
# Upload
task upload
# Check it works
task monitor
What's Different from the Last Version (v1.12.1)?¶
✅ Added¶
- DeviceResponseBuilder::nested() method for semantic nested object creation
- Returns JsonObject reference for convenient field population
- Uses modern ArduinoJson API (
doc[key].to<JsonObject>()) for forward compatibility - Clear API intent:
nested()indicates single named nested object pattern
🔧 Changed¶
- All 7 Phase 2B handlers refactored:
- GET_THRESHOLD, SET_THRESHOLD - DAC threshold configuration with nested response
- GET_RTC_TIME, SET_RTC_TIME - Real-time clock queries
- GET_GNSS_STATUS - GNSS receiver state with conditional fields
- GET_GNSS_POSITION - GNSS location data with precision fields
- GET_GNSS_TIME - GNSS timestamp and fix validity
- Now use
DeviceResponseBuilder::nested()instead of manual JSON construction - Code improvement: Cleaner, more intentional API with semantic method names
🐛 Fixed¶
- Deprecated ArduinoJson API warnings eliminated by using modern
doc[key].to<JsonObject>()pattern - Inconsistent nested object creation patterns consolidated
- Improved code maintainability through semantic factory method naming
Is It Safe to Upgrade?¶
Backward Compatible: Yes ✅
- All handlers maintain identical JSON output format
- No behavioral changes (type, status, sent_at fields unchanged)
- Legacy code paths preserved via
ENABLE_DEVICE_RESPONSE=1flag - Existing client parsers continue to work without modification
- Previous v1.12.1 output example:
{"type":"response","status":"ok","sent_at":47,"threshold":{"channel":1,"value":2048}} - v1.12.2 output: Identical
Tests Passed¶
- ✅ Build: SUCCESS (Zero errors, zero warnings, Flash 26.6%, RAM 8.8%)
- ✅ Nested builder method: Compiles and links without conflicts
- ✅ All 7 Phase 2B refactored handlers: Zero compilation errors
- ✅ JSON output validation: Nested responses correctly formatted
- ✅ GET_THRESHOLD tested:
{"type":"response","status":"ok","sent_at":37,"threshold":{"channel":1,"value":300}} - ✅ GET_GNSS_POSITION tested:
{"type":"response","status":"ok","sent_at":64,"gnss":{"latitude":0.000000,"longitude":0.000000,"altitude":0.00,"fix_valid":false}} - ✅ Serial output: Verified at 115200 baud, all nested objects well-formed
- ✅ Firmware upload: SUCCESS (19.91 seconds)
- ✅ Backward compatibility: All legacy code paths intact
- ✅ ArduinoJson API: Modern API usage, no deprecation warnings
Release Details¶
- Date: 2025-12-08
- Version: v1.12.2
- Files Changed: 3
- Modified:
include/device_response_builder.h(enhanced withnested()method) - Modified:
src/device_response_builder.cpp(implementation ofnested()) - Modified:
src/text_command_manager.cpp(7 Phase 2B handlers refactored) - Total Insertions: 171
- Total Deletions: 12
- Git Commits:
7751faf- refactor(device_response): add nested() builder and migrate Phase 2B handlersd8c4fa5- docs(progress): phase 2b nested handler migration - completed
Next Steps¶
Phase 2C (Complex Handlers): Coming next is migration of complex handlers with multiple nested objects and conditional fields:
- GET_STATUS - Multiple nested objects (system info, detection stats, configuration)
- GET_HELP - Complex nested command documentation with descriptions and argument details
These handlers will extend the DeviceResponseBuilder pattern to handle sophisticated response structures, completing Phase 2 before moving to Phase 3 (event unification).
Phase 3 (Event Output): Detection events will be unified to use the same device_response_t structure with type="event".
Phase 4 (Legacy Cleanup): Once all handlers use device_response_t, the legacy response.h and response.cpp files will be removed, providing additional binary size savings.