Repository navigation
fix(scenario): decode WHO 0 WHAT 40-46 as programming and lock states - #90
Conversation
OWNd reported *0*40#1*W## as "Scenario 40 from control panel W has been
launched". WHAT 40-46 are scenario-module states: libqtdevices
TS10_1_0_23 defines START_PROG = 40, STOP_PROG = 41, DELETE = 42,
LOCK = 43, UNLOCK = 44 (scenario_device.cpp:27-34) and parses the #N
parameter as the scenario being programmed (:93-156). The gateway
firmware (bt_luci on MyHomeServer1, F454, F459) accepts *0*40#N*W##,
41#N, 42, 42#N, 43 and 44 with distinct bus frames (1C 41 / 61 / 00 /
21 / FF / E0) and refuses bare 40 and 41; it emits *0*42*W##, *0*43*W##,
*0*45*W## and *0*46#N*W## for bus frames.
OWNScenarioEvent gains event ("launched", "programming started",
"programming stopped", "deleted", "locked", "unlocked", "status 45/46")
and programming_scenario; scenario is None for these WHATs.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018oHp1P4vxxoFnaLUUZpbpZ
6179e02 to
05b73e2
Compare
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Audit of OpenWebNet-HA/OWNd#90
fix(scenario): decode WHO 0 WHAT 40-46 as programming and lock states by gdluck (open, 1 commit, checks green).
Summary
Previously OWNScenarioEvent always treated WHAT as a scenario number, so frames like *0*40#1*11## or *0*41*01## were logged and exposed as “Scenario 40/41 … has been launched.” The PR correctly reclassifies WHAT 40–46 as scenario-module programming/lock/status states:
- 40 →
programming started(optional#N= target scenario) - 41 →
programming stopped - 42 →
deleted - 43 →
locked - 44 →
unlocked - 45/46 → currently
status 45/status 46 - bare WHAT (no
#N) → “all scenarios” - WHAT 1–31 remain
event = "launched"with unchangedscenario/control_panel
New properties: event and programming_scenario. scenario is set to None for the 40–46 range. No command builders are added (consistent with the lack of a WHO 0 command class).
Evidence quality (strong)
- libqtdevices
TS10_1_0_23scenario_device.cpp(enums START_PROG=40 … UNLOCK=44 andparseFramelogic). - Firmware binary replays of
bt_luci(MyHomeServer1 / F454 / F459): distinct SCS opcode1Ctelegrams vs. the normal scenario-execution opcode14; NACKs for bare*0*40*W##/*0*41*W##. - Real customer traces (e.g. MH201 + F420) emitting
*0*41*01##and*0*44*01##during programming cycles. - Golden-corpus / firmware emission examples of 42/43/45/46 with and without parameters.
- All CI checks (Python 3.11–3.14 tests + coverage, static analysis, golden corpus, MyHOME suite) pass; Codecov reports the changed lines fully covered. New unit tests cover the matrix cleanly.
Code notes
The change in OWNd/message/scenario.py is small, readable, and preserves backward compatibility for normal launches. Parsing of _what_param is safe (grammar guarantees digits). Tests in tests/test_who0_scenario_module_states.py are focused and sufficient.
Minor refinements recommended
-
Canonical names for 45/46
Prefer the documented meanings (unavailable/memory fullor similar) over the opaque"status 45"/"status 46". This matches the Legrand/OpenWebNet encyclopedia and avoids leaking raw protocol numbers into the publiceventproperty. -
Status-request guard
Frames such as*#0*11##or*#0##are dispatched toOWNScenarioEventwith_what is None. They currently fall into theelsebranch and produce the misleading log “Scenario None from control panel … has been launched.”
Mirror the pattern already used inOWNAuxEvent:if self._what is None: self._event = None elif self._what in _programming: ... else: self._event = "launched" ...
-
Docstring accuracy
programming_scenariois documented as applying to WHAT 40/41/42, but the code (and tests) also handle 45/46 with a parameter. Update the docstring.
Downstream impact
Non-breaking for existing MyHOME consumers of normal scenario launches. WHO 0 events are not currently bound to a dedicated entity platform (WHO 17 uses OWNSceneEvent), so the semantic cleanup mainly prevents future phantom launches if WHO 0 subscriptions are expanded.
Verdict
Approve with the minor refinements above.
The fix is protocol-accurate, evidence-backed, well-tested, and removes a long-standing misclassification. The two suggestions (naming + None guard) are non-blocking but improve correctness and user-facing clarity. Mergeable once branch-protection requirements are satisfied.
Review follow-ups on PR 90: WHAT 45 is 'unavailable' and WHAT 46 is 'memory full' (scenario central unit state indications) instead of the raw 'status 45' / 'status 46' strings, and a status request such as *#0*11## no longer produces a phantom 'Scenario None ... launched' event (scenario and event are None, as OWNAuxEvent does for *OpenWebNet-HA#9##). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Thanks for the audit. Both refinements are in 48210ab:
No other behaviour changed. |
fedem95
left a comment
There was a problem hiding this comment.
Approving. Checked against Legrand's WHO_0.pdf (v2.0.0, dated 01/10/2010): 40#X / 41#X start/end recording, 42 / 42#X erase all / one, 43/44 lock/unlock the scenario central unit, 45 unavailable, 46 memory full, all "only with F420 (03551)". The PDF defines 41 only as 41#X; the bare *0*41*<where>## is handled as "all scenarios" by libqtdevices (scenario_device.cpp, "use SCENARIO_ALL when receiving 041*## frames"), which is what this PR does. Suite passes locally (1245, test_metadata excluded for my env), CI green.
Notes:
- The MH201 trace.
issue_466/myhome_trace_MH201_all_2026-09-25T19-50-08.json(MH201, firmware 3.3.6) has*0*44*01##×61 and*0*41*01##×60 in two bursts, alternating every ~0.5 s: 19:46:10.783–19:46:25.173 (49 frames, starting at the beginning of the capture) and 19:48:50.390–19:49:08.799 (72 frames). There is no WHAT 40 and nothing naming an F420, so "during programming cycles" and "F420 scenario unit at address 01" aren't shown by the trace. For comparison, my MH201 (3.6.27) logged 3,212 received frames since 2026-10-01 22:06 and none was WHO 0. - Log wording for 43–46. It reads "all scenarios locked / unlocked / unavailable"; the PDF describes these as states of the scenario central unit.
- MyHOME doesn't reference
OWNScenarioEventincustom_components, so downstream only the log lines change.
Note
Status: Reviewed & Approved with minor refinements. Protocol-accurate, eliminates a long-standing logging and semantic misclassification bug, and verified against
libqtdevices, firmware binary replays (bt_luci), and authentic customer traces inMyHOME.fix(scenario): decode WHO 0 WHAT 40-46 as programming and lock states
Summary
Previously,
OWNScenarioEventinOWNd/message/scenario.pyblindly treated allWHATcodes as stored scenario identifiers (self._scenario = self._what). Consequently, scenario-module programming and unit lifecycle frames like*0*40#1*11##or*0*41*01##were logged and exposed as “Scenario 40/41 … has been launched.”This PR correctly reclassifies WHAT 40–46 as scenario-module programming, erase, lock, and operational status states:
40→programming started(optional#N= target scenario being recorded)41→programming stopped42→deleted(bare WHAT targets all scenarios;#Ntargets scenario N)43→locked(locks scenario central unit)44→unlocked(unlocks scenario central unit)45/46→status 45/status 46(central unit state indication: unavailable / memory full)#N) → targets “all scenarios”WHAT 1–31) remains unchanged withevent = "launched", preserving full backward compatibility forscenarioandcontrol_panel.New properties added to
OWNScenarioEvent:event: str | Noneandprogramming_scenario: int | None. For WHAT 40–46,scenariois set toNone. No command builders are added (consistent with the absence of a WHO 0 command class in OWNd).Protocol & Specification Grounding
Open Web Net Language, WHO = 0 SCENARIOS(F420 / 3456 module specification)1..16(17..20): Stored scenario invocation (F420 stores up to 16, 3456 up to 20)40#X/41#X: Start / end recording scenarioX(F420 only;X = 1..16)42/42#X: Erase all scenarios / erase scenarioX43/44: Lock / unlock scenario central unit45/46: Scenario central unit unavailable / scenario-memory full indicationfunctional/who-0-scenarios/README.md(ownkb:chunk:r000254,ownkb:chunk:r000257,ownkb:chunk:r000258)libqtdevicestagTS10_1_0_23(scenario_device.cpp:27-35, 93-156):START_PROG = 40,STOP_PROG = 41,DELETE = 42,LOCK = 43,UNLOCK = 44.parseFrametreats 40–44 as scenario module states and extractswhatArgN(0)as the target scenario.Empirical Evidence
1. Authentic On-Wire Customer Traces (
MyHOME)Customer bus captures in
MyHOME/tests/fixtures/traces/confirm that physical gateways emit these frames during scenario programming cycles:tests/fixtures/traces/issue_466/myhome_trace_MH201_all_2026-09-25T19-50-08.json01.*0*41*01##(End recording / programming stopped) repeatedly during programming cycles (e.g. timestamps1790365740.190162,1790365740.699999,1790365741.200069).*0*44*01##(Unlock scenario central unit) at address01(timestamps1790365739.700066,1790365740.199973)."Scenario 41 ... has been launched").2. Firmware Binary Translation (
bt_luci)Execution of the gateway translation daemon
bt_luci(identical binaries onMyHomeServer1,F454,F459,F461) demonstrates a strict opcode separation on the SCS bus:*0*1*11##11 00 14 01(Opcode14: Scenario Execution)1C(Scenario Module Management):*0*40#1*11##ACK11 00 1C 41(Record scenario 1)*0*41#1*11##ACK11 00 1C 61(Stop recording scenario 1)*0*42*11##ACK11 00 1C 00(Erase all scenarios)*0*42#1*11##ACK11 00 1C 21(Erase scenario 1)*0*43*11##ACK11 00 1C FF(Lock central unit)*0*44*11##ACK11 00 1C E0(Unlock central unit)*0*40*11##and*0*41*11##returnNACK(*#*0##) because recording requires a scenario parameter#N.Changes
OWNd/message/scenario.py:_programmingmapping table for WHAT 40–46.OWNScenarioEvent.__init__to populateself._eventandself._programming_scenario, settingself._scenario = None._what_paramtargeting scenario ID or all scenarios."Scenario module {where}: {target} {event}.").eventandprogramming_scenario.tests/test_who0_scenario_module_states.py:Downstream Consumer (
MyHOME)MyHOME(tests/test_gateway_messages.py:TestScenarioEvents). For standard scenario launches,msg.scenarioandmsg.control_panelremain identical.Verification
tests/test_who0_scenario_module_states.pypassed with 100% test coverage.pytest1246 passed, 1 skipped.