PyPlumIO 0.5.30__tar.gz → 0.5.31__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 (142) hide show
  1. {pyplumio-0.5.30 → pyplumio-0.5.31}/.github/workflows/ci.yml +6 -4
  2. {pyplumio-0.5.30 → pyplumio-0.5.31}/.github/workflows/deploy.yml +2 -2
  3. {pyplumio-0.5.30 → pyplumio-0.5.31}/.pre-commit-config.yaml +3 -3
  4. {pyplumio-0.5.30 → pyplumio-0.5.31}/PKG-INFO +8 -7
  5. {pyplumio-0.5.30 → pyplumio-0.5.31}/PyPlumIO.egg-info/PKG-INFO +8 -7
  6. {pyplumio-0.5.30 → pyplumio-0.5.31}/PyPlumIO.egg-info/requires.txt +5 -5
  7. {pyplumio-0.5.30 → pyplumio-0.5.31}/docs/source/callbacks.rst +12 -1
  8. {pyplumio-0.5.30 → pyplumio-0.5.31}/docs/source/mixers_thermostats.rst +1 -1
  9. {pyplumio-0.5.30 → pyplumio-0.5.31}/docs/source/reading.rst +10 -11
  10. {pyplumio-0.5.30 → pyplumio-0.5.31}/docs/source/writing.rst +22 -22
  11. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/_version.py +2 -2
  12. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/devices/__init__.py +0 -3
  13. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/filters.py +52 -6
  14. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/helpers/parameter.py +21 -30
  15. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/ecomax_parameters.py +2 -13
  16. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/mixer_parameters.py +0 -11
  17. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/schedules.py +0 -11
  18. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/thermostat_parameters.py +0 -14
  19. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyproject.toml +7 -6
  20. {pyplumio-0.5.30 → pyplumio-0.5.31}/requirements_test.txt +5 -5
  21. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/helpers/test_parameter.py +28 -61
  22. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/test_devices.py +7 -44
  23. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/test_filters.py +25 -1
  24. {pyplumio-0.5.30 → pyplumio-0.5.31}/.gitattributes +0 -0
  25. {pyplumio-0.5.30 → pyplumio-0.5.31}/.github/CODE_OF_CONDUCT.md +0 -0
  26. {pyplumio-0.5.30 → pyplumio-0.5.31}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  27. {pyplumio-0.5.30 → pyplumio-0.5.31}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  28. {pyplumio-0.5.30 → pyplumio-0.5.31}/.github/dependabot.yml +0 -0
  29. {pyplumio-0.5.30 → pyplumio-0.5.31}/.github/workflows/codeql-analysis.yml +0 -0
  30. {pyplumio-0.5.30 → pyplumio-0.5.31}/.github/workflows/documentation.yml +0 -0
  31. {pyplumio-0.5.30 → pyplumio-0.5.31}/.gitignore +0 -0
  32. {pyplumio-0.5.30 → pyplumio-0.5.31}/.vscode/settings.json +0 -0
  33. {pyplumio-0.5.30 → pyplumio-0.5.31}/LICENSE +0 -0
  34. {pyplumio-0.5.30 → pyplumio-0.5.31}/MANIFEST.in +0 -0
  35. {pyplumio-0.5.30 → pyplumio-0.5.31}/PyPlumIO.egg-info/SOURCES.txt +0 -0
  36. {pyplumio-0.5.30 → pyplumio-0.5.31}/PyPlumIO.egg-info/dependency_links.txt +0 -0
  37. {pyplumio-0.5.30 → pyplumio-0.5.31}/PyPlumIO.egg-info/top_level.txt +0 -0
  38. {pyplumio-0.5.30 → pyplumio-0.5.31}/README.md +0 -0
  39. {pyplumio-0.5.30 → pyplumio-0.5.31}/docs/Makefile +0 -0
  40. {pyplumio-0.5.30 → pyplumio-0.5.31}/docs/make.bat +0 -0
  41. {pyplumio-0.5.30 → pyplumio-0.5.31}/docs/source/conf.py +0 -0
  42. {pyplumio-0.5.30 → pyplumio-0.5.31}/docs/source/connecting.rst +0 -0
  43. {pyplumio-0.5.30 → pyplumio-0.5.31}/docs/source/frames.rst +0 -0
  44. {pyplumio-0.5.30 → pyplumio-0.5.31}/docs/source/index.rst +0 -0
  45. {pyplumio-0.5.30 → pyplumio-0.5.31}/docs/source/protocol.rst +0 -0
  46. {pyplumio-0.5.30 → pyplumio-0.5.31}/docs/source/schedules.rst +0 -0
  47. {pyplumio-0.5.30 → pyplumio-0.5.31}/images/ecomax.png +0 -0
  48. {pyplumio-0.5.30 → pyplumio-0.5.31}/images/rs485.png +0 -0
  49. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/__init__.py +0 -0
  50. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/__main__.py +0 -0
  51. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/connection.py +0 -0
  52. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/const.py +0 -0
  53. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/devices/ecomax.py +0 -0
  54. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/devices/ecoster.py +0 -0
  55. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/devices/mixer.py +0 -0
  56. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/devices/thermostat.py +0 -0
  57. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/exceptions.py +0 -0
  58. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/frames/__init__.py +0 -0
  59. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/frames/messages.py +0 -0
  60. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/frames/requests.py +0 -0
  61. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/frames/responses.py +0 -0
  62. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/helpers/__init__.py +0 -0
  63. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/helpers/data_types.py +0 -0
  64. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/helpers/event_manager.py +0 -0
  65. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/helpers/factory.py +0 -0
  66. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/helpers/schedule.py +0 -0
  67. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/helpers/task_manager.py +0 -0
  68. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/helpers/timeout.py +0 -0
  69. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/helpers/uid.py +0 -0
  70. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/protocol.py +0 -0
  71. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/py.typed +0 -0
  72. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/stream.py +0 -0
  73. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/__init__.py +0 -0
  74. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/alerts.py +0 -0
  75. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/boiler_load.py +0 -0
  76. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/boiler_power.py +0 -0
  77. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/fan_power.py +0 -0
  78. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/frame_versions.py +0 -0
  79. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/fuel_consumption.py +0 -0
  80. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/fuel_level.py +0 -0
  81. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/lambda_sensor.py +0 -0
  82. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/mixer_sensors.py +0 -0
  83. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/modules.py +0 -0
  84. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/network_info.py +0 -0
  85. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/output_flags.py +0 -0
  86. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/outputs.py +0 -0
  87. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/pending_alerts.py +0 -0
  88. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/product_info.py +0 -0
  89. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/program_version.py +0 -0
  90. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/regulator_data.py +0 -0
  91. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/regulator_data_schema.py +0 -0
  92. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/statuses.py +0 -0
  93. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/temperatures.py +0 -0
  94. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/structures/thermostat_sensors.py +0 -0
  95. {pyplumio-0.5.30 → pyplumio-0.5.31}/pyplumio/utils.py +0 -0
  96. {pyplumio-0.5.30 → pyplumio-0.5.31}/requirements.txt +0 -0
  97. {pyplumio-0.5.30 → pyplumio-0.5.31}/requirements_docs.txt +0 -0
  98. {pyplumio-0.5.30 → pyplumio-0.5.31}/setup.cfg +0 -0
  99. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/__init__.py +0 -0
  100. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/conftest.py +0 -0
  101. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/frames/test_init.py +0 -0
  102. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/frames/test_messages.py +0 -0
  103. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/frames/test_requests.py +0 -0
  104. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/frames/test_responses.py +0 -0
  105. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/helpers/__init__.py +0 -0
  106. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/helpers/test_data_types.py +0 -0
  107. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/helpers/test_event_manager.py +0 -0
  108. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/helpers/test_factory.py +0 -0
  109. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/helpers/test_schedule.py +0 -0
  110. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/helpers/test_task_manager.py +0 -0
  111. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/helpers/test_timeout.py +0 -0
  112. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/helpers/test_uid.py +0 -0
  113. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/ruff.toml +0 -0
  114. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/test_connection.py +0 -0
  115. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/test_init.py +0 -0
  116. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/test_main.py +0 -0
  117. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/test_protocol.py +0 -0
  118. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/test_stream.py +0 -0
  119. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/test_utils.py +0 -0
  120. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/messages/regulator_data.json +0 -0
  121. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/messages/sensor_data.json +0 -0
  122. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/requests/alerts.json +0 -0
  123. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/requests/ecomax_control.json +0 -0
  124. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/requests/ecomax_parameters.json +0 -0
  125. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/requests/mixer_parameters.json +0 -0
  126. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/requests/set_ecomax_parameter.json +0 -0
  127. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/requests/set_mixer_parameter.json +0 -0
  128. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/requests/set_schedule.json +0 -0
  129. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/requests/set_thermostat_parameter.json +0 -0
  130. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/requests/thermostat_parameters.json +0 -0
  131. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/responses/alerts.json +0 -0
  132. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/responses/device_available.json +0 -0
  133. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/responses/ecomax_parameters.json +0 -0
  134. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/responses/mixer_parameters.json +0 -0
  135. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/responses/password.json +0 -0
  136. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/responses/program_version.json +0 -0
  137. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/responses/regulator_data_schema.json +0 -0
  138. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/responses/schedules.json +0 -0
  139. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/responses/thermostat_parameters.json +0 -0
  140. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/responses/uid.json +0 -0
  141. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/unknown/unknown_ecomax_parameter.json +0 -0
  142. {pyplumio-0.5.30 → pyplumio-0.5.31}/tests/testdata/unknown/unknown_mixer_parameter.json +0 -0
@@ -1,15 +1,17 @@
1
1
  name: ci
2
2
 
3
3
  on:
4
- - push
5
- - pull_request
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
6
8
 
7
9
  jobs:
8
10
  build:
9
11
  runs-on: ubuntu-latest
10
12
  strategy:
11
13
  matrix:
12
- python-version: ["3.9", "3.10", "3.11", "3.12"]
14
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
13
15
 
14
16
  steps:
15
17
  - uses: actions/checkout@v4
@@ -33,7 +35,7 @@ jobs:
33
35
  - name: Test with tox
34
36
  run: tox
35
37
 
36
- - if: github.event_name != 'pull_request'
38
+ - if: github.event_name == 'push'
37
39
  uses: paambaati/codeclimate-action@v5.0.0
38
40
  env:
39
41
  CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
@@ -22,8 +22,6 @@ jobs:
22
22
 
23
23
  - name: Set up Python
24
24
  uses: actions/setup-python@v5
25
- with:
26
- python-version: "3.9"
27
25
 
28
26
  - name: Install dependencies
29
27
  run: |
@@ -36,3 +34,5 @@ jobs:
36
34
 
37
35
  - name: Publish package distributions to PyPI
38
36
  uses: pypa/gh-action-pypi-publish@release/v1
37
+ with:
38
+ attestations: false
@@ -2,16 +2,16 @@
2
2
  # See https://pre-commit.com/hooks.html for more hooks
3
3
  repos:
4
4
  - repo: https://github.com/astral-sh/ruff-pre-commit
5
- rev: v0.8.1
5
+ rev: v0.9.3
6
6
  hooks:
7
7
  - id: ruff
8
8
  args:
9
9
  - --fix
10
10
  - repo: https://github.com/codespell-project/codespell
11
- rev: v2.3.0
11
+ rev: v2.4.0
12
12
  hooks:
13
13
  - id: codespell
14
14
  - repo: https://github.com/pre-commit/mirrors-mypy
15
- rev: v1.13.0
15
+ rev: v1.14.1
16
16
  hooks:
17
17
  - id: mypy
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: PyPlumIO
3
- Version: 0.5.30
3
+ Version: 0.5.31
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
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.9
17
17
  Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
20
21
  Classifier: Topic :: Software Development :: Libraries
21
22
  Classifier: Topic :: Home Automation
22
23
  Requires-Python: >=3.9
@@ -27,14 +28,14 @@ Requires-Dist: pyserial-asyncio==0.6
27
28
  Requires-Dist: typing-extensions==4.12.2
28
29
  Provides-Extra: test
29
30
  Requires-Dist: codespell==2.3.0; extra == "test"
30
- Requires-Dist: coverage==7.6.8; extra == "test"
31
- Requires-Dist: mypy==1.13.0; extra == "test"
31
+ Requires-Dist: coverage==7.6.10; extra == "test"
32
+ Requires-Dist: mypy==1.14.1; extra == "test"
32
33
  Requires-Dist: pyserial-asyncio-fast==0.14; extra == "test"
33
34
  Requires-Dist: pytest==8.3.4; extra == "test"
34
- Requires-Dist: pytest-asyncio==0.24.0; extra == "test"
35
- Requires-Dist: ruff==0.8.1; extra == "test"
35
+ Requires-Dist: pytest-asyncio==0.25.2; extra == "test"
36
+ Requires-Dist: ruff==0.9.2; extra == "test"
36
37
  Requires-Dist: tox==4.23.2; extra == "test"
37
- Requires-Dist: types-pyserial==3.5.0.20240826; extra == "test"
38
+ Requires-Dist: types-pyserial==3.5.0.20241221; extra == "test"
38
39
  Provides-Extra: docs
39
40
  Requires-Dist: sphinx==8.1.3; extra == "docs"
40
41
  Requires-Dist: sphinx_rtd_theme==3.0.2; extra == "docs"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: PyPlumIO
3
- Version: 0.5.30
3
+ Version: 0.5.31
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
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.9
17
17
  Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
20
21
  Classifier: Topic :: Software Development :: Libraries
21
22
  Classifier: Topic :: Home Automation
22
23
  Requires-Python: >=3.9
@@ -27,14 +28,14 @@ Requires-Dist: pyserial-asyncio==0.6
27
28
  Requires-Dist: typing-extensions==4.12.2
28
29
  Provides-Extra: test
29
30
  Requires-Dist: codespell==2.3.0; extra == "test"
30
- Requires-Dist: coverage==7.6.8; extra == "test"
31
- Requires-Dist: mypy==1.13.0; extra == "test"
31
+ Requires-Dist: coverage==7.6.10; extra == "test"
32
+ Requires-Dist: mypy==1.14.1; extra == "test"
32
33
  Requires-Dist: pyserial-asyncio-fast==0.14; extra == "test"
33
34
  Requires-Dist: pytest==8.3.4; extra == "test"
34
- Requires-Dist: pytest-asyncio==0.24.0; extra == "test"
35
- Requires-Dist: ruff==0.8.1; extra == "test"
35
+ Requires-Dist: pytest-asyncio==0.25.2; extra == "test"
36
+ Requires-Dist: ruff==0.9.2; extra == "test"
36
37
  Requires-Dist: tox==4.23.2; extra == "test"
37
- Requires-Dist: types-pyserial==3.5.0.20240826; extra == "test"
38
+ Requires-Dist: types-pyserial==3.5.0.20241221; extra == "test"
38
39
  Provides-Extra: docs
39
40
  Requires-Dist: sphinx==8.1.3; extra == "docs"
40
41
  Requires-Dist: sphinx_rtd_theme==3.0.2; extra == "docs"
@@ -14,11 +14,11 @@ readthedocs-sphinx-search==0.3.2
14
14
 
15
15
  [test]
16
16
  codespell==2.3.0
17
- coverage==7.6.8
18
- mypy==1.13.0
17
+ coverage==7.6.10
18
+ mypy==1.14.1
19
19
  pyserial-asyncio-fast==0.14
20
20
  pytest==8.3.4
21
- pytest-asyncio==0.24.0
22
- ruff==0.8.1
21
+ pytest-asyncio==0.25.2
22
+ ruff==0.9.2
23
23
  tox==4.23.2
24
- types-pyserial==3.5.0.20240826
24
+ types-pyserial==3.5.0.20241221
@@ -23,7 +23,7 @@ Subscribing to events
23
23
  .. autofunction:: pyplumio.devices.Device.subscribe_once
24
24
 
25
25
  To remove the previously registered callback, use the
26
- ``unsubcribe()`` method.
26
+ ``unsubscribe()`` method.
27
27
 
28
28
  .. autofunction:: pyplumio.devices.Device.unsubscribe
29
29
 
@@ -48,6 +48,17 @@ then calls the callback with the sum of values collected.
48
48
  # Await the callback with the fuel burned during 30 seconds.
49
49
  ecomax.subscribe("fuel_burned", aggregate(my_callback, seconds=30))
50
50
 
51
+ .. autofunction:: pyplumio.filters.clamp
52
+
53
+ This filter clamps value between specified boundaries.
54
+ It can be used to filter out outliers and corrupted data.
55
+
56
+ .. code-block:: python
57
+
58
+ from pyplumio.filters import clamp
59
+
60
+ # Await the callback with value clamped between 0 and 100.
61
+ ecomax.subscribe("load", clamp(my_callback, min_value=0, max_value=100))
51
62
 
52
63
  .. autofunction:: pyplumio.filters.on_change
53
64
 
@@ -52,7 +52,7 @@ get it's current_temp property and set it's target temperature to
52
52
  Thermostat Examples
53
53
  -------------------
54
54
 
55
- In the following example, we'll get single thermostat by it's index,
55
+ In the following example, we'll get a single thermostat by it's index,
56
56
  get current room temperature and set daytime target temperature to 20
57
57
  degrees Celsius.
58
58
 
@@ -78,8 +78,8 @@ available and print it out.
78
78
 
79
79
  Regulator Data
80
80
  --------------
81
- Regulator Data messages are broadcasted by the ecoMAX controller
82
- once per second and allow access to some device-specific
81
+ Regulator Data message is broadcasted by the ecoMAX controller
82
+ once per second and allows access to some device-specific
83
83
  information that isn't available elsewhere.
84
84
 
85
85
  .. note::
@@ -87,9 +87,8 @@ information that isn't available elsewhere.
87
87
  RegData is device-specific. Each ecoMAX controller has different
88
88
  keys and their associated meanings.
89
89
 
90
- It's represented by a dictionary mapped with numerical keys.
91
-
92
- RegData can be accessed via the **regdata** property.
90
+ Regulator data is represented by a dictionary mapped with numerical
91
+ keys, and can be accessed via the **regdata** property.
93
92
 
94
93
  .. code-block:: python
95
94
 
@@ -103,8 +102,8 @@ RegData can be accessed via the **regdata** property.
103
102
  Reading Examples
104
103
  ----------------
105
104
 
106
- The following example make uses of all available methods to get
107
- heating current and target temperatures and device state and outputs it
105
+ The following example makes use of all available methods to get
106
+ current and target heating temperatures and device state and outputs it
108
107
  to the terminal.
109
108
 
110
109
  .. code-block:: python
@@ -122,14 +121,14 @@ to the terminal.
122
121
  # Get the ecoMAX device.
123
122
  ecomax = await conn.get("ecomax")
124
123
 
125
- # Get the heating temperature if it is available, or return
126
- # default value (65).
124
+ # Get heating temperature, if it's available otherwise
125
+ # return the default value (65).
127
126
  heating_temp = ecomax.get_nowait("heating_temp", default=65)
128
127
 
129
- # Wait for the heating temperature and get it's value.
128
+ # Wait for heating temperature and get it's value.
130
129
  heating_target_temp = await ecomax.get("heating_target_temp")
131
130
 
132
- # Wait until regulator data is available, and grab key 1792.
131
+ # Wait until regulator data is available then grab key 1792.
133
132
  await ecomax.wait_for("regdata")
134
133
  status = ecomax.regdata[1792]
135
134
 
@@ -6,9 +6,8 @@ Setters
6
6
 
7
7
  .. autofunction:: pyplumio.devices.Device.set
8
8
 
9
- When using blocking setter, you will get the result
10
- represented by the boolean value. `True` if write was successful,
11
- `False` otherwise.
9
+ When using blocking setter, you'll get the result represented by a
10
+ boolean value. `True` if write was successful, `False` otherwise.
12
11
 
13
12
  .. code-block:: python
14
13
 
@@ -20,9 +19,9 @@ represented by the boolean value. `True` if write was successful,
20
19
 
21
20
  .. autofunction:: pyplumio.devices.Device.set_nowait
22
21
 
23
- You can't access result, when using non-blocking setter as task is done
24
- in the background. You will, however, still get error message in the log
25
- in case of failure.
22
+ When using non-blocking setter, you can't access the result as task is
23
+ done in background. However, you'll still get error log message in case
24
+ of a failure.
26
25
 
27
26
  .. code-block:: python
28
27
 
@@ -31,13 +30,13 @@ in case of failure.
31
30
  Parameters
32
31
  ----------
33
32
 
34
- It's possible to get the ``Parameter`` object and then modify it using
35
- it's own setter methods.
33
+ For the parameters, it's possible to retrieve the ``Parameter`` object
34
+ and then modify it via it's own setter method.
36
35
 
37
36
  .. autoclass:: pyplumio.helpers.parameter.Parameter
38
37
 
39
- When using the parameter object, you don't
40
- need to pass the parameter name.
38
+ When using the parameter object directly, you don't need to pass the
39
+ parameter name to the setter method.
41
40
 
42
41
  Numbers
43
42
  ^^^^^^^
@@ -52,12 +51,13 @@ Numbers are parameters that have numerical value associated with them.
52
51
  heating_target: Number = ecomax.get("heating_target_temp")
53
52
  result = heating_target.set(65)
54
53
 
55
- Each number has a range of allowed values.
54
+ Each number has an unique range of allowed values.
56
55
  PyPlumIO will raise ``ValueError`` if value isn't within acceptable
57
56
  range.
58
57
 
59
- You can check allowed range by reading ``min_value`` and ``max_value``
60
- properties of the parameter object. Both values are **inclusive**.
58
+ You can check allowed range boundaries by reading ``min_value`` and
59
+ ``max_value`` properties of the parameter object.
60
+ Please note, that both values are **inclusive**.
61
61
 
62
62
  .. code-block:: python
63
63
 
@@ -73,7 +73,7 @@ Switches
73
73
 
74
74
  Switches are parameters that could only have two possible states: on or off.
75
75
 
76
- Thus, for switches, you can use boolean `True` or `False`,
76
+ Thus, for switches, you can use Python's boolean keywords `True` or `False`,
77
77
  string literals "on" or "off" or special ``turn_on()`` and
78
78
  ``turn_off()`` methods.
79
79
 
@@ -82,15 +82,15 @@ string literals "on" or "off" or special ``turn_on()`` and
82
82
  .. autofunction:: pyplumio.helpers.parameter.Switch.turn_off
83
83
  .. autofunction:: pyplumio.helpers.parameter.Switch.turn_off_nowait
84
84
 
85
- One such switch is "ecomax_control" that allows you to switch
86
- the ecoMAX on or off.
85
+ Good example of a switch is "ecomax_control" that allows you to switch
86
+ the ecoMAX controller on or off.
87
87
 
88
88
  .. code-block:: python
89
89
 
90
90
  # Set an ecomax_control parameter value to "on".
91
91
  result = await ecomax.set("ecomax_control", "on")
92
92
 
93
- If you want to use **turn_on()** method, you must first get a parameter
93
+ If you want to use **turn_on()** method, you must first get the parameter
94
94
  object.
95
95
 
96
96
  .. code-block:: python
@@ -101,8 +101,8 @@ object.
101
101
  ecomax_control: Switch = await ecomax.get("ecomax_control")
102
102
  result = await ecomax_control.turn_on()
103
103
 
104
- If you simply want to turn on or off the ecoMAX controller itself,
105
- there's a handy shortcut built in the controller handler.
104
+ If you simply want to switch the ecoMAX controller on or off,
105
+ there's a handy built-in way to do that.
106
106
 
107
107
  .. code-block:: python
108
108
 
@@ -115,9 +115,9 @@ there's a handy shortcut built in the controller handler.
115
115
  Writing Examples
116
116
  ----------------
117
117
 
118
- The following example opens the connection, enables the controller
119
- without waiting for result and tries to set a target temperature,
120
- outputting result to the terminal.
118
+ The following example opens a connection, enables the controller
119
+ without waiting for the result and tries to set a target temperature,
120
+ outputting the result to the terminal.
121
121
 
122
122
  .. code-block:: python
123
123
 
@@ -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.30'
16
- __version_tuple__ = version_tuple = (0, 5, 30)
15
+ __version__ = version = '0.5.31'
16
+ __version_tuple__ = version_tuple = (0, 5, 31)
@@ -107,9 +107,6 @@ class Device(ABC, EventManager):
107
107
  this value is used to determine failure when
108
108
  retrying and doesn't block, defaults to `None`
109
109
  :type timeout: float, optional
110
- :return: `True` if parameter was successfully set, `False`
111
- otherwise.
112
- :rtype: bool
113
110
  """
114
111
  self.create_task(self.set(name, value, retries, timeout))
115
112
 
@@ -125,6 +125,52 @@ class Filter(ABC):
125
125
  """Set a new value for the callback."""
126
126
 
127
127
 
128
+ class _Clamp(Filter):
129
+ """Represents a clamp filter.
130
+
131
+ Calls callback with a value clamped between specified boundaries.
132
+ """
133
+
134
+ __slots__ = ("_min_value", "_max_value")
135
+
136
+ _min_value: float
137
+ _max_value: float
138
+
139
+ def __init__(self, callback: Callback, min_value: float, max_value: float) -> None:
140
+ """Initialize a new clamp filter."""
141
+ super().__init__(callback)
142
+ self._min_value = min_value
143
+ self._max_value = max_value
144
+
145
+ async def __call__(self, new_value: Any) -> Any:
146
+ """Set a new value for the callback."""
147
+ if new_value < self._min_value:
148
+ return await self._callback(self._min_value)
149
+
150
+ if new_value > self._max_value:
151
+ return await self._callback(self._max_value)
152
+
153
+ return await self._callback(new_value)
154
+
155
+
156
+ def clamp(callback: Callback, min_value: float, max_value: float) -> _Clamp:
157
+ """Return a clamp filter.
158
+
159
+ A callback function will be called with value clamped between
160
+ specified boundaries.
161
+
162
+ :param callback: A callback function to be awaited on new value
163
+ :type callback: Callback
164
+ :param min_value: A lower boundary
165
+ :type min_value: float
166
+ :param max_value: An upper boundary
167
+ :type max_value: float
168
+ :return: An instance of callable filter
169
+ :rtype: _Clamp
170
+ """
171
+ return _Clamp(callback, min_value, max_value)
172
+
173
+
128
174
  class _OnChange(Filter):
129
175
  """Represents a value changed filter.
130
176
 
@@ -151,7 +197,7 @@ def on_change(callback: Callback) -> _OnChange:
151
197
 
152
198
  :param callback: A callback function to be awaited on value change
153
199
  :type callback: Callback
154
- :return: A instance of callable filter
200
+ :return: An instance of callable filter
155
201
  :rtype: _OnChange
156
202
  """
157
203
  return _OnChange(callback)
@@ -201,7 +247,7 @@ def debounce(callback: Callback, min_calls: int) -> _Debounce:
201
247
  :param min_calls: Value shouldn't change for this amount of
202
248
  filter calls
203
249
  :type min_calls: int
204
- :return: A instance of callable filter
250
+ :return: An instance of callable filter
205
251
  :rtype: _Debounce
206
252
  """
207
253
  return _Debounce(callback, min_calls)
@@ -248,7 +294,7 @@ def throttle(callback: Callback, seconds: float) -> _Throttle:
248
294
  :param seconds: A callback will be awaited at most once per
249
295
  this amount of seconds
250
296
  :type seconds: float
251
- :return: A instance of callable filter
297
+ :return: An instance of callable filter
252
298
  :rtype: _Throttle
253
299
  """
254
300
  return _Throttle(callback, seconds)
@@ -285,7 +331,7 @@ def delta(callback: Callback) -> _Delta:
285
331
  :param callback: A callback function that will be awaited with
286
332
  difference between values in two subsequent calls
287
333
  :type callback: Callback
288
- :return: A instance of callable filter
334
+ :return: An instance of callable filter
289
335
  :rtype: _Delta
290
336
  """
291
337
  return _Delta(callback)
@@ -340,7 +386,7 @@ def aggregate(callback: Callback, seconds: float) -> _Aggregate:
340
386
  :param seconds: A callback will be awaited with a sum of values
341
387
  aggregated over this amount of seconds.
342
388
  :type seconds: float
343
- :return: A instance of callable filter
389
+ :return: An instance of callable filter
344
390
  :rtype: _Aggregate
345
391
  """
346
392
  return _Aggregate(callback, seconds)
@@ -382,7 +428,7 @@ def custom(callback: Callback, filter_fn: Callable[[Any], bool]) -> _Custom:
382
428
  :param filter_fn: Filter function, that will be called with a
383
429
  value and should return `True` to await filter's callback
384
430
  :type filter_fn: Callable[[Any], bool]
385
- :return: A instance of callable filter
431
+ :return: An instance of callable filter
386
432
  :rtype: _Custom
387
433
  """
388
434
  return _Custom(callback, filter_fn)
@@ -184,16 +184,31 @@ class Parameter(ABC):
184
184
  )
185
185
  return type(self)(self.device, self.description, values)
186
186
 
187
- async def set(self, value: Any, retries: int = 5, timeout: float = 5.0) -> bool:
188
- """Set a parameter value."""
189
- if (value := _normalize_parameter_value(value)) == self.values.value:
190
- return True
187
+ def validate(self, value: ParameterValue) -> int:
188
+ """Validate a parameter value."""
189
+ value = _normalize_parameter_value(value)
190
+ if value == self.values.value:
191
+ raise ValueError("Parameter value is unchanged.")
191
192
 
192
193
  if value < self.values.min_value or value > self.values.max_value:
193
194
  raise ValueError(
194
195
  f"Value must be between '{self.min_value}' and '{self.max_value}'"
195
196
  )
196
197
 
198
+ return value
199
+
200
+ async def set(self, value: Any, retries: int = 5, timeout: float = 5.0) -> bool:
201
+ """Set a parameter value."""
202
+ return await self._try_set(self.validate(value), retries, timeout)
203
+
204
+ def set_nowait(self, value: Any, retries: int = 5, timeout: float = 5.0) -> None:
205
+ """Set a parameter value without waiting."""
206
+ self.device.create_task(self._try_set(self.validate(value), retries, timeout))
207
+
208
+ async def _try_set(
209
+ self, value: Any, retries: int = 5, timeout: float = 5.0
210
+ ) -> bool:
211
+ """Try to set a parameter value."""
197
212
  self._previous_value = self._values.value
198
213
  self._values.value = value
199
214
  self._pending_update = True
@@ -206,9 +221,6 @@ class Parameter(ABC):
206
221
  return False
207
222
 
208
223
  await self.device.queue.put(await self.create_request())
209
- if not self.is_tracking_changes:
210
- await self.force_refresh()
211
-
212
224
  await asyncio.sleep(timeout)
213
225
  retries -= 1
214
226
 
@@ -221,15 +233,6 @@ class Parameter(ABC):
221
233
 
222
234
  self._values = values
223
235
 
224
- async def force_refresh(self) -> None:
225
- """Refresh the parameter from remote."""
226
- await self.device.queue.put(await self.create_refresh_request())
227
-
228
- @property
229
- def is_tracking_changes(self) -> bool:
230
- """Return True if remote's tracking changes, False otherwise."""
231
- return False
232
-
233
236
  @property
234
237
  def pending_update(self) -> bool:
235
238
  """Check if parameter is pending update on the device."""
@@ -278,10 +281,6 @@ class Parameter(ABC):
278
281
  async def create_request(self) -> Request:
279
282
  """Create a request to change the parameter."""
280
283
 
281
- @abstractmethod
282
- async def create_refresh_request(self) -> Request:
283
- """Create a request to refresh the parameter."""
284
-
285
284
 
286
285
  @dataslots
287
286
  @dataclass
@@ -308,16 +307,12 @@ class Number(Parameter):
308
307
  self, value: int | float, retries: int = 5, timeout: float = 5.0
309
308
  ) -> None:
310
309
  """Set a parameter value without waiting."""
311
- self.device.create_task(self.set(value, retries, timeout))
310
+ super().set_nowait(value, retries, timeout)
312
311
 
313
312
  async def create_request(self) -> Request:
314
313
  """Create a request to change the number."""
315
314
  return Request()
316
315
 
317
- async def create_refresh_request(self) -> Request:
318
- """Create a request to refresh the number."""
319
- return Request()
320
-
321
316
  @property
322
317
  def value(self) -> int | float:
323
318
  """Return the value."""
@@ -362,7 +357,7 @@ class Switch(Parameter):
362
357
  self, value: bool | Literal["off", "on"], retries: int = 5, timeout: float = 5.0
363
358
  ) -> None:
364
359
  """Set a switch value without waiting."""
365
- self.device.create_task(self.set(value, retries, timeout))
360
+ super().set_nowait(value, retries, timeout)
366
361
 
367
362
  async def turn_on(self) -> bool:
368
363
  """Set a switch value to 'on'.
@@ -394,10 +389,6 @@ class Switch(Parameter):
394
389
  """Create a request to change the switch."""
395
390
  return Request()
396
391
 
397
- async def create_refresh_request(self) -> Request:
398
- """Create a request to refresh the switch."""
399
- return Request()
400
-
401
392
  @property
402
393
  def value(self) -> Literal["off", "on"]:
403
394
  """Return the value."""
@@ -83,17 +83,6 @@ class EcomaxParameter(Parameter):
83
83
  data={ATTR_INDEX: self._index, ATTR_VALUE: self.values.value},
84
84
  )
85
85
 
86
- async def create_refresh_request(self) -> Request:
87
- """Create a request to refresh the parameter."""
88
- return await Request.create(
89
- FrameType.REQUEST_ECOMAX_PARAMETERS, recipient=self.device.address
90
- )
91
-
92
- @property
93
- def is_tracking_changes(self) -> bool:
94
- """Return True if remote's tracking changes, False otherwise."""
95
- return self.device.has_frame_version(FrameType.REQUEST_ECOMAX_PARAMETERS)
96
-
97
86
 
98
87
  @dataslots
99
88
  @dataclass
@@ -479,8 +468,8 @@ ECOMAX_PARAMETERS: dict[ProductType, tuple[EcomaxParameterDescription, ...]] = {
479
468
  ),
480
469
  EcomaxNumberDescription(
481
470
  name="max_fuel_flow",
482
- multiplier=0.1,
483
- unit_of_measurement=UnitOfMeasurement.KILOGRAMS_PER_HOUR,
471
+ multiplier=20,
472
+ unit_of_measurement=UnitOfMeasurement.GRAMS,
484
473
  ),
485
474
  EcomaxNumberDescription(
486
475
  name="feeder_calibration",
@@ -65,17 +65,6 @@ class MixerParameter(Parameter):
65
65
  },
66
66
  )
67
67
 
68
- async def create_refresh_request(self) -> Request:
69
- """Create a request to refresh the parameter."""
70
- return await Request.create(
71
- FrameType.REQUEST_MIXER_PARAMETERS, recipient=self.device.parent.address
72
- )
73
-
74
- @property
75
- def is_tracking_changes(self) -> bool:
76
- """Return True if remote's tracking changes, False otherwise."""
77
- return self.device.parent.has_frame_version(FrameType.REQUEST_MIXER_PARAMETERS)
78
-
79
68
 
80
69
  @dataslots
81
70
  @dataclass
@@ -107,17 +107,6 @@ class ScheduleParameter(Parameter):
107
107
  data=collect_schedule_data(schedule_name, self.device),
108
108
  )
109
109
 
110
- async def create_refresh_request(self) -> Request:
111
- """Create a request to refresh the parameter."""
112
- return await Request.create(
113
- FrameType.REQUEST_SCHEDULES, recipient=self.device.address
114
- )
115
-
116
- @property
117
- def is_tracking_changes(self) -> bool:
118
- """Return True if remote's tracking changes, False otherwise."""
119
- return self.device.has_frame_version(FrameType.REQUEST_SCHEDULES)
120
-
121
110
 
122
111
  @dataslots
123
112
  @dataclass