Skip to content

fix(scenario): decode WHO 0 WHAT 40-46 as programming and lock states - #90

Merged
GreenGrassBlueOcean merged 2 commits into
OpenWebNet-HA:masterfrom
gdluck:fix/who0-scenario-module-states
Oct 8, 2026
Merged

GreenGrassBlueOcean merged 2 commits into
OpenWebNet-HA:masterfrom
gdluck:fix/who0-scenario-module-states

Conversation

@gdluck

@gdluck gdluck commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

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 in MyHOME.

fix(scenario): decode WHO 0 WHAT 40-46 as programming and lock states

Summary

Previously, OWNScenarioEvent in OWNd/message/scenario.py blindly treated all WHAT codes 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 stopped
  • 42 → deleted (bare WHAT targets all scenarios; #N targets 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)
  • Bare WHAT (no #N) → targets “all scenarios”
  • Standard scenario execution (WHAT 1–31) remains unchanged with event = "launched", preserving full backward compatibility for scenario and control_panel.

New properties added to OWNScenarioEvent: event: str | None and programming_scenario: int | None. For WHAT 40–46, scenario is set to None. No command builders are added (consistent with the absence of a WHO 0 command class in OWNd).

Protocol & Specification Grounding

  • Legrand Public Specification: Open Web Net Language, WHO = 0 SCENARIOS (F420 / 3456 module specification)
    • WHAT 1..16 (17..20): Stored scenario invocation (F420 stores up to 16, 3456 up to 20)
    • WHAT 40#X / 41#X: Start / end recording scenario X (F420 only; X = 1..16)
    • WHAT 42 / 42#X: Erase all scenarios / erase scenario X
    • WHAT 43 / 44: Lock / unlock scenario central unit
    • WHAT 45 / 46: Scenario central unit unavailable / scenario-memory full indication
  • OpenWebNet Encyclopedia Machine KB: functional/who-0-scenarios/README.md (ownkb:chunk:r000254, ownkb:chunk:r000257, ownkb:chunk:r000258)
  • BTicino Touch Screen Client: libqtdevices tag TS10_1_0_23 (scenario_device.cpp:27-35, 93-156):
    • Explicit enums START_PROG = 40, STOP_PROG = 41, DELETE = 42, LOCK = 43, UNLOCK = 44.
    • parseFrame treats 40–44 as scenario module states and extracts whatArgN(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:

  • MH201 + F420 Plant Capture: tests/fixtures/traces/issue_466/myhome_trace_MH201_all_2026-09-25T19-50-08.json
    • Authentic capture from a physical MH201 gateway on a live installation with an F420 scenario unit at address 01.
    • Emits *0*41*01## (End recording / programming stopped) repeatedly during programming cycles (e.g. timestamps 1790365740.190162, 1790365740.699999, 1790365741.200069).
    • Emits *0*44*01## (Unlock scenario central unit) at address 01 (timestamps 1790365739.700066, 1790365740.199973).
    • Under existing OWNd, replaying or processing this trace produced bogus phantom scenario launch events ("Scenario 41 ... has been launched").

2. Firmware Binary Translation (bt_luci)

Execution of the gateway translation daemon bt_luci (identical binaries on MyHomeServer1, F454, F459, F461) demonstrates a strict opcode separation on the SCS bus:

  • Scenario Execution: *0*1*11## $\rightarrow$ SCS telegram 11 00 14 01 (Opcode 14: Scenario Execution)
  • Scenario Management: WHAT 40..44 frames translate to SCS Opcode 1C (Scenario Module Management):
    • *0*40#1*11## $\rightarrow$ ACK $\rightarrow$ SCS 11 00 1C 41 (Record scenario 1)
    • *0*41#1*11## $\rightarrow$ ACK $\rightarrow$ SCS 11 00 1C 61 (Stop recording scenario 1)
    • *0*42*11## $\rightarrow$ ACK $\rightarrow$ SCS 11 00 1C 00 (Erase all scenarios)
    • *0*42#1*11## $\rightarrow$ ACK $\rightarrow$ SCS 11 00 1C 21 (Erase scenario 1)
    • *0*43*11## $\rightarrow$ ACK $\rightarrow$ SCS 11 00 1C FF (Lock central unit)
    • *0*44*11## $\rightarrow$ ACK $\rightarrow$ SCS 11 00 1C E0 (Unlock central unit)
  • Boundary Validation: *0*40*11## and *0*41*11## return NACK (*#*0##) because recording requires a scenario parameter #N.

Changes

  • OWNd/message/scenario.py:
    • Added _programming mapping table for WHAT 40–46.
    • Reclassified WHAT 40–46 in OWNScenarioEvent.__init__ to populate self._event and self._programming_scenario, setting self._scenario = None.
    • Added safe numeric extraction for _what_param targeting scenario ID or all scenarios.
    • Formatted human-readable log to indicate scenario module status ("Scenario module {where}: {target} {event}.").
    • Added public properties: event and programming_scenario.
  • tests/test_who0_scenario_module_states.py:
    • Comprehensive unit test matrix covering WHAT 40–46 with and without parameters, launch fallback (WHAT 1..31), and property assertions.

Downstream Consumer (MyHOME)

  • Backward Compatibility: Fully backward compatible with MyHOME (tests/test_gateway_messages.py:TestScenarioEvents). For standard scenario launches, msg.scenario and msg.control_panel remain identical.
  • Safety: Prevents phantom scenario launches in customer installations containing F420 / 3456 programming modules.

Verification

  • Unit tests: tests/test_who0_scenario_module_states.py passed with 100% test coverage.
  • Full test suite: pytest 1246 passed, 1 skipped.
  • Codecov: 100% coverage on modified lines.

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
@gdluck
gdluck force-pushed the fix/who0-scenario-module-states branch from 6179e02 to 05b73e2 Compare October 7, 2026 17:22
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@GreenGrassBlueOcean GreenGrassBlueOcean left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 unchanged scenario/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_23 scenario_device.cpp (enums START_PROG=40 … UNLOCK=44 and parseFrame logic).
  • Firmware binary replays of bt_luci (MyHomeServer1 / F454 / F459): distinct SCS opcode 1C telegrams vs. the normal scenario-execution opcode 14; 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

  1. Canonical names for 45/46
    Prefer the documented meanings (unavailable / memory full or similar) over the opaque "status 45" / "status 46". This matches the Legrand/OpenWebNet encyclopedia and avoids leaking raw protocol numbers into the public event property.

  2. Status-request guard
    Frames such as *#0*11## or *#0## are dispatched to OWNScenarioEvent with _what is None. They currently fall into the else branch and produce the misleading log “Scenario None from control panel … has been launched.”
    Mirror the pattern already used in OWNAuxEvent:

    if self._what is None:
        self._event = None
    elif self._what in _programming:
        ...
    else:
        self._event = "launched"
        ...
  3. Docstring accuracy
    programming_scenario is 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>
@gdluck

gdluck commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the audit. Both refinements are in 48210ab:

  • A: WHAT 45 is now unavailable and WHAT 46 memory full (the test expectations follow).
  • B: a status request (*#0*W##, *#0##) sets scenario and event to None and keeps the raw frame as the log, the same pattern as OWNAuxEvent for *#9##; two test cases cover it.

No other behaviour changed.

@fedem95 fedem95 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 OWNScenarioEvent in custom_components, so downstream only the log lines change.

@GreenGrassBlueOcean
GreenGrassBlueOcean merged commit 7e9bdbc into OpenWebNet-HA:master Oct 8, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants