From 4ee5b1efdf41beb000e44066e6bb63818bbd60d5 Mon Sep 17 00:00:00 2001 From: gdluck Date: Wed, 28 Oct 2026 18:02:04 +0200 Subject: [PATCH 1/3] fix(profiles): drop WHO 16 from the F461 and MyHomeServer1 profiles Both profiles advertised WHO 16 (Sound System), so MyHOME's startup sweep sent `*#16*0*5##` and configured WHO 16 media players only ever produced refused frames on these gateways. own-firmware-oracle: all 86 WHO 16 frames of the suites (77 of them in sound-who16-source) are nack/silent on F461 020011 and MyHomeServer1 028206, i.e. no translator forwards them; the firmware table shows both images ship bt_multi (WHO 22) and no bt_difson (WHO 16), and the F461 openserver Stackopen lists no WHO 16 client. Live: the F461 2.0.11 NACKs `*#16*0*5##` within 5 ms (2026-10-23) and a MyHomeServer1 NACKs it in the MyHOME issue_466 trace fixture; MyHOME#459 already notes the MyHomeServer1 "firmware discards 5, 16, 22". WHO 22 stays listed and supports_audio is unchanged: sound-who22 is forwarded by both. No new flag: supported_who already carries the meaning. MH202 keeps WHO 16 although its oracle refuses WHO 16 commands, because a live MH202 relays WHO 16 state from an F441M (MyHOME#669) and supported_who also decides which gateway owns those events. F459 and F460 resolve to GenericGatewayProfile and are not changed. scripts/sync_documentation.py --update reports the README already in sync (the profile table does not list WHOs). Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 1 + OWNd/profiles.py | 28 ++++++++++++++++++++++++++-- tests/test_profiles.py | 28 +++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4a75d5..4c7778d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ All notable changes to `OWNd` are documented in this file. - **`AsyncSerialTransport.send` validates outbound frames** like `OWNCommandSession.send`: an invalid or signaling frame (for example the single-`#` concatenation `*1*1*11#*#4*2*22##`) is refused with `reason="invalid_frame"` instead of being written to the port. The frame is serialised once, terminator included: the string checked is the string written. - **WHO 4 central-unit builders (`OWNHeatingCommand.set_central_mode`, `set_central_temperature`, `set_central_program`, `set_central_scenario`, `set_central_weekend`, `set_central_holiday`, `set_holiday_end_date`, `set_holiday_end_time`, `set_timed_manual_end_time`)**: one shared `_validate_central_where` guard raises `ValueError` unless `where` is the central unit itself: `#0` (3550, 99 zones) or `#0#C` with `C` the 4695 unit's configured zone 1..99 or `01`..`99` (the forms MyHOME and the golden corpus use; whitespace is stripped, the stripped value is quoted). A bare zone (`*4*3101*1##`, `*#4*1*#30*...##` used to be built), a zone through the central unit (`#Z`, `#Z#C`: libqtdevices `ControlledProbeDevice` addresses, bus command byte `C2`, built by `set_mode`; the error names it), `#00`, `#0#0`, `#0#00` and three-digit values are refused. The plain zone form `*4*311*Z##` is `nack`/`silent` on all 12 emulated gateways (own-firmware-oracle `thermo-zone-mode`, `ownd-pr82-heating`) and on a live F461 2.0.11. - **WHO 4 (`OWNHeatingEvent`)**: a Dimension 14 setpoint reply with a `#`-prefixed value (`*#4*#1*14*#0230*1##`, `MyHOME#139`) parses as a 23.0 °C target temperature instead of `None`; `dimension_value` keeps `#0230`. +- **Profiles (`F461Profile`, `MyHomeServer1Profile`)**: WHO 16 is no longer in `supported_who`: all 86 WHO 16 frames of the firmware oracle are `nack`/`silent` on F461 020011 and MyHomeServer1 028206 (`sound-who16-source` and the other suites), a live F461 2.0.11 NACKs `*#16*0*5##`, and the MyHomeServer1 firmware discards WHO 16 (`MyHOME#459`). WHO 22 stays listed and `supports_audio` is unchanged; MH202 keeps WHO 16. ## [2.0.0b11] - 2026-10-20 diff --git a/OWNd/profiles.py b/OWNd/profiles.py index a31485c..f70eabc 100644 --- a/OWNd/profiles.py +++ b/OWNd/profiles.py @@ -185,6 +185,17 @@ def can_support_workers(self, count: int) -> bool: who for who in DEFAULT_SUPPORTED_WHO if who != WHO_CEN_PLUS ) +# Gateways without a WHO 16 translator: the F461 020011 and MyHomeServer1 +# 028206 images ship bt_multi (WHO 22) but no bt_difson (WHO 16) +# (own-firmware-oracle README firmware table; the F461 openserver Stackopen +# lists no WHO 16 client), every WHO 16 frame of the firmware oracle (86 per +# gateway, sound-who16-source and the other suites) is nack/silent on them, a +# live F461 2.0.11 NACKs *#16*0*5## within 5 ms, and a MyHomeServer1 NACKs it +# in the MyHOME issue_466 trace fixture (MyHOME#459: "firmware discards 5, 16, +# 22"). Their sound system is WHO 22 (sound-who22), so supports_audio stays +# True and WHO 22 stays listed. +_WITHOUT_WHO16 = (WHO_SOUND,) + # Firmware at which Legrand documents CEN+ on the F453AV (WHO 25 p. 13). _F453AV_CEN_PLUS_FIRMWARE = (2, 1, 7) @@ -391,6 +402,8 @@ class F461Profile(GatewayProfile): (`020011`) does not package the ``bt_energia`` translator and strictly NACKs all WHO 3 requests without bus activity (`results/F461/020011/oracle/full/energy-ts10.tsv`). WHO 3 is therefore excluded from ``supported_who``. + - WHO 16 (Sound System): refused, the sound system is WHO 22 (see + ``_WITHOUT_WHO16``). Injection flags, from the live F461 2.0.11 capture of 2026-10-18 (MyHOME ``tests/fixtures/traces/f461_live_2026-10-18``) and the plant @@ -419,7 +432,9 @@ def __init__(self) -> None: supports_who4_command_injection=True, supports_who18_response_injection=True, supported_who=tuple( - w for w in DEFAULT_SUPPORTED_WHO if w != WHO_LOAD_CONTROL + w + for w in DEFAULT_SUPPORTED_WHO + if w != WHO_LOAD_CONTROL and w not in _WITHOUT_WHO16 ), ) @@ -536,6 +551,11 @@ class MH202Profile(GatewayProfile): *5*9*0##) and its zone states (WHAT 11/18). WHO 5 here means reading the alarm: the plant owner reports the central unit rejects SCS arm/disarm from any gateway and arms through WHO 9 AUX frames instead (see OWNAlarmCommand). + + WHO 16 stays listed although the firmware oracle (MH202 010024) refuses + every WHO 16 command: a live MH202 with an F441M matrix relays WHO 16 + state frames (``*#16*41*1*1##``, ``*#16*41*1*6##``; MyHOME#669 trace), + and ``supported_who`` also decides which gateway owns those events. """ def __init__(self) -> None: @@ -639,6 +659,8 @@ class MyHomeServer1Profile(GatewayProfile): NACKs all WHO 3 requests without bus activity (see own-firmware-oracle findings/MyHomeServer1/energy-ts10.md). WHO 3 is therefore excluded from ``supported_who``. + - WHO 16 (Sound System): refused, the sound system is WHO 22 (see + ``_WITHOUT_WHO16``). """ def __init__(self) -> None: @@ -653,7 +675,9 @@ def __init__(self) -> None: supports_native_transitions=True, supports_extended_frames=True, supported_who=tuple( - w for w in DEFAULT_SUPPORTED_WHO if w != WHO_LOAD_CONTROL + w + for w in DEFAULT_SUPPORTED_WHO + if w != WHO_LOAD_CONTROL and w not in _WITHOUT_WHO16 ), ) diff --git a/tests/test_profiles.py b/tests/test_profiles.py index 2237725..74caf21 100644 --- a/tests/test_profiles.py +++ b/tests/test_profiles.py @@ -103,7 +103,9 @@ def test_profile_lookup_accepts_common_name_variants() -> None: assert profile.model_name == "MyHomeServer1" assert profile.supports_session_count(4) - assert profile.supports_who(WHO_SOUND) + # Firmware oracle: every WHO 16 frame is refused; sound is WHO 22. + assert not profile.supports_who(WHO_SOUND) + assert profile.supports_who(WHO_SOUND_DIFFUSION) # Firmware oracle proves MyHomeServer1 omits bt_energia and rejects WHO 3 assert not profile.supports_who(WHO_LOAD_CONTROL) @@ -685,3 +687,27 @@ def test_profile_injection_capability_flags() -> None: assert p.supports_who18_response_injection is None +@pytest.mark.parametrize("model", ["F461", "MyHomeServer1", "MHS1", "MyHome Server 1"]) +def test_who16_is_not_advertised_where_the_oracle_refuses_it(model: str) -> None: + """Every WHO 16 frame of the oracle (86 per gateway, sound-who16-source and + others) is ``nack``/``silent`` on F461 020011 and MyHomeServer1 028206: no + translator forwards it; a live F461 2.0.11 NACKs ``*#16*0*5##`` within + 5 ms and a MyHomeServer1 NACKs it in the MyHOME issue_466 trace fixture + (MyHOME#459). They forward WHO 22 (``sound-who22``), so sound stays + advertised through WHO 22 and ``supports_audio``. + """ + profile = get_gateway_profile(model) + + assert not profile.supports_who(WHO_SOUND) + assert profile.supports_who(WHO_SOUND_DIFFUSION) + assert profile.supports_audio is True + + +@pytest.mark.parametrize( + "model", ["F454", "MH200N", "MH200", "H4684", "F453", "F453AV", "H4890", "MH202"] +) +def test_who16_stays_on_gateways_that_forward_it(model: str) -> None: + """MH202: the oracle refuses WHO 16 commands, but a live MH202 relays WHO 16 + state frames from an F441M (``*#16*41*1*1##``, MyHOME#669), so it keeps + owning WHO 16 events.""" + assert get_gateway_profile(model).supports_who(WHO_SOUND) From 0d265b9b419429a38a4ceae98d26c8895a6b6b7e Mon Sep 17 00:00:00 2001 From: gdluck Date: Wed, 28 Oct 2026 18:02:04 +0200 Subject: [PATCH 2/3] docs(profiles): bound the MyHomeServer1 WHO 22 wording to what the oracle measured (review follow-up) Review 5684987167 on #142: the comments next to the WHO 16 removal said the MyHomeServer1's sound system "is WHO 22" as if settled; only the emulated 028206 image's bt_multi translating WHO 22 onto the bus (sound-who22) is measured, live routing on production plants is not, and MyHOME's topology strips 22 on it. Comment and docstring now say so. No code change. Co-Authored-By: Claude Fable 5.1 --- OWNd/profiles.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/OWNd/profiles.py b/OWNd/profiles.py index f70eabc..515f9cc 100644 --- a/OWNd/profiles.py +++ b/OWNd/profiles.py @@ -192,8 +192,11 @@ def can_support_workers(self, count: int) -> bool: # gateway, sound-who16-source and the other suites) is nack/silent on them, a # live F461 2.0.11 NACKs *#16*0*5## within 5 ms, and a MyHomeServer1 NACKs it # in the MyHOME issue_466 trace fixture (MyHOME#459: "firmware discards 5, 16, -# 22"). Their sound system is WHO 22 (sound-who22), so supports_audio stays -# True and WHO 22 stays listed. +# 22"). WHO 22 is out of scope here and left as it is: in the oracle the +# 028206 image's bt_multi translates WHO 22 commands onto the bus +# (sound-who22, ack/out), so supports_audio stays True and WHO 22 stays +# listed; whether a live MyHomeServer1 3.87.x routes WHO 22 on a production +# plant is not settled by this change (MyHOME's topology strips 22 on it). _WITHOUT_WHO16 = (WHO_SOUND,) # Firmware at which Legrand documents CEN+ on the F453AV (WHO 25 p. 13). @@ -402,8 +405,11 @@ class F461Profile(GatewayProfile): (`020011`) does not package the ``bt_energia`` translator and strictly NACKs all WHO 3 requests without bus activity (`results/F461/020011/oracle/full/energy-ts10.tsv`). WHO 3 is therefore excluded from ``supported_who``. - - WHO 16 (Sound System): refused, the sound system is WHO 22 (see - ``_WITHOUT_WHO16``). + - WHO 16 (Sound System): refused (no ``bt_difson``, every WHO 16 frame + nack/silent in the oracle and on a live plant; see ``_WITHOUT_WHO16``). + WHO 22 is listed because the emulated image translates it onto the bus + (``sound-who22``); live routing on production plants is not measured + here. Injection flags, from the live F461 2.0.11 capture of 2026-10-18 (MyHOME ``tests/fixtures/traces/f461_live_2026-10-18``) and the plant From e00b725cfd5b0ba9601f2bea9d8d23df9e1ab87e Mon Sep 17 00:00:00 2001 From: gdluck Date: Wed, 28 Oct 2026 18:02:04 +0200 Subject: [PATCH 3/3] docs(profiles): the same WHO 22 wording on the MyHomeServer1 profile docstring Co-Authored-By: Claude Fable 5.1 --- OWNd/profiles.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/OWNd/profiles.py b/OWNd/profiles.py index 515f9cc..511d482 100644 --- a/OWNd/profiles.py +++ b/OWNd/profiles.py @@ -665,8 +665,13 @@ class MyHomeServer1Profile(GatewayProfile): NACKs all WHO 3 requests without bus activity (see own-firmware-oracle findings/MyHomeServer1/energy-ts10.md). WHO 3 is therefore excluded from ``supported_who``. - - WHO 16 (Sound System): refused, the sound system is WHO 22 (see - ``_WITHOUT_WHO16``). + - WHO 16 (Sound System): refused (no ``bt_difson``, every WHO 16 frame + nack/silent in the oracle and on a live plant; see ``_WITHOUT_WHO16``). + WHO 22 is listed because the emulated 028206 image's ``bt_multi`` + translates it onto the bus (``sound-who22``); whether a live + MyHomeServer1 3.87.x routes WHO 22 on a production plant is not + measured here (MyHOME#459 reports it discarded; MyHOME's topology + strips 22 on it). """ def __init__(self) -> None: