Repository navigation
fix(events): fire scope events only for frames that carry a state (#367) - #797
Conversation
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
c1a3aad to
a89a7a9
Compare
f555c2d to
2015c3e
Compare
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Requesting changes before undrafting/merging. Suppressing false on / stop scope bus events for #367 is the right direction, and CI is green on f555c2db, but a few behavioural, protocol, and test issues need to be addressed first:
LightingResyncManagerpolarity bug onWHAT 30/31+ brokenOWNdfollow-up path (gateway_events.py,gateway_resync.py):- Today
OWNLightingEvent.is_onisTrueonWHAT 30/31, so_schedule_resync(message)records_resync_expected_on[target] = True(gateway_resync.py:251-291). When aWHAT 31dim-down step dims members to0%(*1*0*<ptp>##),handle_ptp_echoand leading-echo filtering discard validOFFmember echoes (False != True), preventing sweep cancellation and forcing unnecessary*#1*#G##/*#1*A##polls. - Tomorrow, if
OWNdmakesOWNLightingEvent.is_onreturnNoneonWHAT 30/31as proposed in the PR body,_schedule_resync(message)will silently stop running altogether because bothgateway_events.py:309andgateway_resync.py:252gate resync onis_on is not None.
- Today
- Assumed-state
MyHOMELightGroup(and point-to-pointWHAT 30/31) still flipon(light_group.py,light.py,gateway_events.py):MyHOMELightGroup.handle_event(light_group.py:195-199) still readsmsg.is_on is Trueand flips anoffassumed-state group entity toonon*1*31*#5##(andhandle_ptp_echo/MyHOMELight.handle_eventtreat point-to-point*1*31*12##asis_on = True).
- WHO 2
DIMENSION 10scope status readbacks still firestopevents ¢ralized_shutter_stoptriggers (gateway_events.py):- Unlike WHO 1 (
is_not_dimension), the WHO 2 branch does not exclude dimension frames, so a stationaryDIMENSION 10status frame on a scope address (*#2*#5*10*10*50*0*0##/*#2*0*10*10*50*0*0##) hasis_opening=False, is_closing=Falseand still firesevent: "stop"(contradictingdocs/configuration/lights.mdand firingdevice_trigger.py'scentralized_shutter_stoponWHERE=0).
- Unlike WHO 1 (
- Protocol / Encyclopedia & PR description alignment:
- Encyclopedia citations (
## What does the Firmware Oracle / Encyclopedia say): Please cite the relevant Machine KB records rather than leaving the section empty:- WHO 1 relative dim steps:
ownkb:claim:c001418,c001419,c001421(functional/who-1-lighting/what.md—WHAT 30/31and30#LEVELS#SPEED/31#LEVELS#SPEEDare relative step commands, not ON/OFF states). - WHO 2 command
WHATtable:ownkb:claim:c002649&ownkb:chunk:r000468(functional/who-2-automation/what.md— advanced commandWHATs are10(Advanced Stop),11(Advanced Up),12(Advanced Down); the PR body currently omitsWHAT 10while listing13and14, which areDIMENSION 10shutter-status values rather than commandWHATs). - WHO 2 dimensions:
ownkb:claim:c002578/ownkb:chunk:r000453(functional/who-2-automation/dimensions.md— onlyDIMENSION 10and write-onlyDIMENSION 11exist for WHO 2;DIMENSION 12/13in the test/diagram are undocumented).
- WHO 1 relative dim steps:
- Traces & rebase (
## Where are the traces): Update#799 (open)->#799 (merged), rebase onto currentv2-phase1-architectureso #799's trace is in-tree (f555c2dbis parented on974306b5), and note existing physical WHO 2 scope captures intests/fixtures/traces/issue_445/(*2*10#001#1*0##,*2*11#100#001#1*0##,*2*12#100#001#1*0##) andtests/fixtures/traces/issue_645/(*2*0*100##).
- Encyclopedia citations (
Inline comments below cover the specific changes in each file.
|
All five points are on the branch (head 5c2ccc0, one commit per finding, still draft, on the current base). A second-opinion review of the diff found no remaining correctness issue; its one question (what a point-to-point WHAT 30/31 frame does after the new guard in
Full suite 4507 passed (base 4498), 100 % coverage, ruff / mypy-strict / HA standards / docs sync clean. |
5c2ccc0 to
99a12b3
Compare
|
All 17 checks green on 99a12b3 (review points above addressed); marking ready for review. |
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Thanks for addressing the five logic and test findings cleanly (cdd2f4b7..5c2ccc0f). Before merging, we need to tighten the evidence and protocol framing in the PR description, docs, and test comments—relying on "OWNd parser truth" where no physical capture exists is not enough to claim real bus behaviour in our evidence hierarchy (live capture > QEMU firmware oracle > Encyclopedia / official spec > OWNd parser).
Auditing the cited fixtures and replaying PR #797's exact frames through the QEMU firmware oracle (MH200N 010108, F461 020011, and MyHomeServer1 028206) turned up three issues (including one from my own earlier note on issue_645/):
1. *2*0*100## in tests/fixtures/traces/issue_645/ is not a physical Area 10 stop capture
- On #645 (
#issuecomment-6111193057),@Interstellar0verdriveposted a prose timing summary with redacted<WHERE>placeholders ("*#2*<WHERE>##to ten different covers from one script"), without a raw JSON attachment. myhome_trace_MyHomeServer1_cover_burst_latency_2026-10-11.jsonreconstructed those 10 redacted point-to-point covers as91, 92, …, 99, 100(91 + 9), accidentally turning the 10th point-to-point cover (0910/1001) into100(which inWHO 2is Area 10), and wrotetx: *#2*100##→rx: *2*0*100##as a status reply rather than a stop command.- How to back
*2*0*100##properly: Replayingdown *2*0*100##againstMyHomeServer1 028206inown-firmware-oracleproducesbus:$04B30A120A\r(Area0x0A= 10, opcode12 0A= Stop) andown:*2*0*100##on the event session (whereasdown *#2*100##emits$04B30A1500\r, to which real actuators reply on their individualAPLaddresses, never*2*0*100##). Please drop theissue_645/citation for Area 10 stop in## Where are the tracesandtests/test_gateway.pyand cite the firmware oracle instead.
2. WHO 1 scope dim steps (*1*30*#5##, *1*31*3##, *1*30*0##, *1*30#2#100*#5##) are provable via the Firmware Oracle (and live gateway echo)—not "parser truth only"
## What does the Firmware Oracle / Encyclopedia saycurrently states: "the oracle records what a gateway does with a request; this PR changes what MyHOME fires when such a frame is heard." In fact,own-firmware-oracletests bothdown(OpenWebNet → SCS bus + event session) andup(inbound wall-switch SCS telegram → OpenWebNet event session).- Replaying PR #797's WHO 1 scope dim-step frames against real
bt_lucibinaries in QEMU confirms both directions empirically:
| Direction & Stimulus | MH200N 010108 |
F461 020011 / MyHomeServer1 028206 |
|---|---|---|
down *1*30*#5## |
ack, bus:$04B5051203\r, own:*1*30*#5## |
bus:$004B5051203\r / $04B5051203\r |
down *1*31*3## |
ack, bus:$04B3031204\r, own:*1*31*3## |
bus:$004B3031204\r / $04B3031204\r |
down *1*30*0## |
ack, bus:$04B1001203\r, own:*1*30*0## |
bus:$004B1001203\r / $04B1001203\r |
down *1*30#2#100*#5## |
ack, bus:$06D50501320D030264\r, own:*1*30#2#100*#5## |
bus:$006D50501420D030264\r / $06D50501420D030264\r |
up $17B5051203\r (wall keypad → #5) |
— | MyHomeServer1: own:*1*30*#5## |
up $17B3031204\r (wall keypad → 3) |
— | MyHomeServer1: own:*1*31*3## |
up $17B1001203\r (wall keypad → 0) |
— | MyHomeServer1: own:*1*30*0## |
up $18D50501320D030264\r (ext step → #5) |
— | MyHomeServer1: own:*1*30#2#100*#5## |
- Even on the live
F461bench without a group dimmer,myhome.send_message*1*30*0##on a populated bus gets PIC-ACKed ($00800\r) by existing relays and echoed on the event session. Please either add/link a small suite inown-firmware-oracle(or cite these oracle verdicts + a live*1*30*0##check) rather than labelling WHO 1 dim steps "parser-truth only".
3. WHO 2 DIMENSION 10 on scope addresses (*#2*#5*10*…##, *#2*0*10*…##) is not normal actuator behaviour—frame it as a defensive parser guard
- Physical shutters never reply with a collective
WHERE(#5,3,0) onDIMENSION 10: a group or general scope has no single physical position (50%). Perownkb:chunk:r000458(functional/who-2-automation/dimensions.md) andfunctional/who-2-automation/addressing.md, scope requests/commands expand into oneDIMENSION 10frame per individual shutter (APL)—directly matchingtests/fixtures/traces/issue_445/(*2*10#001#1*0##followed by*#2*02*10*10*86*001*0##,*#2*03*10*10*76*001*0##, …, never*#2*0*10*…##). - What
coso(F461 020011/MyHomeServer1 028206) actually emits in QEMU:- Scope
DIMENSION 11Go-To-Level writes (down *#2*#5*#11#001*50##,down *#2*0*#11#001*50##) echo on the event session as*#2*#5*#11#001#1*50##and*#2*0*#11#001#1*50##. coso's inbound0x44 0x09decoder is address-mode generic (up $018D2000144090A3200\r→own:*#2*0*10*10*50*010*0##,up $018D5050144090A3200\r→own:*#2*#5*10*10*50*010*0##—note 3-digit priority010/001, not0), so only a synthetic/third-party bus frame orOWNdparsing a grammatically valid scopeDIMENSION 10frame reaches that branch.
- Scope
- Requested updates:
- Keep the
is_not_dimensionguard ingateway_events.py(it is a sound defensive invariant symmetrical with WHO 1), but updatedocs/configuration/covers.md,.github/release-notes/2.0.0b16.md,gateway_events.py, and the PR body / State Diagram so they do not present*#2*#5*10*10*50*…##/*#2*0*10*10*50*…##as routine physical shutter readbacks ("the shutter is stationary at 50 %" on#5/0). - Use
coso's 3-digit priority (001or010) in theDIMENSION 10test frames (*#2*#5*10*10*50*001*0##,*#2*0*10*10*50*001*0##) and includecoso's real event-sessionDIMENSION 11echoes (*#2*#5*#11#001#1*50##,*#2*0*#11#001#1*50##) intest_scope_events_fire_only_for_state_frames.
- Keep the
Inline comments below mark the specific places to adjust.
…enWebNet-HA#367) A WHAT 30/31 dim step to a group, area or general address parses with `is_on True`, so `myhome_{group,area,general}_light_event` fired as "on" although the frame only says the light changed one step. A WHO 2 dimension frame to a scope address (`*#2*michnovka#5*11*50##`, `*#2*3*12*1##`, `*#2*0*13*1##`) parses with `is_opening` / `is_closing` None, so the automation event fired as "stop" with no state behind it. Dim steps now fire no light event (the member re-sync still runs) and the WHO 2 branch fires only when the frame states a motion. On a physical F461 2.0.11, `*2*0*0##` (general stop) fires exactly one `myhome_general_automation_event` with `event: stop`, as before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…enWebNet-HA#367) OWNd 2.0.0b11 parses `*1*31*michnovka#5##` with `is_on True`, so the member re-sync recorded `expected_on = True` for the group. When the step dims the members down to 0 % their `*1*0*<ptp>##` echoes were discarded as the wrong polarity, the sweep could not be cancelled and `*#1*#G##` / `*#1*A##` went out although every member had spoken. A dim step commands no polarity: `_resync_expected_on[target]` is now `None` for WHAT 30/31 and both ON and OFF member echoes count towards cancelling the sweep. Resync eligibility no longer hangs on `is_on is not None` in the dispatcher or in `LightingResyncManager.schedule_resync`, so an OWNd that reports `is_on None` for a dim step keeps the member re-sync running instead of silently disabling it. Tests: `*1*31*michnovka#5##` / `*1*31*3##` schedule the `michnovka#5` and `3` sweeps with no expected polarity; OFF and mixed member echoes after a dim step cancel them; a WHAT 31 frame with `is_on None` still schedules. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ebNet-HA#367) OWNd 2.0.0b11 reports a dim step as `is_on True`, so an assumed-state `MyHOMELightGroup` flipped to on at `*1*31*michnovka#5##`, a point-to-point `MyHOMELight` did the same at `*1*31*12##`, and the dispatcher counted the point-to-point step as an ON member echo of a resync sweep. A step is a relative command: from 0 % it may well leave the light off. `MyHOMELightGroup.handle_event`, `MyHOMELight.handle_event` and the member-echo path in the dispatcher now skip `what in (30, 31)` until OWNd stops calling a step "on"; the entity keeps its last known state and a step neither seeds nor cancels a sweep. Tests: an assumed-state group and a dimmer stay unknown / off / on across `*1*30*…##`, `*1*31*…##` and `*1*30#2#100*…##`; point-to-point dim steps around `*1*1*michnovka#6##` leave its sweep armed until the members' own on/off echoes arrive. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…OpenWebNet-HA#367) The WHO 2 branch had no `is_not_dimension` guard like WHO 1, so a stationary DIMENSION 10 status readback on a scope address (`*#2*michnovka#5*10*10*50*0*0##`, `*#2*0*10*10*50*0*0##`, shutterStatus 10 at 50 %) parsed with `is_opening False, is_closing False` and fired `myhome_{group,area,general}_automation_event` with `event: stop`; on WHERE 0 that also ran the gateway's `centralized_shutter_stop` device trigger when nothing had stopped. A dimension frame is never an open/close/stop event; a WHAT outside the table (`is_opening` / `is_closing` None) states nothing either. Command frames `*2*0*0##`, `*2*10#001#1*0##` (advanced stop) fire as before. Tests: the two DIMENSION 10 scope readbacks fire nothing through the dispatcher, and the `centralized_shutter_stop` trigger attached to a real handler fires for `*2*0*0##` and `*2*10#001#1*0##` only. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…OpenWebNet-HA#367) The test fed `*#2*3*12*1##` and `*#2*0*13*1##` (WHO 2 has no DIMENSION 12 or 13) and `*#2*michnovka#5*11*50##` (read-reply syntax on the write-only DIMENSION 11). It now uses the frames a plant produces: DIMENSION 10 status readbacks on a group and the general address, one malformed DIMENSION 10 frame OWNd hands through unparsed, the advanced scope commands captured in `tests/fixtures/traces/issue_445/` (`*2*10#001#1*0##`, `*2*11#100#001#1*0##`, `*2*12#100#001#1*0##`) and `issue_645/` (`*2*0*100##`), and the parameterised and general WHO 1 dim steps `*1*30#2#100*michnovka#5##` / `*1*30*0##`. The exact fired list is pinned: the advanced commands fire general stop / open / close, the area stop and the on/off and open/stop frames fire as before, and nothing fires for the dim steps, the dimension readbacks and the unparsed frame. On the base branch the same test fails with six extra events. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The three `myhome_{general,area,group}_automation_event` events were
described only inside one long sentence of the lighting page, where a
cover user does not look. The cover docs now have a "Bus Events for
Scope Commands" section: addresses and payload, the command frames that
fire (`WHAT 0/1/2` and the advanced `WHAT 10/11/12` from the issue_445
captures), the frames that do not (DIMENSION 10 status readbacks,
dimension writes, a WHAT outside the table), the link to the gateway's
centralized shutter triggers and an example automation. The lighting
paragraph keeps its own three events and links across.
Release note: names `myhome_general_light_event` with the other two,
mentions the entity and re-sync fixes, and uses a valid DIMENSION 10
frame (`*#2*michnovka#5*10*10*50*0*0##`) as the WHO 2 example.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…enWebNet-HA#367) The `issue_645/` burst trace reconstructed ten redacted point-to-point status requests as WHERE 91..100, so its `*#2*100##` -> `*2*0*100##` pair is a status reply on a reconstructed address, not a captured area 10 stop. The scope-event test now cites the MyHomeServer1 028206 firmware oracle replay of `down *2*0*100##` (`bus:$04B30A120A`, `own:*2*0*100##`) for that frame instead; the frame and the pinned `myhome_area_automation_event` `stop` are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…et-HA#367) Live F461 2.0.11 capture of 2026-10-25 (plain OWNd master as sender, event session recorded): `*1*30*0##` (general dim up, no dimmer on the plant) was acknowledged in 69 ms, echoed on the event session and followed by every point re-reporting its own state twice over about two seconds (`*1*0*06##`, `*1*0*07##`, `*1*0*09##`, `*2*0*05##`); `*1*31*#1##` (group 1 dim down) was acknowledged in 62 ms, echoed, and followed by `*1*0*06##` only. The new test replays that sequence through `listening_loop` and pins that no scope light or automation event fires, that every frame reaches the point-to-point dispatch once in bus order, that both dim steps reach the member re-sync, and that the leading point reports satisfy the group 1 sweep before it is armed. On the base branch it fails with two extra events (`myhome_general_light_event` and `myhome_group_light_event` as `on`). The capture is on file and will be ingested by a trace PR. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…iant (OpenWebNet-HA#367) A physical shutter reports DIMENSION 10 on its own point-to-point address, never on a group, area or general WHERE (issue_445 captures: one `*#2*<APL>*10*...##` per actuator after a scope command). The `is_not_dimension` guard on the WHO 2 scope events is therefore a defensive parser invariant symmetrical with WHO 1, not the handling of a routine readback: the dispatcher comment, the cover docs and the b16 release note now say so, and no longer describe `*#2*michnovka#5*10*...##` as "the shutter is stationary at 50 %". The test frames use the 3-digit priority an F461 / MyHomeServer1 encodes (`*#2*michnovka#5*10*10*50*001*0##`, `*#2*0*10*10*50*001*0##`), and the real event-session echoes of a scope DIMENSION 11 level write (`*#2*michnovka#5*michnovka#11#001#1*50##`, `*#2*0*michnovka#11#001#1*50##`) join `test_scope_events_fire_only_for_state_frames` and the centralized stop trigger test, so both OWNd dimension paths are pinned: plain OWNd master parses the DIMENSION 11 echoes as `OWNAutomationCommand` (dimension 11, never reaching the WHO 2 event branch) and the scope DIMENSION 10 frames as `OWNAutomationEvent` (dimension 10, `is_opening` / `is_closing` False), which the guard keeps away from `stop`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
99a12b3 to
ef3417d
Compare
|
Thanks, all three are addressed on the branch (head ef3417d, rebased on 089fef5, one commit per point); the guard itself is unchanged.
4569 passed / 1 skipped (base 4559), 100 % coverage, ruff, mypy --strict, HA standards, docs sync and translations clean. |
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
LGTM — all behavioural, protocol, evidence-hierarchy, and documentation points from both review rounds are cleanly addressed on ef3417de.
Verified against OWNd==2.0.0b11 merged onto v2-phase1-architecture (5641f917): 4,691 passed, 100.0% statement coverage across all touched modules, mypy --strict, ruff, verify_ha_standards.py, and sync_documentation.py --check clean. Thanks for the live F461 dim-step verification as well!
d465db8
into
OpenWebNet-HA:v2-phase1-architecture
Requested by A
What is the problem
Two scope events still fire without a state behind them, the remaining part of #367 (the group/area on/off path was fixed earlier), and the review of this PR (5639737594) found three more places where a WHAT 30/31 dim step is taken for an "on":
*1*30*#5##,*1*31*3##,*1*30*0##,*1*30#2#100*#5##) parses withis_on Truein OWNd 2.0.0b11, somyhome_group_light_event/myhome_area_light_event/myhome_general_light_eventfired withevent: "on", an assumed-stateMyHOMELightGroupflipped to on, a point-to-pointMyHOMELightdid the same at*1*31*12##, and the member re-sync recordedexpected_on = True, so the members'*1*0*<ptp>##echoes (dimmed down to 0 %) were discarded as the wrong polarity and*#1*#G##/*#1*A##went out although every member had spoken. The frame only says the lights moved one step; from 0 % they may still be off.*#2*#5*10*10*50*001*0##,*#2*0*10*10*50*001*0##) parses withis_opening False, is_closing False, so the automation event fired withevent: "stop"and, on WHERE 0, ran the gateway'scentralized_shutter_stopdevice trigger. A physical shutter never sends such a frame: it reports DIMENSION 10 on its own point-to-point address (one*#2*<APL>*10*…##per actuator after a scope command,tests/fixtures/traces/issue_445/), so this is a defensive parser guard, symmetrical with the WHO 1 dimension guard, for a grammatically valid frame that only a third-party sender or a synthetic bus frame would put on the event session.An automation that mirrors a wall group onto an HA group therefore switched it on at every dim step. Refs #367 (closed).
The
what in (30, 31)checks in MyHOME are a bridge: the right place for "a dim step has no on/off state" is OWNd, whereOWNLightingEvent.is_onwould returnNonefor WHAT 30/31. Resync eligibility no longer hangs onis_on is not None, so when that OWNd change lands the member re-sync keeps running and only the bridge checks become dead code. That OWNd follow-up is named here, not opened.Based on the current
v2-phase1-architecture(089fef5, #812 in-tree), no conflict.What is the solution
gateway_events.py, WHO 1 branch: a dim step (what30/31) fires no on/off event; the group/area/general member re-sync runs whether OWNd reports the step'sis_onasTrueorNone. A point-to-point dim step (*1*31*12##) is no longer a member echo.gateway_resync.py: a dim step is swept with_resync_expected_on[target] = None, so both ON and OFF member echoes count towards cancelling the sweep;schedule_resyncacceptswhat in (30, 31)regardless ofis_on.light_group.py(assumed-state group) andlight.py(point-to-point light):handle_eventskipswhat in (30, 31); the entity keeps its last known state.gateway_events.py, WHO 2 branch: the sameis_not_dimensionguard as WHO 1, as a defensive parser invariant: a WHO 2 dimension frame on a scope address never fires a scope automation event, and a WHAT outside the table (is_opening/is_closingNone, e.g.*2*7*0##) states nothing either. WHAT 0/1/2 and the advanced WHAT 10 (stop) / 11 (up) / 12 (down) fireopen/close/stopas before. The DIMENSION 11 level echo a gateway really puts on the event session for a scope level write (*#2*#5*#11#001#1*50##) is parsed by OWNd as anOWNAutomationCommandand never reached this branch, before or after: it is pinned as a regression control, not as a behaviour change.covers.mdgets a "Bus Events for Scope Commands" section (addresses, payload, which frames fire and which do not, the centralized shutter triggers, an example);lights.mdkeeps its three light events and links across. The release-notes delta names all three light events and presents the WHO 2 change as the defensive guard it is.Companion PR: none (the OWNd-side
is_on = Nonefor WHAT 30/31 is a named follow-up, not opened).State Diagram
stateDiagram-v2 direction TB state "1. WHO 1 scope frame (group #G, area A, general 0)" as Who1 { [*] --> OnOff : *1*1*#6## / *1*0*#6##\nis_on True / False [*] --> DimStep : *1*30*#5## / *1*31*3## / *1*30*0## / *1*30#2#100*#5##\nrelative step, WHAT 30/31 - OWNd is_on True today, None later OnOff --> LightEvent : myhome_group / area / general_light_event on / off\nthen member resync with expected polarity DimStep --> LightEvent : before - fired as on, assumed group and ptp light flipped on DimStep --> ResyncOnly : after - no event, no entity flip,\nmember resync with expected polarity None - ON and OFF echoes cancel\nlive F461 - the points re-report *1*0*06## etc. as point events only } state "2. WHO 2 scope frame" as Who2 { [*] --> Command : *2*0*0## / *2*1*3## / *2*2*#5## / *2*0*100##\nadvanced *2*10#001#1*0## stop, *2*11#100#001#1*0## up, *2*12#100#001#1*0## down [*] --> Dimension : *#2*#5*#11#001#1*50## level echo (OWNAutomationCommand)\n*#2*#5*10*10*50*001*0## scope DIMENSION 10 - defensive guard,\nnot a readback a shutter sends (OWNAutomationEvent) Command --> AutomationEvent : myhome_group / area / general_automation_event\nopen / close / stop, unchanged - general drives centralized_shutter_* triggers Dimension --> AutomationEvent : before - scope DIMENSION 10 fired as stop Dimension --> NoEvent : after - no dimension frame fires anything }Hardware Verification
Live F461 2.0.11 plant (local Home Assistant, combined build of all our PRs, 2026-10-24; plain OWNd
masterf04d8aa as sender on 2026-10-25) and, for status reads, the MyHomeServer1 3.87.13 plant; captures in #799 (merged) and the 2026-10-25 capture on file.Live retest (2026-10-24)
Consensus label: confirmed for the tested case (combined build, F461 2.0.11, HA 2026.10.2).
myhome.send_message*2*0*0##while the cover was opening →tx *2*0*0##,rx *2*0*0##and exactly onemyhome_general_automation_event {"message": "*2*0*0##", "event": "stop", "where": "0"};*1*1*#1##→ onemyhome_group_light_eventon,*1*0*0##→ onemyhome_general_light_eventoff, and none for the resync reads*#1*#1##/*#1*00##. The advanced WHO 2 scope commands come from the issue_445 captures below.Live dim-step check (2026-10-25, F461 2.0.11, plain OWNd master as sender, event session recorded)
*1*30*0##(general dim up one level; the plant has no dimmer): acknowledged in 69 ms, echoed on the event session as*1*30*0##, and then every point re-reported its own state, twice over about 2 s:*1*0*06##,*1*0*07##,*1*0*09##,*2*0*05##.*1*31*#1##(group 1 dim down one level): acknowledged in 62 ms, echoed as*1*31*#1##, followed by*1*0*06##only.This is the "live
*1*30*0##check" asked for in review 5642831869: a populated bus acknowledges and echoes the scope dim step even without a group dimmer, and the per-point reports that follow it are exactly the frames this PR must keep as point events.tests/test_gateway.py::test_general_dim_step_point_reports_fire_no_scope_eventsreplays the sequence. The capture is on file and will be ingested as a fixture by a later trace PR (the WHO 2 dimension guard was not exercised live: a physical shutter does not send a scope DIMENSION 10 frame, see below).How is it tested
Every finding of the reviews has its own commit, each with tests that fail before it and pass after it (plain upstream OWNd
masterf04d8aa onPYTHONPATH, HA 2026.10.2, Python 3.14):tests/test_gateway.py::test_scope_events_fire_only_for_state_frames: eighteen real frames throughlistening_loop(WHO 1 dim steps plain / parameterised / general; the two DIMENSION 11 scope level echoes*#2*#5*#11#001#1*50##/*#2*0*#11#001#1*50##, pinned asOWNAutomationCommanddimension 11; the two scope DIMENSION 10 frames in the gateway's 3-digit priority format*#2*#5*10*10*50*001*0##/*#2*0*10*10*50*001*0##, pinned asOWNAutomationEventdimension 10; one malformed DIMENSION 10 frame OWNd hands through unparsed; one WHAT outside the table*2*7*0##pinned asis_opening/is_closingNone; the issue_445 advanced general stop / up / down; the area 10 stop*2*0*100##; and on/off, stop/open frames). The exact fired list is pinned: eight events, none for the dim steps, the dimension frames, the unparsed frame or the unknown WHAT;_resync_timersholds#5,#6,3with_resync_expected_on == {"#5": None, "3": None, "#6": False}. On the base branch it fails with seven extra events (four dim steps, two scope DIMENSION 10 frames and the unknown WHAT, all ason/stop).tests/test_gateway.py::test_general_dim_step_point_reports_fire_no_scope_events: the live 2026-10-25 sequence above (eleven frames) throughlistening_loop; pins that no scope light or automation event fires, that every frame reaches the point-to-point dispatch once in bus order, that both dim steps reach_schedule_resync, and that the leading point reports (06, 07, 09) satisfy the group 1 sweep before it is armed. On the base branch it fails with two extra events (myhome_general_light_eventandmyhome_group_light_eventason).tests/test_issue_368_resync.py:test_dim_step_sweeps_members_with_unknown_polarity(*1*31*#5##+ two OFF echoes,*1*30*#6##+ one ON and one OFF echo,*1*31*3##+ one OFF echo all cancel their sweep; silence still sends*#1*#7##),test_dim_step_resync_does_not_depend_on_is_on(a WHAT 31 frame withis_on Nonestill schedules),test_point_to_point_dim_steps_are_not_member_echoes(ptp dim steps neither seed nor cancel#6).tests/test_issue_368_groups.py::test_dim_step_does_not_flip_assumed_group,tests/test_component_light.py::test_dim_step_does_not_flip_point_to_point_light(x3 frames): unknown / off / on survive*1*30*…##,*1*31*…##,*1*30#2#100*…##.tests/test_device_trigger.py::test_centralized_shutter_stop_ignores_dimension_10_status: through a realMyHOMEGatewayHandler,*#2*0*10*10*50*001*0##/*#2*#5*10*10*50*001*0##and the DIMENSION 11 echoes*#2*0*#11#001#1*50##/*#2*#5*#11#001#1*50##do not run thecentralized_shutter_stoptrigger;*2*0*0##and*2*10#001#1*0##do.-n 4: 4569 passed, 1 skipped (base 089fef5, same setup: 4559 passed, 1 skipped).scripts/verify_ownd_coverage.py: 100 % statement coverage (16,075 / 16,075).ruff check .,scripts/typing_ratchet.py(mypy --strict, 0 errors),scripts/verify_ha_standards.py,scripts/sync_documentation.py --check(links and anchors validated),scripts/manage_translations.py checkpass.Where are the traces
tests/fixtures/traces/f461_live_2026-10-23/…json,run6_event_session_ha_fixed_integration;tests/test_f461_live_2026_10_23_trace.py::test_general_stop_was_seen_exactly_once*2*0*0##appears exactly once (one general stop from Home Assistant); a command frame, so it fires exactly onemyhome_general_automation_eventstopbefore and after this PR.tests/fixtures/traces/issue_445/(LN4660M2 centralized button, 2026-09-26 / 09-28)*2*10#001#1*0##(advanced stop),*2*11#100#001#1*0##(advanced up),*2*12#100#001#1*0##(advanced down) on WHERE 0: command frames, fire generalstop/open/closein the pinned test; the same captures carry the per-actuator DIMENSION 10 reports (*#2*<APL>*10*…##, one per shutter, never on a scope address) that this PR keeps away from the scope events.tests/fixtures/traces/issue_645/is not a capture of it: its burst trace reconstructed ten redacted point-to-point status requests as WHERE 91..100, so its*#2*100##→*2*0*100##pair is a status reply on a reconstructed address (the earlier revision of this description cited it wrongly).*2*0*100##is backed by the firmware oracle verdict below (reviewer's QEMU replay): a command frame to area 10, firesmyhome_area_automation_eventstop.*1*30*0##and*1*31*#1##acknowledged and echoed on the event session, followed by per-point state reports; replayed bytest_general_dim_step_point_reports_fire_no_scope_events. The group / area / parameterised variants (*1*30*#5##,*1*31*3##,*1*30#2#100*#5##) are backed by the firmware oracle verdicts below.*#2*<scope>*10*…##. The DIMENSION 11 scope level echoes*#2*#5*#11#001#1*50##/*#2*0*#11#001#1*50##and the 3-digit priority format of the DIMENSION 10 test frames come from the firmware oracle verdicts below.is_not_dimensionguard is a defensive parser invariant; the test frames are what the gateway firmware emits (DIMENSION 11) or would parse (DIMENSION 10), fed throughOWNMessage.parseandlistening_loop.What does the Firmware Oracle / Encyclopedia say
own-firmware-oracletests both directions:down(OpenWebNet request → SCS bus frame + event-session echo) andup(inbound SCS telegram → event-session frame).results/<P>/<V>/**/*.tsvat52d354b(main) holds no published row for*1*30*0##,*1*30*#5##,*1*31*3##,*1*30#2#100*#5##,*2*0*100##,*#2*#5*#11#001*50##or any WHO 2 scope dimension frame (checked by grep over all 636 TSVs). The verdicts below are therefore the reviewer's own QEMU replays, quoted from review 5642831869 (GreenGrassBlueOcean, 2026-10-25) against the realbt_luci/cosobinaries, and are cited as such; a published suite would supersede this quote.WHO 1 scope dim steps (reviewer's table):
MH200N 010108F461 020011/MyHomeServer1 028206down *1*30*#5##ack,bus:$04B5051203,own:*1*30*#5##bus:$004B5051203/$04B5051203down *1*31*3##ack,bus:$04B3031204,own:*1*31*3##bus:$004B3031204/$04B3031204down *1*30*0##ack,bus:$04B1001203,own:*1*30*0##bus:$004B1001203/$04B1001203down *1*30#2#100*#5##ack,bus:$06D50501320D030264,own:*1*30#2#100*#5##bus:$006D50501420D030264/$06D50501420D030264up $17B5051203(wall keypad →#5)own:*1*30*#5##up $17B3031204(wall keypad →3)own:*1*31*3##up $17B1001203(wall keypad →0)own:*1*30*0##up $18D50501320D030264(ext step →#5)own:*1*30#2#100*#5##So a scope dim step is a real bus frame in both directions, a wall keypad produces exactly the frames this PR handles, and the live F461 capture above confirms the
downrow for*1*30*0##on a physical plant (ACK 69 ms, event-session echo, per-point re-reports). None of these frames carries an on/off state, which is why they fire no light event and sweep their members with an unknown polarity.WHO 2 area stop (reviewer's replay on
MyHomeServer1 028206):down *2*0*100##→bus:$04B30A120A(area0x0A= 10, opcode12 0A= stop) andown:*2*0*100##on the event session, whereasdown *#2*100##emits$04B30A1500, to which actuators reply on their individual APL addresses, never with*2*0*100##. This is the citation for the*2*0*100##test frame.WHO 2 scope dimension frames (reviewer's replay on
coso,F461 020011/MyHomeServer1 028206): the scope DIMENSION 11 level writesdown *#2*#5*#11#001*50##/down *#2*0*#11#001*50##echo on the event session as*#2*#5*#11#001#1*50##/*#2*0*#11#001#1*50##(now in the tests; plain OWNd master parses them asOWNAutomationCommand, dimension 11).coso's inbound0x44 0x09decoder is address-mode generic (up $018D5050144090A3200→own:*#2*#5*10*10*50*010*0##, 3-digit priority), so a scope DIMENSION 10 frame reaches the guard only from a synthetic or third-party bus frame; the test frames use that 3-digit priority (001).The Encyclopedia settles the protocol facts the PR relies on:
ownkb:claim:c001418,ownkb:claim:c001419,ownkb:claim:c001421(functional/who-1-lighting/what.md): WHAT 30 / 31 and30#LEVELS#SPEED/31#LEVELS#SPEEDare relative step commands, not ON/OFF states.ownkb:claim:c002649andownkb:chunk:r000468(functional/who-2-automation/what.md): the advanced commands are WHAT 10 (Advanced Stop), 11 (Advanced Up), 12 (Advanced Down); the diagram and the test use exactly these.ownkb:claim:c002578/ownkb:chunk:r000453,ownkb:chunk:r000458(functional/who-2-automation/dimensions.md,addressing.md): WHO 2 has DIMENSION 10 (shutter status) and the write-only DIMENSION 11 (go to level); a scope request or command expands into one DIMENSION 10 frame per individual shutter (APL), which is why a scope DIMENSION 10 frame is a parser guard and not a readback.Review follow-up (review 5639737594)
LightingResyncManagerpolarity on WHAT 30/31 and theis_on is not Nonecoupling: 6469570 (gateway_events.py,gateway_resync.py, tests intest_issue_368_resync.py,test_gateway.py).MyHOMELightGroup, point-to-pointMyHOMELightandhandle_ptp_echoflipping on at WHAT 30/31: dfc1034 (light_group.py,light.py,gateway_events.py, tests intest_issue_368_groups.py,test_component_light.py,test_issue_368_resync.py).stop/centralized_shutter_stop: 0e9ad84 (gateway_events.py, tests intest_gateway.py,test_device_trigger.py).test_gateway.py, real frames from the Encyclopedia tables and the issue_445 captures).covers.md,myhome_general_light_event, valid DIMENSION 10 example): 828468b. Encyclopedia citations,#799 (merged)and the physical WHO 2 scope captures: this description.Review follow-up (review 5642831869)
*2*0*100##is not an issue_645 capture: 9ebde27 drops theissue_645/citation fromtest_gateway.py(docstring and frame comment); this description's "Where are the traces" row says what the fixture really holds and cites the MyHomeServer1 028206 oracle replay (down *2*0*100##→bus:$04B30A120A,own:*2*0*100##) as the reviewer's QEMU verdict, no TSV row being published yet.test_general_dim_step_point_reports_fire_no_scope_events, the replay of the live F461 2.0.11*1*30*0##/*1*31*#1##capture of 2026-10-25 (capture on file, fixture to follow in a trace PR); "What does the Firmware Oracle / Encyclopedia say" now states that the oracle tests both directions and quotes the reviewer's down/up table; every "parser truth only" wording is gone from this description (none was in the tree).gateway_events.pycomment, thecovers.mdbullets and the b16 release note (no "the shutter is stationary at 50 %" on a scope address; the DIMENSION 11 echo is named as what a gateway really emits and as a frame that never fired), switches the test frames to the 3-digit priority (*#2*#5*10*10*50*001*0##,*#2*0*10*10*50*001*0##) intest_gateway.pyandtest_device_trigger.py, adds the real DIMENSION 11 echoes*#2*#5*#11#001#1*50##/*#2*0*#11#001#1*50##to both tests with their OWNd types pinned (OWNAutomationCommanddimension 11 vsOWNAutomationEventdimension 10), and adds*2*7*0##so the "WHAT outside the table" case is pinned by a real frame. The guard itself is unchanged.Checklist
What is the problem,What is the solution,State Diagram,How is it tested,Where are the traces,What does the Firmware Oracle / Encyclopedia say).handle_eventonly, no new platform code.pytest,ruff, andscripts/verify_ha_standards.pypass.🤖 Generated with Claude Code