Skip to content

feat(heating): OWNHeatingCommand.actuators_status(where) builder for *#4*WHERE*20## - #140

Draft
gdluck wants to merge 3 commits into
OpenWebNet-HA:masterfrom
gdluck:feat/who4-actuators-status-builder
Draft

gdluck wants to merge 3 commits into
OpenWebNet-HA:masterfrom
gdluck:feat/who4-actuators-status-builder

Conversation

@gdluck

@gdluck gdluck commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

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 with hvac_action: None and 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 (MyHOME climate.py initial None). On the live plant, sending *#4*2*20## by hand through myhome.send_message switched the entity to hvac_action: idle immediately.

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##. where may 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#NN routing suffix below and Unicode digits raise ValueError. 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 crashes bt_termo).
  • The address grammar: Z#N (actuator N of zone Z) and #Z#N are the WHO 4 WHERE forms for actuators, which OWNd's _normalize_zone already documents ("4-zone central (#0#N) or probe under central (#Z#C / Z#C)") and OWNHeatingEvent already decodes (_actuator). Live evidence exists for 2, #0 (F461) and 35, #0 (MyHomeServer1) only; 2#1, #2, #0#1 and #2#1 are 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_REGEX now allows an optional #4#NN routing 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#N and #Z are 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. The suffix is accepted as spec-legal grammar only: OpenWebNet-HA/own-firmware-oracle#65 (thermo-zone-requests, decoded scs_bus_hex) shows that no emulated firmware routes *#4*2#4#01*20## across an F422. F450, F454, F459, F460, F461 and MyHomeServer1 truncate WHERE at the first # and poll local zone 2 ($04A9023010 / $004A9023010, the bytes of *#4*2*20##, also for *#4*2#4#12*20## and *#4*2#1#4#01*20##); MH202 010024 reads the trailing #4#01 as local zone 4 actuator 1 ($0499843110) and rejects #4#12; F453, F453AV, H4684, MH200 and MH200N drop all three routed forms (nack/silent). Callers must therefore gate #4#NN polling on the profile's supports_interface_zone_requests (OpenWebNet-HA/MyHOME#816). The MH202 010024 daemon crash (SIGSEGV, thermo_open2scs+0x394) hits the dimensionless status forms *#4*Z#N## (N 1..9) and *#4*Z#4#01..09##; this builder only emits the dimension-20 form, so it never builds them. Docstring, test docstring and CHANGELOG entry updated (review of 2026-10-26, commit d8bbbae); an earlier revision of this paragraph read the out verdicts of oracle #60 as "forwarded".

On the reply *#4*2#2*20*5## being pinned as is_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. OWNd master already decodes 5 as off: OWNHeatingEvent._actuator_active_states = ["1", "2", "6", "7", "8", "9"] and the _fan_mode branch (_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
    }
Loading

Where are the traces

Subsystem Capture Plant Frames verified by this PR
WHO 4 dimension 20, per zone and #0 trace PR: OpenWebNet-HA/MyHOME#799 (merged), f461_live_2026-10-23 run 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##) BTicino F461 2.0.11, standalone zone 2 (valve on actuator 1, fan coil on actuator 2) *#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.
WHO 4 dimension 20 on a 99-zone plant trace PR: OpenWebNet-HA/MyHOME#799 (merged), myhomeserver1_live_2026-10-24 run 2 (run2_reads_thermo_sound_alarm_lights) MyHomeServer1 3.87.13 (*#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#4

What 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 / out on F450, F453, F453AV, F454, F459, F460, F461, MH200N and MyHomeServer1 (forwarded to the emulated bus as $04B9003010, $004B9003010 on F460 / F461, where no actuator answers) and nack / silent on H4684, MH200 and MH202, which refuse #0 locally. 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 the nack / out rows of an emulated bus that has no actuator. The per-zone form is covered by the thermo-zone-requests suite merged in OpenWebNet-HA/own-firmware-oracle#60 (main 52d354b, 2026-10-25): findings/thermo-zone-requests.md records *#4*2*20##, *#4*2#1*20##, *#4*2#0*20## and *#4*2#4#01*20## per gateway as a firmware_observed verdict on emulated firmware (the TSV rows under results/<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, 2 as int, #2, 2#1, #0, #0#1, #2#1, 22) build the expected frame, are is_dimension_request with dimension == 20, round-trip through OWNMessage.parse (same string, same where), and never match a Dimension 22 shape; 12 bad addresses raise ValueError; the three live replies (*#4*2#1*20*0##, *#4*2#2*20*5##, *#4*35#1*20*0##) parse as OWNHeatingEvent with the zone, the actuator 1 / 2 / 1, is_active() is False, fan_on None / False / None.
  • Fail before / pass after: on master 38 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).
  • Full suite: 1930 passed, 1 skipped (master: 1907 passed, 1 skipped); 100 % statement coverage, mypy clean, ruff clean, scripts/sync_documentation.py --check and scripts/verify_library_standards.py pass.
  • Review follow-up tests (routed addresses): 5 accepted forms (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 the ValueError case; 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 suite 1948 passed, 1 skipped, 100 % statement coverage (the 14 partial branches in connection/session.py and message/sound.py are unchanged from master), mypy OWNd tests/typing, ruff, scripts/sync_documentation.py --check and scripts/verify_library_standards.py pass.

Live retest (2026-10-24)

Confirmed for the standalone zone-2 path: with OpenWebNet-HA/MyHOME#792 on top of this builder, every start sent *#4*#0*20## and received *#4*2#1*20*0##, *#4*2#2*20*5##; hvac_action was idle immediately. 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, aggregating hvac_action over the zone's actuators).

🤖 Generated with Claude Code

@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!

@gdluck
gdluck marked this pull request as draft October 24, 2026 13:39
@gdluck
gdluck force-pushed the feat/who4-actuators-status-builder branch from 33cb2b7 to 7766f21 Compare October 24, 2026 15:49
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>
gdluck and others added 2 commits October 26, 2026 05:49
…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>
…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>

@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.

The actuators_status builder and _WHO4_WHERE_REGEX grammar look clean — keeping the wire grammar permissive for spec-legal #4#NN forms makes sense at the OWNd message layer.

One important factual update for the docstrings and CHANGELOG.md from our follow-up investigation of the MH202 010024 crash in own-firmware-oracle#63 (own-firmware-oracle#65, OpenWebNet-Encyclopedia#52, openwebnet-mcp#22, MyHOME#816):

Inspecting the decoded scs_bus_hex bytes across all twelve emulated gateways shows that none of the seven bus_outcome=out gateways actually forward *#4*2#4#01*20## (or *#4*2#1#4#01*20##) across an F422 interface ($06EC... / $06E4...):

  • F450 020010, F454 020051, F459 020105, F460 020012, F461 020011, MyHomeServer1 028206 truncate WHERE at the first # and emit $04A9023010\r ($004A9023010\r on F460/F461) — the exact same bytes as unqualified *#4*2*20##, even for *#4*2#4#12*20## and *#4*2#1#4#01*20## — cross-polling local main-bus zone 2.
  • MH202 010024 (libthermo.so FUN_00018a04 + libplug_thermo_tr.so thermo_open2scs) takes the trailing two #-delimited integers ("4" and "01") of both *#4*2#4#01*20## and *#4*2#1#4#01*20## as local zone 4, actuator 1 and emits $0499843110\r on the local bus (*#4*2#4#12*20## is rejected with nack, silent because actuator 12 > 9).
  • For the same reason, MH202 010024 crashes coso (SIGSEGV at thermo_open2scs+0x394) not only on *#4*2#4#01## (01..09), but also on plain per-actuator status *#4*2#1## (OWNHeatingCommand.status("2#1"), Z=1..99, N=1..9).

Inline comments below point to the three prose spots so the documentation doesn't suggest those seven firmwares route #4#NN across an F422 interface.

Comment thread OWNd/message/heating.py Outdated
Comment on lines +878 to +883
``2#1#4#01``): 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.

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.

In own-firmware-oracle#65 (and OpenWebNet-Encyclopedia#52), decoding scs_bus_hex on *#4*2#4#01*20##, *#4*2#4#12*20##, and *#4*2#1#4#01*20## shows that none of these seven images emit an interface-routed $06EC/$06E4 SCS telegram:

  • F450, F454, F459, F460, F461, and MyHomeServer1 truncate WHERE at # and emit $04A9023010\r ($004A9023010\r on F460/F461), querying local main-bus zone 2.
  • MH202 010024 misparses the trailing #4#01 tokens of 2#4#01*20 and 2#1#4#01*20 as local zone 4, actuator 1 ($0499843110\r) and rejects 2#4#12*20 (12 > 9, nack/silent).
  • Also worth noting that the MH202 010024 coso SIGSEGV (thermo_open2scs+0x394) affects both dimensionless per-actuator status *#4*Z#N## (N=1..9) and *#4*Z#4#01..09## (because Z#4#01 is misparsed as zone=4, actuator=1).

Keeping #4#NN accepted in _WHO4_WHERE_REGEX as spec-legal grammar is fine, as long as this docstring notes that callers must gate #4#NN polling on supports_interface_zone_requests (see MyHOME#816) rather than expecting those seven firmwares to route it across an F422.

Comment thread tests/test_who4_actuators_status.py Outdated
Comment on lines +15 to +20
sends them for zones behind an F422) are accepted since the MyHOME#792 review:
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
daemon there is ``*#4*2#4#01##`` (zone status, no dimension), which this
builder never emits: it only builds the dimension-20 form.

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.

Same clarification as in OWNd/message/heating.py: own-firmware-oracle#65 shows the seven out images emit unrouted local-bus queries ($04A9023010\r / $004A9023010\r for local zone 2, or $0499843110\r for local zone 4 actuator 1 on MH202 010024) rather than forwarding #4#NN across an F422, and MyHOME#816 withholds #4#NN WHO 4 status polls unless supports_interface_zone_requests is True.

Comment thread CHANGELOG.md Outdated
- Automatic detection of legacy pre-`09W29` BTicino `F414` coarse-only dimmers via `DIMENSION 1` / `DIMENSION 4` transition speed `240` (`0xF0`, `DIMMER_SPEED_COARSE_ONLY`) with multi-gateway plant isolation (`gateway=` scoping on `OWNMessage.parse`, `OWNEvent.parse`, `OWNEventSession.get_next`, `is_coarse_dimmer_where`, `register_coarse_dimmer_where`, `clear_coarse_dimmer_wheres`, and `OWNLightingCommand.set_brightness`) (`#134`, `MyHOME#767`).
- Canonical `libqtdevices` (`TS10_1_0_23`) coarse `WHAT 2..10` $\leftrightarrow$ fine `1..100%` conversion helpers (`COARSE_PRESET_TO_PERCENT`, `brightness_preset_to_percent`, `percent_to_brightness_preset`), `OWNLightingEvent.is_coarse_dimmer`, `OWNLightingEvent.brightness_preset_percent`, and `OWNLightingCommand.set_brightness(..., coarse=..., gateway=...)` (`#134`).
- `WHAT 39` end of movement (`*1*39*WHERE##`) from WHO 1 motion detectors: `message_type` `motion_detected` with `motion` `False`, new `OWNLightingEvent.motion_end`, `is_sensor` `True`, `is_on` `None`. It was `unknown_state` `39` before (`MyHOME#777`).
- **WHO 4 (`OWNHeatingCommand.actuators_status`)**: Actuator status request `*#4*WHERE*20##` for a zone, one actuator (`2#1`) or `#0` (all zones); a live F461 2.0.11 answers it per actuator (`*#4*2#1*20*0##`, `*#4*2#2*20*5##`). The address may be routed through a bus interface (`3#4#01`, `#3#4#01`, `2#1#4#01`; two-digit interface): own-firmware-oracle#60 shows `*#4*2#4#01*20##` forwarded by F450, F454, F459, F460, F461, MH202 and MyHomeServer1 (dropped by F453, F453AV, H4684, MH200, MH200N); the MH202 crash in that suite is the routed zone status without a dimension (`*#4*2#4#01##`), which the builder never emits (`MyHOME#792` review).

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.

Same note here: suggest updating "forwarded by F450, F454, F459, F460, F461, MH202 and MyHomeServer1" (and noting *#4*2#1## alongside *#4*2#4#01## for the MH202 010024 crash) to match own-firmware-oracle#65.

@gdluck
gdluck force-pushed the feat/who4-actuators-status-builder branch from 307dcc9 to e3b600d Compare October 26, 2026 08:56
@gdluck

gdluck commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

Rebased on master 87cafc3 (after #136): the CHANGELOG [Unreleased] entry and the heating.py regex block now sit beside the merged dimension-22 guard; content unchanged, suite and 100 % coverage green.

…#65 (review follow-up)

The `actuators_status` docstring, the test module docstring and the
CHANGELOG entry said own-firmware-oracle#60 showed `*michnovka#4*2#4#01*20##`
"forwarded by F450, F454, F459, F460, F461, MH202 and MyHomeServer1".
own-firmware-oracle#65 decodes the SCS bytes of those `out` verdicts: the
six Linux gateways truncate WHERE at the first `#` and poll local zone 2
(the bytes of `*michnovka#4*2*20##`), MH202 010024 reads the trailing `michnovka#4#01` as
local zone 4 actuator 1 and rejects `michnovka#4#12`, and F453, F453AV, H4684,
MH200 and MH200N drop all three routed forms. Nothing routes `michnovka#4#NN`
across an F422, so the text now says callers must gate it on the profile's
`supports_interface_zone_requests` (MyHOME#816), and the MH202 crash note
covers the plain per-actuator status `*michnovka#4*Z#N##` as well as
`*michnovka#4*Z#4#01..09##`. Grammar and behaviour unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gdluck

gdluck commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, applied in d8bbbae (prose only, grammar and behaviour unchanged): the actuators_status docstring, the test module docstring and the CHANGELOG entry now follow OpenWebNet-HA/own-firmware-oracle#65 instead of reading the out verdicts of #60 as "forwarded".

  • F450, F454, F459, F460, F461 and MyHomeServer1 truncate WHERE at the first # and poll local zone 2 ($04A9023010 / $004A9023010) for *#4*2#4#01*20##, *#4*2#4#12*20## and *#4*2#1#4#01*20##; MH202 010024 reads the trailing #4#01 as local zone 4 actuator 1 ($0499843110) and rejects #4#12; F453, F453AV, H4684, MH200 and MH200N drop all three (nack/silent in the chore(release): bump version to 2.0.0-b9 #65 TSVs).
  • Both docstrings and the CHANGELOG now say callers must gate #4#NN polling on supports_interface_zone_requests (fix(climate): gate unrouted WHO 4 #4#II and actuator status polls MyHOME#816) rather than expecting an F422 route.
  • The MH202 010024 crash note now covers *#4*Z#N## (N 1..9) next to *#4*Z#4#01..09##, with the thermo_open2scs+0x394 location.

The PR body paragraph with the old sentence is corrected the same way. tests/test_who4_actuators_status.py (41 passed) and ruff are unchanged.

This branch has not been deployed

No deployments
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.

3 participants