PyPlumIO 0.6.6__tar.gz → 0.6.8__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 (153) hide show
  1. {pyplumio-0.6.6 → pyplumio-0.6.8}/.pre-commit-config.yaml +2 -2
  2. {pyplumio-0.6.6 → pyplumio-0.6.8}/PKG-INFO +8 -8
  3. {pyplumio-0.6.6 → pyplumio-0.6.8}/PyPlumIO.egg-info/PKG-INFO +8 -8
  4. {pyplumio-0.6.6 → pyplumio-0.6.8}/PyPlumIO.egg-info/SOURCES.txt +0 -2
  5. {pyplumio-0.6.6 → pyplumio-0.6.8}/PyPlumIO.egg-info/requires.txt +7 -7
  6. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/_version.py +3 -3
  7. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/connection.py +22 -15
  8. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/devices/__init__.py +1 -2
  9. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/devices/ecomax.py +7 -5
  10. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/devices/mixer.py +3 -3
  11. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/devices/thermostat.py +1 -1
  12. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/filters.py +14 -9
  13. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/frames/__init__.py +53 -39
  14. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/frames/messages.py +3 -3
  15. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/frames/requests.py +15 -10
  16. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/frames/responses.py +10 -10
  17. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/helpers/async_cache.py +2 -2
  18. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/helpers/event_manager.py +61 -31
  19. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/parameters/custom/__init__.py +1 -2
  20. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/protocol.py +1 -1
  21. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/structures/__init__.py +7 -9
  22. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/structures/regulator_data.py +3 -3
  23. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/utils.py +28 -1
  24. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyproject.toml +7 -7
  25. pyplumio-0.6.8/requirements.txt +1 -0
  26. {pyplumio-0.6.6 → pyplumio-0.6.8}/requirements_test.txt +6 -6
  27. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/devices/test_ecomax.py +1 -1
  28. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/devices/test_init.py +1 -1
  29. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/frames/test_init.py +2 -13
  30. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/frames/test_requests.py +64 -36
  31. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/helpers/test_event_manager.py +25 -4
  32. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/test_connection.py +29 -36
  33. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/test_filters.py +15 -1
  34. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/test_protocol.py +3 -3
  35. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/test_utils.py +31 -0
  36. pyplumio-0.6.6/pyplumio/helpers/factory.py +0 -36
  37. pyplumio-0.6.6/requirements.txt +0 -1
  38. pyplumio-0.6.6/tests/helpers/test_factory.py +0 -41
  39. {pyplumio-0.6.6 → pyplumio-0.6.8}/.gitattributes +0 -0
  40. {pyplumio-0.6.6 → pyplumio-0.6.8}/.github/CODE_OF_CONDUCT.md +0 -0
  41. {pyplumio-0.6.6 → pyplumio-0.6.8}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  42. {pyplumio-0.6.6 → pyplumio-0.6.8}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  43. {pyplumio-0.6.6 → pyplumio-0.6.8}/.github/dependabot.yml +0 -0
  44. {pyplumio-0.6.6 → pyplumio-0.6.8}/.github/workflows/ci.yml +0 -0
  45. {pyplumio-0.6.6 → pyplumio-0.6.8}/.github/workflows/codeql.yml +0 -0
  46. {pyplumio-0.6.6 → pyplumio-0.6.8}/.github/workflows/deploy.yml +0 -0
  47. {pyplumio-0.6.6 → pyplumio-0.6.8}/.github/workflows/documentation.yml +0 -0
  48. {pyplumio-0.6.6 → pyplumio-0.6.8}/.gitignore +0 -0
  49. {pyplumio-0.6.6 → pyplumio-0.6.8}/.qlty/qlty.toml +0 -0
  50. {pyplumio-0.6.6 → pyplumio-0.6.8}/.vscode/settings.json +0 -0
  51. {pyplumio-0.6.6 → pyplumio-0.6.8}/LICENSE +0 -0
  52. {pyplumio-0.6.6 → pyplumio-0.6.8}/MANIFEST.in +0 -0
  53. {pyplumio-0.6.6 → pyplumio-0.6.8}/PyPlumIO.egg-info/dependency_links.txt +0 -0
  54. {pyplumio-0.6.6 → pyplumio-0.6.8}/PyPlumIO.egg-info/top_level.txt +0 -0
  55. {pyplumio-0.6.6 → pyplumio-0.6.8}/README.md +0 -0
  56. {pyplumio-0.6.6 → pyplumio-0.6.8}/docs/Makefile +0 -0
  57. {pyplumio-0.6.6 → pyplumio-0.6.8}/docs/make.bat +0 -0
  58. {pyplumio-0.6.6 → pyplumio-0.6.8}/docs/source/callbacks.rst +0 -0
  59. {pyplumio-0.6.6 → pyplumio-0.6.8}/docs/source/conf.py +0 -0
  60. {pyplumio-0.6.6 → pyplumio-0.6.8}/docs/source/connecting.rst +0 -0
  61. {pyplumio-0.6.6 → pyplumio-0.6.8}/docs/source/frames.rst +0 -0
  62. {pyplumio-0.6.6 → pyplumio-0.6.8}/docs/source/index.rst +0 -0
  63. {pyplumio-0.6.6 → pyplumio-0.6.8}/docs/source/mixers_thermostats.rst +0 -0
  64. {pyplumio-0.6.6 → pyplumio-0.6.8}/docs/source/protocol.rst +0 -0
  65. {pyplumio-0.6.6 → pyplumio-0.6.8}/docs/source/reading.rst +0 -0
  66. {pyplumio-0.6.6 → pyplumio-0.6.8}/docs/source/schedules.rst +0 -0
  67. {pyplumio-0.6.6 → pyplumio-0.6.8}/docs/source/writing.rst +0 -0
  68. {pyplumio-0.6.6 → pyplumio-0.6.8}/images/ecomax.png +0 -0
  69. {pyplumio-0.6.6 → pyplumio-0.6.8}/images/rs485.png +0 -0
  70. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/__init__.py +0 -0
  71. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/__main__.py +0 -0
  72. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/const.py +0 -0
  73. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/data_types.py +0 -0
  74. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/devices/ecoster.py +0 -0
  75. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/exceptions.py +0 -0
  76. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/helpers/__init__.py +0 -0
  77. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/helpers/task_manager.py +0 -0
  78. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/parameters/__init__.py +0 -0
  79. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/parameters/custom/ecomax_860d3_hb.py +0 -0
  80. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/parameters/ecomax.py +0 -0
  81. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/parameters/mixer.py +0 -0
  82. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/parameters/thermostat.py +0 -0
  83. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/py.typed +0 -0
  84. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/stream.py +0 -0
  85. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/structures/alerts.py +0 -0
  86. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/structures/ecomax_parameters.py +0 -0
  87. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/structures/frame_versions.py +0 -0
  88. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/structures/mixer_parameters.py +0 -0
  89. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/structures/network_info.py +0 -0
  90. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/structures/product_info.py +0 -0
  91. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/structures/program_version.py +0 -0
  92. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/structures/regulator_data_schema.py +0 -0
  93. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/structures/schedules.py +0 -0
  94. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/structures/sensor_data.py +0 -0
  95. {pyplumio-0.6.6 → pyplumio-0.6.8}/pyplumio/structures/thermostat_parameters.py +0 -0
  96. {pyplumio-0.6.6 → pyplumio-0.6.8}/requirements_docs.txt +0 -0
  97. {pyplumio-0.6.6 → pyplumio-0.6.8}/setup.cfg +0 -0
  98. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/__init__.py +0 -0
  99. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/conftest.py +0 -0
  100. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/devices/__init__.py +0 -0
  101. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/devices/test_ecoster.py +0 -0
  102. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/devices/test_mixer.py +0 -0
  103. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/devices/test_thermostat.py +0 -0
  104. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/frames/test_messages.py +0 -0
  105. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/frames/test_responses.py +0 -0
  106. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/helpers/__init__.py +0 -0
  107. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/helpers/test_async_cache.py +0 -0
  108. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/helpers/test_task_manager.py +0 -0
  109. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/helpers/test_uid.py +0 -0
  110. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/parameters/__init__.py +0 -0
  111. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/parameters/custom/__init__.py +0 -0
  112. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/parameters/custom/test_ecomax_860d3_hb.py +0 -0
  113. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/parameters/custom/test_init.py +0 -0
  114. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/parameters/test_ecomax.py +0 -0
  115. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/parameters/test_init.py +0 -0
  116. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/parameters/test_mixers.py +0 -0
  117. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/parameters/test_thermostats.py +0 -0
  118. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/ruff.toml +0 -0
  119. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/structures/__init__.py +0 -0
  120. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/structures/test_alerts.py +0 -0
  121. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/structures/test_ecomax_parameters.py +0 -0
  122. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/structures/test_frame_versions.py +0 -0
  123. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/structures/test_mixer_parameters.py +0 -0
  124. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/structures/test_product_info.py +0 -0
  125. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/structures/test_schedules.py +0 -0
  126. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/test_data_types.py +0 -0
  127. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/test_init.py +0 -0
  128. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/test_main.py +0 -0
  129. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/test_stream.py +0 -0
  130. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/messages/regulator_data.json +0 -0
  131. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/messages/sensor_data.json +0 -0
  132. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/parameters/ecomax_860d3_hb.json +0 -0
  133. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/requests/alerts.json +0 -0
  134. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/requests/ecomax_control.json +0 -0
  135. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/requests/ecomax_parameters.json +0 -0
  136. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/requests/mixer_parameters.json +0 -0
  137. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/requests/set_ecomax_parameter.json +0 -0
  138. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/requests/set_mixer_parameter.json +0 -0
  139. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/requests/set_schedule.json +0 -0
  140. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/requests/set_thermostat_parameter.json +0 -0
  141. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/requests/thermostat_parameters.json +0 -0
  142. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/responses/alerts.json +0 -0
  143. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/responses/device_available.json +0 -0
  144. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/responses/ecomax_parameters.json +0 -0
  145. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/responses/mixer_parameters.json +0 -0
  146. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/responses/password.json +0 -0
  147. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/responses/program_version.json +0 -0
  148. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/responses/regulator_data_schema.json +0 -0
  149. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/responses/schedules.json +0 -0
  150. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/responses/thermostat_parameters.json +0 -0
  151. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/responses/uid.json +0 -0
  152. {pyplumio-0.6.6 → pyplumio-0.6.8}/tests/testdata/unknown/unknown_ecomax_parameter.json +0 -0
  153. {pyplumio-0.6.6 → pyplumio-0.6.8}/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.14.4
6
+ rev: v0.14.10
7
7
  hooks:
8
8
  - id: ruff
9
9
  args:
@@ -13,6 +13,6 @@ repos:
13
13
  hooks:
14
14
  - id: codespell
15
15
  - repo: https://github.com/pre-commit/mirrors-mypy
16
- rev: v1.18.2
16
+ rev: v1.19.1
17
17
  hooks:
18
18
  - id: mypy
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPlumIO
3
- Version: 0.6.6
3
+ Version: 0.6.8
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
@@ -23,17 +23,17 @@ Classifier: Topic :: Home Automation
23
23
  Requires-Python: >=3.10
24
24
  Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
- Requires-Dist: pyserial-asyncio==0.6
26
+ Requires-Dist: pyserial-asyncio-fast==0.16
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.13.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.1; 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.2; extra == "test"
35
+ Requires-Dist: pytest-asyncio==1.3.0; extra == "test"
36
+ Requires-Dist: ruff==0.14.10; 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.1; 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.8
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
@@ -23,17 +23,17 @@ Classifier: Topic :: Home Automation
23
23
  Requires-Python: >=3.10
24
24
  Description-Content-Type: text/markdown
25
25
  License-File: LICENSE
26
- Requires-Dist: pyserial-asyncio==0.6
26
+ Requires-Dist: pyserial-asyncio-fast==0.16
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.13.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.1; 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.2; extra == "test"
35
+ Requires-Dist: pytest-asyncio==1.3.0; extra == "test"
36
+ Requires-Dist: ruff==0.14.10; 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.1; 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
@@ -1,8 +1,8 @@
1
- pyserial-asyncio==0.6
1
+ pyserial-asyncio-fast==0.16
2
2
 
3
3
  [dev]
4
4
  pyplumio[docs,test]
5
- pre-commit==4.4.0
5
+ pre-commit==4.5.1
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.13.0
16
16
  freezegun==1.5.5
17
- mypy==1.18.2
17
+ mypy==1.19.1
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.2
21
+ pytest-asyncio==1.3.0
22
+ ruff==0.14.10
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.8'
32
+ __version_tuple__ = version_tuple = (0, 6, 8)
33
33
 
34
- __commit_id__ = commit_id = 'g40bf0ee75'
34
+ __commit_id__ = commit_id = 'gbde57b4a1'
@@ -7,9 +7,11 @@ 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
14
+ import serial_asyncio_fast
13
15
 
14
16
  from pyplumio.devices import PhysicalDevice
15
17
  from pyplumio.exceptions import ConnectionFailedError
@@ -22,13 +24,6 @@ _LOGGER = logging.getLogger(__name__)
22
24
  TRY_CONNECT_FOR_SECONDS: Final = 5
23
25
  RECONNECT_AFTER_SECONDS: Final = 20
24
26
 
25
- try:
26
- import serial_asyncio_fast as pyserial_asyncio
27
-
28
- _LOGGER.info("Using pyserial-asyncio-fast in place of pyserial-asyncio")
29
- except ImportError:
30
- import serial_asyncio as pyserial_asyncio # type: ignore[no-redef]
31
-
32
27
 
33
28
  class Connection(ABC, TaskManager):
34
29
  """Represents a connection.
@@ -36,11 +31,12 @@ class Connection(ABC, TaskManager):
36
31
  All specific connection classes MUST be inherited from this class.
37
32
  """
38
33
 
39
- __slots__ = ("_protocol", "_reconnect_on_failure", "_options")
34
+ __slots__ = ("_protocol", "_reconnect_on_failure", "_options", "_retries")
40
35
 
41
36
  _protocol: Protocol
42
37
  _reconnect_on_failure: bool
43
38
  _options: dict[str, Any]
39
+ _retries: int
44
40
 
45
41
  def __init__(
46
42
  self,
@@ -59,6 +55,7 @@ class Connection(ABC, TaskManager):
59
55
  self._reconnect_on_failure = reconnect_on_failure
60
56
  self._protocol = protocol
61
57
  self._options = options
58
+ self._retries = 0
62
59
 
63
60
  async def __aenter__(self) -> Connection:
64
61
  """Provide an entry point for the context manager."""
@@ -85,11 +82,21 @@ class Connection(ABC, TaskManager):
85
82
  """Try to connect and reconnect on failure."""
86
83
  try:
87
84
  await self._connect()
85
+ if self._retries > 0:
86
+ _LOGGER.info(
87
+ "Connection to device restored after %i retries", self._retries
88
+ )
89
+ self._retries = 0
88
90
  except ConnectionFailedError:
89
- _LOGGER.error(
90
- "Can't connect to the device, retrying in %.1f seconds",
91
- RECONNECT_AFTER_SECONDS,
92
- )
91
+ if self._retries == 0:
92
+ _LOGGER.error(
93
+ (
94
+ "Unable to connect to the device. Connection we be retried "
95
+ "automatically every %i seconds in background."
96
+ ),
97
+ RECONNECT_AFTER_SECONDS,
98
+ )
99
+ self._retries += 1
93
100
  await asyncio.sleep(RECONNECT_AFTER_SECONDS)
94
101
  self.create_task(self._reconnect(), name="reconnect_task")
95
102
 
@@ -122,9 +129,9 @@ class Connection(ABC, TaskManager):
122
129
  return self._protocol
123
130
 
124
131
  @property
125
- def options(self) -> dict[str, Any]:
132
+ def options(self) -> MappingProxyType[str, Any]:
126
133
  """Return connection options."""
127
- return self._options
134
+ return MappingProxyType(self._options)
128
135
 
129
136
  @timeout(TRY_CONNECT_FOR_SECONDS)
130
137
  @abstractmethod
@@ -208,7 +215,7 @@ class SerialConnection(Connection):
208
215
  self,
209
216
  ) -> tuple[asyncio.StreamReader, asyncio.StreamWriter]:
210
217
  """Open the connection and return reader and writer objects."""
211
- return await pyserial_asyncio.open_serial_connection(
218
+ return await serial_asyncio_fast.open_serial_connection(
212
219
  url=self.url,
213
220
  baudrate=self.baudrate,
214
221
  bytesize=EIGHTBITS,
@@ -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__ = ()