Repository navigation
Conversation
4 tasks done
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
gdluck
marked this pull request as draft
October 24, 2026 13:39
…ension 20 OWNd had valves_status() for dimension 19 (`*michnovka#4*ZONE*19##`) but no builder for the actuator status request `*michnovka#4*WHERE*20##`, although the gateways answer it: on the live F461 2.0.11 (2026-10-23, MyHOME#799) `*michnovka#4*2*20##` returned `*michnovka#4*2#1*20*0##` (valve, off) on the command session and `*michnovka#4*2#2*20*5##` (fan coil, off) on the event session, one frame per actuator, and `*michnovka#4*#0*20##` returned both; a live MyHomeServer1 3.87.13 answered `*michnovka#4*35*20##` with `*michnovka#4*35#1*20*0##`. MyHOME needs the request to fill hvac_action at startup instead of waiting for a spontaneous actuator frame. actuators_status(where) accepts a zone (`2`, `michnovka#2`), one actuator (`2#1`), a central-unit form (`#0`, `#0#1`) or `#Z#N` (WHO 4 WHERE table; pinned by a test): the address must fully match `#?[0-9]+(?:#[0-9]+)?`, so `*`, whitespace, Unicode digits and anything else raise ValueError and no dimension can be injected through it. Value 5 in a reply is fan coil off (master `_actuator_active_states`), pinned unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gdluck
force-pushed
the
feat/who4-actuators-status-builder
branch
from
October 24, 2026 15:49
33cb2b7 to
7766f21
Compare
…resses (review follow-up) The MyHOME#792 review (finding 3) points out that MyHOME sends the actuator request for zones behind an F422 bus interface as `*michnovka#4*3#4#01*20##`, `*michnovka#4*michnovka#3#4#01*20##` or `*michnovka#4*2#1#4#01*20##`, while `OWNHeatingCommand.actuators_status` rejected every three-part address with `ValueError`. The `_WHO4_WHERE_REGEX` now allows an optional `michnovka#4#NN` routing suffix (interface of exactly two digits) after the zone, the central-unit zone or the actuator: `Z#4#NN`, `#Z#4#NN`, `Z#N#4#NN`. `#0`, `#0#N`, `Z`, `Z#N` and `#Z` are unchanged; `*`, an empty string, a one-digit (`3#4#1`) or three-digit (`3#4#001`) interface, a suffix that is not `michnovka#4#`, a double suffix and a dimension smuggled after the suffix stay refused. Evidence: own-firmware-oracle#60 (`thermo-zone-requests`) shows `*michnovka#4*2#4#01*20##` forwarded by F450, F454, F459, F460, F461, MH202 and MyHomeServer1 and dropped by F453, F453AV, H4684, MH200 and MH200N. The frame that crashes the MH202 thermoregulation daemon in that suite is the routed zone status without a dimension, `*michnovka#4*2#4#01##`, which this builder never emits: it only builds the dimension-20 form. Tests: 5 accepted routed forms (fail before: `ValueError`) and 13 refused forms added to `tests/test_who4_actuators_status.py`; on the previous tip 5 fail / 36 pass, now 41 pass. Full suite 1948 passed, 1 skipped, 100 % statement coverage; ruff, mypy (`OWNd tests/typing`), `sync_documentation.py --check` and `verify_library_standards.py` pass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gdluck
pushed a commit
to gdluck/HomeAssistant-MyHOME
that referenced
this pull request
Oct 24, 2026
…d#140's builder requires (OpenWebNet-HA#792 review, 3) `_ZONE_WHERE` accepted a one- or two-digit interface (`3#4#1`, `3#4#01`) while the companion builder `OWNHeatingCommand.actuators_status` (OpenWebNet-HA/OWNd#140, 307dcc9) requires exactly two (`michnovka#4#[0-9]{2}`) and rejects `3#4#1`, so swapping the string builder for it would have broken a zone discovered from a frame that spells its interface with one digit. The address is normalised before it is validated: `_validate_zone_where` returns the address with a two-digit interface (`3#4#1` -> `3#4#01`), `request_zone_dimension` and `watch_zone_actuators` store that form (one watch count per zone, whichever spelling asked), and `_ZONE_WHERE` is tightened to `{2}`. In `climate.py`, `_calling_zones` and the entity's `_full_where` spell the interface the way the bus frames and the configuration validator (`BusInterface`, `zfill(2)`) do, so a routed zone has one address everywhere. Tests: two fail before, pass after (`watch_zone_actuators("3#4#1")` counts `3#4#01` and sends `*michnovka#4*3#4#01*20##`, unwatch removes it; `*michnovka#4*3#4#1*14*0215##` and `interface="1"` both give `3#4#01`). `3#4#001` joins the refused addresses; `3#4#1` leaves the list of addresses accepted as they are. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gdluck
pushed a commit
to gdluck/HomeAssistant-MyHOME
that referenced
this pull request
Oct 25, 2026
…d#140's builder requires (OpenWebNet-HA#792 review, 3) `_ZONE_WHERE` accepted a one- or two-digit interface (`3#4#1`, `3#4#01`) while the companion builder `OWNHeatingCommand.actuators_status` (OpenWebNet-HA/OWNd#140, 307dcc9) requires exactly two (`michnovka#4#[0-9]{2}`) and rejects `3#4#1`, so swapping the string builder for it would have broken a zone discovered from a frame that spells its interface with one digit. The address is normalised before it is validated: `_validate_zone_where` returns the address with a two-digit interface (`3#4#1` -> `3#4#01`), `request_zone_dimension` and `watch_zone_actuators` store that form (one watch count per zone, whichever spelling asked), and `_ZONE_WHERE` is tightened to `{2}`. In `climate.py`, `_calling_zones` and the entity's `_full_where` spell the interface the way the bus frames and the configuration validator (`BusInterface`, `zfill(2)`) do, so a routed zone has one address everywhere. Tests: two fail before, pass after (`watch_zone_actuators("3#4#1")` counts `3#4#01` and sends `*michnovka#4*3#4#01*20##`, unwatch removes it; `*michnovka#4*3#4#1*14*0215##` and `interface="1"` both give `3#4#01`). `3#4#001` joins the refused addresses; `3#4#1` leaves the list of addresses accepted as they are. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
4 of 6 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by A
Summary
OWNd has
valves_status()for Dimension 19 (*#4*ZONE*19##) but no builder for the actuator status request*#4*WHERE*20##, although the gateways answer it per actuator. MyHOME's climate entity therefore starts withhvac_action: Noneand stays there until an actuator frame happens to arrive on the bus: its zone poll*#4*Z##does not include actuator status, and it has no way to request Dimension 20 (MyHOMEclimate.pyinitialNone). On the live plant, sending*#4*2*20##by hand throughmyhome.send_messageswitched the entity tohvac_action: idleimmediately.Refs
OpenWebNet-HA/MyHOME#792(the consumer),OpenWebNet-HA/MyHOME#799(the captures).Changes
OWNd/message/heating.py:OWNHeatingCommand.actuators_status(where)builds*#4*<where>*20##.wheremay be a zone (2,#2), one actuator of a zone (2#1), a central-unit form (#0,#0#1) or#Z#N(an actuator addressed through the central unit). It must fully match#?[0-9]+(?:#[0-9]+)?(?:#4#[0-9]{2})?(ASCII digits only,fullmatch, whitespace stripped as the other builders do), so*,None,2##,##0, three-part forms other than the#4#NNrouting suffix below and Unicode digits raiseValueError. No dimension can be injected through the address; the tests also assert the built frame never matches a WHO 4 Dimension 22 shape (the frame that crashesbt_termo).Z#N(actuator N of zone Z) and#Z#Nare the WHO 4 WHERE forms for actuators, which OWNd's_normalize_zonealready documents ("4-zone central (#0#N) or probe under central (#Z#C / Z#C)") andOWNHeatingEventalready decodes (_actuator). Live evidence exists for2,#0(F461) and35,#0(MyHomeServer1) only;2#1,#2,#0#1and#2#1are accepted on the strength of the spec and pinned by the tests so that a reviewer sees they were considered.human_readable_log: "Requesting climate actuator status update for 2."CHANGELOG.md: entry under[Unreleased]/Added.Review follow-up (OpenWebNet-HA/MyHOME#792 review, finding 3). MyHOME sends the actuator request for a zone behind an F422 bus interface as
*#4*3#4#01*20##(also#3#4#01,2#1#4#01), and the first version of this builder rejected every three-part address._WHO4_WHERE_REGEXnow allows an optional#4#NNrouting suffix (interface of exactly two digits) on the zone, the central-unit zone and the actuator form:Z#4#NN,#Z#4#NN,Z#N#4#NN.#0,#0#N,Z,Z#Nand#Zare unchanged;*, an empty string, a one-digit (3#4#1) or three-digit (3#4#001) interface, a suffix other than#4#, a doubled suffix and a dimension after the suffix (3#4#01*22) stay refused. Evidence: own-firmware-oracle OpenWebNet-HA/own-firmware-oracle#60 (thermo-zone-requests) shows*#4*2#4#01*20##forwarded by F450, F454, F459, F460, F461, MH202 and MyHomeServer1 and dropped by F453, F453AV, H4684, MH200 and MH200N. The frame that crashes the MH202 thermoregulation daemon in that suite is the routed zone status without a dimension,*#4*2#4#01##; this builder only emits the dimension-20 form, so it never builds that frame. Docstring and CHANGELOG entry updated.On the reply
*#4*2#2*20*5##being pinned asis_active() is False. Dimension 20 values are 0 off, 1 on, 2 opened, 3 closed, 4 stop, 5 fan coil off, 6..8 fan speeds, 9 fan auto. OWNdmasteralready decodes 5 as off:OWNHeatingEvent._actuator_active_states = ["1", "2", "6", "7", "8", "9"]and the_fan_modebranch (_fan_mode = value - 5;0->self._fan_on = False,self._is_active = False). The test pins that unchanged behaviour; the live plant's fan coil was off.State Diagram
stateDiagram-v2 direction TB state "1. OWNHeatingCommand.actuators_status(where)" as Builder { [*] --> Validate Validate --> Built : fullmatch #?[0-9]+, #[0-9]+?, #4#[0-9]{2}?\n2 -> *#4*2*20##, #0 -> *#4*#0*20##, 2#1, #2, #0#1, #2#1, 3#4#01, #3#4#01, 2#1#4#01 Validate --> Refused : , 2*22, #0*22, 2##, ##0, 2#1#4, a, Unicode digit, None\nValueError , before - no builder at all, AttributeError } state "2. Gateways answering the request (OpenWebNet-HA/MyHOME#799 captures)" as Gateway { [*] --> Idle Idle --> F461 : *#4*2*20##\ncommand session - *#4*2#1*20*0## then ACK , 111 ms\nevent session - *#4*2#2*20*5## Idle --> F461all : *#4*#0*20##\ncommand session - *#4*2#1*20*0##, *#4*2#2*20*5##, ACK , 2.1 s Idle --> MHS1 : *#4*35*20##\n*#4*35#1*20*0##, ACK , 189 ms Idle --> MHS1all : *#4*#0*20##\nACK , 0.39 s - 43 distinct actuator frames on the event session , 44 with the zone-35 reply } state "3. OWNHeatingEvent for a dimension 20 reply (unchanged)" as Reply { [*] --> Decode Decode --> Active : value 1, 2, 6, 7, 8, 9 Decode --> Inactive : value 0 , off, 5 , fan coil off, 3, 4 }Where are the traces
#0OpenWebNet-HA/MyHOME#799(merged),f461_live_2026-10-23run 1 (run1_oracle_replay_readonly:*#4*2*20##,*#4*#0*20##), run 2 (run2_reviewer_reads), run 6 and run 7 (the actuator frames after every*#4*#0*20##of the patched integration and after the manual*#4*2*20##)*#4*2*20##->*#4*2#1*20*0##on the command session (ACK 111 ms),*#4*2#2*20*5##on the event session;*#4*#0*20##-> both on the command session (ACK 2.1 s).*#4*2#2*20*5##also seen as a spontaneous event.OpenWebNet-HA/MyHOME#799(merged),myhomeserver1_live_2026-10-24run 2 (run2_reads_thermo_sound_alarm_lights)*#13**16*3*87*13##)*#4*35*20##->*#4*35#1*20*0##(ACK 189 ms);*#4*#0*20##-> ACK 0.39 s with 43 distinct actuator frames (zones 0, 35..53, 55, 57..70; 44 counting the zone-35 reply), including the zone-0 actuators*#4*0#1*20*0##..0#4What does the Oracle say
The published suites (oracle main
72759e8) contain no per-zone*#4*Z*20##request. They do contain the central-unit form*#4*#0*20##(results/<P>/<V>/oracle/full/thermo-actuator.tsv):nack/outon F450, F453, F453AV, F454, F459, F460, F461, MH200N and MyHomeServer1 (forwarded to the emulated bus as$04B9003010,$004B9003010on F460 / F461, where no actuator answers) andnack/silenton H4684, MH200 and MH202, which refuse#0locally. On the real plants*#4*#0*20##is forwarded and answered by the actuators (the F461 and MyHomeServer1 rows above: ACK with the dimension-20 frames), which is consistent with thenack/outrows of an emulated bus that has no actuator. The per-zone form is covered by thethermo-zone-requestssuite merged in OpenWebNet-HA/own-firmware-oracle#60 (main 52d354b, 2026-10-25):findings/thermo-zone-requests.mdrecords*#4*2*20##,*#4*2#1*20##,*#4*2#0*20##and*#4*2#4#01*20##per gateway as afirmware_observedverdict on emulated firmware (the TSV rows underresults/<P>/<V>/oracle/full/follow the next oracle CI run). This PR does not depend on those verdicts (a builder needs none); the two physical captures above are the evidence.Testing
tests/test_who4_actuators_status.py(new, 23 cases): 8 addresses (2,2as int,#2,2#1,#0,#0#1,#2#1,22) build the expected frame, areis_dimension_requestwithdimension == 20, round-trip throughOWNMessage.parse(same string, samewhere), and never match a Dimension 22 shape; 12 bad addresses raiseValueError; the three live replies (*#4*2#1*20*0##,*#4*2#2*20*5##,*#4*35#1*20*0##) parse asOWNHeatingEventwith the zone, the actuator1/2/1,is_active() is False,fan_onNone/False/None.master38 fail (AttributeError: ... has no attribute 'actuators_status'), 3 pass (the reply parsing already worked); on this branch all 41 pass. Re-verified 2026-10-24 with plain OWNd master as the sender: F461*#4*2*20##ACK 95 ms with*#4*2#1*20*0##and*#4*2#2*20*5##(which of the two arrives on the command session varies between captures, so a consumer must collect both sessions); MyHomeServer1 3.87.13, status reads on all 33 zones: every*#4*Z*20##ACK in 155-227 ms, 28 zones answering actuator 1 only, zones 47 / 48 / 57 / 60 / 70 also*#4*Z#2*20*5##, zone 60 actuator 1*#4*60#1*20*1##(on).1930 passed, 1 skipped(master:1907 passed, 1 skipped); 100 % statement coverage,mypyclean,ruffclean,scripts/sync_documentation.py --checkandscripts/verify_library_standards.pypass.3#4#01," 3#4#01 ",#3#4#01,2#1#4#01,12#4#15) added to the build/round-trip case, 13 refused forms (3#4#1,3#4#001,#3#4#1,#3#4#001,2#1#4#1,2#1#4#001,3#4#,3#4#0a,3#4#01#,3#3#01,3#4#01#4#01,3#4#01*22,*#4#01) added to theValueErrorcase; the module is now 41 cases. Fail before / pass after: on the previous tip of this branch 5 fail (ValueError: Invalid zone address), 36 pass; after the follow-up commit 41 pass. Full suite1948 passed, 1 skipped, 100 % statement coverage (the 14 partial branches inconnection/session.pyandmessage/sound.pyare unchanged frommaster),mypy OWNd tests/typing,ruff,scripts/sync_documentation.py --checkandscripts/verify_library_standards.pypass.Live retest (2026-10-24)
Confirmed for the standalone zone-2 path: with
OpenWebNet-HA/MyHOME#792on top of this builder, every start sent*#4*#0*20##and received*#4*2#1*20*0##,*#4*2#2*20*5##;hvac_actionwasidleimmediately. The per-zone form*#4*2*20##was not sent in that run (it is in the 2026-10-23 capture above); no central unit and no other gateway model was tested.Companion
MyHOME:
OpenWebNet-HA/MyHOME#792(request*#4*Z*20##once when a zone is added and after a reconnect, aggregatinghvac_actionover the zone's actuators).🤖 Generated with Claude Code