Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ All notable changes to `OWNd` are documented in this file.
- 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`).

### Fixed
- **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

### Added
Expand Down
28 changes: 26 additions & 2 deletions OWNd/profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -387,6 +398,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
Expand Down Expand Up @@ -415,7 +428,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
),
)

Expand Down Expand Up @@ -532,6 +547,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:
Expand Down Expand Up @@ -610,6 +630,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:
Expand All @@ -624,7 +646,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
),
)

Expand Down
28 changes: 27 additions & 1 deletion tests/test_profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -651,3 +653,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)
Loading