ramses-rf 0.52.4__tar.gz → 0.52.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (376) hide show
  1. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/.github/workflows/publish-hatch.yml +2 -2
  2. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/PKG-INFO +1 -1
  3. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_cli/client.py +1 -0
  4. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_cli/debug.py +1 -1
  5. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_cli/utils/convert.py +2 -2
  6. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/database.py +40 -17
  7. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/device/base.py +14 -3
  8. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/device/heat.py +1 -1
  9. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/device/hvac.py +24 -21
  10. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/entity_base.py +6 -6
  11. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/gateway.py +21 -10
  12. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/schemas.py +1 -1
  13. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/system/zones.py +22 -2
  14. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/version.py +1 -1
  15. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/address.py +21 -6
  16. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/command.py +18 -2
  17. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/helpers.py +30 -10
  18. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/message.py +11 -5
  19. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/packet.py +13 -5
  20. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/parsers.py +1039 -16
  21. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/protocol.py +93 -18
  22. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/transport.py +30 -6
  23. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/version.py +1 -1
  24. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/requirements/requirements_docs.txt +3 -2
  25. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_apis_heat.py +3 -3
  26. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_schemas.py +2 -1
  27. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/test_database.py +5 -0
  28. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/test_protocol_fsm.py +3 -0
  29. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  30. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/.github/dependabot.yml +0 -0
  31. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/.github/workflows/check-cov.yml +0 -0
  32. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/.github/workflows/check-lint.yml +0 -0
  33. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/.github/workflows/check-test.yml +0 -0
  34. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/.github/workflows/check-type.yml +0 -0
  35. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/.gitignore +0 -0
  36. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/.pre-commit-config.yaml +0 -0
  37. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/LICENSE +0 -0
  38. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/README-developers.md +0 -0
  39. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/README.md +0 -0
  40. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/client.py +0 -0
  41. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/docs/Makefile +0 -0
  42. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/docs/make.bat +0 -0
  43. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/docs/source/_static/ramses_rf_logo.png +0 -0
  44. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/docs/source/binding_process.md +0 -0
  45. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/docs/source/conf.py +0 -0
  46. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/docs/source/glossary.rst +0 -0
  47. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/docs/source/index.rst +0 -0
  48. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/docs/source/modules.rst +0 -0
  49. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/docs/source/ramses_cli.rst +0 -0
  50. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/docs/source/ramses_rf.device.rst +0 -0
  51. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/docs/source/ramses_rf.rst +0 -0
  52. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/docs/source/ramses_rf.system.rst +0 -0
  53. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/docs/source/ramses_tx.rst +0 -0
  54. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/docs/source/usage.md +0 -0
  55. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/misc/2411_parser.py +0 -0
  56. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/misc/fingerprints.log +0 -0
  57. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/misc/ser2net.yaml +0 -0
  58. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/misc/ti_3410/notes.sh +0 -0
  59. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/misc/ti_3410/ti_3410.fw +0 -0
  60. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/pyproject.toml +0 -0
  61. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_cli/__init__.py +0 -0
  62. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_cli/discovery.py +0 -0
  63. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_cli/utils/cat_slow.py +0 -0
  64. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/__init__.py +0 -0
  65. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/binding_fsm.py +0 -0
  66. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/const.py +0 -0
  67. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/device/__init__.py +0 -0
  68. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/dispatcher.py +0 -0
  69. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/exceptions.py +0 -0
  70. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/helpers.py +0 -0
  71. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/py.typed +0 -0
  72. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/system/__init__.py +0 -0
  73. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/system/faultlog.py +0 -0
  74. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/system/heat.py +0 -0
  75. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_rf/system/schedule.py +0 -0
  76. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/__init__.py +0 -0
  77. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/const.py +0 -0
  78. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/exceptions.py +0 -0
  79. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/fingerprints.py +0 -0
  80. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/frame.py +0 -0
  81. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/gateway.py +0 -0
  82. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/logger.py +0 -0
  83. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/opentherm.py +0 -0
  84. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/protocol_fsm.py +0 -0
  85. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/py.typed +0 -0
  86. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/ramses.py +0 -0
  87. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/schemas.py +0 -0
  88. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/typed_dicts.py +0 -0
  89. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/ramses_tx/typing.py +0 -0
  90. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/requirements/requirements.txt +0 -0
  91. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/requirements/requirements_dev.txt +0 -0
  92. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/deprecated/_test_apis_mock.py +0 -0
  93. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/deprecated/_test_mock_faultlog.py +0 -0
  94. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/deprecated/_test_mock_schedule.py +0 -0
  95. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/deprecated/_test_packets_bad.py +0 -0
  96. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/deprecated/_test_performance_WIP.py +0 -0
  97. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/deprecated/_test_state_mgt.py +0 -0
  98. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/deprecated/common.py +0 -0
  99. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/deprecated/mocked_devices/__init__.py +0 -0
  100. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/deprecated/mocked_devices/command.py +0 -0
  101. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/deprecated/mocked_devices/const.py +0 -0
  102. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/deprecated/mocked_devices/device_heat.py +0 -0
  103. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/deprecated/mocked_devices/device_hvac.py +0 -0
  104. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/deprecated/mocked_devices/transport.py +0 -0
  105. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/__init__.py +0 -0
  106. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/bindings/heat/ctl_bdr_91t.log +0 -0
  107. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/bindings/heat/dts_ctl_sensor.log +0 -0
  108. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/bindings/heat/hcw_ctl_sensor.log +0 -0
  109. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/bindings/heat/rnd_ctl_sensor.log +0 -0
  110. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/bindings/heat/rnd_ctl_sensor.yaml +0 -0
  111. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/bindings/heat/trv_ctl.log +0 -0
  112. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/bindings/hvac/co2_fan_itho.json +0 -0
  113. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/bindings/hvac/co2_fan_itho.log +0 -0
  114. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/bindings/hvac/co2_fan_itho.yaml +0 -0
  115. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/bindings/hvac/dis_fan_orcon.json +0 -0
  116. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/bindings/hvac/dis_fan_orcon.log +0 -0
  117. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/bindings/hvac/rem_fan_climarad.log +0 -0
  118. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/bindings/hvac/rem_fan_nuaire.json +0 -0
  119. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/bindings/hvac/rem_fan_nuaire.log +0 -0
  120. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/bindings/hvac/rem_fan_nuaire.yaml +0 -0
  121. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/bindings/hvac/rem_fan_vasco.log +0 -0
  122. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/bindings/hvac/rem_fan_ventura.log +0 -0
  123. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/devices/device_02.log +0 -0
  124. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/devices/device_04.log +0 -0
  125. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/devices/device_10.log +0 -0
  126. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/devices/device_13.log +0 -0
  127. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/devices/device_22.log +0 -0
  128. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_dev_class/hvac/known_list_eavesdrop_off.json +0 -0
  129. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_dev_class/hvac/known_list_eavesdrop_on.json +0 -0
  130. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_dev_class/hvac/packet.log +0 -0
  131. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/_trv_actuator_long/packet.log +0 -0
  132. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/_trv_actuator_long/schema_eavesdrop_off.json +0 -0
  133. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/_trv_actuator_long/schema_eavesdrop_on.json +0 -0
  134. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/_trv_actuator_mixed/packet.log +0 -0
  135. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/_trv_actuator_mixed/schema_eavesdrop_off.json +0 -0
  136. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/_trv_actuator_mixed/schema_eavesdrop_on.json +0 -0
  137. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/_ufh_circuits/packet.log +0 -0
  138. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/_ufh_circuits/schema_eavesdrop_off.json +0 -0
  139. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/_ufh_circuits/schema_eavesdrop_on.json +0 -0
  140. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/app_cntrl/packet.log +0 -0
  141. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/app_cntrl/schema_eavesdrop_off.json +0 -0
  142. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/app_cntrl/schema_eavesdrop_on.json +0 -0
  143. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/trv_actuators/packet.log +0 -0
  144. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/trv_actuators/schema_eavesdrop_off.json +0 -0
  145. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/trv_actuators/schema_eavesdrop_on.json +0 -0
  146. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/zone_sensors_000/packet.log +0 -0
  147. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/zone_sensors_000/schema_eavesdrop_off.json +0 -0
  148. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/zone_sensors_000/schema_eavesdrop_on.json +0 -0
  149. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/zone_sensors_001/packet.log +0 -0
  150. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/zone_sensors_001/schema_eavesdrop_off.json +0 -0
  151. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/zone_sensors_001/schema_eavesdrop_on.json +0 -0
  152. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/zone_sensors_002/packet.log +0 -0
  153. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/zone_sensors_002/schema_eavesdrop_off.json +0 -0
  154. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/zone_sensors_002/schema_eavesdrop_on.json +0 -0
  155. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/zone_sensors_003/packet.log +0 -0
  156. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/zone_sensors_003/schema_eavesdrop_off.json +0 -0
  157. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/zone_sensors_003/schema_eavesdrop_on.json +0 -0
  158. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/zone_sensors_004/packet.log +0 -0
  159. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/zone_sensors_004/schema_eavesdrop_off.json +0 -0
  160. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/eavesdrop_schema/zone_sensors_004/schema_eavesdrop_on.json +0 -0
  161. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/10e0_xxxx.log +0 -0
  162. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/_gather.sh +0 -0
  163. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/heat/01_EvoTouch_Colour.log +0 -0
  164. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/heat/01_Evo_Color.log +0 -0
  165. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/heat/01_IONA_RAI_Prototype.log +0 -0
  166. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/heat/02_HCE80_V3.10_061117..log +0 -0
  167. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/heat/04_HR92 Radiator Ctrl_.log +0 -0
  168. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/heat/08_Jasper_EIM.log +0 -0
  169. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/heat/10_R8810A_Bridge.log +0 -0
  170. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/heat/10_R8820.log +0 -0
  171. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/heat/30_Internet_Gateway.log +0 -0
  172. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/heat/31_Jasper_Stat_TXXX.log +0 -0
  173. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/heat/34_T87RF2025.log +0 -0
  174. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/18_BRDG-02A55.log +0 -0
  175. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/18_HRA82.log +0 -0
  176. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/20.log +0 -0
  177. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/21_CCU-12T20.log +0 -0
  178. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/29_VMC-07RP01.log +0 -0
  179. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/29_VMC-15RP01.log +0 -0
  180. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/29_VMC-17RP01.log +0 -0
  181. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/29_VMN-07LM01.log +0 -0
  182. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/29_VMN-15LF01.log +0 -0
  183. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/29_VMN-17LMP01.log +0 -0
  184. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/29_VMS-15C16.log +0 -0
  185. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/29_VMS-17HB01.log +0 -0
  186. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/30_BRDG-02EM23.log +0 -0
  187. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/30_BRDG-02JAS01.log +0 -0
  188. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/32_VMC-15RPS34.log +0 -0
  189. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/32_VMD-15RMS64.log +0 -0
  190. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/32_VMD-15RMS86.log +0 -0
  191. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/32_VMD-17RPS01.log +0 -0
  192. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/32_VMN-23LM33.log +0 -0
  193. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/32_VMN-23LMH23.log +0 -0
  194. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/32_VMS-15CM17.log +0 -0
  195. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/32_VMS-23C33.log +0 -0
  196. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/32_VMS-23HB33.log +0 -0
  197. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/32_VMZ-15V13.log +0 -0
  198. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/37_VMD-07RPS13.log +0 -0
  199. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/37_VMI-15MC01log +0 -0
  200. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/37_VMI-15WSJ53.log +0 -0
  201. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/37_VMS-02J52.log +0 -0
  202. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/37_VMS-12C39.log +0 -0
  203. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/99_CVE-RF.log +0 -0
  204. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/fingerprints/hvac/99_VMS-17C01.log +0 -0
  205. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/helpers.py +0 -0
  206. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/logger/packet_in.log +0 -0
  207. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/logger/packet_out.log +0 -0
  208. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/logs/pkts_bad_000.log +0 -0
  209. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/logs/pkts_tba_000.log +0 -0
  210. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/logs/system_cache.json +0 -0
  211. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parser_helpers/pkt_addr_set.log +0 -0
  212. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parser_helpers/pkt_dev_class.log +0 -0
  213. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_0001_wip.log +0 -0
  214. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_0002.log +0 -0
  215. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_0004_wip.log +0 -0
  216. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_0005.log +0 -0
  217. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_0006.log +0 -0
  218. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_0008.log +0 -0
  219. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_0009.log +0 -0
  220. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_000a.log +0 -0
  221. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_000c.log +0 -0
  222. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_000e.log +0 -0
  223. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_01ff_wip.log +0 -0
  224. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_0418.log +0 -0
  225. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_042f.log +0 -0
  226. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_1030.log +0 -0
  227. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_1060.log +0 -0
  228. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_10d0.log +0 -0
  229. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_10e0.log +0 -0
  230. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_1260.log +0 -0
  231. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_1298.log +0 -0
  232. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_12a0.log +0 -0
  233. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_12c0.log +0 -0
  234. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_1300.log +0 -0
  235. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_1f09.log +0 -0
  236. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_1fc9.log +0 -0
  237. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_1fd4.log +0 -0
  238. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_2210.log +0 -0
  239. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_22c9.log +0 -0
  240. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_22d0.log +0 -0
  241. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_22d9.log +0 -0
  242. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_22e0.log +0 -0
  243. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_22e5.log +0 -0
  244. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_22e9.log +0 -0
  245. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_22f1.log +0 -0
  246. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_22f2.log +0 -0
  247. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_22f3.log +0 -0
  248. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_22f4.log +0 -0
  249. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_22f7.log +0 -0
  250. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_2309.log +0 -0
  251. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_2349.log +0 -0
  252. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_2411_wip.log +0 -0
  253. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_2e04.log +0 -0
  254. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_2e10_wip.log +0 -0
  255. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_30c9.log +0 -0
  256. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_3110_wip.log +0 -0
  257. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_3120.log +0 -0
  258. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_313e_wip.log +0 -0
  259. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_3150.log +0 -0
  260. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_31d9.log +0 -0
  261. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_31da.log +0 -0
  262. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_3200.log +0 -0
  263. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_3210.log +0 -0
  264. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_3220.log +0 -0
  265. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_3222.log +0 -0
  266. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_3ef0_wip.log +0 -0
  267. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_3ef1_wip.log +0 -0
  268. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_4e01.log +0 -0
  269. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_4e02.log +0 -0
  270. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_4e04.log +0 -0
  271. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/parsers/code_4e15.log +0 -0
  272. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schedules/_sched_002/packet.log +0 -0
  273. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schedules/_sched_002/schedule.json +0 -0
  274. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schedules/sched_001/packet.log +0 -0
  275. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schedules/sched_001/schedule.json +0 -0
  276. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schedules/sched_dhw/packet.log +0 -0
  277. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schedules/sched_dhw/schedule.json +0 -0
  278. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/jsn_files/schema_100.json +0 -0
  279. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/jsn_files/schema_101.json +0 -0
  280. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/jsn_files/schema_102.json +0 -0
  281. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/jsn_files/schema_103.json +0 -0
  282. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/jsn_files/schema_104.json +0 -0
  283. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/jsn_files/schema_105.json +0 -0
  284. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/jsn_files/schema_108.json +0 -0
  285. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_000.json +0 -0
  286. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_000.log +0 -0
  287. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_001.json +0 -0
  288. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_001.log +0 -0
  289. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_002.json +0 -0
  290. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_002.log +0 -0
  291. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_010.json +0 -0
  292. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_010.log +0 -0
  293. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_011.json +0 -0
  294. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_011.log +0 -0
  295. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_012.json +0 -0
  296. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_012.log +0 -0
  297. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_013.json +0 -0
  298. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_013.log +0 -0
  299. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_014.json +0 -0
  300. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_014.log +0 -0
  301. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_300.json +0 -0
  302. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_300.log +0 -0
  303. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_301.json +0 -0
  304. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_301.log +0 -0
  305. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_302.json +0 -0
  306. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_302.log +0 -0
  307. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_303.json +0 -0
  308. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_303.log +0 -0
  309. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_304.json +0 -0
  310. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_304.log +0 -0
  311. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_310.json +0 -0
  312. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/schemas/log_files/schema_310.log +0 -0
  313. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/_heat_trv_00/config.json +0 -0
  314. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/_heat_trv_00/packet.log +0 -0
  315. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/_heat_trv_00/schema.json +0 -0
  316. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/_hvac_nuaire/config.json +0 -0
  317. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/_hvac_nuaire/known_list.json +0 -0
  318. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/_hvac_nuaire/packet.log +0 -0
  319. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/_hvac_nuaire/schema.json +0 -0
  320. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/_hvac_nuaire/status.json +0 -0
  321. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/heat_otb_00/config.json +0 -0
  322. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/heat_otb_00/packet.log +0 -0
  323. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/heat_otb_00/schema.json +0 -0
  324. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/heat_simple/packet.log +0 -0
  325. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/heat_simple/schema.json +0 -0
  326. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/heat_ufc_00/config.json +0 -0
  327. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/heat_ufc_00/packet.log +0 -0
  328. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/heat_ufc_01/packet.log +0 -0
  329. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/heat_zxdavb/config.json +0 -0
  330. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/heat_zxdavb/known_list.json +0 -0
  331. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/heat_zxdavb/packet.log +0 -0
  332. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/systems/heat_zxdavb/schema.json +0 -0
  333. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_api_faultlog.py +0 -0
  334. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_api_schedule.py +0 -0
  335. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_apis_binding.py +0 -0
  336. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_apis_common.py +0 -0
  337. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_apis_hvac.py +0 -0
  338. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_cli_utility.py +0 -0
  339. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_devices.py +0 -0
  340. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_eavesdrop_dev_class.py +0 -0
  341. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_eavesdrop_schema.py +0 -0
  342. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_helpers.py +0 -0
  343. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_parser_helpers.py +0 -0
  344. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_parsers.py +0 -0
  345. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_ramses_schema.py +0 -0
  346. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_schema_bits.py +0 -0
  347. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_systems.py +2 -2
  348. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests/test_vol_schemas.py +0 -0
  349. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/__init__.py +0 -0
  350. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/configs/config_heat.json +0 -0
  351. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/configs/config_hvac.json +0 -0
  352. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/conftest.py +0 -0
  353. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/device/__init__.py +0 -0
  354. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/device/test_hvac_ventilator.py +0 -0
  355. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/logs/test_api_faultlog.log +0 -0
  356. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/test_api_faultlog.py +0 -0
  357. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/test_api_schedule.py +0 -0
  358. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/test_binding_fsm.py +0 -0
  359. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/test_create_stack.py +0 -0
  360. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/test_dispatcher.py +0 -0
  361. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/test_entity_base.py +0 -0
  362. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/test_hgi_behaviors.py +0 -0
  363. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/test_use_regex.py +0 -0
  364. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/test_virt_network.py +0 -0
  365. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/virtual_rf/__init__.py +0 -0
  366. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/virtual_rf/const.py +0 -0
  367. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/virtual_rf/helpers.py +0 -0
  368. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_rf/virtual_rf/virtual_rf.py +0 -0
  369. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_tx/__init__.py +0 -0
  370. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/tests_tx/test_command.py +0 -0
  371. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/wip/_test_eavesdrop_dhw_sensor.py +0 -0
  372. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/wip/_test_eavesdrop_htg_control.py +0 -0
  373. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/wip/_test_eavesdrop_ufc_circuits.py +0 -0
  374. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/wip/_test_eavesdrop_zone_sensors.py +0 -0
  375. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/wip/_test_eavesdrop_zone_type.py +0 -0
  376. {ramses_rf-0.52.4 → ramses_rf-0.52.5}/tests/wip/test_wip_cli.sh +0 -0
@@ -26,7 +26,7 @@ jobs:
26
26
  run: hatch build
27
27
 
28
28
  - name: Upload dist
29
- uses: actions/upload-artifact@v5
29
+ uses: actions/upload-artifact@v6
30
30
  with:
31
31
  name: dist
32
32
  path: dist/
@@ -40,7 +40,7 @@ jobs:
40
40
  url: https://pypi.org/project/ramses_rf
41
41
  steps:
42
42
  - name: Download dist
43
- uses: actions/download-artifact@v6
43
+ uses: actions/download-artifact@v7
44
44
  with:
45
45
  name: dist
46
46
  path: dist/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ramses_rf
3
- Version: 0.52.4
3
+ Version: 0.52.5
4
4
  Summary: A stateful RAMSES-II protocol decoder & analyser.
5
5
  Project-URL: Homepage, https://github.com/ramses-rf/ramses_rf
6
6
  Project-URL: Bug Tracker, https://github.com/ramses-rf/ramses_rf/issues
@@ -588,6 +588,7 @@ def main() -> None:
588
588
  print(" - event_loop_policy set for win32") # do before asyncio.run()
589
589
  asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
590
590
 
591
+ profile = None
591
592
  try:
592
593
  if _PROFILE_LIBRARY:
593
594
  profile = cProfile.Profile()
@@ -9,7 +9,7 @@ DEBUG_PORT = 5678
9
9
 
10
10
 
11
11
  def start_debugging(wait_for_client: bool) -> None:
12
- import debugpy # type: ignore[import-untyped]
12
+ import debugpy
13
13
 
14
14
  debugpy.listen(address=(DEBUG_ADDR, DEBUG_PORT))
15
15
  print(f" - Debugging is enabled, listening on: {DEBUG_ADDR}:{DEBUG_PORT}")
@@ -17,7 +17,7 @@ parser.add_argument("-i", "--input-file", type=argparse.FileType("r"), default="
17
17
  args = parser.parse_args()
18
18
 
19
19
 
20
- def convert_json_to_yaml(data: dict) -> str:
20
+ def convert_json_to_yaml(data: dict) -> None:
21
21
  """Convert from json (client.py -C config.json) to yaml (HA configuration.yaml)."""
22
22
  (config, schema, include, exclude) = load_config("/dev/ttyMOCK", None, **data)
23
23
 
@@ -37,7 +37,7 @@ def convert_json_to_yaml(data: dict) -> str:
37
37
  print(yaml.dump({"ramses_cc": result}, sort_keys=False))
38
38
 
39
39
 
40
- def convert_yaml_to_json(data: dict) -> str:
40
+ def convert_yaml_to_json(data: dict) -> None:
41
41
  """Convert from yaml (HA configuration.yaml) to json (client.py -C config.json)."""
42
42
 
43
43
  result = data["ramses_cc"]
@@ -30,7 +30,7 @@ from collections import OrderedDict
30
30
  from datetime import datetime as dt, timedelta as td
31
31
  from typing import TYPE_CHECKING, Any, NewType
32
32
 
33
- from ramses_tx import CODES_SCHEMA, Code, Message
33
+ from ramses_tx import CODES_SCHEMA, RQ, Code, Message, Packet
34
34
 
35
35
  if TYPE_CHECKING:
36
36
  DtmStrT = NewType("DtmStrT", str)
@@ -155,7 +155,7 @@ class MessageIndex:
155
155
  - verb " I", "RQ" etc.
156
156
  - src message origin address
157
157
  - dst message destination address
158
- - code packet code aka command class e.g. _0005, _31DA
158
+ - code packet code aka command class e.g. 0005, 31DA
159
159
  - ctx message context, created from payload as index + extra markers (Heat)
160
160
  - hdr packet header e.g. 000C|RP|01:223036|0208 (see: src/ramses_tx/frame.py)
161
161
  - plk the keys stored in the parsed payload, separated by the | char
@@ -187,7 +187,7 @@ class MessageIndex:
187
187
 
188
188
  async def _housekeeping_loop(self) -> None:
189
189
  """Periodically remove stale messages from the index,
190
- unless `self.maintain` is False."""
190
+ unless `self.maintain` is False - as in (most) tests."""
191
191
 
192
192
  async def housekeeping(dt_now: dt, _cutoff: td = td(days=1)) -> None:
193
193
  """
@@ -195,7 +195,8 @@ class MessageIndex:
195
195
  :param dt_now: current timestamp
196
196
  :param _cutoff: the oldest timestamp to retain, default is 24 hours ago
197
197
  """
198
- dtm = dt_now - _cutoff # .isoformat(timespec="microseconds") < needed?
198
+ msgs = None
199
+ dtm = dt_now - _cutoff
199
200
 
200
201
  self._cu.execute("SELECT dtm FROM messages WHERE dtm >= ?", (dtm,))
201
202
  rows = self._cu.fetchall() # fetch dtm of current messages to retain
@@ -212,6 +213,10 @@ class MessageIndex:
212
213
  self._msgs = msgs
213
214
  finally:
214
215
  self._lock.release()
216
+ if msgs:
217
+ _LOGGER.debug(
218
+ "MessageIndex size was: %d, now: %d", len(rows), len(msgs)
219
+ )
215
220
 
216
221
  while True:
217
222
  self._last_housekeeping = dt.now()
@@ -246,13 +251,17 @@ class MessageIndex:
246
251
  else:
247
252
  # _msgs dict requires a timestamp reformat
248
253
  dtm: DtmStrT = msg.dtm.isoformat(timespec="microseconds") # type: ignore[assignment]
254
+ # add msg to self._msgs dict
249
255
  self._msgs[dtm] = msg
250
256
 
251
257
  finally:
252
258
  pass # self._lock.release()
253
259
 
254
260
  if (
255
- dup and msg.src is not msg.dst and not msg.dst.id.startswith("18:") # HGI
261
+ dup
262
+ and (msg.src is not msg.dst)
263
+ and not msg.dst.id.startswith("18:") # HGI
264
+ and msg.verb != RQ # these may come very quickly
256
265
  ): # when src==dst, expect to add duplicate, don't warn
257
266
  _LOGGER.debug(
258
267
  "Overwrote dtm (%s) for %s: %s (contrived log?)",
@@ -260,8 +269,6 @@ class MessageIndex:
260
269
  msg._pkt._hdr,
261
270
  dup[0]._pkt,
262
271
  )
263
- if old is not None:
264
- _LOGGER.debug("Old msg replaced: %s", old)
265
272
 
266
273
  return old
267
274
 
@@ -274,7 +281,8 @@ class MessageIndex:
274
281
  :param verb: two letter verb str to use
275
282
  """
276
283
  # Used by OtbGateway init, via entity_base.py
277
- dtm: DtmStrT = dt.strftime(dt.now(), "%Y-%m-%dT%H:%M:%S") # type: ignore[assignment]
284
+ _now: dt = dt.now()
285
+ dtm: DtmStrT = _now.isoformat(timespec="microseconds") # type: ignore[assignment]
278
286
  hdr = f"{code}|{verb}|{src}|00" # dummy record has no contents
279
287
 
280
288
  dup = self._delete_from(hdr=hdr)
@@ -287,7 +295,7 @@ class MessageIndex:
287
295
  self._cu.execute(
288
296
  sql,
289
297
  (
290
- dtm,
298
+ _now,
291
299
  verb,
292
300
  src,
293
301
  src,
@@ -299,6 +307,14 @@ class MessageIndex:
299
307
  )
300
308
  except sqlite3.Error:
301
309
  self._cx.rollback()
310
+ else:
311
+ # also add dummy 3220 msg to self._msgs dict to allow maintenance loop
312
+ msg: Message = Message._from_pkt(
313
+ Packet(
314
+ _now, f"... {verb} --- {src} --:------ {src} {code} 005 0000000000"
315
+ )
316
+ )
317
+ self._msgs[dtm] = msg
302
318
 
303
319
  if dup: # expected when more than one heat system in schema
304
320
  _LOGGER.debug("Replaced record with same hdr: %s", hdr)
@@ -359,7 +375,7 @@ class MessageIndex:
359
375
  if not bool(msg) ^ bool(kwargs):
360
376
  raise ValueError("Either a Message or kwargs should be provided, not both")
361
377
  if msg:
362
- kwargs["dtm"] = msg.dtm # .isoformat(timespec="microseconds")
378
+ kwargs["dtm"] = msg.dtm
363
379
 
364
380
  msgs = None
365
381
  try: # make this operation atomic, i.e. update self._msgs only on success
@@ -410,7 +426,7 @@ class MessageIndex:
410
426
  raise ValueError("Either a Message or kwargs should be provided, not both")
411
427
 
412
428
  if msg:
413
- kwargs["dtm"] = msg.dtm # .isoformat(timespec="microseconds")
429
+ kwargs["dtm"] = msg.dtm
414
430
 
415
431
  return self._select_from(**kwargs)
416
432
 
@@ -432,10 +448,15 @@ class MessageIndex:
432
448
  :returns: a tuple of qualifying messages
433
449
  """
434
450
 
435
- return tuple(
436
- self._msgs[row[0].isoformat(timespec="microseconds")]
437
- for row in self.qry_dtms(**kwargs)
438
- )
451
+ # CHANGE: Use a list comprehension with a check to avoid KeyError
452
+ res: list[Message] = []
453
+ for row in self.qry_dtms(**kwargs):
454
+ ts: DtmStrT = row[0].isoformat(timespec="microseconds")
455
+ if ts in self._msgs:
456
+ res.append(self._msgs[ts])
457
+ else:
458
+ _LOGGER.debug("MessageIndex timestamp %s not in device messages", ts)
459
+ return tuple(res)
439
460
 
440
461
  def qry_dtms(self, **kwargs: bool | dt | str) -> list[Any]:
441
462
  """
@@ -487,6 +508,7 @@ class MessageIndex:
487
508
  # _msgs stamp format: 2022-09-08T13:40:52.447364
488
509
  if ts in self._msgs:
489
510
  lst.append(self._msgs[ts])
511
+ # _LOGGER.debug("MessageIndex ts %s added to qry.lst", ts) # too frequent
490
512
  else: # happens in tests with artificial msg from heat
491
513
  _LOGGER.info("MessageIndex timestamp %s not in device messages", ts)
492
514
  return tuple(lst)
@@ -550,8 +572,9 @@ class MessageIndex:
550
572
  if ts in self._msgs:
551
573
  # if include_expired or not self._msgs[ts].HAS_EXPIRED: # not working
552
574
  lst.append(self._msgs[ts])
553
- else: # happens in tests with dummy msg from heat init
554
- _LOGGER.info("MessageIndex ts %s not in device messages", ts)
575
+ _LOGGER.debug("MessageIndex ts %s added to all.lst", ts)
576
+ else: # happens in tests and real evohome setups with dummy msg from heat init
577
+ _LOGGER.debug("MessageIndex ts %s not in device messages", ts)
555
578
  return tuple(lst)
556
579
 
557
580
  def clr(self) -> None:
@@ -87,9 +87,10 @@ class DeviceBase(Entity):
87
87
  return self.id < other.id # type: ignore[no-any-return]
88
88
 
89
89
  def _update_traits(self, **traits: Any) -> None:
90
- """Update a device with new schema attrs.
90
+ """Update a device with new schema attributes.
91
91
 
92
- Raise an exception if the new schema is not a superset of the existing schema.
92
+ :param traits: The traits to apply (e.g., alias, class, faked)
93
+ :raises TypeError: If the device is not fakeable but 'faked' is set.
93
94
  """
94
95
 
95
96
  traits = shrink(SCH_TRAITS(traits))
@@ -342,7 +343,17 @@ class Fakeable(DeviceBase):
342
343
  idx: IndexT = "00",
343
344
  require_ratify: bool = False,
344
345
  ) -> tuple[Packet, Packet, Packet, Packet | None]:
345
- """Listen for a binding and return the Offer, or raise an exception."""
346
+ """Listen for a binding and return the Offer packets.
347
+
348
+ :param accept_codes: The codes allowed for this binding
349
+ :type accept_codes: Iterable[Code]
350
+ :param idx: The index to bind to, defaults to "00"
351
+ :type idx: IndexT
352
+ :param require_ratify: Whether a ratification step is required, defaults to False
353
+ :type require_ratify: bool
354
+ :return: A tuple of the four binding transaction packets
355
+ :rtype: tuple[Packet, Packet, Packet, Packet | None]
356
+ """
346
357
 
347
358
  if not self._bind_context:
348
359
  raise TypeError(f"{self}: Faking not enabled")
@@ -669,7 +669,7 @@ class OtbGateway(Actuator, HeatDemand): # OTB (10): 3220 (22D9, others)
669
669
 
670
670
  # TODO(eb): cleanup
671
671
  if self._gwy.msg_db:
672
- self._add_record(address=self.addr, code=Code._3220, verb="RP")
672
+ self._add_record(id=self.id, code=Code._3220, verb="RP")
673
673
  # adds a "sim" RP opentherm_msg to the SQLite MessageIndex with code _3220
674
674
  # causes exc when fetching ALL, when no "real" msg was added to _msgs_. We skip those.
675
675
  else:
@@ -921,27 +921,30 @@ class HvacVentilator(FilterChange): # FAN: RP/31DA, I/31D[9A], 2411
921
921
 
922
922
  :return: string describing fan mode, speed
923
923
  """
924
- if self._gwy.msg_db:
925
- # Use SQLite query on MessageIndex. res_rate/res_mode not exposed yet
926
- sql = f"""
927
- SELECT code from messages WHERE verb in (' I', 'RP')
928
- AND (src = ? OR dst = ?)
929
- AND (plk LIKE '%{SZ_FAN_MODE}%')
930
- """
931
- res_mode: list = self._msg_qry(sql)
932
- # SQLite query on MessageIndex
933
- _LOGGER.debug(f"{res_mode} # FAN_MODE FETCHED from MessageIndex")
934
-
935
- sql = f"""
936
- SELECT code from messages WHERE verb in (' I', 'RP')
937
- AND (src = ? OR dst = ?)
938
- AND (plk LIKE '%{SZ_FAN_RATE}%')
939
- """
940
- res_rate: list = self._msg_qry(sql)
941
- # SQLite query on MessageIndex
942
- _LOGGER.debug(
943
- f"{res_rate} # FAN_RATE FETCHED from MessageIndex"
944
- ) # DEBUG always empty?
924
+ # if self._gwy.msg_db:
925
+ # Use SQLite query on MessageIndex. res_rate/res_mode not exposed yet
926
+ # working fine in 0.52.4, no need to specify code, only payload key
927
+ # sql = f"""
928
+ # SELECT code from messages WHERE verb in (' I', 'RP')
929
+ # AND (src = ? OR dst = ?)
930
+ # AND (plk LIKE '%{SZ_FAN_MODE}%')
931
+ # """
932
+ # res_mode: list = self._msg_qry(sql)
933
+ # # SQLite query on MessageIndex
934
+ # _LOGGER.debug(
935
+ # f"# Fetched FAN_MODE for {self.id} from MessageIndex: {res_mode}"
936
+ # )
937
+
938
+ # sql = f"""
939
+ # SELECT code from messages WHERE verb in (' I', 'RP')
940
+ # AND (src = ? OR dst = ?)
941
+ # AND (plk LIKE '%{SZ_FAN_RATE}%')
942
+ # """
943
+ # res_rate: list = self._msg_qry(sql)
944
+ # # SQLite query on MessageIndex
945
+ # _LOGGER.debug(
946
+ # f"# Fetched FAN_RATE for {self.id} from MessageIndex: {res_rate}"
947
+ # )
945
948
 
946
949
  if Code._31D9 in self._msgs:
947
950
  # was a dict by Code
@@ -15,7 +15,7 @@ from types import ModuleType
15
15
  from typing import TYPE_CHECKING, Any, Final
16
16
 
17
17
  from ramses_rf.helpers import schedule_task
18
- from ramses_tx import Address, Priority, QosParams
18
+ from ramses_tx import Priority, QosParams
19
19
  from ramses_tx.address import ALL_DEVICE_ID
20
20
  from ramses_tx.const import MsgId
21
21
  from ramses_tx.opentherm import OPENTHERM_MESSAGES
@@ -247,7 +247,7 @@ class _MessageDB(_Entity):
247
247
 
248
248
  if self._gwy.msg_db: # central SQLite MessageIndex
249
249
  _LOGGER.debug(
250
- "For %s (_z_id %s) add msg %s, src %s, dst %s to msg_db.",
250
+ "For %s (_z_id %s) add to msg_db: %s, src %s, dst %s",
251
251
  self.id,
252
252
  self._z_id,
253
253
  msg,
@@ -324,12 +324,12 @@ class _MessageDB(_Entity):
324
324
  ]
325
325
 
326
326
  def _add_record(
327
- self, address: Address, code: Code | None = None, verb: str = " I"
327
+ self, id: DeviceIdT, code: Code | None = None, verb: str = " I"
328
328
  ) -> None:
329
329
  """Add a (dummy) record to the central SQLite MessageIndex."""
330
330
  # used by heat.py init
331
331
  if self._gwy.msg_db:
332
- self._gwy.msg_db.add_record(str(address), code=str(code), verb=verb)
332
+ self._gwy.msg_db.add_record(id, code=str(code), verb=verb)
333
333
  # else:
334
334
  # _LOGGER.warning("Missing MessageIndex")
335
335
  # raise NotImplementedError
@@ -1029,7 +1029,7 @@ class _Discovery(_MessageDB):
1029
1029
  sql = """
1030
1030
  SELECT dtm from messages WHERE
1031
1031
  code = ?
1032
- AND verb = ' I'
1032
+ AND verb in (' I', 'RP')
1033
1033
  AND ctx = 'True'
1034
1034
  AND (src = ? OR dst = ?)
1035
1035
  """
@@ -1045,7 +1045,7 @@ class _Discovery(_MessageDB):
1045
1045
  msgs += res[0] # expect 1 Message in returned tuple
1046
1046
  else:
1047
1047
  _LOGGER.debug(
1048
- f"No msg found for hdr {hdr}, tesk code {task[_SZ_COMMAND].code}"
1048
+ f"No msg found for hdr {hdr}, task code {task[_SZ_COMMAND].code}"
1049
1049
  )
1050
1050
  else: # TODO(eb) remove next Q1 2026
1051
1051
  msgs += [self.tcs._msgz[task[_SZ_COMMAND].code][I_][True]]
@@ -360,13 +360,25 @@ class Gateway(Engine):
360
360
  child_id: str | None = None,
361
361
  is_sensor: bool | None = None,
362
362
  ) -> Device: # TODO: **schema/traits) -> Device: # may: LookupError
363
- """Return a device, create it if required.
364
-
365
- First, use the traits to create/update it, then pass it any msg to handle.
366
- All devices have traits, but only controllers (CTL, UFC) have a schema.
367
-
368
- Devices are uniquely identified by a device id.
369
- If a device is created, attach it to the gateway.
363
+ """Return a device, creating it if it does not already exist.
364
+
365
+ This method uses provided traits to create or update a device and optionally
366
+ passes a message for it to handle. All devices have traits, but only
367
+ controllers (CTL, UFC) have a schema.
368
+
369
+ :param device_id: The unique identifier for the device (e.g., '01:123456')
370
+ :type device_id: DeviceIdT
371
+ :param msg: An optional initial message for the device to process, defaults to None
372
+ :type msg: Message | None
373
+ :param parent: The parent entity of this device, if any, defaults to None
374
+ :type parent: Parent | None
375
+ :param child_id: The specific ID of the child component if applicable, defaults to None
376
+ :type child_id: str | None
377
+ :param is_sensor: Indicates if this device should be treated as a sensor, defaults to None
378
+ :type is_sensor: bool | None
379
+ :return: The existing or newly created device instance
380
+ :rtype: Device
381
+ :raises LookupError: If the device ID is blocked or not in the allowed known_list.
370
382
  """
371
383
 
372
384
  def check_filter_lists(dev_id: DeviceIdT) -> None: # may: LookupError
@@ -620,9 +632,8 @@ class Gateway(Engine):
620
632
  If wait_for_reply is True (*and* the Command has a rx_header), return the
621
633
  reply Packet. Otherwise, simply return the echo Packet.
622
634
 
623
- If the expected Packet can't be returned, raise:
624
- ProtocolSendFailed: tried to Tx Command, but didn't get echo/reply
625
- ProtocolError: didn't attempt to Tx Command for some reason
635
+ :raises ProtocolSendFailed: If the command was sent but no reply/echo was received.
636
+ :raises ProtocolError: If the system failed to attempt the transmission.
626
637
  """
627
638
 
628
639
  return await super().async_send_cmd(
@@ -285,7 +285,7 @@ SCH_GLOBAL_CONFIG = (
285
285
  #
286
286
  # 6/7: External Schemas, to be used by clients of this library
287
287
  def NormaliseRestoreCache() -> Callable[[bool | dict[str, bool]], dict[str, bool]]:
288
- """Convert a short-hand restore_cache bool to a dict.
288
+ """Convert a shorthand restore_cache bool to a dict.
289
289
 
290
290
  restore_cache: bool -> restore_cache:
291
291
  restore_schema: bool
@@ -36,7 +36,7 @@ from ramses_rf.device import (
36
36
  TrvActuator,
37
37
  UfhController,
38
38
  )
39
- from ramses_rf.entity_base import Child, Entity, Parent, class_by_attr
39
+ from ramses_rf.entity_base import _ID_SLICE, Child, Entity, Parent, class_by_attr
40
40
  from ramses_rf.helpers import shrink
41
41
  from ramses_rf.schemas import (
42
42
  SCH_TCS_DHW,
@@ -728,13 +728,33 @@ class Zone(ZoneSchedule):
728
728
  """Set the target temperature, until the next scheduled setpoint."""
729
729
 
730
730
  if value is None:
731
- return self.reset_mode()
731
+ self.reset_mode()
732
732
 
733
733
  cmd = Command.set_zone_setpoint(self.ctl.id, self.idx, value)
734
734
  self._gwy.send_cmd(cmd, priority=Priority.HIGH)
735
735
 
736
736
  @property
737
737
  def temperature(self) -> float | None: # 30C9
738
+ if self._gwy.msg_db:
739
+ # evohome zones only get initial temp from src + idx, so use zone sensor if newer
740
+ sql = f"""
741
+ SELECT dtm from messages WHERE verb in (' I', 'RP')
742
+ AND code = '30C9'
743
+ AND (plk LIKE '%{SZ_TEMPERATURE}%')
744
+ AND ((src = ? AND ctx = ?) OR src = ?)
745
+ """
746
+ sensor_id = "aa:aaaaaa" # should not match any device_id
747
+ if self._sensor:
748
+ sensor_id = self._sensor.id
749
+ # custom SQLite query on MessageIndex
750
+ msgs = self._gwy.msg_db.qry(
751
+ sql, (self.id[:_ID_SLICE], self.idx, sensor_id[:_ID_SLICE])
752
+ )
753
+ if msgs and len(msgs) > 0:
754
+ msgs_sorted = sorted(msgs, reverse=True)
755
+ return msgs_sorted[0].payload.get(SZ_TEMPERATURE) # type: ignore[no-any-return]
756
+ return None
757
+ # else: TODO Q1 2026 remove remainder
738
758
  return self._msg_value(Code._30C9, key=SZ_TEMPERATURE) # type: ignore[no-any-return]
739
759
 
740
760
  @property
@@ -1,4 +1,4 @@
1
1
  """RAMSES RF - a RAMSES-II protocol decoder & analyser (application layer)."""
2
2
 
3
- __version__ = "0.52.4"
3
+ __version__ = "0.52.5"
4
4
  VERSION = __version__
@@ -39,7 +39,12 @@ class Address:
39
39
  _SLUG = None
40
40
 
41
41
  def __init__(self, device_id: DeviceIdT) -> None:
42
- """Create an address from a valid device id."""
42
+ """Create an address from a valid device ID.
43
+
44
+ :param device_id: The RAMSES II device ID (e.g., '01:123456')
45
+ :type device_id: DeviceIdT
46
+ :raises ValueError: If the device_id is not a valid format.
47
+ """
43
48
 
44
49
  # if device_id is None:
45
50
  # device_id = NON_DEVICE_ID
@@ -91,7 +96,15 @@ class Address:
91
96
 
92
97
  @classmethod
93
98
  def convert_from_hex(cls, device_hex: str, friendly_id: bool = False) -> str:
94
- """Convert (say) '06368E' to '01:145038' (or 'CTL:145038')."""
99
+ """Convert a 6-character hex string to a device ID.
100
+
101
+ :param device_hex: The hex string to convert (e.g., '06368E')
102
+ :type device_hex: str
103
+ :param friendly_id: If True, returns a named ID (e.g., 'CTL:145038'), defaults to False
104
+ :type friendly_id: bool
105
+ :return: The formatted device ID string
106
+ :rtype: str
107
+ """
95
108
 
96
109
  if device_hex == "FFFFFE": # aka '63:262142'
97
110
  return ">null dev<" if friendly_id else ALL_DEVICE_ID
@@ -191,11 +204,13 @@ def is_valid_dev_id(value: str, dev_class: None | str = None) -> bool:
191
204
 
192
205
  @lru_cache(maxsize=256) # there is definite benefit in caching this
193
206
  def pkt_addrs(addr_fragment: str) -> tuple[Address, Address, Address, Address, Address]:
194
- """Return the address fields from (e.g): '01:078710 --:------ 01:144246'.
195
-
196
- returns: src_addr, dst_addr, addr_0, addr_1, addr_2
207
+ """Parse address fields from a 30-character address fragment.
197
208
 
198
- Will raise an InvalidAddrSetError if the address fields are not valid.
209
+ :param addr_fragment: The 30-char fragment (e.g., '01:078710 --:------ 01:144246')
210
+ :type addr_fragment: str
211
+ :return: A tuple of (src_addr, dst_addr, addr_0, addr_1, addr_2)
212
+ :rtype: tuple[Address, Address, Address, Address, Address]
213
+ :raises PacketAddrSetInvalid: If the address fields are not valid.
199
214
  """
200
215
  # for debug: print(pkt_addrs.cache_info())
201
216
 
@@ -2254,7 +2254,13 @@ class Command(Frame):
2254
2254
 
2255
2255
  @classmethod # constructor for RQ|2E04
2256
2256
  def get_system_mode(cls, ctl_id: DeviceIdT | str) -> Command:
2257
- """Constructor to get the mode of a system (c.f. parser_2e04)."""
2257
+ """Get the mode of a system (c.f. parser_2e04).
2258
+
2259
+ :param ctl_id: The device ID of the controller
2260
+ :type ctl_id: DeviceIdT | str
2261
+ :return: A Command object for the RQ|2E04 message
2262
+ :rtype: Command
2263
+ """
2258
2264
 
2259
2265
  return cls.from_attrs(RQ, ctl_id, Code._2E04, FF)
2260
2266
 
@@ -2453,7 +2459,17 @@ class Command(Frame):
2453
2459
  datetime: dt | str,
2454
2460
  is_dst: bool = False,
2455
2461
  ) -> Command:
2456
- """Constructor to set the datetime of a system (c.f. parser_313f)."""
2462
+ """Set the datetime of a system (c.f. parser_313f).
2463
+
2464
+ :param ctl_id: The device ID of the controller
2465
+ :type ctl_id: DeviceIdT | str
2466
+ :param datetime: The target date and time
2467
+ :type datetime: dt | str
2468
+ :param is_dst: Whether Daylight Saving Time is active, defaults to False
2469
+ :type is_dst: bool
2470
+ :return: A Command object for the W|313F message
2471
+ :rtype: Command
2472
+ """
2457
2473
  # .W --- 30:185469 01:037519 --:------ 313F 009 0060003A0C1B0107E5
2458
2474
 
2459
2475
  dt_str = hex_from_dtm(datetime, is_dst=is_dst, incl_seconds=True)
@@ -131,28 +131,36 @@ file_time = _FILE_TIME()
131
131
  def timestamp() -> float:
132
132
  """Return the number of seconds since the Unix epoch.
133
133
 
134
- Return an accurate value, even for Windows-based systems.
134
+ This function attempts to return a high-precision value, using specific
135
+ system calls on Windows if available.
136
+ :return: The current timestamp in seconds.
137
+ :rtype: float
135
138
  """
136
139
 
137
140
  # see: https://www.python.org/dev/peps/pep-0564/
138
- if sys.platform != "win32": # since 1970-01-01T00:00:00Z, time.gmtime(0)
141
+ if sys.platform == "win32":
142
+ # Windows uses a different epoch (1601-01-01)
143
+ ctypes.windll.kernel32.GetSystemTimePreciseAsFileTime(ctypes.byref(file_time))
144
+ _time = (file_time.dwLowDateTime + (file_time.dwHighDateTime << 32)) / 1e7
145
+ return float(_time - 134774 * 24 * 60 * 60)
146
+ else:
147
+ # Linux/macOS uses the Unix epoch (1970-01-01)
139
148
  return time.time_ns() / 1e9
140
149
 
141
- # otherwise, is since 1601-01-01T00:00:00Z
142
- ctypes.windll.kernel32.GetSystemTimePreciseAsFileTime(ctypes.byref(file_time)) # type: ignore[unreachable]
143
- _time = (file_time.dwLowDateTime + (file_time.dwHighDateTime << 32)) / 1e7
144
- return _time - 134774 * 24 * 60 * 60
145
-
146
150
 
147
151
  def dt_now() -> dt:
148
152
  """Return the current datetime as a local/naive datetime object.
149
153
 
150
154
  This is slower, but potentially more accurate, than dt.now(), and is used mainly for
151
155
  packet timestamps.
156
+
157
+ :return: The current local datetime.
158
+ :rtype: dt
152
159
  """
153
160
  if sys.platform == "win32":
154
161
  return dt.fromtimestamp(timestamp())
155
- return dt.now()
162
+ else:
163
+ return dt.now()
156
164
 
157
165
 
158
166
  def dt_str() -> str:
@@ -369,7 +377,14 @@ def hex_from_str(value: str) -> str:
369
377
 
370
378
 
371
379
  def hex_to_temp(value: HexStr4) -> bool | float | None: # TODO: remove bool
372
- """Convert a 2's complement 4-byte hex string to a float."""
380
+ """Convert a 4-byte 2's complement hex string to a float temperature ('C).
381
+
382
+ :param value: The 4-character hex string (e.g., '07D0')
383
+ :type value: HexStr4
384
+ :return: The temperature in Celsius, or None if N/A
385
+ :rtype: float | None
386
+ :raises ValueError: If input is not a 4-char hex string or temperature is invalid.
387
+ """
373
388
  if not isinstance(value, str) or len(value) != 4:
374
389
  raise ValueError(f"Invalid value: {value}, is not a 4-char hex string")
375
390
  if value == "31FF": # means: N/A (== 127.99, 2s complement), signed?
@@ -488,13 +503,18 @@ AIR_QUALITY_BASIS: dict[str, str] = {
488
503
 
489
504
  # 31DA[2:6] and 12C8[2:6]
490
505
  def parse_air_quality(value: HexStr4) -> PayDictT.AIR_QUALITY:
491
- """Return the air quality (%): poor (0.0) to excellent (1.0).
506
+ """Return the air quality percentage (0.0 to 1.0) and its basis.
492
507
 
493
508
  The basis of the air quality level should be one of: VOC, CO2 or relative humidity.
494
509
  If air_quality is EF, air_quality_basis should be 00.
495
510
 
496
511
  The sensor value is None if there is no sensor present (is not an error).
497
512
  The dict does not include the key if there is a sensor fault.
513
+
514
+ :param value: The 4-character hex string encoding quality and basis
515
+ :type value: HexStr4
516
+ :return: A dictionary containing the air quality and its basis (e.g., CO2, VOC)
517
+ :rtype: PayDictT.AIR_QUALITY
498
518
  """ # VOC: Volatile organic compounds
499
519
 
500
520
  # TODO: remove this as API used only internally...