PyPlumIO 0.6.6__tar.gz → 0.6.7__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 (152) hide show
  1. {pyplumio-0.6.6 → pyplumio-0.6.7}/PKG-INFO +7 -7
  2. {pyplumio-0.6.6 → pyplumio-0.6.7}/PyPlumIO.egg-info/PKG-INFO +7 -7
  3. {pyplumio-0.6.6 → pyplumio-0.6.7}/PyPlumIO.egg-info/SOURCES.txt +0 -2
  4. {pyplumio-0.6.6 → pyplumio-0.6.7}/PyPlumIO.egg-info/requires.txt +6 -6
  5. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/_version.py +3 -3
  6. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/connection.py +3 -2
  7. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/devices/__init__.py +1 -2
  8. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/devices/ecomax.py +7 -5
  9. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/devices/mixer.py +3 -3
  10. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/devices/thermostat.py +1 -1
  11. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/filters.py +14 -9
  12. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/frames/__init__.py +53 -39
  13. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/frames/messages.py +3 -3
  14. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/frames/requests.py +15 -10
  15. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/frames/responses.py +10 -10
  16. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/helpers/async_cache.py +2 -2
  17. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/helpers/event_manager.py +61 -31
  18. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/parameters/custom/__init__.py +1 -2
  19. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/protocol.py +1 -1
  20. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/structures/__init__.py +7 -9
  21. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/structures/regulator_data.py +3 -3
  22. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/utils.py +28 -1
  23. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyproject.toml +6 -6
  24. {pyplumio-0.6.6 → pyplumio-0.6.7}/requirements_test.txt +6 -6
  25. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/devices/test_ecomax.py +1 -1
  26. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/devices/test_init.py +1 -1
  27. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/frames/test_init.py +2 -13
  28. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/frames/test_requests.py +64 -36
  29. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/helpers/test_event_manager.py +25 -4
  30. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/test_filters.py +15 -1
  31. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/test_protocol.py +3 -3
  32. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/test_utils.py +31 -0
  33. pyplumio-0.6.6/pyplumio/helpers/factory.py +0 -36
  34. pyplumio-0.6.6/tests/helpers/test_factory.py +0 -41
  35. {pyplumio-0.6.6 → pyplumio-0.6.7}/.gitattributes +0 -0
  36. {pyplumio-0.6.6 → pyplumio-0.6.7}/.github/CODE_OF_CONDUCT.md +0 -0
  37. {pyplumio-0.6.6 → pyplumio-0.6.7}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  38. {pyplumio-0.6.6 → pyplumio-0.6.7}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  39. {pyplumio-0.6.6 → pyplumio-0.6.7}/.github/dependabot.yml +0 -0
  40. {pyplumio-0.6.6 → pyplumio-0.6.7}/.github/workflows/ci.yml +0 -0
  41. {pyplumio-0.6.6 → pyplumio-0.6.7}/.github/workflows/codeql.yml +0 -0
  42. {pyplumio-0.6.6 → pyplumio-0.6.7}/.github/workflows/deploy.yml +0 -0
  43. {pyplumio-0.6.6 → pyplumio-0.6.7}/.github/workflows/documentation.yml +0 -0
  44. {pyplumio-0.6.6 → pyplumio-0.6.7}/.gitignore +0 -0
  45. {pyplumio-0.6.6 → pyplumio-0.6.7}/.pre-commit-config.yaml +0 -0
  46. {pyplumio-0.6.6 → pyplumio-0.6.7}/.qlty/qlty.toml +0 -0
  47. {pyplumio-0.6.6 → pyplumio-0.6.7}/.vscode/settings.json +0 -0
  48. {pyplumio-0.6.6 → pyplumio-0.6.7}/LICENSE +0 -0
  49. {pyplumio-0.6.6 → pyplumio-0.6.7}/MANIFEST.in +0 -0
  50. {pyplumio-0.6.6 → pyplumio-0.6.7}/PyPlumIO.egg-info/dependency_links.txt +0 -0
  51. {pyplumio-0.6.6 → pyplumio-0.6.7}/PyPlumIO.egg-info/top_level.txt +0 -0
  52. {pyplumio-0.6.6 → pyplumio-0.6.7}/README.md +0 -0
  53. {pyplumio-0.6.6 → pyplumio-0.6.7}/docs/Makefile +0 -0
  54. {pyplumio-0.6.6 → pyplumio-0.6.7}/docs/make.bat +0 -0
  55. {pyplumio-0.6.6 → pyplumio-0.6.7}/docs/source/callbacks.rst +0 -0
  56. {pyplumio-0.6.6 → pyplumio-0.6.7}/docs/source/conf.py +0 -0
  57. {pyplumio-0.6.6 → pyplumio-0.6.7}/docs/source/connecting.rst +0 -0
  58. {pyplumio-0.6.6 → pyplumio-0.6.7}/docs/source/frames.rst +0 -0
  59. {pyplumio-0.6.6 → pyplumio-0.6.7}/docs/source/index.rst +0 -0
  60. {pyplumio-0.6.6 → pyplumio-0.6.7}/docs/source/mixers_thermostats.rst +0 -0
  61. {pyplumio-0.6.6 → pyplumio-0.6.7}/docs/source/protocol.rst +0 -0
  62. {pyplumio-0.6.6 → pyplumio-0.6.7}/docs/source/reading.rst +0 -0
  63. {pyplumio-0.6.6 → pyplumio-0.6.7}/docs/source/schedules.rst +0 -0
  64. {pyplumio-0.6.6 → pyplumio-0.6.7}/docs/source/writing.rst +0 -0
  65. {pyplumio-0.6.6 → pyplumio-0.6.7}/images/ecomax.png +0 -0
  66. {pyplumio-0.6.6 → pyplumio-0.6.7}/images/rs485.png +0 -0
  67. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/__init__.py +0 -0
  68. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/__main__.py +0 -0
  69. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/const.py +0 -0
  70. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/data_types.py +0 -0
  71. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/devices/ecoster.py +0 -0
  72. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/exceptions.py +0 -0
  73. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/helpers/__init__.py +0 -0
  74. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/helpers/task_manager.py +0 -0
  75. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/parameters/__init__.py +0 -0
  76. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/parameters/custom/ecomax_860d3_hb.py +0 -0
  77. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/parameters/ecomax.py +0 -0
  78. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/parameters/mixer.py +0 -0
  79. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/parameters/thermostat.py +0 -0
  80. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/py.typed +0 -0
  81. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/stream.py +0 -0
  82. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/structures/alerts.py +0 -0
  83. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/structures/ecomax_parameters.py +0 -0
  84. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/structures/frame_versions.py +0 -0
  85. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/structures/mixer_parameters.py +0 -0
  86. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/structures/network_info.py +0 -0
  87. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/structures/product_info.py +0 -0
  88. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/structures/program_version.py +0 -0
  89. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/structures/regulator_data_schema.py +0 -0
  90. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/structures/schedules.py +0 -0
  91. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/structures/sensor_data.py +0 -0
  92. {pyplumio-0.6.6 → pyplumio-0.6.7}/pyplumio/structures/thermostat_parameters.py +0 -0
  93. {pyplumio-0.6.6 → pyplumio-0.6.7}/requirements.txt +0 -0
  94. {pyplumio-0.6.6 → pyplumio-0.6.7}/requirements_docs.txt +0 -0
  95. {pyplumio-0.6.6 → pyplumio-0.6.7}/setup.cfg +0 -0
  96. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/__init__.py +0 -0
  97. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/conftest.py +0 -0
  98. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/devices/__init__.py +0 -0
  99. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/devices/test_ecoster.py +0 -0
  100. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/devices/test_mixer.py +0 -0
  101. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/devices/test_thermostat.py +0 -0
  102. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/frames/test_messages.py +0 -0
  103. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/frames/test_responses.py +0 -0
  104. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/helpers/__init__.py +0 -0
  105. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/helpers/test_async_cache.py +0 -0
  106. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/helpers/test_task_manager.py +0 -0
  107. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/helpers/test_uid.py +0 -0
  108. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/parameters/__init__.py +0 -0
  109. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/parameters/custom/__init__.py +0 -0
  110. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/parameters/custom/test_ecomax_860d3_hb.py +0 -0
  111. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/parameters/custom/test_init.py +0 -0
  112. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/parameters/test_ecomax.py +0 -0
  113. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/parameters/test_init.py +0 -0
  114. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/parameters/test_mixers.py +0 -0
  115. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/parameters/test_thermostats.py +0 -0
  116. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/ruff.toml +0 -0
  117. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/structures/__init__.py +0 -0
  118. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/structures/test_alerts.py +0 -0
  119. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/structures/test_ecomax_parameters.py +0 -0
  120. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/structures/test_frame_versions.py +0 -0
  121. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/structures/test_mixer_parameters.py +0 -0
  122. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/structures/test_product_info.py +0 -0
  123. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/structures/test_schedules.py +0 -0
  124. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/test_connection.py +0 -0
  125. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/test_data_types.py +0 -0
  126. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/test_init.py +0 -0
  127. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/test_main.py +0 -0
  128. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/test_stream.py +0 -0
  129. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/messages/regulator_data.json +0 -0
  130. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/messages/sensor_data.json +0 -0
  131. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/parameters/ecomax_860d3_hb.json +0 -0
  132. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/requests/alerts.json +0 -0
  133. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/requests/ecomax_control.json +0 -0
  134. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/requests/ecomax_parameters.json +0 -0
  135. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/requests/mixer_parameters.json +0 -0
  136. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/requests/set_ecomax_parameter.json +0 -0
  137. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/requests/set_mixer_parameter.json +0 -0
  138. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/requests/set_schedule.json +0 -0
  139. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/requests/set_thermostat_parameter.json +0 -0
  140. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/requests/thermostat_parameters.json +0 -0
  141. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/responses/alerts.json +0 -0
  142. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/responses/device_available.json +0 -0
  143. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/responses/ecomax_parameters.json +0 -0
  144. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/responses/mixer_parameters.json +0 -0
  145. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/responses/password.json +0 -0
  146. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/responses/program_version.json +0 -0
  147. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/responses/regulator_data_schema.json +0 -0
  148. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/responses/schedules.json +0 -0
  149. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/responses/thermostat_parameters.json +0 -0
  150. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/responses/uid.json +0 -0
  151. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/unknown/unknown_ecomax_parameter.json +0 -0
  152. {pyplumio-0.6.6 → pyplumio-0.6.7}/tests/testdata/unknown/unknown_mixer_parameter.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPlumIO
3
- Version: 0.6.6
3
+ Version: 0.6.7
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
@@ -26,14 +26,14 @@ License-File: LICENSE
26
26
  Requires-Dist: pyserial-asyncio==0.6
27
27
  Provides-Extra: test
28
28
  Requires-Dist: codespell==2.4.1; extra == "test"
29
- Requires-Dist: coverage==7.11.3; extra == "test"
29
+ Requires-Dist: coverage==7.12.0; extra == "test"
30
30
  Requires-Dist: freezegun==1.5.5; extra == "test"
31
- Requires-Dist: mypy==1.18.2; extra == "test"
31
+ Requires-Dist: mypy==1.19.0; extra == "test"
32
32
  Requires-Dist: numpy<3.0.0,>=2.0.0; extra == "test"
33
33
  Requires-Dist: pyserial-asyncio-fast==0.16; extra == "test"
34
- Requires-Dist: pytest==8.4.2; extra == "test"
35
- Requires-Dist: pytest-asyncio==1.2.0; extra == "test"
36
- Requires-Dist: ruff==0.14.4; extra == "test"
34
+ Requires-Dist: pytest==9.0.1; extra == "test"
35
+ Requires-Dist: pytest-asyncio==1.3.0; extra == "test"
36
+ Requires-Dist: ruff==0.14.7; extra == "test"
37
37
  Requires-Dist: tox==4.32.0; extra == "test"
38
38
  Requires-Dist: types-pyserial==3.5.0.20251001; extra == "test"
39
39
  Provides-Extra: docs
@@ -42,7 +42,7 @@ Requires-Dist: sphinx_rtd_theme==3.0.2; extra == "docs"
42
42
  Requires-Dist: readthedocs-sphinx-search==0.3.2; extra == "docs"
43
43
  Provides-Extra: dev
44
44
  Requires-Dist: pyplumio[docs,test]; extra == "dev"
45
- Requires-Dist: pre-commit==4.4.0; extra == "dev"
45
+ Requires-Dist: pre-commit==4.5.0; extra == "dev"
46
46
  Requires-Dist: tomli==2.3.0; extra == "dev"
47
47
  Dynamic: license-file
48
48
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPlumIO
3
- Version: 0.6.6
3
+ Version: 0.6.7
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
@@ -26,14 +26,14 @@ License-File: LICENSE
26
26
  Requires-Dist: pyserial-asyncio==0.6
27
27
  Provides-Extra: test
28
28
  Requires-Dist: codespell==2.4.1; extra == "test"
29
- Requires-Dist: coverage==7.11.3; extra == "test"
29
+ Requires-Dist: coverage==7.12.0; extra == "test"
30
30
  Requires-Dist: freezegun==1.5.5; extra == "test"
31
- Requires-Dist: mypy==1.18.2; extra == "test"
31
+ Requires-Dist: mypy==1.19.0; extra == "test"
32
32
  Requires-Dist: numpy<3.0.0,>=2.0.0; extra == "test"
33
33
  Requires-Dist: pyserial-asyncio-fast==0.16; extra == "test"
34
- Requires-Dist: pytest==8.4.2; extra == "test"
35
- Requires-Dist: pytest-asyncio==1.2.0; extra == "test"
36
- Requires-Dist: ruff==0.14.4; extra == "test"
34
+ Requires-Dist: pytest==9.0.1; extra == "test"
35
+ Requires-Dist: pytest-asyncio==1.3.0; extra == "test"
36
+ Requires-Dist: ruff==0.14.7; extra == "test"
37
37
  Requires-Dist: tox==4.32.0; extra == "test"
38
38
  Requires-Dist: types-pyserial==3.5.0.20251001; extra == "test"
39
39
  Provides-Extra: docs
@@ -42,7 +42,7 @@ Requires-Dist: sphinx_rtd_theme==3.0.2; extra == "docs"
42
42
  Requires-Dist: readthedocs-sphinx-search==0.3.2; extra == "docs"
43
43
  Provides-Extra: dev
44
44
  Requires-Dist: pyplumio[docs,test]; extra == "dev"
45
- Requires-Dist: pre-commit==4.4.0; extra == "dev"
45
+ Requires-Dist: pre-commit==4.5.0; extra == "dev"
46
46
  Requires-Dist: tomli==2.3.0; extra == "dev"
47
47
  Dynamic: license-file
48
48
 
@@ -61,7 +61,6 @@ pyplumio/frames/responses.py
61
61
  pyplumio/helpers/__init__.py
62
62
  pyplumio/helpers/async_cache.py
63
63
  pyplumio/helpers/event_manager.py
64
- pyplumio/helpers/factory.py
65
64
  pyplumio/helpers/task_manager.py
66
65
  pyplumio/parameters/__init__.py
67
66
  pyplumio/parameters/ecomax.py
@@ -106,7 +105,6 @@ tests/frames/test_responses.py
106
105
  tests/helpers/__init__.py
107
106
  tests/helpers/test_async_cache.py
108
107
  tests/helpers/test_event_manager.py
109
- tests/helpers/test_factory.py
110
108
  tests/helpers/test_task_manager.py
111
109
  tests/helpers/test_uid.py
112
110
  tests/parameters/__init__.py
@@ -2,7 +2,7 @@ pyserial-asyncio==0.6
2
2
 
3
3
  [dev]
4
4
  pyplumio[docs,test]
5
- pre-commit==4.4.0
5
+ pre-commit==4.5.0
6
6
  tomli==2.3.0
7
7
 
8
8
  [docs]
@@ -12,13 +12,13 @@ readthedocs-sphinx-search==0.3.2
12
12
 
13
13
  [test]
14
14
  codespell==2.4.1
15
- coverage==7.11.3
15
+ coverage==7.12.0
16
16
  freezegun==1.5.5
17
- mypy==1.18.2
17
+ mypy==1.19.0
18
18
  numpy<3.0.0,>=2.0.0
19
19
  pyserial-asyncio-fast==0.16
20
- pytest==8.4.2
21
- pytest-asyncio==1.2.0
22
- ruff==0.14.4
20
+ pytest==9.0.1
21
+ pytest-asyncio==1.3.0
22
+ ruff==0.14.7
23
23
  tox==4.32.0
24
24
  types-pyserial==3.5.0.20251001
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.6.6'
32
- __version_tuple__ = version_tuple = (0, 6, 6)
31
+ __version__ = version = '0.6.7'
32
+ __version_tuple__ = version_tuple = (0, 6, 7)
33
33
 
34
- __commit_id__ = commit_id = 'g40bf0ee75'
34
+ __commit_id__ = commit_id = 'g6d0ba3b08'
@@ -7,6 +7,7 @@ import asyncio
7
7
  from collections.abc import AsyncGenerator
8
8
  from contextlib import asynccontextmanager
9
9
  import logging
10
+ from types import MappingProxyType
10
11
  from typing import Any, Final
11
12
 
12
13
  from serial import EIGHTBITS, PARITY_NONE, STOPBITS_ONE
@@ -122,9 +123,9 @@ class Connection(ABC, TaskManager):
122
123
  return self._protocol
123
124
 
124
125
  @property
125
- def options(self) -> dict[str, Any]:
126
+ def options(self) -> MappingProxyType[str, Any]:
126
127
  """Return connection options."""
127
- return self._options
128
+ return MappingProxyType(self._options)
128
129
 
129
130
  @timeout(TRY_CONNECT_FOR_SECONDS)
130
131
  @abstractmethod
@@ -14,10 +14,9 @@ from pyplumio.exceptions import RequestError, UnknownDeviceError
14
14
  from pyplumio.filters import on_change
15
15
  from pyplumio.frames import Frame, Request, is_known_frame_type
16
16
  from pyplumio.helpers.event_manager import EventManager, event_listener
17
- from pyplumio.helpers.factory import create_instance
18
17
  from pyplumio.parameters import Numeric, Parameter
19
18
  from pyplumio.structures.network_info import NetworkInfo
20
- from pyplumio.utils import to_camelcase
19
+ from pyplumio.utils import create_instance, to_camelcase
21
20
 
22
21
  _LOGGER = logging.getLogger(__name__)
23
22
 
@@ -43,7 +43,7 @@ from pyplumio.structures.ecomax_parameters import (
43
43
  )
44
44
  from pyplumio.structures.mixer_parameters import ATTR_MIXER_PARAMETERS
45
45
  from pyplumio.structures.network_info import ATTR_NETWORK_INFO, NetworkInfo
46
- from pyplumio.structures.product_info import ATTR_PRODUCT, ProductInfo
46
+ from pyplumio.structures.product_info import ATTR_PRODUCT
47
47
  from pyplumio.structures.regulator_data_schema import ATTR_REGDATA_SCHEMA
48
48
  from pyplumio.structures.schedules import (
49
49
  ATTR_SCHEDULES,
@@ -142,7 +142,9 @@ class EcoMAX(PhysicalDevice):
142
142
  def handle_frame(self, frame: Frame) -> None:
143
143
  """Handle frame received from the ecoMAX device."""
144
144
  if isinstance(frame, Request) and (
145
- response := frame.response(data={ATTR_NETWORK_INFO: self._network_info})
145
+ response := frame.create_response(
146
+ data={ATTR_NETWORK_INFO: self._network_info}
147
+ )
146
148
  ):
147
149
  self.queue_send(response)
148
150
 
@@ -253,10 +255,10 @@ class EcoMAX(PhysicalDevice):
253
255
  ) -> bool:
254
256
  """Update ecoMAX parameters and dispatch the events."""
255
257
  _LOGGER.debug("Received device parameters")
256
- product_info: ProductInfo = await self.get(ATTR_PRODUCT)
258
+ product_info = await self.get(ATTR_PRODUCT)
257
259
  parameter_types = await get_ecomax_parameter_types(product_info)
258
260
 
259
- def _ecomax_parameter_events() -> Generator[Coroutine]:
261
+ def _ecomax_parameter_events() -> Generator[Coroutine[Any, Any, None]]:
260
262
  """Get dispatch calls for ecoMAX parameter events."""
261
263
  for index, values in parameters:
262
264
  try:
@@ -333,7 +335,7 @@ class EcoMAX(PhysicalDevice):
333
335
  ) -> bool:
334
336
  """Update schedule parameters and dispatch the events."""
335
337
 
336
- def _schedule_parameter_events() -> Generator[Coroutine]:
338
+ def _schedule_parameter_events() -> Generator[Coroutine[Any, Any, None]]:
337
339
  """Get dispatch calls for schedule parameter events."""
338
340
  for index, values in parameters:
339
341
  description = SCHEDULE_PARAMETERS[index]
@@ -16,7 +16,7 @@ from pyplumio.parameters.mixer import (
16
16
  MixerSwitchDescription,
17
17
  get_mixer_parameter_types,
18
18
  )
19
- from pyplumio.structures.product_info import ATTR_PRODUCT, ProductInfo
19
+ from pyplumio.structures.product_info import ATTR_PRODUCT
20
20
 
21
21
  _LOGGER = logging.getLogger(__name__)
22
22
 
@@ -41,10 +41,10 @@ class Mixer(LogicalDevice):
41
41
  ) -> bool:
42
42
  """Update mixer parameters and dispatch the events."""
43
43
  _LOGGER.debug("Received mixer %i parameters", self.index)
44
- product_info: ProductInfo = await self.parent.get(ATTR_PRODUCT)
44
+ product_info = await self.parent.get(ATTR_PRODUCT)
45
45
  parameter_types = get_mixer_parameter_types(product_info)
46
46
 
47
- def _mixer_parameter_events() -> Generator[Coroutine]:
47
+ def _mixer_parameter_events() -> Generator[Coroutine[Any, Any, None]]:
48
48
  """Get dispatch calls for mixer parameter events."""
49
49
  for index, values in parameters:
50
50
  try:
@@ -42,7 +42,7 @@ class Thermostat(LogicalDevice):
42
42
  _LOGGER.debug("Received thermostat %i parameters", self.index)
43
43
  parameter_types = get_thermostat_parameter_types()
44
44
 
45
- def _thermostat_parameter_events() -> Generator[Coroutine]:
45
+ def _thermostat_parameter_events() -> Generator[Coroutine[Any, Any, None]]:
46
46
  """Get dispatch calls for thermostat parameter events."""
47
47
  for index, values in parameters:
48
48
  description = parameter_types[index]
@@ -69,7 +69,7 @@ DEFAULT_TOLERANCE: Final = 1e-6
69
69
 
70
70
 
71
71
  @overload
72
- def is_close(old: Parameter, new: Parameter, *, tolerance: None = None) -> bool: ...
72
+ def is_close(old: Parameter, new: Parameter) -> bool: ...
73
73
 
74
74
 
75
75
  @overload
@@ -79,9 +79,7 @@ def is_close(
79
79
 
80
80
 
81
81
  @overload
82
- def is_close(
83
- old: SupportsComparison, new: SupportsComparison, *, tolerance: None = None
84
- ) -> bool: ...
82
+ def is_close(old: SupportsComparison, new: SupportsComparison) -> bool: ...
85
83
 
86
84
 
87
85
  def is_close(
@@ -100,6 +98,9 @@ def is_close(
100
98
  return old.__eq__(new)
101
99
 
102
100
 
101
+ _SubtractableT = TypeVar("_SubtractableT", list, SupportsSubtraction)
102
+
103
+
103
104
  @overload
104
105
  def diffence_between(old: list, new: list) -> list: ...
105
106
 
@@ -110,9 +111,7 @@ def diffence_between(
110
111
  ) -> SupportsSubtraction: ...
111
112
 
112
113
 
113
- def diffence_between(
114
- old: SupportsSubtraction | list, new: SupportsSubtraction | list
115
- ) -> SupportsSubtraction | list | None:
114
+ def diffence_between(old: _SubtractableT, new: _SubtractableT) -> Any:
116
115
  """Return a difference between values."""
117
116
  if isinstance(old, list) and isinstance(new, list):
118
117
  return [x for x in new if x not in old]
@@ -120,7 +119,7 @@ def diffence_between(
120
119
  if isinstance(old, SupportsSubtraction) and isinstance(new, SupportsSubtraction):
121
120
  return new.__sub__(old)
122
121
 
123
- return None
122
+ return NotImplemented
124
123
 
125
124
 
126
125
  _Numeric: TypeAlias = float | int | Decimal | Number
@@ -172,6 +171,11 @@ class Filter(ABC):
172
171
 
173
172
  return NotImplemented
174
173
 
174
+ @property
175
+ def __name__(self) -> str:
176
+ """Return a callback name."""
177
+ return self._callback.__name__
178
+
175
179
  @abstractmethod
176
180
  async def __call__(self, new_value: Any) -> Any:
177
181
  """Set a new value for the callback."""
@@ -536,7 +540,8 @@ class _Delta(ComparisonFilter):
536
540
  if self.is_undefined() or not is_close(self.value, new_value):
537
541
  old_value = self.value
538
542
  self.value = new_value
539
- if (difference := diffence_between(old_value, new_value)) is not None:
543
+ difference = diffence_between(old_value, new_value)
544
+ if difference is not NotImplemented:
540
545
  return await self._callback(difference)
541
546
 
542
547
 
@@ -10,8 +10,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, Final, TypeVar
10
10
 
11
11
  from pyplumio.const import DeviceType, FrameType
12
12
  from pyplumio.exceptions import UnknownFrameError
13
- from pyplumio.helpers.factory import create_instance
14
- from pyplumio.utils import ensure_dict, to_camelcase
13
+ from pyplumio.utils import create_instance, ensure_dict, to_camelcase
15
14
 
16
15
  if TYPE_CHECKING:
17
16
  from pyplumio.structures import Structure
@@ -243,11 +242,29 @@ class Frame(ABC):
243
242
  """Decode a frame message."""
244
243
 
245
244
 
245
+ def frame_handler(
246
+ frame_type: FrameType, structure: type[Structure] | None = None
247
+ ) -> Callable[[type[_FrameT]], type[_FrameT]]:
248
+ """Specify frame type for the frame class."""
249
+
250
+ def wrapper(cls: type[_FrameT]) -> type[_FrameT]:
251
+ """Wrap the frame class."""
252
+ setattr(cls, "frame_type", frame_type)
253
+ if structure and issubclass(cls, Response):
254
+ setattr(cls, "structures", (structure,))
255
+
256
+ return cls
257
+
258
+ return wrapper
259
+
260
+
246
261
  class Request(Frame):
247
262
  """Represents a request."""
248
263
 
249
264
  __slots__ = ()
250
265
 
266
+ response: ClassVar[type[Response]]
267
+
251
268
  def create_message(self, data: dict[str, Any]) -> bytearray:
252
269
  """Create a frame message."""
253
270
  return bytearray()
@@ -256,38 +273,47 @@ class Request(Frame):
256
273
  """Decode a frame message."""
257
274
  return {}
258
275
 
259
- def response(self, **kwargs: Any) -> Response | None:
260
- """Return a response frame."""
276
+ def create_response(self, **kwargs: Any) -> Response | None:
277
+ """Create response frame."""
278
+ if hasattr(self, "response"):
279
+ return self.response(recipient=self.sender, **kwargs)
280
+
261
281
  return None
262
282
 
283
+ def validate_response(self, response: Response) -> bool:
284
+ """Validate if response is valid for the request."""
285
+ if not hasattr(self, "response"):
286
+ raise NotImplementedError
263
287
 
264
- class Response(Frame):
265
- """Represents a response."""
288
+ if isinstance(response, self.response):
289
+ return True
266
290
 
267
- __slots__ = ()
291
+ return False
268
292
 
269
- def create_message(self, data: dict[str, Any]) -> bytearray:
270
- """Create a frame message."""
271
- return bytearray()
272
293
 
273
- def decode_message(self, message: bytearray) -> dict[str, Any]:
274
- """Decode a frame message."""
275
- return {}
294
+ _RequestT = TypeVar("_RequestT", bound=Request)
276
295
 
277
296
 
278
- class Message(Response):
279
- """Represents a message."""
297
+ def expect_response(
298
+ response: type[Response],
299
+ ) -> Callable[[type[_RequestT]], type[_RequestT]]:
300
+ """Assign response to the request class."""
280
301
 
281
- __slots__ = ()
302
+ def wrapper(cls: type[_RequestT]) -> type[_RequestT]:
303
+ """Wrap the request class."""
304
+ setattr(cls, "response", response)
305
+ return cls
282
306
 
307
+ return wrapper
283
308
 
284
- class Structured(Frame):
285
- """Represents a frame that has known structure."""
309
+
310
+ class Response(Frame):
311
+ """Represents a response."""
286
312
 
287
313
  __slots__ = ("_structures",)
288
314
 
289
315
  container: ClassVar[str]
290
- structures: ClassVar[list[type[Structure]]]
316
+ structures: ClassVar[list[type[Structure]]] = []
291
317
 
292
318
  _structures: list[Structure]
293
319
 
@@ -302,9 +328,7 @@ class Structured(Frame):
302
328
  **kwargs: Any,
303
329
  ) -> None:
304
330
  """Initialize a new structured frame."""
305
- if hasattr(self, "structures"):
306
- self._structures = [structure(self) for structure in self.structures]
307
-
331
+ self._structures = [structure(self) for structure in self.structures]
308
332
  super().__init__(
309
333
  recipient, sender, econet_type, econet_version, message, data, **kwargs
310
334
  )
@@ -330,28 +354,18 @@ class Structured(Frame):
330
354
  return data
331
355
 
332
356
 
333
- def frame_handler(
334
- frame_type: FrameType, structure: type[Structure] | None = None
335
- ) -> Callable[[type[_FrameT]], type[_FrameT]]:
336
- """Specify frame type for the frame class."""
337
-
338
- def wrapper(cls: type[_FrameT]) -> type[_FrameT]:
339
- """Wrap the frame class."""
340
- setattr(cls, "frame_type", frame_type)
341
- if structure and issubclass(cls, Structured):
342
- setattr(cls, "structures", (structure,))
343
-
344
- return cls
357
+ class Message(Response):
358
+ """Represents a message."""
345
359
 
346
- return wrapper
360
+ __slots__ = ()
347
361
 
348
362
 
349
- _StructuredT = TypeVar("_StructuredT", bound=Structured)
363
+ _ResponseT = TypeVar("_ResponseT", bound=Response)
350
364
 
351
365
 
352
366
  def contains(
353
367
  *structures: type[Structure], container: str | None = None
354
- ) -> Callable[[type[_StructuredT]], type[_StructuredT]]:
368
+ ) -> Callable[[type[_ResponseT]], type[_ResponseT]]:
355
369
  """Decorate frame class with structure.
356
370
 
357
371
  Indicate which structures need to be used for encoding/decoding
@@ -359,7 +373,7 @@ def contains(
359
373
  a single key.
360
374
  """
361
375
 
362
- def wrapper(cls: type[_StructuredT]) -> type[_StructuredT]:
376
+ def wrapper(cls: type[_ResponseT]) -> type[_ResponseT]:
363
377
  """Wrap the frame class."""
364
378
  setattr(cls, "structures", structures)
365
379
  if container:
@@ -375,9 +389,9 @@ __all__ = [
375
389
  "Request",
376
390
  "Response",
377
391
  "Message",
378
- "Structured",
379
392
  "bcc",
380
393
  "contains",
394
+ "expect_response",
381
395
  "frame_handler",
382
396
  "get_frame_handler",
383
397
  "is_known_frame_type",
@@ -3,14 +3,14 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from pyplumio.const import ATTR_SENSORS, FrameType
6
- from pyplumio.frames import Message, Structured, contains, frame_handler
6
+ from pyplumio.frames import Message, contains, frame_handler
7
7
  from pyplumio.structures.frame_versions import FrameVersionsStructure
8
8
  from pyplumio.structures.regulator_data import RegulatorDataStructure
9
9
  from pyplumio.structures.sensor_data import SensorDataStructure
10
10
 
11
11
 
12
12
  @frame_handler(FrameType.MESSAGE_REGULATOR_DATA, structure=RegulatorDataStructure)
13
- class RegulatorDataMessage(Structured, Message):
13
+ class RegulatorDataMessage(Message):
14
14
  """Represents a regulator data message."""
15
15
 
16
16
  __slots__ = ()
@@ -18,7 +18,7 @@ class RegulatorDataMessage(Structured, Message):
18
18
 
19
19
  @frame_handler(FrameType.MESSAGE_SENSOR_DATA)
20
20
  @contains(FrameVersionsStructure, SensorDataStructure, container=ATTR_SENSORS)
21
- class SensorDataMessage(Structured, Message):
21
+ class SensorDataMessage(Message):
22
22
  """Represents a sensor data message."""
23
23
 
24
24
  __slots__ = ()
@@ -19,13 +19,13 @@ from pyplumio.const import (
19
19
  FrameType,
20
20
  )
21
21
  from pyplumio.exceptions import FrameDataError
22
- from pyplumio.frames import Request, Response, frame_handler
23
- from pyplumio.frames.responses import DeviceAvailableResponse, ProgramVersionResponse
22
+ from pyplumio.frames import Request, expect_response, frame_handler, responses
24
23
  from pyplumio.structures.schedules import SCHEDULES
25
24
  from pyplumio.utils import join_bits
26
25
 
27
26
 
28
27
  @frame_handler(FrameType.REQUEST_ALERTS)
28
+ @expect_response(responses.AlertsResponse)
29
29
  class AlertsRequest(Request):
30
30
  """Represents an alerts request.
31
31
 
@@ -41,17 +41,15 @@ class AlertsRequest(Request):
41
41
 
42
42
 
43
43
  @frame_handler(FrameType.REQUEST_CHECK_DEVICE)
44
+ @expect_response(responses.DeviceAvailableResponse)
44
45
  class CheckDeviceRequest(Request):
45
46
  """Represents a check device request."""
46
47
 
47
48
  __slots__ = ()
48
49
 
49
- def response(self, **kwargs: Any) -> Response | None:
50
- """Return a response frame."""
51
- return DeviceAvailableResponse(recipient=self.sender, **kwargs)
52
-
53
50
 
54
51
  @frame_handler(FrameType.REQUEST_ECOMAX_CONTROL)
52
+ @expect_response(responses.EcomaxControlResponse)
55
53
  class EcomaxControlRequest(Request):
56
54
  """Represents an ecoMAX control request.
57
55
 
@@ -70,6 +68,7 @@ class EcomaxControlRequest(Request):
70
68
 
71
69
 
72
70
  @frame_handler(FrameType.REQUEST_ECOMAX_PARAMETERS)
71
+ @expect_response(responses.EcomaxParametersResponse)
73
72
  class EcomaxParametersRequest(Request):
74
73
  """Represents an ecoMAX parameters request.
75
74
 
@@ -84,6 +83,7 @@ class EcomaxParametersRequest(Request):
84
83
 
85
84
 
86
85
  @frame_handler(FrameType.REQUEST_MIXER_PARAMETERS)
86
+ @expect_response(responses.MixerParametersResponse)
87
87
  class MixerParametersRequest(Request):
88
88
  """Represents a mixer parameters request.
89
89
 
@@ -99,6 +99,7 @@ class MixerParametersRequest(Request):
99
99
 
100
100
 
101
101
  @frame_handler(FrameType.REQUEST_PASSWORD)
102
+ @expect_response(responses.PasswordResponse)
102
103
  class PasswordRequest(Request):
103
104
  """Represents a password request."""
104
105
 
@@ -106,17 +107,15 @@ class PasswordRequest(Request):
106
107
 
107
108
 
108
109
  @frame_handler(FrameType.REQUEST_PROGRAM_VERSION)
110
+ @expect_response(responses.ProgramVersionResponse)
109
111
  class ProgramVersionRequest(Request):
110
112
  """Represents a program version request."""
111
113
 
112
114
  __slots__ = ()
113
115
 
114
- def response(self, **kwargs: Any) -> Response | None:
115
- """Return a response frame."""
116
- return ProgramVersionResponse(recipient=self.sender, **kwargs)
117
-
118
116
 
119
117
  @frame_handler(FrameType.REQUEST_REGULATOR_DATA_SCHEMA)
118
+ @expect_response(responses.RegulatorDataSchemaResponse)
120
119
  class RegulatorDataSchemaRequest(Request):
121
120
  """Represents regulator data schema request."""
122
121
 
@@ -124,6 +123,7 @@ class RegulatorDataSchemaRequest(Request):
124
123
 
125
124
 
126
125
  @frame_handler(FrameType.REQUEST_SCHEDULES)
126
+ @expect_response(responses.SchedulesResponse)
127
127
  class SchedulesRequest(Request):
128
128
  """Represents a schedules request."""
129
129
 
@@ -131,6 +131,7 @@ class SchedulesRequest(Request):
131
131
 
132
132
 
133
133
  @frame_handler(FrameType.REQUEST_SET_ECOMAX_PARAMETER)
134
+ @expect_response(responses.SetEcomaxParameterResponse)
134
135
  class SetEcomaxParameterRequest(Request):
135
136
  """Represents a request to set an ecoMAX parameter.
136
137
 
@@ -148,6 +149,7 @@ class SetEcomaxParameterRequest(Request):
148
149
 
149
150
 
150
151
  @frame_handler(FrameType.REQUEST_SET_MIXER_PARAMETER)
152
+ @expect_response(responses.SetMixerParameterResponse)
151
153
  class SetMixerParameterRequest(Request):
152
154
  """Represents a request to set a mixer parameter.
153
155
 
@@ -190,6 +192,7 @@ class SetScheduleRequest(Request):
190
192
 
191
193
 
192
194
  @frame_handler(FrameType.REQUEST_SET_THERMOSTAT_PARAMETER)
195
+ @expect_response(responses.SetThermostatParameterResponse)
193
196
  class SetThermostatParameterRequest(Request):
194
197
  """Represents a request to set a thermostat parameter.
195
198
 
@@ -241,6 +244,7 @@ class StopMasterRequest(Request):
241
244
 
242
245
 
243
246
  @frame_handler(FrameType.REQUEST_THERMOSTAT_PARAMETERS)
247
+ @expect_response(responses.ThermostatParametersResponse)
244
248
  class ThermostatParametersRequest(Request):
245
249
  """Represents a thermostat parameters request.
246
250
 
@@ -256,6 +260,7 @@ class ThermostatParametersRequest(Request):
256
260
 
257
261
 
258
262
  @frame_handler(FrameType.REQUEST_UID)
263
+ @expect_response(responses.UIDResponse)
259
264
  class UIDRequest(Request):
260
265
  """Represents an UID request."""
261
266