Smart Circuits & Generator — API and CLI Design¶
Status: BACKLOG — design only, no code. Queued per AP-1 (Queue → Plan → Execute). Write paths are API-affecting and need sign-off (
CLAUDE.mdrule 6).Evidence tiers per AP-14.
1. Goal¶
One coherent surface for Smart Circuits and the Generator module:
- Read per-circuit configuration, schedule and live metrics together.
- Write switch state (exists), schedules (missing) and generator mode (exists), through a consistent interface.
Today these are split across two commands, two payload shapes and two CLI commands, and the schedule half cannot be written at all.
2. What exists¶
| Concern | Source | Method | CLI |
|---|---|---|---|
| Circuit config, mode, SoC cutoff, schedule | cmdType 311 | get_smart_circuits() |
sc |
| Circuit live metrics | cmdType 353/354 | get_accessories_power_info() |
accessories |
| Circuit on/off, mode | 311 write | set_smart_circuit_state(), set_smart_switch_state() |
sc --on/--off |
| SoC cutoff, load limit | 311 write | set_smart_circuit_soc_cutoff(), set_smart_circuit_load_limit() |
sc --cutoff/--load-limit |
| Circuit schedule write | — | none | none |
| Generator config/state | 311 + 203 | get_generator_info() |
accessories |
| Generator live metrics | cmdType 354 | get_accessories_power_info(3) |
— |
| Generator mode | set_generator_mode() |
— |
CONFIRMED cmdType 354 payload (corpus sample, 2025-10-04):
opt, result, Sw1Volt, Sw2Volt, SW1Curr, SW2Curr, SW1ExpPower, SW2ExpPower,
SW1ExpEnergy, SW2ExpEnergy, CarSWCurr, CarSWPower, CarSWExpEnergy,
CarSWImpEnergy, CarSwConsSupExpEnerge, power, curr, volt, freq, genpowerGen
Note: circuits use an SW/Sw prefix, V2L uses CarSW, and the generator
fields are unprefixed (power, curr, volt, freq, genpowerGen) — so
they cannot be disambiguated by name alone.
3. Defects this design must not inherit¶
DEF-ACCESSORY-POWER-OPTION-TYPE—get_accessories_power_info(option=1)declares an int default but compares against strings (option == "1"). The default call therefore matches nothing and returns the raw payload, not the documented Smart Circuits view. Only caller passes"0", so it has gone unnoticed.- Hardcoded to two circuits. The Smart Circuits branch builds ids 1 and 2
only. US SC V2 has three (
circuit_count: 3); aHub has 4–8. DEF-SC-SCHEDULE-NOT-RENDERED(fixed) showed the config path already carried schedule data nothing displayed. The unified view must not repeat that: if a field is parsed, it is rendered or explicitly excluded.
4. Proposed SDK surface¶
Composes 311 (config + schedule) with 354 (live metrics) into one
per-circuit view. circuit=None returns all. Circuit count comes from
ResolvedCapabilities.circuit_count, never a hardcoded range.
{
"id": 1, "name": "Circuit 1",
"config": {"mode", "is_on", "soc_cutoff_enabled", "soc_cutoff_limit",
"load_limit", "pro_load_type"},
"schedule": {"slots": [...], "enabled": [...], "raw_time_set": [...]},
"metrics": {"current", "voltage", "power", "energy", "scale_note"},
"source": {"config_cmd": 311, "metrics_cmd": 354},
}
Same shape for the generator: config from 311/203 (genStat, genStartSoc,
genStopSoc, genEn), metrics from 354 (power, curr, volt, freq,
genpowerGen).
Writes¶
async def set_smart_circuit_schedule(self, circuit, slots, *, confirm=False)
async def set_generator_soc_thresholds(self, start_soc, stop_soc, *, confirm=False)
Both ride the existing _update_smart_circuit_config() read-modify-write 311
cycle, which already handles the full-overwrite semantics. confirm=True
required, matching switch_to_wifi().
set_smart_circuit_schedule is blocked — see §6.
5. Proposed CLI¶
fwh sc # unchanged: config + schedule for all circuits
fwh sc --detail [N] # config + schedule + live metrics, one or all
fwh sc --metrics # metrics only, watch-friendly
fwh sc --set-schedule N ... # BLOCKED, see §6
fwh gen # generator config + metrics + mode
fwh gen --mode MODE # wraps set_generator_mode()
fwh gen --soc START STOP # start/stop SoC thresholds
--json everywhere, already global. Generator currently has no CLI command at
all — gen is new; accessories keeps its existing summary role.
6. Blockers — do not implement past these¶
- ~~
DEF-SC-TIMESET-UNDECIPHERED~~ — CLEARED 2026-09-18.SwNTimeSetis[1,0,1,0]: the four slots are two start/end pairs. Confirmed by the app's own message "Only two time slots can be scheduled" and by live reads. - ~~Slot pairing unverified~~ — CLEARED. Also established live:
SwNTimeEnarms slots individually (the app's Time Schedule row read Off with the bits zeroed, On with them set);SwNFreqis a cycle in days with0= "Once only"; and execution date isbase + k x cycle(2026-06-19 + 2x60 = 2026-10-17, matching the app exactly). - Metric scaling is unverified.
freq: 500is almost certainly 50.0 Hz (÷10), andSw1Volt: 1004plausibly 100.4 V — butvolt: 2alongside suggests the generator fields may scale differently, or be inactive. Do not apply a divisor without establishing it per field. Until then, expose raw values with an explicitscale_note, as the JA12 block does. - Generator field names are unprefixed, so
power/curr/volt/freqcannot be attributed to the generator by name alone — only by position in the 354 payload. Confirm before presenting them as generator readings.
Blockers 1 and 2 were cleared by live hardware, not a capture. Blocker 3 still stands: a 354 response with the generator running would settle the scaling.
The write path — solved, and not where it was expected¶
set_smart_circuit_schedule() shipped and then silently did nothing: the
gateway returned result: 0 and discarded every write. The cause was one
field. SwNMsgType selects what KIND of change a 311 write is, and the
firmware drops a write whose kind does not match its content:
SwNMsgType |
corpus | content | meaning |
|---|---|---|---|
1 |
39 writes | Mode 0/1, schedule disarmed |
switch the circuit on/off |
2 |
9 writes | Mode 2, schedule armed |
edit the schedule / config |
_update_smart_circuit_config() hardcoded 1, so schedule, SoC-cutoff and
load-limit writes all went out as switch commands and vanished. Switch
writes were never broken, which is why it went unnoticed for months — nothing
read back. Fixed by making msg_type a parameter. See
DEF-311-CONFIG-WRITES-WRONG-MSGTYPE.
Method note worth keeping: the first diagnosis ran five experiments against
the transport — stale dates, propagation delay, payload shape, frame envelope —
and found nothing, because the fault was in a field already being sent. The
answer was in the capture corpus the whole time: 278 cmdType 311 requests
across 24 HAR files, 52 of them writes. One query correlating MsgType
against write content found it. Check the corpus before instrumenting the wire.
7. Sequencing¶
| Step | Scope | Blocked by |
|---|---|---|
| A | Fix DEF-ACCESSORY-POWER-OPTION-TYPE; drive circuit count from capabilities |
— |
| B | get_smart_circuit_detail() + sc --detail (read-only) |
— |
| C | get_generator_detail() + fwh gen (read-only) |
blocker 4 for labelling |
| D | fwh gen --mode / --soc (wraps existing setters) |
sign-off |
| E | set_smart_circuit_schedule() + sc --set-schedule |
✅ done — verified live |
A–C are unblocked and deliver most of the value: every metric and schedule already retrievable, in one place, for circuits and generator alike. E is complete and verified against hardware: the write reports stored and verified, circuit 1 and the global fields are untouched by a circuit-2 write, and the read-back reports honestly when a write does not land.
7a. Transport asymmetry — generator is REST, circuits are not¶
Worth knowing before looking for an endpoint that does not exist:
| read | write | |
|---|---|---|
| Generator | selectIotGenerator (REST) |
updateIotGenerator (REST) |
| Smart Circuits | cmdType 311 over sendMqtt |
cmdType 311 over sendMqtt |
There is no REST endpoint for Smart Circuits — no selectIotSwitch,
no updateIotSwitch, nothing in the corpus. Both directions go through the
CRC-framed MQTT envelope, which is why circuit writes are a read-modify-write
of the whole Sw* block while generator writes post a handful of fields. It
also explains why set_generator_charge_schedule() worked immediately while
the circuit equivalent did not: they share no transport code.
8. Open¶
- Does
353/354exposeSW3*on a three-circuit system? The corpus is an AU two-circuit gateway, so absence ofSW3Currhere is not evidence. - aHub's 4–8 circuits cannot be represented by 311's
Sw1/Sw2/Sw3slots — seeFEAT-SC-CMDTYPE-387-389. This design targets 311; an aHub path is separate work.