PyPlumIO 0.5.50__tar.gz → 0.5.52__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 (177) hide show
  1. {pyplumio-0.5.50 → pyplumio-0.5.52}/.pre-commit-config.yaml +1 -1
  2. {pyplumio-0.5.50 → pyplumio-0.5.52}/.qlty/qlty.toml +1 -1
  3. {pyplumio-0.5.50 → pyplumio-0.5.52}/PKG-INFO +9 -9
  4. {pyplumio-0.5.50 → pyplumio-0.5.52}/PyPlumIO.egg-info/PKG-INFO +9 -9
  5. {pyplumio-0.5.50 → pyplumio-0.5.52}/PyPlumIO.egg-info/SOURCES.txt +13 -3
  6. {pyplumio-0.5.50 → pyplumio-0.5.52}/PyPlumIO.egg-info/requires.txt +8 -8
  7. {pyplumio-0.5.50 → pyplumio-0.5.52}/docs/source/index.rst +1 -1
  8. {pyplumio-0.5.50 → pyplumio-0.5.52}/docs/source/schedules.rst +3 -3
  9. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/_version.py +2 -2
  10. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/const.py +0 -7
  11. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/devices/__init__.py +4 -4
  12. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/devices/ecomax.py +6 -11
  13. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/filters.py +1 -1
  14. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/frames/__init__.py +8 -4
  15. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/parameters/__init__.py +65 -35
  16. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/parameters/custom/__init__.py +1 -3
  17. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/parameters/custom/ecomax_860d3_hb.py +0 -2
  18. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/protocol.py +2 -2
  19. pyplumio-0.5.52/pyplumio/stream.py +243 -0
  20. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/alerts.py +2 -2
  21. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/lambda_sensor.py +1 -4
  22. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/product_info.py +37 -2
  23. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/schedules.py +172 -3
  24. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyproject.toml +8 -8
  25. {pyplumio-0.5.50 → pyplumio-0.5.52}/requirements.txt +1 -1
  26. {pyplumio-0.5.50 → pyplumio-0.5.52}/requirements_test.txt +7 -7
  27. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/conftest.py +11 -1
  28. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/devices/test_ecomax.py +4 -3
  29. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/devices/test_init.py +2 -2
  30. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/helpers/test_uid.py +1 -1
  31. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/parameters/custom/test_init.py +11 -2
  32. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/parameters/test_init.py +81 -25
  33. pyplumio-0.5.52/tests/structures/__init__.py +1 -0
  34. pyplumio-0.5.52/tests/structures/test_alerts.py +66 -0
  35. pyplumio-0.5.52/tests/structures/test_boiler_load.py +33 -0
  36. pyplumio-0.5.52/tests/structures/test_boiler_power.py +32 -0
  37. pyplumio-0.5.52/tests/structures/test_ecomax_parameters.py +33 -0
  38. pyplumio-0.5.52/tests/structures/test_fan_power.py +30 -0
  39. pyplumio-0.5.52/tests/structures/test_frame_versions.py +27 -0
  40. pyplumio-0.5.52/tests/structures/test_fuel_consumption.py +35 -0
  41. pyplumio-0.5.52/tests/structures/test_fuel_level.py +34 -0
  42. pyplumio-0.5.52/tests/structures/test_lambda_sensor.py +42 -0
  43. pyplumio-0.5.52/tests/structures/test_mixer_parameters.py +31 -0
  44. pyplumio-0.5.52/tests/structures/test_product_info.py +33 -0
  45. pyplumio-0.5.50/tests/helpers/test_schedule.py → pyplumio-0.5.52/tests/structures/test_schedules.py +90 -3
  46. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/test_filters.py +4 -4
  47. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/test_protocol.py +2 -12
  48. pyplumio-0.5.52/tests/test_stream.py +467 -0
  49. pyplumio-0.5.50/pyplumio/helpers/schedule.py +0 -180
  50. pyplumio-0.5.50/pyplumio/helpers/uid.py +0 -44
  51. pyplumio-0.5.50/pyplumio/stream.py +0 -158
  52. pyplumio-0.5.50/tests/test_stream.py +0 -267
  53. {pyplumio-0.5.50 → pyplumio-0.5.52}/.gitattributes +0 -0
  54. {pyplumio-0.5.50 → pyplumio-0.5.52}/.github/CODE_OF_CONDUCT.md +0 -0
  55. {pyplumio-0.5.50 → pyplumio-0.5.52}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  56. {pyplumio-0.5.50 → pyplumio-0.5.52}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  57. {pyplumio-0.5.50 → pyplumio-0.5.52}/.github/dependabot.yml +0 -0
  58. {pyplumio-0.5.50 → pyplumio-0.5.52}/.github/workflows/ci.yml +0 -0
  59. {pyplumio-0.5.50 → pyplumio-0.5.52}/.github/workflows/codeql.yml +0 -0
  60. {pyplumio-0.5.50 → pyplumio-0.5.52}/.github/workflows/deploy.yml +0 -0
  61. {pyplumio-0.5.50 → pyplumio-0.5.52}/.github/workflows/documentation.yml +0 -0
  62. {pyplumio-0.5.50 → pyplumio-0.5.52}/.gitignore +0 -0
  63. {pyplumio-0.5.50 → pyplumio-0.5.52}/.vscode/settings.json +0 -0
  64. {pyplumio-0.5.50 → pyplumio-0.5.52}/LICENSE +0 -0
  65. {pyplumio-0.5.50 → pyplumio-0.5.52}/MANIFEST.in +0 -0
  66. {pyplumio-0.5.50 → pyplumio-0.5.52}/PyPlumIO.egg-info/dependency_links.txt +0 -0
  67. {pyplumio-0.5.50 → pyplumio-0.5.52}/PyPlumIO.egg-info/top_level.txt +0 -0
  68. {pyplumio-0.5.50 → pyplumio-0.5.52}/README.md +0 -0
  69. {pyplumio-0.5.50 → pyplumio-0.5.52}/docs/Makefile +0 -0
  70. {pyplumio-0.5.50 → pyplumio-0.5.52}/docs/make.bat +0 -0
  71. {pyplumio-0.5.50 → pyplumio-0.5.52}/docs/source/callbacks.rst +0 -0
  72. {pyplumio-0.5.50 → pyplumio-0.5.52}/docs/source/conf.py +0 -0
  73. {pyplumio-0.5.50 → pyplumio-0.5.52}/docs/source/connecting.rst +0 -0
  74. {pyplumio-0.5.50 → pyplumio-0.5.52}/docs/source/frames.rst +0 -0
  75. {pyplumio-0.5.50 → pyplumio-0.5.52}/docs/source/mixers_thermostats.rst +0 -0
  76. {pyplumio-0.5.50 → pyplumio-0.5.52}/docs/source/protocol.rst +0 -0
  77. {pyplumio-0.5.50 → pyplumio-0.5.52}/docs/source/reading.rst +0 -0
  78. {pyplumio-0.5.50 → pyplumio-0.5.52}/docs/source/writing.rst +0 -0
  79. {pyplumio-0.5.50 → pyplumio-0.5.52}/images/ecomax.png +0 -0
  80. {pyplumio-0.5.50 → pyplumio-0.5.52}/images/rs485.png +0 -0
  81. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/__init__.py +0 -0
  82. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/__main__.py +0 -0
  83. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/connection.py +0 -0
  84. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/data_types.py +0 -0
  85. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/devices/ecoster.py +0 -0
  86. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/devices/mixer.py +0 -0
  87. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/devices/thermostat.py +0 -0
  88. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/exceptions.py +0 -0
  89. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/frames/messages.py +0 -0
  90. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/frames/requests.py +0 -0
  91. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/frames/responses.py +0 -0
  92. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/helpers/__init__.py +0 -0
  93. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/helpers/async_cache.py +0 -0
  94. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/helpers/event_manager.py +0 -0
  95. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/helpers/factory.py +0 -0
  96. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/helpers/task_manager.py +0 -0
  97. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/helpers/timeout.py +0 -0
  98. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/parameters/ecomax.py +0 -0
  99. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/parameters/mixer.py +0 -0
  100. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/parameters/thermostat.py +0 -0
  101. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/py.typed +0 -0
  102. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/__init__.py +0 -0
  103. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/boiler_load.py +0 -0
  104. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/boiler_power.py +0 -0
  105. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/ecomax_parameters.py +0 -0
  106. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/fan_power.py +0 -0
  107. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/frame_versions.py +0 -0
  108. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/fuel_consumption.py +0 -0
  109. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/fuel_level.py +0 -0
  110. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/mixer_parameters.py +0 -0
  111. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/mixer_sensors.py +0 -0
  112. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/modules.py +0 -0
  113. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/network_info.py +0 -0
  114. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/output_flags.py +0 -0
  115. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/outputs.py +0 -0
  116. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/pending_alerts.py +0 -0
  117. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/program_version.py +0 -0
  118. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/regulator_data.py +0 -0
  119. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/regulator_data_schema.py +0 -0
  120. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/statuses.py +0 -0
  121. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/temperatures.py +0 -0
  122. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/thermostat_parameters.py +0 -0
  123. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/structures/thermostat_sensors.py +0 -0
  124. {pyplumio-0.5.50 → pyplumio-0.5.52}/pyplumio/utils.py +0 -0
  125. {pyplumio-0.5.50 → pyplumio-0.5.52}/requirements_docs.txt +0 -0
  126. {pyplumio-0.5.50 → pyplumio-0.5.52}/setup.cfg +0 -0
  127. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/__init__.py +0 -0
  128. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/devices/__init__.py +0 -0
  129. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/devices/test_ecoster.py +0 -0
  130. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/devices/test_mixer.py +0 -0
  131. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/devices/test_thermostat.py +0 -0
  132. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/frames/test_init.py +0 -0
  133. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/frames/test_messages.py +0 -0
  134. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/frames/test_requests.py +0 -0
  135. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/frames/test_responses.py +0 -0
  136. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/helpers/__init__.py +0 -0
  137. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/helpers/test_async_cache.py +0 -0
  138. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/helpers/test_event_manager.py +0 -0
  139. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/helpers/test_factory.py +0 -0
  140. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/helpers/test_task_manager.py +0 -0
  141. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/helpers/test_timeout.py +0 -0
  142. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/parameters/__init__.py +0 -0
  143. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/parameters/custom/__init__.py +0 -0
  144. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/parameters/custom/test_ecomax_860d3_hb.py +0 -0
  145. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/parameters/test_ecomax.py +0 -0
  146. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/parameters/test_mixers.py +0 -0
  147. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/parameters/test_thermostats.py +0 -0
  148. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/ruff.toml +0 -0
  149. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/test_connection.py +0 -0
  150. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/test_data_types.py +0 -0
  151. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/test_init.py +0 -0
  152. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/test_main.py +0 -0
  153. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/test_utils.py +0 -0
  154. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/messages/regulator_data.json +0 -0
  155. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/messages/sensor_data.json +0 -0
  156. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/parameters/ecomax_860d3_hb.json +0 -0
  157. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/requests/alerts.json +0 -0
  158. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/requests/ecomax_control.json +0 -0
  159. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/requests/ecomax_parameters.json +0 -0
  160. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/requests/mixer_parameters.json +0 -0
  161. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/requests/set_ecomax_parameter.json +0 -0
  162. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/requests/set_mixer_parameter.json +0 -0
  163. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/requests/set_schedule.json +0 -0
  164. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/requests/set_thermostat_parameter.json +0 -0
  165. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/requests/thermostat_parameters.json +0 -0
  166. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/responses/alerts.json +0 -0
  167. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/responses/device_available.json +0 -0
  168. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/responses/ecomax_parameters.json +0 -0
  169. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/responses/mixer_parameters.json +0 -0
  170. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/responses/password.json +0 -0
  171. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/responses/program_version.json +0 -0
  172. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/responses/regulator_data_schema.json +0 -0
  173. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/responses/schedules.json +0 -0
  174. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/responses/thermostat_parameters.json +0 -0
  175. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/responses/uid.json +0 -0
  176. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/unknown/unknown_ecomax_parameter.json +0 -0
  177. {pyplumio-0.5.50 → pyplumio-0.5.52}/tests/testdata/unknown/unknown_mixer_parameter.json +0 -0
@@ -3,7 +3,7 @@
3
3
  # See https://pre-commit.com/hooks.html for more hooks
4
4
  repos:
5
5
  - repo: https://github.com/astral-sh/ruff-pre-commit
6
- rev: v0.11.6
6
+ rev: v0.11.11
7
7
  hooks:
8
8
  - id: ruff
9
9
  args:
@@ -49,7 +49,7 @@ file_patterns = ["pyplumio/structures/network_info.py"]
49
49
 
50
50
  # Ignore unused **kwargs argument due to inheritance.
51
51
  [[ignore]]
52
- rules = ["radarlint-python:python.S1172"]
52
+ rules = ["radarlint-python:python:S1172"]
53
53
  file_patterns = ["pyplumio/frames/__init__.py"]
54
54
 
55
55
  [[source]]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPlumIO
3
- Version: 0.5.50
3
+ Version: 0.5.52
4
4
  Summary: PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
5
5
  Author-email: Denis Paavilainen <denpa@denpa.pro>
6
6
  License: MIT License
@@ -25,18 +25,18 @@ Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
26
  Requires-Dist: dataslots==1.2.0
27
27
  Requires-Dist: pyserial-asyncio==0.6
28
- Requires-Dist: typing-extensions==4.13.2
28
+ Requires-Dist: typing-extensions==4.14.0
29
29
  Provides-Extra: test
30
30
  Requires-Dist: codespell==2.4.1; extra == "test"
31
- Requires-Dist: coverage==7.8.0; extra == "test"
32
- Requires-Dist: freezegun==1.5.1; extra == "test"
33
- Requires-Dist: mypy==1.15.0; extra == "test"
31
+ Requires-Dist: coverage==7.9.1; extra == "test"
32
+ Requires-Dist: freezegun==1.5.2; extra == "test"
33
+ Requires-Dist: mypy==1.16.1; extra == "test"
34
34
  Requires-Dist: numpy<3.0.0,>=2.0.0; extra == "test"
35
35
  Requires-Dist: pyserial-asyncio-fast==0.16; extra == "test"
36
- Requires-Dist: pytest==8.3.5; extra == "test"
37
- Requires-Dist: pytest-asyncio==0.26.0; extra == "test"
38
- Requires-Dist: ruff==0.11.9; extra == "test"
39
- Requires-Dist: tox==4.25.0; extra == "test"
36
+ Requires-Dist: pytest==8.4.1; extra == "test"
37
+ Requires-Dist: pytest-asyncio==1.0.0; extra == "test"
38
+ Requires-Dist: ruff==0.11.13; extra == "test"
39
+ Requires-Dist: tox==4.26.0; extra == "test"
40
40
  Requires-Dist: types-pyserial==3.5.0.20250326; extra == "test"
41
41
  Provides-Extra: docs
42
42
  Requires-Dist: sphinx==8.1.3; extra == "docs"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPlumIO
3
- Version: 0.5.50
3
+ Version: 0.5.52
4
4
  Summary: PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
5
5
  Author-email: Denis Paavilainen <denpa@denpa.pro>
6
6
  License: MIT License
@@ -25,18 +25,18 @@ Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
26
  Requires-Dist: dataslots==1.2.0
27
27
  Requires-Dist: pyserial-asyncio==0.6
28
- Requires-Dist: typing-extensions==4.13.2
28
+ Requires-Dist: typing-extensions==4.14.0
29
29
  Provides-Extra: test
30
30
  Requires-Dist: codespell==2.4.1; extra == "test"
31
- Requires-Dist: coverage==7.8.0; extra == "test"
32
- Requires-Dist: freezegun==1.5.1; extra == "test"
33
- Requires-Dist: mypy==1.15.0; extra == "test"
31
+ Requires-Dist: coverage==7.9.1; extra == "test"
32
+ Requires-Dist: freezegun==1.5.2; extra == "test"
33
+ Requires-Dist: mypy==1.16.1; extra == "test"
34
34
  Requires-Dist: numpy<3.0.0,>=2.0.0; extra == "test"
35
35
  Requires-Dist: pyserial-asyncio-fast==0.16; extra == "test"
36
- Requires-Dist: pytest==8.3.5; extra == "test"
37
- Requires-Dist: pytest-asyncio==0.26.0; extra == "test"
38
- Requires-Dist: ruff==0.11.9; extra == "test"
39
- Requires-Dist: tox==4.25.0; extra == "test"
36
+ Requires-Dist: pytest==8.4.1; extra == "test"
37
+ Requires-Dist: pytest-asyncio==1.0.0; extra == "test"
38
+ Requires-Dist: ruff==0.11.13; extra == "test"
39
+ Requires-Dist: tox==4.26.0; extra == "test"
40
40
  Requires-Dist: types-pyserial==3.5.0.20250326; extra == "test"
41
41
  Provides-Extra: docs
42
42
  Requires-Dist: sphinx==8.1.3; extra == "docs"
@@ -62,10 +62,8 @@ pyplumio/helpers/__init__.py
62
62
  pyplumio/helpers/async_cache.py
63
63
  pyplumio/helpers/event_manager.py
64
64
  pyplumio/helpers/factory.py
65
- pyplumio/helpers/schedule.py
66
65
  pyplumio/helpers/task_manager.py
67
66
  pyplumio/helpers/timeout.py
68
- pyplumio/helpers/uid.py
69
67
  pyplumio/parameters/__init__.py
70
68
  pyplumio/parameters/ecomax.py
71
69
  pyplumio/parameters/mixer.py
@@ -123,7 +121,6 @@ tests/helpers/__init__.py
123
121
  tests/helpers/test_async_cache.py
124
122
  tests/helpers/test_event_manager.py
125
123
  tests/helpers/test_factory.py
126
- tests/helpers/test_schedule.py
127
124
  tests/helpers/test_task_manager.py
128
125
  tests/helpers/test_timeout.py
129
126
  tests/helpers/test_uid.py
@@ -135,6 +132,19 @@ tests/parameters/test_thermostats.py
135
132
  tests/parameters/custom/__init__.py
136
133
  tests/parameters/custom/test_ecomax_860d3_hb.py
137
134
  tests/parameters/custom/test_init.py
135
+ tests/structures/__init__.py
136
+ tests/structures/test_alerts.py
137
+ tests/structures/test_boiler_load.py
138
+ tests/structures/test_boiler_power.py
139
+ tests/structures/test_ecomax_parameters.py
140
+ tests/structures/test_fan_power.py
141
+ tests/structures/test_frame_versions.py
142
+ tests/structures/test_fuel_consumption.py
143
+ tests/structures/test_fuel_level.py
144
+ tests/structures/test_lambda_sensor.py
145
+ tests/structures/test_mixer_parameters.py
146
+ tests/structures/test_product_info.py
147
+ tests/structures/test_schedules.py
138
148
  tests/testdata/messages/regulator_data.json
139
149
  tests/testdata/messages/sensor_data.json
140
150
  tests/testdata/parameters/ecomax_860d3_hb.json
@@ -1,6 +1,6 @@
1
1
  dataslots==1.2.0
2
2
  pyserial-asyncio==0.6
3
- typing-extensions==4.13.2
3
+ typing-extensions==4.14.0
4
4
 
5
5
  [dev]
6
6
  pyplumio[docs,test]
@@ -14,13 +14,13 @@ readthedocs-sphinx-search==0.3.2
14
14
 
15
15
  [test]
16
16
  codespell==2.4.1
17
- coverage==7.8.0
18
- freezegun==1.5.1
19
- mypy==1.15.0
17
+ coverage==7.9.1
18
+ freezegun==1.5.2
19
+ mypy==1.16.1
20
20
  numpy<3.0.0,>=2.0.0
21
21
  pyserial-asyncio-fast==0.16
22
- pytest==8.3.5
23
- pytest-asyncio==0.26.0
24
- ruff==0.11.9
25
- tox==4.25.0
22
+ pytest==8.4.1
23
+ pytest-asyncio==1.0.0
24
+ ruff==0.11.13
25
+ tox==4.26.0
26
26
  types-pyserial==3.5.0.20250326
@@ -6,7 +6,7 @@
6
6
  Welcome to PyPlumIO's documentation!
7
7
  ====================================
8
8
 
9
- The `PyPlumIO <https://github.com/denpamusic/PyPlumIO/>`_ projects aims to
9
+ The `PyPlumIO <https://github.com/denpamusic/PyPlumIO/>`_ project aims to
10
10
  provide complete and easy to use solution for communicating with
11
11
  climate devices manufactured by `Plum Sp. z o.o. <https://www.plum.pl/>`_
12
12
 
@@ -60,11 +60,11 @@ switches back to daytime mode from 07:00 to 00:00.
60
60
  await heating_schedule.commit()
61
61
 
62
62
  For clarity sake, you might want to use ``STATE_OFF`` and
63
- ``STATE_ON`` constants from ``pyplumio.helpers.schedule`` module.
63
+ ``STATE_ON`` constants from ``pyplumio.const`` module.
64
64
 
65
65
  .. code-block:: python
66
66
 
67
- from pyplumio.helpers.schedule import STATE_OFF
67
+ from pyplumio.const import STATE_OFF
68
68
 
69
69
  heating_schedule.monday["18:00"] = STATE_OFF
70
70
  heating_schedule.monday.set_state(STATE_OFF, "00:00", "07:00")
@@ -110,7 +110,7 @@ Schedule Examples
110
110
  .. code-block:: python
111
111
 
112
112
  import pyplumio
113
- from pyplumio.helpers.schedule import STATE_ON, STATE_OFF
113
+ from pyplumio.const import STATE_ON, STATE_OFF
114
114
 
115
115
 
116
116
  async def main():
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.5.50'
21
- __version_tuple__ = version_tuple = (0, 5, 50)
20
+ __version__ = version = '0.5.52'
21
+ __version_tuple__ = version_tuple = (0, 5, 52)
@@ -91,13 +91,6 @@ class ProductType(IntEnum):
91
91
  ECOMAX_I = 1
92
92
 
93
93
 
94
- @unique
95
- class ProductModel(Enum):
96
- """Contains device models."""
97
-
98
- ECOMAX_860D3_HB = "ecoMAX 860D3-HB"
99
-
100
-
101
94
  @unique
102
95
  class AlertType(IntEnum):
103
96
  """Contains alert types."""
@@ -64,7 +64,7 @@ class Device(ABC, EventManager):
64
64
  self,
65
65
  name: str,
66
66
  value: NumericType | State | bool,
67
- retries: int = 5,
67
+ retries: int = 0,
68
68
  timeout: float | None = None,
69
69
  ) -> bool:
70
70
  """Set a parameter value.
@@ -74,7 +74,7 @@ class Device(ABC, EventManager):
74
74
  :param value: New value for the parameter
75
75
  :type value: int | float | bool | Literal["on", "off"]
76
76
  :param retries: Try setting parameter for this amount of
77
- times, defaults to 5
77
+ times, defaults to 0 (disabled)
78
78
  :type retries: int, optional
79
79
  :param timeout: Wait this amount of seconds for confirmation,
80
80
  defaults to `None`
@@ -96,7 +96,7 @@ class Device(ABC, EventManager):
96
96
  self,
97
97
  name: str,
98
98
  value: NumericType | State | bool,
99
- retries: int = 5,
99
+ retries: int = 0,
100
100
  timeout: float | None = None,
101
101
  ) -> None:
102
102
  """Set a parameter value without waiting for the result.
@@ -106,7 +106,7 @@ class Device(ABC, EventManager):
106
106
  :param value: New value for the parameter
107
107
  :type value: int | float | bool | Literal["on", "off"]
108
108
  :param retries: Try setting parameter for this amount of
109
- times, defaults to 5
109
+ times, defaults to 0 (disabled)
110
110
  :type retries: int, optional
111
111
  :param timeout: Wait this amount of seconds for confirmation.
112
112
  As this method operates in the background without waiting,
@@ -27,7 +27,6 @@ from pyplumio.exceptions import RequestError
27
27
  from pyplumio.filters import on_change
28
28
  from pyplumio.frames import DataFrameDescription, Frame, Request
29
29
  from pyplumio.helpers.event_manager import event_listener
30
- from pyplumio.helpers.schedule import Schedule, ScheduleDay
31
30
  from pyplumio.parameters import ParameterValues
32
31
  from pyplumio.parameters.ecomax import (
33
32
  ECOMAX_CONTROL_PARAMETER,
@@ -51,6 +50,8 @@ from pyplumio.structures.schedules import (
51
50
  ATTR_SCHEDULES,
52
51
  SCHEDULE_PARAMETERS,
53
52
  SCHEDULES,
53
+ Schedule,
54
+ ScheduleDay,
54
55
  ScheduleNumber,
55
56
  ScheduleSwitch,
56
57
  ScheduleSwitchDescription,
@@ -303,8 +304,7 @@ class EcoMAX(PhysicalDevice):
303
304
 
304
305
  @event_listener
305
306
  async def on_event_mixer_parameters(
306
- self,
307
- parameters: dict[int, list[tuple[int, ParameterValues]]] | None,
307
+ self, parameters: dict[int, Any] | None
308
308
  ) -> bool:
309
309
  """Handle mixer parameters and dispatch the events."""
310
310
  _LOGGER.debug("Received mixer parameters")
@@ -320,9 +320,7 @@ class EcoMAX(PhysicalDevice):
320
320
  return False
321
321
 
322
322
  @event_listener
323
- async def on_event_mixer_sensors(
324
- self, sensors: dict[int, dict[str, Any]] | None
325
- ) -> bool:
323
+ async def on_event_mixer_sensors(self, sensors: dict[int, Any] | None) -> bool:
326
324
  """Update mixer sensors and dispatch the events."""
327
325
  _LOGGER.debug("Received mixer sensors")
328
326
  if sensors:
@@ -372,8 +370,7 @@ class EcoMAX(PhysicalDevice):
372
370
 
373
371
  @event_listener
374
372
  async def on_event_thermostat_parameters(
375
- self,
376
- parameters: dict[int, list[tuple[int, ParameterValues]]] | None,
373
+ self, parameters: dict[int, Any] | None
377
374
  ) -> bool:
378
375
  """Handle thermostat parameters and dispatch the events."""
379
376
  _LOGGER.debug("Received thermostat parameters")
@@ -403,9 +400,7 @@ class EcoMAX(PhysicalDevice):
403
400
  return None
404
401
 
405
402
  @event_listener
406
- async def on_event_thermostat_sensors(
407
- self, sensors: dict[int, dict[str, Any]] | None
408
- ) -> bool:
403
+ async def on_event_thermostat_sensors(self, sensors: dict[int, Any] | None) -> bool:
409
404
  """Update thermostat sensors and dispatch the events."""
410
405
  _LOGGER.debug("Received thermostat sensors")
411
406
  if sensors:
@@ -86,7 +86,7 @@ def is_close(
86
86
  ) -> bool:
87
87
  """Check if value is significantly changed."""
88
88
  if isinstance(old, Parameter) and isinstance(new, Parameter):
89
- return new.pending_update or old.values.__ne__(new.values)
89
+ return new.update_pending.is_set() or old.values.__ne__(new.values)
90
90
 
91
91
  if tolerance and isinstance(old, SupportsFloat) and isinstance(new, SupportsFloat):
92
92
  return not math.isclose(old, new, abs_tol=tolerance)
@@ -15,10 +15,14 @@ from pyplumio.utils import ensure_dict, to_camelcase
15
15
 
16
16
  FRAME_START: Final = 0x68
17
17
  FRAME_END: Final = 0x16
18
- HEADER_OFFSET: Final = 0
18
+
19
+
20
+ HEADER_INDEX: Final = 0
19
21
  FRAME_TYPE_SIZE: Final = 1
20
- CRC_SIZE: Final = 1
22
+ BCC_SIZE: Final = 1
23
+ BCC_INDEX: Final = -2
21
24
  DELIMITER_SIZE: Final = 1
25
+
22
26
  ECONET_TYPE: Final = 48
23
27
  ECONET_VERSION: Final = 5
24
28
 
@@ -199,7 +203,7 @@ class Frame(ABC):
199
203
  struct_header.size
200
204
  + FRAME_TYPE_SIZE
201
205
  + len(self.message)
202
- + CRC_SIZE
206
+ + BCC_SIZE
203
207
  + DELIMITER_SIZE
204
208
  )
205
209
 
@@ -209,7 +213,7 @@ class Frame(ABC):
209
213
  buffer = bytearray(struct_header.size)
210
214
  struct_header.pack_into(
211
215
  buffer,
212
- HEADER_OFFSET,
216
+ HEADER_INDEX,
213
217
  FRAME_START,
214
218
  self.length,
215
219
  int(self.recipient),
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from abc import ABC, abstractmethod
6
6
  import asyncio
7
+ from contextlib import suppress
7
8
  from dataclasses import dataclass
8
9
  import logging
9
10
  from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union, get_args
@@ -72,11 +73,19 @@ NumericType: TypeAlias = Union[int, float]
72
73
  class Parameter(ABC):
73
74
  """Represents a base parameter."""
74
75
 
75
- __slots__ = ("device", "description", "_pending_update", "_index", "_values")
76
+ __slots__ = (
77
+ "device",
78
+ "description",
79
+ "_update_done",
80
+ "_update_pending",
81
+ "_index",
82
+ "_values",
83
+ )
76
84
 
77
85
  device: Device
78
86
  description: ParameterDescription
79
- _pending_update: bool
87
+ _update_done: asyncio.Event
88
+ _update_pending: asyncio.Event
80
89
  _index: int
81
90
  _values: ParameterValues
82
91
 
@@ -91,8 +100,9 @@ class Parameter(ABC):
91
100
  self.device = device
92
101
  self.description = description
93
102
  self._index = index
94
- self._pending_update = False
95
103
  self._index = index
104
+ self._update_done = asyncio.Event()
105
+ self._update_pending = asyncio.Event()
96
106
  self._values = values if values else ParameterValues(0, 0, 0)
97
107
 
98
108
  def __repr__(self) -> str:
@@ -171,21 +181,19 @@ class Parameter(ABC):
171
181
  )
172
182
  return type(self)(self.device, self.description, values)
173
183
 
174
- async def set(self, value: Any, retries: int = 5, timeout: float = 5.0) -> bool:
184
+ async def set(self, value: Any, retries: int = 0, timeout: float = 5.0) -> bool:
175
185
  """Set a parameter value."""
176
186
  self.validate(value)
177
187
  return await self._attempt_update(self._pack_value(value), retries, timeout)
178
188
 
179
- def set_nowait(self, value: Any, retries: int = 5, timeout: float = 5.0) -> None:
189
+ def set_nowait(self, value: Any, retries: int = 0, timeout: float = 5.0) -> None:
180
190
  """Set a parameter value without waiting."""
181
191
  self.validate(value)
182
192
  self.device.create_task(
183
193
  self._attempt_update(self._pack_value(value), retries, timeout)
184
194
  )
185
195
 
186
- async def _attempt_update(
187
- self, value: int, retries: int = 5, timeout: float = 5.0
188
- ) -> bool:
196
+ async def _attempt_update(self, value: int, retries: int, timeout: float) -> bool:
189
197
  """Attempt to update a parameter value on the remote device."""
190
198
  _LOGGER.info(
191
199
  "Attempting to update '%s' parameter to %d", self.description.name, value
@@ -196,36 +204,58 @@ class Parameter(ABC):
196
204
 
197
205
  self._values.value = value
198
206
  request = await self.create_request()
199
- if self.description.optimistic or not (initial_retries := retries):
200
- # No retries
207
+ if self.description.optimistic:
201
208
  await self.device.queue.put(request)
202
209
  return True
203
210
 
204
- self._pending_update = True
205
- while self.pending_update:
206
- if retries <= 0:
207
- _LOGGER.warning(
208
- "Unable to confirm update of '%s' parameter after %d retries",
209
- self.description.name,
210
- initial_retries,
211
- )
212
- return False
211
+ self.update_done.clear()
212
+ self.update_pending.set()
213
+ if retries > 0:
214
+ return await self._attempt_update_with_retries(
215
+ request, retries=retries, timeout=timeout
216
+ )
213
217
 
214
- await self.device.queue.put(request)
215
- await asyncio.sleep(timeout)
216
- retries -= 1
218
+ return await self._send_update_request(request, timeout=timeout)
217
219
 
218
- return True
220
+ async def _attempt_update_with_retries(
221
+ self, request: Request, retries: int, timeout: float
222
+ ) -> bool:
223
+ """Send update request and retry until success."""
224
+ for _ in range(retries):
225
+ if await self._send_update_request(request, timeout=timeout):
226
+ return True
227
+
228
+ _LOGGER.warning(
229
+ "Unable to confirm update of '%s' parameter after %d retries",
230
+ self.description.name,
231
+ retries,
232
+ )
233
+ return False
234
+
235
+ async def _send_update_request(self, request: Request, timeout: float) -> bool:
236
+ """Send update request to the remote and confirm the result."""
237
+ await self.device.queue.put(request)
238
+ with suppress(asyncio.TimeoutError):
239
+ # Wait for the update to be done
240
+ await asyncio.wait_for(self.update_done.wait(), timeout=timeout)
241
+
242
+ return self.update_done.is_set()
219
243
 
220
244
  def update(self, values: ParameterValues) -> None:
221
245
  """Update the parameter values."""
222
- self._pending_update = False
246
+ self.update_done.set()
247
+ self.update_pending.clear()
223
248
  self._values = values
224
249
 
225
250
  @property
226
- def pending_update(self) -> bool:
227
- """Check if parameter is pending update on the device."""
228
- return self._pending_update
251
+ def update_done(self) -> asyncio.Event:
252
+ """Check if parameter is updated on the device."""
253
+ return self._update_done
254
+
255
+ @property
256
+ def update_pending(self) -> asyncio.Event:
257
+ """Check if parameter is updated on the device."""
258
+ return self._update_pending
229
259
 
230
260
  @property
231
261
  def values(self) -> ParameterValues:
@@ -325,16 +355,16 @@ class Number(Parameter):
325
355
  return True
326
356
 
327
357
  async def set(
328
- self, value: NumericType, retries: int = 5, timeout: float = 5.0
358
+ self, value: NumericType, retries: int = 0, timeout: float = 5.0
329
359
  ) -> bool:
330
360
  """Set a parameter value."""
331
- return await super().set(value, retries, timeout)
361
+ return await super().set(value, retries=retries, timeout=timeout)
332
362
 
333
363
  def set_nowait(
334
- self, value: NumericType, retries: int = 5, timeout: float = 5.0
364
+ self, value: NumericType, retries: int = 0, timeout: float = 5.0
335
365
  ) -> None:
336
366
  """Set a parameter value without waiting."""
337
- super().set_nowait(value, retries, timeout)
367
+ super().set_nowait(value, retries=retries, timeout=timeout)
338
368
 
339
369
  async def create_request(self) -> Request:
340
370
  """Create a request to change the number."""
@@ -420,16 +450,16 @@ class Switch(Parameter):
420
450
  return True
421
451
 
422
452
  async def set(
423
- self, value: State | bool, retries: int = 5, timeout: float = 5.0
453
+ self, value: State | bool, retries: int = 0, timeout: float = 5.0
424
454
  ) -> bool:
425
455
  """Set a parameter value."""
426
- return await super().set(value, retries, timeout)
456
+ return await super().set(value, retries=retries, timeout=timeout)
427
457
 
428
458
  def set_nowait(
429
- self, value: State | bool, retries: int = 5, timeout: float = 5.0
459
+ self, value: State | bool, retries: int = 0, timeout: float = 5.0
430
460
  ) -> None:
431
461
  """Set a switch value without waiting."""
432
- super().set_nowait(value, retries, timeout)
462
+ super().set_nowait(value, retries=retries, timeout=timeout)
433
463
 
434
464
  async def turn_on(self) -> bool:
435
465
  """Set a switch value to 'on'.
@@ -29,7 +29,7 @@ class Signature:
29
29
  class CustomParameter:
30
30
  """Represents a custom parameter."""
31
31
 
32
- __slot__ = ("original", "replacement")
32
+ __slots__ = ("original", "replacement")
33
33
 
34
34
  original: str
35
35
  replacement: ParameterDescription
@@ -38,8 +38,6 @@ class CustomParameter:
38
38
  class CustomParameters:
39
39
  """Represents a custom parameters."""
40
40
 
41
- __slots__ = ("signature", "replacements")
42
-
43
41
  signature: ClassVar[Signature]
44
42
  replacements: ClassVar[Sequence[CustomParameter]]
45
43
 
@@ -8,8 +8,6 @@ from pyplumio.parameters.ecomax import EcomaxNumberDescription, EcomaxSwitchDesc
8
8
  class EcoMAX860D3HB(CustomParameters):
9
9
  """Replacements for ecoMAX 860D3-HB."""
10
10
 
11
- __slots__ = ()
12
-
13
11
  signature = Signature(model="ecoMAX 860D3-HB", id=48)
14
12
 
15
13
  replacements = (
@@ -230,15 +230,15 @@ class AsyncProtocol(Protocol, EventManager[PhysicalDevice]):
230
230
  @acache
231
231
  async def get_device_entry(self, device_type: DeviceType) -> PhysicalDevice:
232
232
  """Return the device entry."""
233
+ name = device_type.name.lower()
233
234
  async with self._entry_lock:
234
- name = device_type.name.lower()
235
235
  if name not in self.data:
236
236
  device = await PhysicalDevice.create(
237
237
  device_type, queue=self._queues.write, network=self._network
238
238
  )
239
239
  device.dispatch_nowait(ATTR_CONNECTED, True)
240
240
  device.dispatch_nowait(ATTR_SETUP, True)
241
- await self.dispatch(device_type.name.lower(), device)
241
+ await self.dispatch(name, device)
242
242
 
243
243
  return self.data[name]
244
244