diff --git a/OWNd/message/gateway.py b/OWNd/message/gateway.py index cabd90b..7c94fde 100644 --- a/OWNd/message/gateway.py +++ b/OWNd/message/gateway.py @@ -123,10 +123,15 @@ def __init__(self, data: str) -> None: elif self._dimension_value[0] == "13": self._device_type = "H4684" elif self._dimension_value[0] == "200": - # Code 200 is shared by every Linux-based gateway generation seen so - # far (F454, MyHomeServer1, MH202, F461, H4890; MyHOME #370, #466 and - # the F461 plant capture). WHO 1013 dimension 1 tells them apart. - self._device_type = "F454 / MyHomeServer1 / MH202 / F461 / H4890" + # Code 200 is shared across modern gateways (F454, MyHomeServer1, + # MH202, F461, H4890; plus emulated F450/F459/F460 in trace-corpus.tsv) + # and MH201 (fw 3.3.6, 3.6.27; MyHOME #370, #466 and plant fixtures; + # catalogued on 030644 outside the Linux QEMU fleet). WHO 1013 + # dimension 1 tells them apart (the MH201 on 3.6.27 answers only + # *#1013**1##, model 10, and NACKs *#1013*0*1##). + self._device_type = ( + "F454 / MyHomeServer1 / MH202 / F461 / H4890 / MH201" + ) else: self._device_type = f"Unknown ({self._dimension_value[0]})" self._human_readable_log = f"Gateway device type is: {self._device_type}." diff --git a/OWNd/message/heating.py b/OWNd/message/heating.py index 99c7482..aa0c868 100644 --- a/OWNd/message/heating.py +++ b/OWNd/message/heating.py @@ -505,7 +505,6 @@ def __init__(self, data: str) -> None: self._type = MESSAGE_TYPE_ACTION self._is_cooling = self._dimension_value[0] in _valve_active_states self._is_heating = self._dimension_value[1] in _valve_active_states - self._is_active = self._is_cooling | self._is_heating # Handle cooling valve status relative to fan speed/status _cooling_value = int(self._dimension_value[0]) if _cooling_value == 0: @@ -528,7 +527,6 @@ def __init__(self, data: str) -> None: _fan_mode = _cooling_value - 5 if _fan_mode > 0: self._cooling_fan_on = True - self._is_active = True self._cooling_fan_speed = _fan_mode self._human_readable_log = f"Zone {self._zone}'s cooling fan is on at speed {self._cooling_fan_speed}" # pylint: disable=line-too-long else: @@ -550,7 +548,6 @@ def __init__(self, data: str) -> None: _fan_mode = _heating_value - 5 if _fan_mode > 0: self._fan_on = True - self._is_active = True self._fan_speed = _fan_mode self._human_readable_log += ( f"; heating fan is on at speed {self._fan_speed}." @@ -560,7 +557,7 @@ def __init__(self, data: str) -> None: self._human_readable_log += "; heating fan is off." # A fan-coil value on one side must not hide an open valve or a running fan # on the other side: *#4*2*19*5*1## (cooling fan off, heating valve on) is an - # active zone (F461 plant capture, tests/fixtures/f461_live_2026-10-15/). + # active zone (F461 plant capture, tests/test_f461_live_plant.py). self._is_active = bool( self._is_cooling or self._is_heating @@ -923,13 +920,16 @@ def set_mode( mode_name = mode if mode == CLIMATE_MODE_AUTO: # Automatic is the central unit's program: the firmware refuses *4*311*Z## - # (NACK, no bus frame; OWNd#77 A3, oracle3/out/termo_f461.tsv) and only - # forwards *4*311*#Z##, a frame addressed to the central unit, so + # (NACK, no bus frame across all 11 emulated gateways in thermo-zone-mode.tsv + # and ownd-pr82-heating.tsv; OWNd#77 A3) and only forwards *4*311*#Z##, a + # frame addressed to the central unit (bus D1 00 03 02 C2 ZZ 00 00), so # standalone does not apply. Protection (302 / 102 / 202) is different: # the plain zone form is forwarded as a direct zone command (bus - # 93 ZZ 30 04) and is the only form a standalone thermostat acts on; - # the #Z form goes to a central unit and is ignored when there is none - # (F461 plant capture, tests/fixtures/f461_live_2026-10-15/). + # 93 ZZ 30 04, like *4*303*Z## -> 93 ZZ 30 0F in thermo-zone-mode.tsv) + # and is the only form a standalone thermostat acts on; the #Z form goes + # to a central unit (bus D1 00 03 02 C2 ZZ 07 00 in thermo-ts10.tsv) and + # is ignored when there is none (F461 plant capture, + # tests/test_f461_live_plant.py). if not zone.startswith("#"): zone = f"#{zone}" diff --git a/README.md b/README.md index 49b9506..c1c0c03 100755 --- a/README.md +++ b/README.md @@ -337,7 +337,7 @@ OWNd maintains an automated test suite with strict **100.0% line coverage** veri | [`OWNd/message/automation.py`](OWNd/message/automation.py) | **100%** | WHO 2: Automation and motorized shutter events and commands | | [`OWNd/message/base.py`](OWNd/message/base.py) | **100%** | Base message, signaling, and protocol dispatcher registries | | [`OWNd/message/cen.py`](OWNd/message/cen.py) | **100%** | WHO 15 & WHO 25: CEN, CEN+, and dry contact control events/commands | -| [`OWNd/message/door_entry.py`](OWNd/message/door_entry.py) | **100%** | WHO 6 & WHO 8: Door entry, lock, and video intercom events and commands | +| [`OWNd/message/door_entry.py`](OWNd/message/door_entry.py) | **100%** | WHO 6 & WHO 8: Door entry, electric lock, and video intercom events/commands | | [`OWNd/message/energy.py`](OWNd/message/energy.py) | **100%** | WHO 18: Energy management, power, and diagnostic pulse events/commands | | [`OWNd/message/gateway.py`](OWNd/message/gateway.py) | **100%** | WHO 13: Gateway management, clock/date configuration, and model telemetry | | [`OWNd/message/heating.py`](OWNd/message/heating.py) | **100%** | WHO 4: Thermoregulation, heating/cooling zone control, and temperature decoders | diff --git a/scripts/sync_documentation.py b/scripts/sync_documentation.py index b127e9a..e93b7c9 100644 --- a/scripts/sync_documentation.py +++ b/scripts/sync_documentation.py @@ -60,7 +60,7 @@ "OWNd/message/automation.py": "WHO 2: Automation and motorized shutter events and commands", "OWNd/message/base.py": "Base message, signaling, and protocol dispatcher registries", "OWNd/message/cen.py": "WHO 15 & WHO 25: CEN, CEN+, and dry contact control events/commands", - "OWNd/message/door_entry.py": "WHO 6 & WHO 8: Door entry, lock, and video intercom events and commands", + "OWNd/message/door_entry.py": "WHO 6 & WHO 8: Door entry, electric lock, and video intercom events/commands", "OWNd/message/energy.py": "WHO 18: Energy management, power, and diagnostic pulse events/commands", "OWNd/message/gateway.py": "WHO 13: Gateway management, clock/date configuration, and model telemetry", "OWNd/message/heating.py": "WHO 4: Thermoregulation, heating/cooling zone control, and temperature decoders", diff --git a/tests/test_f461_live_plant.py b/tests/test_f461_live_plant.py index 6e1c1cf..1f2a00c 100644 --- a/tests/test_f461_live_plant.py +++ b/tests/test_f461_live_plant.py @@ -14,11 +14,13 @@ valve states had set it. The plant reports this frame on every heating-season read. 2. ``set_mode(zone, "protection" | "antifreeze" | "thermal_protection", standalone=True)`` produced the ``#Z`` form, which the gateway forwards to a central unit (emulated - ``bt_termo``: ``*4*302*#23##`` -> ``D1 00 03 02 C2 17 07 00``) and which a standalone - thermostat ignores; the plain form (``93 17 30 04``) is the one that works. Only - ``311`` (automatic) is a central-unit-only command (``*4*311*Z##`` is NACKed). + ``bt_termo``: ``*4*302*#23##`` -> ``D1 00 03 02 C2 17 07 00`` in ``thermo-ts10.tsv``) + and which a standalone thermostat ignores; the plain form (``93 17 30 04``) is the one + that works. Only ``311`` (automatic) is a central-unit-only command (``*4*311*Z##`` is + NACKed across all 11 emulated gateways in ``thermo-zone-mode.tsv``). 3. WHO 13 device type ``200`` was labelled ``F454`` although F454, MyHomeServer1, MH202, - F461 and H4890 all answer it. + F461, H4890 and MH201 (fw 3.3.6, 3.6.27; plus emulated F450/F459/F460 in + ``trace-corpus.tsv``) all answer it. """ from __future__ import annotations @@ -178,7 +180,7 @@ def test_capture_shows_only_the_plain_protection_form_acts() -> None: def test_device_type_200_is_not_a_single_model() -> None: msg = OWNEvent.parse("*#13**15*200##") assert msg.device_type_code == "200" - assert "F454" in msg.device_type and "F461" in msg.device_type + assert "F454" in msg.device_type and "F461" in msg.device_type and "MH201" in msg.device_type assert msg.human_readable_log == f"Gateway device type is: {msg.device_type}." assert OWNEvent.parse("*#13**15*2##").device_type == "MHServer" assert OWNEvent.parse("*#13**16*2*0*11##").device_type_code is None diff --git a/tests/test_profiles.py b/tests/test_profiles.py index d92846a..6e5b816 100644 --- a/tests/test_profiles.py +++ b/tests/test_profiles.py @@ -550,7 +550,7 @@ def test_owngateway_event_properties() -> None: assert evt_fw.device_type is None evt_dev = OWNGatewayEvent("*#13**15*200##") - assert evt_dev.device_type == "F454 / MyHomeServer1 / MH202 / F461 / H4890" + assert evt_dev.device_type == "F454 / MyHomeServer1 / MH202 / F461 / H4890 / MH201" assert evt_dev.device_type_code == "200" assert evt_dev.firmware_version is None