Coverage for tests/tests_rf/test_hgi_behaviors.py: 0%
71 statements
« prev ^ index » next coverage.py v7.11.3, created at 2026-01-05 21:46 +0100
« prev ^ index » next coverage.py v7.11.3, created at 2026-01-05 21:46 +0100
1#!/usr/bin/env python3
3# TODO: Remove unittest.mock.patch (use monkeypatch instead of unittest patch)
4# TODO: Test with strict address checking
6"""RAMSES RF - Check GWY address/type detection and its treatment of addr0."""
8import asyncio
9from unittest.mock import patch
11import pytest
13from ramses_rf import Command, Gateway
14from ramses_tx import exceptions as exc
15from ramses_tx.address import HGI_DEVICE_ID, Address
16from ramses_tx.protocol import PortProtocol
17from ramses_tx.schemas import DeviceIdT
18from ramses_tx.transport import MqttTransport
19from ramses_tx.typing import QosParams
21from .conftest import _GwyConfigDictT
23# patched constants
24_DBG_DISABLE_STRICT_CHECKING = True # # ramses_tx.address
26# other constants
27ASSERT_CYCLE_TIME = 0.001 # max_cycles_per_assert = max_sleep / ASSERT_CYCLE_TIME
28DEFAULT_MAX_SLEEP = 0.05 # 0.01/0.05 minimum for mocked (virtual RF)/actual
31HGI_ID_ = HGI_DEVICE_ID # the sentinel value
32TST_ID_ = Address("18:222222").id # the id of the test HGI80-compatible device
34TEST_CMDS = { # test command strings (no impersonation)
35 10: f"RQ --- {TST_ID_} 63:262142 --:------ 10E0 001 00",
36 11: r"RQ --- 18:000730 63:262142 --:------ 10E0 001 00",
37 20: f" I --- {TST_ID_} {TST_ID_} --:------ 30C9 003 000222",
38 21: f" I --- 18:000730 {TST_ID_} --:------ 30C9 003 000333",
39 30: f"RP --- {TST_ID_} 18:000730 --:------ 30C9 003 000444",
40 31: r"RP --- 18:000730 18:000730 --:------ 30C9 003 000555",
41 40: f" I --- {TST_ID_} --:------ {TST_ID_} 30C9 003 000666",
42 41: f" I --- 18:000730 --:------ {TST_ID_} 30C9 003 000777",
43 50: f" I --- {TST_ID_} --:------ 18:000730 30C9 003 000888",
44 51: r" I --- 18:000730 --:------ 18:000730 30C9 003 000999",
45 60: f" I --- --:------ --:------ {TST_ID_} 0008 002 00AA",
46 61: r" I --- --:------ --:------ 18:000730 0008 002 00BB",
47}
48# NOTE: HGI80 will silently discard all frames that have addr0 != 18:000730
49TEST_CMDS_FAIL_ON_HGI80 = [k for k, v in TEST_CMDS.items() if v[7:16] == TST_ID_]
52# ### FIXTURES #########################################################################
54pytestmark = pytest.mark.asyncio() # scope="module")
57@pytest.fixture()
58def gwy_config() -> _GwyConfigDictT:
59 return {
60 "config": {
61 "disable_discovery": True,
62 "disable_qos": False, # this is required for this test
63 "enforce_known_list": False,
64 },
65 "known_list": {HGI_DEVICE_ID: {}}, # req'd to thwart foreign HGI blacklisting
66 }
69@pytest.fixture()
70def gwy_dev_id() -> DeviceIdT:
71 return TST_ID_
74def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
75 metafunc.parametrize("test_idx", TEST_CMDS)
78# ### TESTS ############################################################################
81@patch( # DISABLE_STRICT_CHECKING
82 "ramses_tx.address._DBG_DISABLE_STRICT_CHECKING",
83 _DBG_DISABLE_STRICT_CHECKING,
84)
85async def _test_gwy_device(gwy: Gateway, test_idx: int) -> None:
86 """Check GWY address/type detection, and behaviour of its treatment of addr0."""
88 assert gwy._loop is asyncio.get_running_loop() # scope BUG is here
90 if not isinstance(gwy._protocol, PortProtocol) or not gwy._protocol._context:
91 assert False, "QoS protocol not enabled" # use assert, not skip
93 assert gwy.hgi # mypy
95 # we replace the (non-sentinel) gwy_id with the real gwy's actual dev_id
96 cmd_str = TEST_CMDS[test_idx].replace(TST_ID_, gwy.hgi.id)
97 # this is irrevelent for fake (virtual) gwys, as they been assigned this id
99 cmd = Command(cmd_str)
100 assert str(cmd) == cmd_str # sanity check
102 # a HGI80 (ti4310) will silently discard all frames that have addr0 != 18:000730
103 is_hgi80 = not gwy._protocol._is_evofw3 # TODO: is_hgi80?
105 assert gwy._transport # mypy
107 # NOTE: timeout values are empirical, and may need to be adjusted
108 if isinstance(gwy._transport, MqttTransport): # MQTT
109 timeout = 0.375 * 2 # intesting, fail: 0.370 work: 0.375: 0.75 margin of safety
110 elif gwy._transport.get_extra_info("virtual_rf"): # # fake
111 timeout = 0.010 * 2 # was 0.003 * 2: increased to 20ms for CI stability
112 else: # # real
113 timeout = 0.355 * 2 # intesting, fail: 0.350 work: 0.355: 0.71 margin of safety
115 try:
116 # using gwy._protocol.send_cmd() instead of gwy.async_send_cmd() as the
117 # latter may swallow the exception we wish to capture (ProtocolSendFailed)
118 pkt = await gwy._protocol.send_cmd(
119 cmd, qos=QosParams(wait_for_reply=False, timeout=timeout)
120 ) # for this test, we only need the cmd echo
121 except exc.ProtocolSendFailed:
122 if is_hgi80 and cmd_str[7:16] != HGI_DEVICE_ID:
123 return # should have failed, and has
124 raise # should not have failed, but has!
126 assert pkt is not None
128 if is_hgi80 and cmd_str[7:16] != HGI_DEVICE_ID:
129 assert False, pkt # should have failed, but has not!
131 # NOTE: both HGI80/evofw3 will swap out addr0 (only) for its own device_id
133 if cmd_str[7:16] == HGI_DEVICE_ID:
134 pkt_str = cmd_str[:7] + gwy.hgi.id + cmd_str[16:]
135 else:
136 pkt_str = cmd_str
138 assert pkt._frame == pkt_str
141# ### TESTS ############################################################################
144@pytest.mark.xdist_group(name="virt_serial")
145async def test_fake_evofw3(fake_evofw3: Gateway, test_idx: int) -> None:
146 """Check the behaviour of the fake (virtual) evofw3 against the GWY test."""
148 await _test_gwy_device(fake_evofw3, test_idx)
151@pytest.mark.xdist_group(name="virt_serial")
152async def test_fake_ti3410(fake_ti3410: Gateway, test_idx: int) -> None:
153 """Check the behaviour of the fake (virtual) HGI80 against the GWY test."""
155 await _test_gwy_device(fake_ti3410, test_idx)
158@pytest.mark.xdist_group(name="real_serial")
159async def test_mqtt_evofw3(mqtt_evofw3: Gateway, test_idx: int) -> None:
160 """Validate the GWY test against an MQTT server."""
162 await _test_gwy_device(mqtt_evofw3, test_idx)
165@pytest.mark.xdist_group(name="real_serial")
166async def test_real_evofw3(real_evofw3: Gateway, test_idx: int) -> None:
167 """Validate the GWY test against a real (physical) evofw3."""
169 await _test_gwy_device(real_evofw3, test_idx)
172@pytest.mark.xdist_group(name="real_serial")
173async def test_real_ti3410(real_ti3410: Gateway, test_idx: int) -> None:
174 """Validate the GWY test against a real (physical) HGI80."""
176 await _test_gwy_device(real_ti3410, test_idx)