Repository navigation
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.
| is_dim_step = getattr(message, "what", None) in (30, 31) | ||
|
|
||
| if message.is_on is not None and is_not_dimension: | ||
| event = "on" if message.is_on else "off" | ||
| if message.is_general: | ||
| if is_dim_step: | ||
| pass # no on/off event; the members are still resynced below |
There was a problem hiding this comment.
Two issues in the WHO 1 dim-step handling and one entity-level gap:
-
False
expected_on = Truepolarity inLightingResyncManagertoday:
InOWNd==2.0.0b11,OWNLightingEvent("*1*31*#5##").is_onisTrue. Passingmessageintoself.handler._schedule_resync(message)at line 341 storesself._resync_expected_on[target] = True(gateway_resync.py:251-291). When aWHAT 31dim-down step dims member actuators down to0%, their spontaneous*1*0*<ptp>##echoes (msg_on = False) failmsg_on != expected_onin both leading-echo filtering (gateway_resync.py:269, 290) andhandle_ptp_echo(gateway_resync.py:157-178), discarding validOFFechoes and forcing redundant*#1*#G##/*#1*A##polls. ForWHAT 30/31, target polarity is unknown (_resync_expected_on[target]should beNone) so bothONandOFFmember echoes count toward cancelling the sweep. -
OWNdfollow-up would silently disable member re-sync:
The PR body notes that onceOWNdreturnsis_on = NoneforWHAT 30/31, "MyHOME's existingis_on is not Noneguard would do the rest ... once it lands the bridge can go." Because_schedule_resync(message)is nested insideif message.is_on is not None and is_not_dimension:here andLightingResyncManager.schedule_resync(gateway_resync.py:252) returns early whenmsg_is_on is None, changingOWNdtois_on = Nonewould silently stop member re-sync from running on group/area/general dim steps. Please decouple resync eligibility fromis_on is not Nonein bothgateway_events.pyandgateway_resync.pynow ((message.is_on is not None or is_dim_step) and is_not_dimension, and allowwhat in (30, 31)inschedule_resync). -
Assumed-state
MyHOMELightGroup(and point-to-pointWHAT 30/31) still flipon:
Messages are dispatched to entities at line 266 before reaching this block. On a group configured withoutmembers:(assumed_state: true),MyHOMELightGroup.handle_event(light_group.py:195-199) still checksif msg.is_on is True: self._attr_is_on = True, so*1*31*#5##still flips anoffgroup entity toon. Likewise, point-to-point*1*31*12##is still treated asis_on = Trueinhandle_ptp_echo(line 295) andMyHOMELight.handle_event(light.py:1058). Please guardwhat not in (30, 31)in those paths untilOWNdhandlesWHAT 30/31directly.
| elif ( | ||
| isinstance(message, OWNAutomationEvent) | ||
| and self._is_active_for_who(2) | ||
| # Only a frame that states the motion is an open/close/stop event: a | ||
| # dimension frame with no known state is none of them (#367). | ||
| and (message.is_opening is not None or message.is_closing is not None) | ||
| ): |
There was a problem hiding this comment.
Missing is_not_dimension symmetry with WHO 1:
In OWNd, a stationary WHO 2 DIMENSION 10 status frame on a scope address (e.g. *#2*#5*10*10*50*0*0## or *#2*0*10*10*50*0*0##, shutterStatus = 10 at 50%) parses with is_opening = False, is_closing = False. Without is_not_dimension, that status readback enters the else: event = "stop" branch and fires myhome_{group,area,general}_automation_event with event: "stop" — which on WHERE=0 also triggers the gateway's centralized_shutter_stop device trigger (device_trigger.py:262-283) when nothing was stopped, and contradicts docs/configuration/lights.md:98 ("which fire for open / close / stop frames only").
Please add the same is_not_dimension guard used on WHO 1 (not (isinstance(dim, int) and not isinstance(dim, bool))) so dimension status readbacks never fire command scope events.
| frames = [ | ||
| "*1*30*#5##", # group 5 dim up one step | ||
| "*1*31*3##", # area 3 dim down one step | ||
| "*#2*#5*11*50##", # WHO 2 dimension 11 to group 5 | ||
| "*#2*3*12*1##", # WHO 2 dimension 12 to area 3 | ||
| "*#2*0*13*1##", # WHO 2 dimension 13, general | ||
| "*1*1*#6##", # group 6 on: a real state, still fired | ||
| "*1*0*#6##", # group 6 off | ||
| "*2*0*#5##", # group 5 stop: a real state | ||
| "*2*1*3##", # area 3 open | ||
| ] | ||
| with patch("custom_components.myhome.gateway.OWNEventSession") as mock_session_class: | ||
| mock_session = MagicMock() | ||
| mock_session.connect = AsyncMock(return_value={"Success": True}) | ||
| mock_session.get_next = AsyncMock(side_effect=[OWNMessage.parse(f) for f in frames] + [asyncio.CancelledError()]) | ||
| mock_session_class.return_value = mock_session | ||
| gateway_handler.send_status_request = AsyncMock() | ||
| try: | ||
| await gateway_handler.listening_loop() | ||
| except asyncio.CancelledError: | ||
| pass | ||
|
|
||
| fired = [ | ||
| (call.args[0], call.args[1].get("message"), call.args[1].get("event")) | ||
| for call in gateway_handler.hass.bus.async_fire.call_args_list | ||
| if call.args[0].endswith(("_light_event", "_automation_event")) | ||
| ] | ||
| assert fired == [ | ||
| ("myhome_group_light_event", "*1*1*#6##", "on"), | ||
| ("myhome_group_light_event", "*1*0*#6##", "off"), | ||
| ("myhome_group_automation_event", "*2*0*#5##", "stop"), | ||
| ("myhome_area_automation_event", "*2*1*3##", "open"), | ||
| ] |
There was a problem hiding this comment.
Two test-quality gaps here:
-
Use real WHO 2 protocol frames instead of non-existent dimensions
12/13:
Per the OpenWebNet Encyclopedia (ownkb:claim:c002578,ownkb:chunk:r000453), WHO 2 defines onlyDIMENSION 10(Shutter status) andDIMENSION 11(Go to level, write-only).*#2*3*12*1##(DIMENSION 12) and*#2*0*13*1##(DIMENSION 13) are undocumented WHO 2 dimensions, and*#2*#5*11*50##is read-reply syntax on write-onlyDIMENSION 11(an actual write*#2*#5*#11#001*50##parses asOWNAutomationCommand). Please test real WHO 2 frames: stationaryDIMENSION 10status frames (*#2*#5*10*10*50*0*0##,*#2*0*10*10*50*0*0##), an unparsed/malformed dimension frame, advanced WHO 2 scope commands (*2*10#001#1*0##,*2*11#100#001#1*0##fromtests/fixtures/traces/issue_445/), and parameterized/general WHO 1 dim steps (*1*30#2#100*#5##,*1*30*0##). -
Assert member re-sync scheduling and
OFFecho cancellation onWHAT 31:
gateway_handler.send_status_requestis mocked on line 541 and the commit message notes "the member re-sync still runs", yet nothing asserts that_resync_manager._resync_timerswas scheduled for#5and3, or thatOFFmember echoes (*1*0*<ptp>##) after aWHAT 31dim-down step cancel the sweep cleanly.
|
|
||
| ### Synchronizing Physical Wall Switches (SCS Group Buttons) | ||
| If you have physical BTicino wall switches configured to trigger an SCS group (e.g. `WHERE = #1`), the integration automatically dispatches a bus event named `myhome_group_light_event` whenever group frames are intercepted on the SCS bus. You can keep your Home Assistant Light Group perfectly synchronized with a simple automation: | ||
| If you have physical BTicino wall switches configured to trigger an SCS group (e.g. `WHERE = #1`), the integration automatically dispatches a bus event named `myhome_group_light_event` whenever a group frame with a state (`on` / `off`) is intercepted on the SCS bus. A dim step to the group (`*1*30*#1##` / `*1*31*#1##`) fires no event, as it does not say whether the group is on; the same holds for the area and general events and for the WHO 2 automation events (`myhome_group_automation_event`, `myhome_area_automation_event`, `myhome_general_automation_event`), which fire for `open` / `close` / `stop` frames only. You can keep your Home Assistant Light Group perfectly synchronized with a simple automation: |
There was a problem hiding this comment.
Documenting WHO 2 shutter scope events (myhome_group_automation_event, myhome_area_automation_event, myhome_general_automation_event) solely inside a single long sentence under Lights & Dimmers (WHO = 1) makes them hard to find for cover users (docs/configuration/covers.md currently has no mention of them). Please document the WHO 2 automation scope events in docs/configuration/covers.md as well (keeping the lighting paragraph focused or cross-linked).
| - **`myhome.send_message` and `myhome.sync_time` reject unknown keys.** A misspelt key such as `gw_mac` or a missing `message` raises a validation error instead of sending the frame to the default gateway; both services get text selectors in the visual editor (#189). | ||
| - **Climate zones show the setpoint in force (dimension 12).** When the thermostat regulates to another setpoint than the commanded one (dimension 14; 23.5 in force while 21.0 commanded on an F461), the entity and `local_target_temperature` now show the one in force instead of the commanded value plus the knob offset. Known limit: once a zone has reported dimension 12, a dimension 14 that is not the echo of a Home Assistant write does not move the slider until the next dimension 12 report or poll (see Known Limitations). | ||
| - **`myhome.sync_time` refuses time zones with a half-hour offset** (e.g. `Asia/Kolkata`, UTC+5:30) with a translated error. The WHO 13 time-zone field carries whole hours only; b15 (on OWNd 2.0.0b10) set the gateway clock with a truncated offset without saying so (#764). | ||
| - **Group, area and general bus events only for frames that carry a state.** A dim step (`*1*30*#1##` / `*1*31*#1##`) no longer fires `myhome_group_light_event` / `myhome_area_light_event` as `on`, and a WHO 2 dimension frame to a group, area or general address (`*#2*#5*11*50##`) no longer fires an automation event as `stop`; on/off, open/close/stop frames fire as before (#367). |
There was a problem hiding this comment.
The bullet title covers Group, area and general bus events and gateway_events.py gates myhome_general_light_event (*1*30*0## / *1*31*0##) too, but the text here only lists myhome_group_light_event / myhome_area_light_event. Please include myhome_general_light_event and replace the synthetic *#2*#5*11*50## example with a valid WHO 2 DIMENSION 10 frame (e.g. *#2*#5*10*10*50*0*0##).
|
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. |
…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>
5c2ccc0 to
99a12b3
Compare
|
All 17 checks green on 99a12b3 (review points above addressed); marking ready for review. |
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#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*0*0##,*#2*0*10*10*50*0*0##: shutterStatus 10, stationary at 50 %) 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 when nothing had stopped.An automation that mirrors a wall group onto an HA group therefore switched it on at every dim step and saw stops that never happened. 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(d8bac15, #799 and #801 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, so a DIMENSION 10 status readback never fires a scope automation event; a WHAT outside the table (is_opening/is_closingNone) states nothing either. WHAT 0/1/2 and the advanced WHAT 10 (stop) / 11 (up) / 12 (down) fireopen/close/stopas before.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 uses*#2*#5*10*10*50*0*0##as the WHO 2 example.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#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 } state "2. WHO 2 scope frame" as Who2 { [*] --> Command : *2*0*0## / *2*1*3## / *2*2*#5##\nadvanced *2*10#001#1*0## stop, *2*11#100#001#1*0## up, *2*12#100#001#1*0## down [*] --> Dimension : *#2*#5*10*10*50*0*0## / *#2*0*10*10*50*0*0##\nDIMENSION 10 status readback, stationary Command --> AutomationEvent : myhome_group / area / general_automation_event\nopen / close / stop, unchanged - general drives centralized_shutter_* triggers Dimension --> AutomationEvent : before - fired as stop Dimension --> NoEvent : after - nothing fired }Hardware Verification
Live F461 2.0.11 plant (local Home Assistant, combined build of all our PRs, 2026-10-24) and, for status reads, the MyHomeServer1 3.87.13 plant; captures in #799 (merged).
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 dim-step and WHO 2 dimension cases were not exercised live (no such device on the plant); the advanced WHO 2 scope commands come from the issue_445 captures below.How is it tested
Every finding of the review 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: fifteen real frames throughlistening_loop(WHO 1 dim steps plain / parameterised / general, two DIMENSION 10 scope readbacks, one malformed DIMENSION 10 frame OWNd hands through unparsed, the issue_445 advanced general stop / up / down, the issue_645*2*0*100##, and on/off, stop/open frames). The exact fired list is pinned: eight events, none for the dim steps, the readbacks or the unparsed frame;_resync_timersholds#5,#6,3with_resync_expected_on == {"#5": None, "3": None, "#6": False}. On the base branch it fails with six extra events.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*0*0##/*#2*#5*10*10*50*0*0##do not run thecentralized_shutter_stoptrigger;*2*0*0##and*2*10#001#1*0##do.-n 4: 4507 passed, 1 skipped (base d8bac15, same setup: 4498 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) pass.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 point-to-point DIMENSION 10 status polls (*#2*<ptp>*10*…##) that this PR keeps away from the scope events.tests/fixtures/traces/issue_645/(2026-10-11)*2*0*100##: a command frame to area 10, firesmyhome_area_automation_eventstop.tests/fixtures/traces/carries*1*30*#G##,*1*31*A##or*#2*<scope>*10*…##. The tests feed those frames throughOWNMessage.parseand the gateway'slistening_loop, so theiris_on/is_opening/is_closing/dimensionvalues are OWNd's own (parser truth), not mocked.What does the Firmware Oracle / Encyclopedia say
No oracle stimulus exists for the frames of this PR (
results/<P>/<V>/oracle/full/*.tsvhas no*2*0*0##,*1*30*#5##,*1*31*3##or WHO 2 dimension frame to a scope address): the oracle records what a gateway does with a request; this PR changes what MyHOME fires when such a frame is heard.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, which is why they fire no light event and sweep their members with an unknown polarity.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(functional/who-2-automation/dimensions.md): WHO 2 has only DIMENSION 10 (shutter status) and the write-only DIMENSION 11 (go to level). The test therefore uses DIMENSION 10 status readbacks and one malformed DIMENSION 10 frame, no other dimension.Review follow-up (review 5639737594)
LightingResyncManagerpolarity on WHAT 30/31 and theis_on is not Nonecoupling: cdd2f4b (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: a0314fc (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: 2387478 (gateway_events.py, tests intest_gateway.py,test_device_trigger.py).test_gateway.py, real frames from the Encyclopedia tables and the issue_445 / issue_645 captures).covers.md,myhome_general_light_event, valid DIMENSION 10 example): 5c2ccc0. Encyclopedia citations,#799 (merged)and the physical WHO 2 scope captures: this description.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