PyPlumIO 0.5.17__tar.gz → 0.5.19__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 (144) hide show
  1. {pyplumio-0.5.17 → pyplumio-0.5.19}/PKG-INFO +9 -9
  2. {pyplumio-0.5.17 → pyplumio-0.5.19}/PyPlumIO.egg-info/PKG-INFO +9 -9
  3. {pyplumio-0.5.17 → pyplumio-0.5.19}/PyPlumIO.egg-info/requires.txt +8 -8
  4. {pyplumio-0.5.17 → pyplumio-0.5.19}/docs/source/schedules.rst +3 -3
  5. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/__init__.py +1 -0
  6. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/__main__.py +1 -0
  7. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/_version.py +2 -2
  8. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/connection.py +1 -0
  9. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/const.py +1 -0
  10. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/devices/__init__.py +25 -19
  11. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/devices/ecomax.py +3 -11
  12. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/devices/ecoster.py +1 -0
  13. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/devices/mixer.py +1 -0
  14. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/devices/thermostat.py +1 -0
  15. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/exceptions.py +1 -0
  16. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/filters.py +8 -10
  17. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/frames/__init__.py +35 -28
  18. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/frames/messages.py +1 -0
  19. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/frames/requests.py +1 -0
  20. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/frames/responses.py +1 -0
  21. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/helpers/data_types.py +1 -0
  22. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/helpers/event_manager.py +1 -0
  23. pyplumio-0.5.19/pyplumio/helpers/factory.py +35 -0
  24. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/helpers/parameter.py +1 -0
  25. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/helpers/schedule.py +9 -5
  26. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/helpers/task_manager.py +1 -0
  27. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/helpers/timeout.py +1 -0
  28. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/helpers/typing.py +1 -0
  29. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/helpers/uid.py +1 -0
  30. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/protocol.py +46 -55
  31. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/stream.py +16 -12
  32. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/__init__.py +1 -0
  33. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/alerts.py +1 -0
  34. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/boiler_load.py +1 -0
  35. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/boiler_power.py +1 -0
  36. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/ecomax_parameters.py +20 -30
  37. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/fan_power.py +1 -0
  38. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/frame_versions.py +1 -0
  39. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/fuel_consumption.py +1 -0
  40. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/fuel_level.py +1 -0
  41. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/lambda_sensor.py +1 -0
  42. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/mixer_parameters.py +11 -13
  43. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/mixer_sensors.py +1 -0
  44. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/modules.py +1 -0
  45. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/network_info.py +1 -0
  46. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/output_flags.py +1 -0
  47. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/outputs.py +1 -0
  48. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/pending_alerts.py +1 -0
  49. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/product_info.py +1 -0
  50. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/program_version.py +3 -2
  51. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/regulator_data.py +3 -4
  52. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/regulator_data_schema.py +1 -0
  53. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/schedules.py +14 -11
  54. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/statuses.py +1 -0
  55. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/temperatures.py +1 -0
  56. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/thermostat_parameters.py +17 -22
  57. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/structures/thermostat_sensors.py +1 -0
  58. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/utils.py +1 -0
  59. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyproject.toml +9 -9
  60. pyplumio-0.5.19/requirements_test.txt +11 -0
  61. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/__init__.py +0 -1
  62. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/frames/test_init.py +9 -17
  63. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/frames/test_messages.py +3 -4
  64. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/frames/test_responses.py +3 -3
  65. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/helpers/test_data_types.py +1 -1
  66. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/helpers/test_event_manager.py +6 -3
  67. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/helpers/test_factory.py +14 -3
  68. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/helpers/test_parameter.py +1 -0
  69. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/helpers/test_schedule.py +6 -4
  70. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/test_connection.py +11 -7
  71. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/test_devices.py +51 -35
  72. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/test_protocol.py +50 -49
  73. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/test_stream.py +22 -4
  74. pyplumio-0.5.17/pyplumio/helpers/factory.py +0 -23
  75. pyplumio-0.5.17/requirements_test.txt +0 -11
  76. {pyplumio-0.5.17 → pyplumio-0.5.19}/.gitattributes +0 -0
  77. {pyplumio-0.5.17 → pyplumio-0.5.19}/.github/CODE_OF_CONDUCT.md +0 -0
  78. {pyplumio-0.5.17 → pyplumio-0.5.19}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  79. {pyplumio-0.5.17 → pyplumio-0.5.19}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  80. {pyplumio-0.5.17 → pyplumio-0.5.19}/.github/dependabot.yml +0 -0
  81. {pyplumio-0.5.17 → pyplumio-0.5.19}/.github/workflows/ci.yml +0 -0
  82. {pyplumio-0.5.17 → pyplumio-0.5.19}/.github/workflows/codeql-analysis.yml +0 -0
  83. {pyplumio-0.5.17 → pyplumio-0.5.19}/.github/workflows/deploy.yml +0 -0
  84. {pyplumio-0.5.17 → pyplumio-0.5.19}/.github/workflows/documentation.yml +0 -0
  85. {pyplumio-0.5.17 → pyplumio-0.5.19}/.gitignore +0 -0
  86. {pyplumio-0.5.17 → pyplumio-0.5.19}/.pre-commit-config.yaml +0 -0
  87. {pyplumio-0.5.17 → pyplumio-0.5.19}/.vscode/settings.json +0 -0
  88. {pyplumio-0.5.17 → pyplumio-0.5.19}/LICENSE +0 -0
  89. {pyplumio-0.5.17 → pyplumio-0.5.19}/MANIFEST.in +0 -0
  90. {pyplumio-0.5.17 → pyplumio-0.5.19}/PyPlumIO.egg-info/SOURCES.txt +0 -0
  91. {pyplumio-0.5.17 → pyplumio-0.5.19}/PyPlumIO.egg-info/dependency_links.txt +0 -0
  92. {pyplumio-0.5.17 → pyplumio-0.5.19}/PyPlumIO.egg-info/top_level.txt +0 -0
  93. {pyplumio-0.5.17 → pyplumio-0.5.19}/README.md +0 -0
  94. {pyplumio-0.5.17 → pyplumio-0.5.19}/docs/Makefile +0 -0
  95. {pyplumio-0.5.17 → pyplumio-0.5.19}/docs/make.bat +0 -0
  96. {pyplumio-0.5.17 → pyplumio-0.5.19}/docs/source/callbacks.rst +0 -0
  97. {pyplumio-0.5.17 → pyplumio-0.5.19}/docs/source/conf.py +0 -0
  98. {pyplumio-0.5.17 → pyplumio-0.5.19}/docs/source/connecting.rst +0 -0
  99. {pyplumio-0.5.17 → pyplumio-0.5.19}/docs/source/index.rst +0 -0
  100. {pyplumio-0.5.17 → pyplumio-0.5.19}/docs/source/mixers_thermostats.rst +0 -0
  101. {pyplumio-0.5.17 → pyplumio-0.5.19}/docs/source/protocol.rst +0 -0
  102. {pyplumio-0.5.17 → pyplumio-0.5.19}/docs/source/reading.rst +0 -0
  103. {pyplumio-0.5.17 → pyplumio-0.5.19}/docs/source/writing.rst +0 -0
  104. {pyplumio-0.5.17 → pyplumio-0.5.19}/images/ecomax.png +0 -0
  105. {pyplumio-0.5.17 → pyplumio-0.5.19}/images/rs485.png +0 -0
  106. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/helpers/__init__.py +0 -0
  107. {pyplumio-0.5.17 → pyplumio-0.5.19}/pyplumio/py.typed +0 -0
  108. {pyplumio-0.5.17 → pyplumio-0.5.19}/requirements.txt +0 -0
  109. {pyplumio-0.5.17 → pyplumio-0.5.19}/requirements_docs.txt +0 -0
  110. {pyplumio-0.5.17 → pyplumio-0.5.19}/setup.cfg +0 -0
  111. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/conftest.py +0 -0
  112. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/frames/test_requests.py +0 -0
  113. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/helpers/__init__.py +0 -0
  114. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/helpers/test_task_manager.py +0 -0
  115. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/helpers/test_timeout.py +0 -0
  116. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/helpers/test_uid.py +0 -0
  117. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/ruff.toml +0 -0
  118. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/test_filters.py +0 -0
  119. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/test_init.py +0 -0
  120. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/test_main.py +0 -0
  121. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/test_utils.py +0 -0
  122. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/messages/regulator_data.json +0 -0
  123. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/messages/sensor_data.json +0 -0
  124. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/requests/alerts.json +0 -0
  125. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/requests/ecomax_control.json +0 -0
  126. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/requests/ecomax_parameters.json +0 -0
  127. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/requests/mixer_parameters.json +0 -0
  128. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/requests/set_ecomax_parameter.json +0 -0
  129. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/requests/set_mixer_parameter.json +0 -0
  130. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/requests/set_schedule.json +0 -0
  131. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/requests/set_thermostat_parameter.json +0 -0
  132. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/requests/thermostat_parameters.json +0 -0
  133. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/responses/alerts.json +0 -0
  134. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/responses/device_available.json +0 -0
  135. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/responses/ecomax_parameters.json +0 -0
  136. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/responses/mixer_parameters.json +0 -0
  137. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/responses/password.json +0 -0
  138. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/responses/program_version.json +0 -0
  139. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/responses/regulator_data_schema.json +0 -0
  140. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/responses/schedules.json +0 -0
  141. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/responses/thermostat_parameters.json +0 -0
  142. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/responses/uid.json +0 -0
  143. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/unknown/unknown_ecomax_parameter.json +0 -0
  144. {pyplumio-0.5.17 → pyplumio-0.5.19}/tests/testdata/unknown/unknown_mixer_parameter.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyPlumIO
3
- Version: 0.5.17
3
+ Version: 0.5.19
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
@@ -24,22 +24,22 @@ Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
25
  Requires-Dist: pyserial-asyncio==0.6
26
26
  Provides-Extra: test
27
- Requires-Dist: codespell==2.2.6; extra == "test"
28
- Requires-Dist: coverage==7.5.0; extra == "test"
29
- Requires-Dist: mypy==1.9.0; extra == "test"
27
+ Requires-Dist: codespell==2.3.0; extra == "test"
28
+ Requires-Dist: coverage==7.5.3; extra == "test"
29
+ Requires-Dist: mypy==1.10.0; extra == "test"
30
30
  Requires-Dist: pyserial-asyncio-fast==0.11; extra == "test"
31
- Requires-Dist: pytest==8.2.0; extra == "test"
32
- Requires-Dist: pytest-asyncio==0.23.6; extra == "test"
33
- Requires-Dist: ruff==0.4.2; extra == "test"
31
+ Requires-Dist: pytest==8.2.1; extra == "test"
32
+ Requires-Dist: pytest-asyncio==0.23.7; extra == "test"
33
+ Requires-Dist: ruff==0.4.5; extra == "test"
34
34
  Requires-Dist: tox==4.15.0; extra == "test"
35
- Requires-Dist: types-pyserial==3.5.0.20240311; extra == "test"
35
+ Requires-Dist: types-pyserial==3.5.0.20240527; extra == "test"
36
36
  Provides-Extra: docs
37
37
  Requires-Dist: sphinx==7.3.7; extra == "docs"
38
38
  Requires-Dist: sphinx_rtd_theme==2.0.0; extra == "docs"
39
39
  Requires-Dist: readthedocs-sphinx-search==0.3.2; extra == "docs"
40
40
  Provides-Extra: dev
41
41
  Requires-Dist: pyplumio[docs,test]; extra == "dev"
42
- Requires-Dist: pre-commit==3.7.0; extra == "dev"
42
+ Requires-Dist: pre-commit==3.7.1; extra == "dev"
43
43
  Requires-Dist: tomli==2.0.1; extra == "dev"
44
44
 
45
45
  # PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyPlumIO
3
- Version: 0.5.17
3
+ Version: 0.5.19
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
@@ -24,22 +24,22 @@ Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
25
  Requires-Dist: pyserial-asyncio==0.6
26
26
  Provides-Extra: test
27
- Requires-Dist: codespell==2.2.6; extra == "test"
28
- Requires-Dist: coverage==7.5.0; extra == "test"
29
- Requires-Dist: mypy==1.9.0; extra == "test"
27
+ Requires-Dist: codespell==2.3.0; extra == "test"
28
+ Requires-Dist: coverage==7.5.3; extra == "test"
29
+ Requires-Dist: mypy==1.10.0; extra == "test"
30
30
  Requires-Dist: pyserial-asyncio-fast==0.11; extra == "test"
31
- Requires-Dist: pytest==8.2.0; extra == "test"
32
- Requires-Dist: pytest-asyncio==0.23.6; extra == "test"
33
- Requires-Dist: ruff==0.4.2; extra == "test"
31
+ Requires-Dist: pytest==8.2.1; extra == "test"
32
+ Requires-Dist: pytest-asyncio==0.23.7; extra == "test"
33
+ Requires-Dist: ruff==0.4.5; extra == "test"
34
34
  Requires-Dist: tox==4.15.0; extra == "test"
35
- Requires-Dist: types-pyserial==3.5.0.20240311; extra == "test"
35
+ Requires-Dist: types-pyserial==3.5.0.20240527; extra == "test"
36
36
  Provides-Extra: docs
37
37
  Requires-Dist: sphinx==7.3.7; extra == "docs"
38
38
  Requires-Dist: sphinx_rtd_theme==2.0.0; extra == "docs"
39
39
  Requires-Dist: readthedocs-sphinx-search==0.3.2; extra == "docs"
40
40
  Provides-Extra: dev
41
41
  Requires-Dist: pyplumio[docs,test]; extra == "dev"
42
- Requires-Dist: pre-commit==3.7.0; extra == "dev"
42
+ Requires-Dist: pre-commit==3.7.1; extra == "dev"
43
43
  Requires-Dist: tomli==2.0.1; extra == "dev"
44
44
 
45
45
  # PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
@@ -2,7 +2,7 @@ pyserial-asyncio==0.6
2
2
 
3
3
  [dev]
4
4
  pyplumio[docs,test]
5
- pre-commit==3.7.0
5
+ pre-commit==3.7.1
6
6
  tomli==2.0.1
7
7
 
8
8
  [docs]
@@ -11,12 +11,12 @@ sphinx_rtd_theme==2.0.0
11
11
  readthedocs-sphinx-search==0.3.2
12
12
 
13
13
  [test]
14
- codespell==2.2.6
15
- coverage==7.5.0
16
- mypy==1.9.0
14
+ codespell==2.3.0
15
+ coverage==7.5.3
16
+ mypy==1.10.0
17
17
  pyserial-asyncio-fast==0.11
18
- pytest==8.2.0
19
- pytest-asyncio==0.23.6
20
- ruff==0.4.2
18
+ pytest==8.2.1
19
+ pytest-asyncio==0.23.7
20
+ ruff==0.4.5
21
21
  tox==4.15.0
22
- types-pyserial==3.5.0.20240311
22
+ types-pyserial==3.5.0.20240527
@@ -55,7 +55,7 @@ switches back to daytime mode from 07:00 to 00:00.
55
55
  heating_schedule = schedules["heating"]
56
56
  heating_schedule.monday.set_off(start="00:00", end="07:00")
57
57
  heating_schedule.monday.set_on(start="07:00", end="00:00")
58
- heating_schedule.commit()
58
+ await heating_schedule.commit()
59
59
 
60
60
  For clarity sake, you might want to use ``STATE_NIGHT`` and
61
61
  ``STATE_DAY`` constants from ``pyplumio.helpers.schedule`` module.
@@ -99,7 +99,7 @@ Schedule object:
99
99
  weekday.set_off("07:00", "00:00")
100
100
 
101
101
  # Commit changes to the device.
102
- heating_schedule.commit()
102
+ await heating_schedule.commit()
103
103
 
104
104
  Schedule Examples
105
105
  -----------------
@@ -131,7 +131,7 @@ Schedule Examples
131
131
  # There will be no nighttime mode on sunday.
132
132
  heating_schedule.sunday.set_state(STATE_DAY)
133
133
 
134
- heating_schedule.commit()
134
+ await heating_schedule.commit()
135
135
 
136
136
 
137
137
  asyncio.run(main())
@@ -1,4 +1,5 @@
1
1
  """Contains connection shortcuts and version information."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import Any
@@ -1,4 +1,5 @@
1
1
  """Contains a serial connection example."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import asyncio
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.5.17'
16
- __version_tuple__ = version_tuple = (0, 5, 17)
15
+ __version__ = version = '0.5.19'
16
+ __version_tuple__ = version_tuple = (0, 5, 19)
@@ -1,4 +1,5 @@
1
1
  """Contains a connection class."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from abc import ABC, abstractmethod
@@ -1,4 +1,5 @@
1
1
  """Contains constants."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from enum import Enum, IntEnum, unique
@@ -1,4 +1,5 @@
1
1
  """Contains device classes."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from abc import ABC
@@ -9,7 +10,7 @@ from typing import Any, ClassVar
9
10
 
10
11
  from pyplumio.const import ATTR_FRAME_ERRORS, ATTR_LOADED, DeviceType, FrameType
11
12
  from pyplumio.exceptions import UnknownDeviceError
12
- from pyplumio.frames import DataFrameDescription, Frame, Request, get_frame_handler
13
+ from pyplumio.frames import DataFrameDescription, Frame, Request
13
14
  from pyplumio.helpers.event_manager import EventManager
14
15
  from pyplumio.helpers.factory import create_instance
15
16
  from pyplumio.helpers.parameter import SET_RETRIES, Parameter
@@ -18,28 +19,29 @@ from pyplumio.structures.network_info import NetworkInfo
18
19
  from pyplumio.utils import to_camelcase
19
20
 
20
21
 
22
+ @cache
23
+ def is_known_device_type(device_type: int) -> bool:
24
+ """Check if device type is known."""
25
+ try:
26
+ DeviceType(device_type)
27
+ return True
28
+ except ValueError:
29
+ return False
30
+
31
+
21
32
  @cache
22
33
  def get_device_handler(device_type: int) -> str:
23
34
  """Return module name and class name for a given device type."""
24
- try:
25
- device_type = DeviceType(device_type)
26
- except ValueError as e:
27
- raise UnknownDeviceError(f"Unknown device ({device_type})") from e
35
+ if not is_known_device_type(device_type):
36
+ raise UnknownDeviceError(f"Unknown device type ({device_type})")
28
37
 
29
38
  type_name = to_camelcase(
30
- device_type.name, overrides={"ecomax": "EcoMAX", "ecoster": "EcoSTER"}
39
+ DeviceType(device_type).name,
40
+ overrides={"ecomax": "EcoMAX", "ecoster": "EcoSTER"},
31
41
  )
32
42
  return f"devices.{type_name.lower()}.{type_name}"
33
43
 
34
44
 
35
- @cache
36
- def get_device_handler_and_name(device_type: int) -> tuple[str, str]:
37
- """Get device handler full path and lowercased class name."""
38
- handler = get_device_handler(device_type)
39
- class_name = handler.rsplit(".", 1)[1]
40
- return handler, class_name.lower()
41
-
42
-
43
45
  class Device(ABC, EventManager):
44
46
  """Represents a device."""
45
47
 
@@ -128,7 +130,7 @@ class AddressableDevice(Device, ABC):
128
130
 
129
131
  def handle_frame(self, frame: Frame) -> None:
130
132
  """Handle frame received from the device."""
131
- frame.sender = self
133
+ frame.sender_device = self
132
134
  if frame.data is not None:
133
135
  for name, value in frame.data.items():
134
136
  self.dispatch_nowait(name, value)
@@ -160,10 +162,7 @@ class AddressableDevice(Device, ABC):
160
162
 
161
163
  If value is not available before timeout, retry request.
162
164
  """
163
- request: Request = await create_instance(
164
- get_frame_handler(frame_type), recipient=self.address
165
- )
166
-
165
+ request = await Request.create(frame_type, recipient=self.address)
167
166
  while retries > 0:
168
167
  try:
169
168
  self.queue.put_nowait(request)
@@ -173,6 +172,13 @@ class AddressableDevice(Device, ABC):
173
172
 
174
173
  raise ValueError(f'could not request "{name}"', frame_type)
175
174
 
175
+ @classmethod
176
+ async def create(cls, device_type: int, **kwargs: Any) -> AddressableDevice:
177
+ """Create a device handler object."""
178
+ return await create_instance(
179
+ get_device_handler(device_type), cls=AddressableDevice, **kwargs
180
+ )
181
+
176
182
 
177
183
  class SubDevice(Device, ABC):
178
184
  """Represents a sub-device."""
@@ -1,4 +1,5 @@
1
1
  """Contains an ecoMAX class."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import asyncio
@@ -21,14 +22,7 @@ from pyplumio.devices import AddressableDevice
21
22
  from pyplumio.devices.mixer import Mixer
22
23
  from pyplumio.devices.thermostat import Thermostat
23
24
  from pyplumio.filters import on_change
24
- from pyplumio.frames import (
25
- DataFrameDescription,
26
- Frame,
27
- Request,
28
- get_frame_handler,
29
- is_known_frame_type,
30
- )
31
- from pyplumio.helpers.factory import create_instance
25
+ from pyplumio.frames import DataFrameDescription, Frame, Request, is_known_frame_type
32
26
  from pyplumio.helpers.parameter import ParameterValues
33
27
  from pyplumio.helpers.schedule import Schedule, ScheduleDay
34
28
  from pyplumio.structures.alerts import ATTR_TOTAL_ALERTS
@@ -235,9 +229,7 @@ class EcoMAX(AddressableDevice):
235
229
  and not self._has_frame_version(frame_type, version)
236
230
  ):
237
231
  # We don't have this frame or it's version has changed.
238
- request: Request = await create_instance(
239
- get_frame_handler(frame_type), recipient=self.address
240
- )
232
+ request = await Request.create(frame_type, recipient=self.address)
241
233
  self.queue.put_nowait(request)
242
234
  self._frame_versions[frame_type] = version
243
235
 
@@ -1,4 +1,5 @@
1
1
  """Contains an ecoSTER class."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import ClassVar
@@ -1,4 +1,5 @@
1
1
  """Contains a mixer class."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import asyncio
@@ -1,4 +1,5 @@
1
1
  """Contains a thermostat class."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import asyncio
@@ -1,4 +1,5 @@
1
1
  """Contains exceptions."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
 
@@ -1,4 +1,5 @@
1
1
  """Contains callback filters."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from abc import ABC, abstractmethod
@@ -18,18 +19,17 @@ Comparable = TypeVar("Comparable", Parameter, SupportsFloat, SupportsComparison)
18
19
 
19
20
 
20
21
  @overload
21
- def _significantly_changed(old: Parameter, new: Parameter) -> bool:
22
- ...
22
+ def _significantly_changed(old: Parameter, new: Parameter) -> bool: ...
23
23
 
24
24
 
25
25
  @overload
26
- def _significantly_changed(old: SupportsFloat, new: SupportsFloat) -> bool:
27
- ...
26
+ def _significantly_changed(old: SupportsFloat, new: SupportsFloat) -> bool: ...
28
27
 
29
28
 
30
29
  @overload
31
- def _significantly_changed(old: SupportsComparison, new: SupportsComparison) -> bool:
32
- ...
30
+ def _significantly_changed(
31
+ old: SupportsComparison, new: SupportsComparison
32
+ ) -> bool: ...
33
33
 
34
34
 
35
35
  def _significantly_changed(old: Comparable, new: Comparable) -> bool:
@@ -45,15 +45,13 @@ def _significantly_changed(old: Comparable, new: Comparable) -> bool:
45
45
 
46
46
 
47
47
  @overload
48
- def _diffence_between(old: list, new: list) -> list:
49
- ...
48
+ def _diffence_between(old: list, new: list) -> list: ...
50
49
 
51
50
 
52
51
  @overload
53
52
  def _diffence_between(
54
53
  old: SupportsSubtraction, new: SupportsSubtraction
55
- ) -> SupportsSubtraction:
56
- ...
54
+ ) -> SupportsSubtraction: ...
57
55
 
58
56
 
59
57
  def _diffence_between(
@@ -1,14 +1,16 @@
1
1
  """Contains frame classes."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from abc import ABC, abstractmethod
5
6
  from dataclasses import dataclass
6
7
  from functools import cache, reduce
7
8
  import struct
8
- from typing import TYPE_CHECKING, Any, ClassVar, Final, cast
9
+ from typing import TYPE_CHECKING, Any, ClassVar, Final, TypeVar
9
10
 
10
11
  from pyplumio.const import DeviceType, FrameType
11
12
  from pyplumio.exceptions import UnknownFrameError
13
+ from pyplumio.helpers.factory import create_instance
12
14
  from pyplumio.utils import ensure_dict, to_camelcase
13
15
 
14
16
  FRAME_START: Final = 0x68
@@ -27,11 +29,15 @@ if TYPE_CHECKING:
27
29
  from pyplumio.devices import AddressableDevice
28
30
 
29
31
 
32
+ T = TypeVar("T")
33
+
34
+
30
35
  def bcc(data: bytes) -> int:
31
36
  """Return a block check character."""
32
37
  return reduce(lambda x, y: x ^ y, data)
33
38
 
34
39
 
40
+ @cache
35
41
  def is_known_frame_type(frame_type: int) -> bool:
36
42
  """Check if frame type is known."""
37
43
  try:
@@ -44,12 +50,10 @@ def is_known_frame_type(frame_type: int) -> bool:
44
50
  @cache
45
51
  def get_frame_handler(frame_type: int) -> str:
46
52
  """Return handler class path for the frame type."""
47
- try:
48
- frame_type = FrameType(frame_type)
49
- except ValueError as e:
50
- raise UnknownFrameError(f"Unknown frame ({frame_type})") from e
53
+ if not is_known_frame_type(frame_type):
54
+ raise UnknownFrameError(f"Unknown frame type ({frame_type})")
51
55
 
52
- module, type_name = frame_type.name.split("_", 1)
56
+ module, type_name = FrameType(frame_type).name.split("_", 1)
53
57
  type_name = to_camelcase(type_name, overrides={"uid": "UID"})
54
58
  return f"frames.{module.lower()}s.{type_name}{module.capitalize()}"
55
59
 
@@ -69,16 +73,20 @@ class Frame(ABC):
69
73
 
70
74
  __slots__ = (
71
75
  "recipient",
76
+ "recipient_device",
72
77
  "sender",
73
- "sender_type",
78
+ "sender_device",
79
+ "econet_type",
74
80
  "econet_version",
75
81
  "_message",
76
82
  "_data",
77
83
  )
78
84
 
79
- recipient: DeviceType | AddressableDevice | int
80
- sender: DeviceType | AddressableDevice | int
81
- sender_type: int
85
+ recipient: DeviceType
86
+ recipient_device: AddressableDevice | None
87
+ sender: DeviceType
88
+ sender_device: AddressableDevice | None
89
+ econet_type: int
82
90
  econet_version: int
83
91
  frame_type: ClassVar[FrameType]
84
92
  _message: bytearray | None
@@ -86,26 +94,20 @@ class Frame(ABC):
86
94
 
87
95
  def __init__(
88
96
  self,
89
- recipient: DeviceType | AddressableDevice | int = DeviceType.ALL,
90
- sender: DeviceType | AddressableDevice | int = DeviceType.ECONET,
91
- sender_type: int = ECONET_TYPE,
97
+ recipient: DeviceType = DeviceType.ALL,
98
+ sender: DeviceType = DeviceType.ECONET,
99
+ econet_type: int = ECONET_TYPE,
92
100
  econet_version: int = ECONET_VERSION,
93
101
  message: bytearray | None = None,
94
102
  data: dict[str, Any] | None = None,
95
103
  **kwargs: Any,
96
104
  ) -> None:
97
105
  """Process a frame data and message."""
98
- try:
99
- self.recipient = DeviceType(cast(int, recipient))
100
- except ValueError:
101
- self.recipient = recipient
102
-
103
- try:
104
- self.sender = DeviceType(cast(int, sender))
105
- except ValueError:
106
- self.sender = sender
107
-
108
- self.sender_type = sender_type
106
+ self.recipient = recipient
107
+ self.recipient_device = None
108
+ self.sender = sender
109
+ self.sender_device = None
110
+ self.econet_type = econet_type
109
111
  self.econet_version = econet_version
110
112
  self._data = data if not kwargs else ensure_dict(data, kwargs)
111
113
  self._message = message
@@ -116,14 +118,14 @@ class Frame(ABC):
116
118
  return (
117
119
  self.recipient,
118
120
  self.sender,
119
- self.sender_type,
121
+ self.econet_type,
120
122
  self.econet_version,
121
123
  self._message,
122
124
  self._data,
123
125
  ) == (
124
126
  self.recipient,
125
127
  self.sender,
126
- self.sender_type,
128
+ self.econet_type,
127
129
  self.econet_version,
128
130
  self._message,
129
131
  self._data,
@@ -137,7 +139,7 @@ class Frame(ABC):
137
139
  f"{self.__class__.__name__}("
138
140
  f"recipient={repr(self.recipient)}, "
139
141
  f"sender={repr(self.sender)}, "
140
- f"sender_type={self.sender_type}, "
142
+ f"econet_type={self.econet_type}, "
141
143
  f"econet_version={self.econet_version}, "
142
144
  f"message={self.message}, "
143
145
  f"data={self.data})"
@@ -205,7 +207,7 @@ class Frame(ABC):
205
207
  self.length,
206
208
  int(self.recipient),
207
209
  int(self.sender),
208
- self.sender_type,
210
+ self.econet_type,
209
211
  self.econet_version,
210
212
  )
211
213
 
@@ -221,6 +223,11 @@ class Frame(ABC):
221
223
  data.append(FRAME_END)
222
224
  return bytes(data)
223
225
 
226
+ @classmethod
227
+ async def create(cls: type[T], frame_type: int, **kwargs: Any) -> T:
228
+ """Create a frame handler object from frame type."""
229
+ return await create_instance(get_frame_handler(frame_type), cls=cls, **kwargs)
230
+
224
231
  @abstractmethod
225
232
  def create_message(self, data: dict[str, Any]) -> bytearray:
226
233
  """Create frame message."""
@@ -1,4 +1,5 @@
1
1
  """Contains message frames."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import Any, ClassVar
@@ -1,4 +1,5 @@
1
1
  """Contains request frames."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import Any, ClassVar
@@ -1,4 +1,5 @@
1
1
  """Contains response frames."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import Any, ClassVar
@@ -1,4 +1,5 @@
1
1
  """Contains data type helper classes."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from abc import ABC, abstractmethod
@@ -1,4 +1,5 @@
1
1
  """Contains an event manager class."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import asyncio
@@ -0,0 +1,35 @@
1
+ """Contains a factory helper."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import importlib
7
+ import logging
8
+ from types import ModuleType
9
+ from typing import Any, TypeVar
10
+
11
+ _LOGGER = logging.getLogger(__name__)
12
+
13
+ T = TypeVar("T")
14
+
15
+
16
+ async def _load_module(module_name: str) -> ModuleType:
17
+ """Load a module by name."""
18
+ return await asyncio.get_running_loop().run_in_executor(
19
+ None, importlib.import_module, f".{module_name}", "pyplumio"
20
+ )
21
+
22
+
23
+ async def create_instance(class_path: str, cls: type[T], **kwargs: Any) -> T:
24
+ """Return a class instance from the class path."""
25
+ module_name, class_name = class_path.rsplit(".", 1)
26
+ try:
27
+ module = await _load_module(module_name)
28
+ instance = getattr(module, class_name)(**kwargs)
29
+ if not isinstance(instance, cls):
30
+ raise TypeError(f"class '{class_name}' should be derived from {cls}")
31
+
32
+ return instance
33
+ except Exception:
34
+ _LOGGER.error("Failed to load module (%s)", class_path)
35
+ raise
@@ -1,4 +1,5 @@
1
1
  """Contains a device parameter class."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from abc import ABC
@@ -1,15 +1,17 @@
1
1
  """Contains a schedule helper classes."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from collections.abc import Iterable, Iterator, MutableMapping
5
6
  from dataclasses import dataclass
6
7
  import datetime as dt
8
+ from functools import lru_cache
7
9
  import math
8
10
  from typing import Final, Literal
9
11
 
10
- from pyplumio.const import STATE_OFF, STATE_ON
12
+ from pyplumio.const import STATE_OFF, STATE_ON, FrameType
11
13
  from pyplumio.devices import AddressableDevice
12
- from pyplumio.frames.requests import SetScheduleRequest
14
+ from pyplumio.frames import Request
13
15
  from pyplumio.structures.schedules import collect_schedule_data
14
16
 
15
17
  TIME_FORMAT: Final = "%H:%M"
@@ -24,6 +26,7 @@ OFF_STATES: Final = (STATE_OFF, STATE_NIGHT)
24
26
  ALLOWED_STATES: Final = ON_STATES + OFF_STATES
25
27
 
26
28
 
29
+ @lru_cache(maxsize=10)
27
30
  def _parse_interval(start: str, end: str) -> tuple[int, int]:
28
31
  """Parse an interval string.
29
32
 
@@ -158,10 +161,11 @@ class Schedule(Iterable):
158
161
  self.saturday,
159
162
  ).__iter__()
160
163
 
161
- def commit(self) -> None:
164
+ async def commit(self) -> None:
162
165
  """Commit a weekly schedule to the device."""
163
- self.device.queue.put_nowait(
164
- SetScheduleRequest(
166
+ await self.device.queue.put(
167
+ await Request.create(
168
+ FrameType.REQUEST_SET_SCHEDULE,
165
169
  recipient=self.device.address,
166
170
  data=collect_schedule_data(self.name, self.device),
167
171
  )
@@ -1,4 +1,5 @@
1
1
  """Contains a task manager class."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import asyncio
@@ -1,4 +1,5 @@
1
1
  """Contains a timeout decorator."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import asyncio