PyPlumIO 0.5.29__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 (143) hide show
  1. {pyplumio-0.5.29 → pyplumio-0.5.31}/.github/workflows/ci.yml +6 -4
  2. {pyplumio-0.5.29 → pyplumio-0.5.31}/.github/workflows/deploy.yml +2 -2
  3. {pyplumio-0.5.29 → pyplumio-0.5.31}/.pre-commit-config.yaml +3 -3
  4. {pyplumio-0.5.29 → pyplumio-0.5.31}/PKG-INFO +11 -10
  5. {pyplumio-0.5.29 → pyplumio-0.5.31}/PyPlumIO.egg-info/PKG-INFO +11 -10
  6. {pyplumio-0.5.29 → pyplumio-0.5.31}/PyPlumIO.egg-info/requires.txt +8 -8
  7. {pyplumio-0.5.29 → pyplumio-0.5.31}/docs/source/callbacks.rst +12 -1
  8. {pyplumio-0.5.29 → pyplumio-0.5.31}/docs/source/connecting.rst +10 -6
  9. {pyplumio-0.5.29 → pyplumio-0.5.31}/docs/source/mixers_thermostats.rst +1 -1
  10. {pyplumio-0.5.29 → pyplumio-0.5.31}/docs/source/reading.rst +10 -11
  11. {pyplumio-0.5.29 → pyplumio-0.5.31}/docs/source/writing.rst +22 -22
  12. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/_version.py +2 -2
  13. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/devices/__init__.py +38 -4
  14. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/devices/ecomax.py +1 -30
  15. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/filters.py +52 -6
  16. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/helpers/parameter.py +35 -8
  17. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/ecomax_parameters.py +7 -5
  18. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/schedules.py +2 -2
  19. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/thermostat_parameters.py +6 -11
  20. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyproject.toml +10 -9
  21. {pyplumio-0.5.29 → pyplumio-0.5.31}/requirements_docs.txt +1 -1
  22. pyplumio-0.5.31/requirements_test.txt +11 -0
  23. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/helpers/test_parameter.py +26 -45
  24. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/test_devices.py +7 -3
  25. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/test_filters.py +25 -1
  26. pyplumio-0.5.29/requirements_test.txt +0 -11
  27. {pyplumio-0.5.29 → pyplumio-0.5.31}/.gitattributes +0 -0
  28. {pyplumio-0.5.29 → pyplumio-0.5.31}/.github/CODE_OF_CONDUCT.md +0 -0
  29. {pyplumio-0.5.29 → pyplumio-0.5.31}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  30. {pyplumio-0.5.29 → pyplumio-0.5.31}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  31. {pyplumio-0.5.29 → pyplumio-0.5.31}/.github/dependabot.yml +0 -0
  32. {pyplumio-0.5.29 → pyplumio-0.5.31}/.github/workflows/codeql-analysis.yml +0 -0
  33. {pyplumio-0.5.29 → pyplumio-0.5.31}/.github/workflows/documentation.yml +0 -0
  34. {pyplumio-0.5.29 → pyplumio-0.5.31}/.gitignore +0 -0
  35. {pyplumio-0.5.29 → pyplumio-0.5.31}/.vscode/settings.json +0 -0
  36. {pyplumio-0.5.29 → pyplumio-0.5.31}/LICENSE +0 -0
  37. {pyplumio-0.5.29 → pyplumio-0.5.31}/MANIFEST.in +0 -0
  38. {pyplumio-0.5.29 → pyplumio-0.5.31}/PyPlumIO.egg-info/SOURCES.txt +0 -0
  39. {pyplumio-0.5.29 → pyplumio-0.5.31}/PyPlumIO.egg-info/dependency_links.txt +0 -0
  40. {pyplumio-0.5.29 → pyplumio-0.5.31}/PyPlumIO.egg-info/top_level.txt +0 -0
  41. {pyplumio-0.5.29 → pyplumio-0.5.31}/README.md +0 -0
  42. {pyplumio-0.5.29 → pyplumio-0.5.31}/docs/Makefile +0 -0
  43. {pyplumio-0.5.29 → pyplumio-0.5.31}/docs/make.bat +0 -0
  44. {pyplumio-0.5.29 → pyplumio-0.5.31}/docs/source/conf.py +0 -0
  45. {pyplumio-0.5.29 → pyplumio-0.5.31}/docs/source/frames.rst +0 -0
  46. {pyplumio-0.5.29 → pyplumio-0.5.31}/docs/source/index.rst +0 -0
  47. {pyplumio-0.5.29 → pyplumio-0.5.31}/docs/source/protocol.rst +0 -0
  48. {pyplumio-0.5.29 → pyplumio-0.5.31}/docs/source/schedules.rst +0 -0
  49. {pyplumio-0.5.29 → pyplumio-0.5.31}/images/ecomax.png +0 -0
  50. {pyplumio-0.5.29 → pyplumio-0.5.31}/images/rs485.png +0 -0
  51. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/__init__.py +0 -0
  52. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/__main__.py +0 -0
  53. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/connection.py +0 -0
  54. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/const.py +0 -0
  55. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/devices/ecoster.py +0 -0
  56. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/devices/mixer.py +0 -0
  57. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/devices/thermostat.py +0 -0
  58. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/exceptions.py +0 -0
  59. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/frames/__init__.py +0 -0
  60. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/frames/messages.py +0 -0
  61. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/frames/requests.py +0 -0
  62. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/frames/responses.py +0 -0
  63. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/helpers/__init__.py +0 -0
  64. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/helpers/data_types.py +0 -0
  65. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/helpers/event_manager.py +0 -0
  66. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/helpers/factory.py +0 -0
  67. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/helpers/schedule.py +0 -0
  68. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/helpers/task_manager.py +0 -0
  69. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/helpers/timeout.py +0 -0
  70. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/helpers/uid.py +0 -0
  71. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/protocol.py +0 -0
  72. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/py.typed +0 -0
  73. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/stream.py +0 -0
  74. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/__init__.py +0 -0
  75. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/alerts.py +0 -0
  76. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/boiler_load.py +0 -0
  77. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/boiler_power.py +0 -0
  78. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/fan_power.py +0 -0
  79. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/frame_versions.py +0 -0
  80. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/fuel_consumption.py +0 -0
  81. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/fuel_level.py +0 -0
  82. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/lambda_sensor.py +0 -0
  83. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/mixer_parameters.py +0 -0
  84. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/mixer_sensors.py +0 -0
  85. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/modules.py +0 -0
  86. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/network_info.py +0 -0
  87. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/output_flags.py +0 -0
  88. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/outputs.py +0 -0
  89. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/pending_alerts.py +0 -0
  90. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/product_info.py +0 -0
  91. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/program_version.py +0 -0
  92. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/regulator_data.py +0 -0
  93. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/regulator_data_schema.py +0 -0
  94. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/statuses.py +0 -0
  95. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/temperatures.py +0 -0
  96. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/structures/thermostat_sensors.py +0 -0
  97. {pyplumio-0.5.29 → pyplumio-0.5.31}/pyplumio/utils.py +0 -0
  98. {pyplumio-0.5.29 → pyplumio-0.5.31}/requirements.txt +0 -0
  99. {pyplumio-0.5.29 → pyplumio-0.5.31}/setup.cfg +0 -0
  100. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/__init__.py +0 -0
  101. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/conftest.py +0 -0
  102. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/frames/test_init.py +0 -0
  103. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/frames/test_messages.py +0 -0
  104. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/frames/test_requests.py +0 -0
  105. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/frames/test_responses.py +0 -0
  106. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/helpers/__init__.py +0 -0
  107. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/helpers/test_data_types.py +0 -0
  108. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/helpers/test_event_manager.py +0 -0
  109. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/helpers/test_factory.py +0 -0
  110. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/helpers/test_schedule.py +0 -0
  111. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/helpers/test_task_manager.py +0 -0
  112. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/helpers/test_timeout.py +0 -0
  113. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/helpers/test_uid.py +0 -0
  114. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/ruff.toml +0 -0
  115. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/test_connection.py +0 -0
  116. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/test_init.py +0 -0
  117. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/test_main.py +0 -0
  118. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/test_protocol.py +0 -0
  119. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/test_stream.py +0 -0
  120. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/test_utils.py +0 -0
  121. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/messages/regulator_data.json +0 -0
  122. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/messages/sensor_data.json +0 -0
  123. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/requests/alerts.json +0 -0
  124. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/requests/ecomax_control.json +0 -0
  125. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/requests/ecomax_parameters.json +0 -0
  126. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/requests/mixer_parameters.json +0 -0
  127. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/requests/set_ecomax_parameter.json +0 -0
  128. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/requests/set_mixer_parameter.json +0 -0
  129. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/requests/set_schedule.json +0 -0
  130. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/requests/set_thermostat_parameter.json +0 -0
  131. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/requests/thermostat_parameters.json +0 -0
  132. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/responses/alerts.json +0 -0
  133. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/responses/device_available.json +0 -0
  134. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/responses/ecomax_parameters.json +0 -0
  135. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/responses/mixer_parameters.json +0 -0
  136. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/responses/password.json +0 -0
  137. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/responses/program_version.json +0 -0
  138. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/responses/regulator_data_schema.json +0 -0
  139. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/responses/schedules.json +0 -0
  140. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/responses/thermostat_parameters.json +0 -0
  141. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/responses/uid.json +0 -0
  142. {pyplumio-0.5.29 → pyplumio-0.5.31}/tests/testdata/unknown/unknown_ecomax_parameter.json +0 -0
  143. {pyplumio-0.5.29 → 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.6.3
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.11.2
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.29
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,22 +28,22 @@ 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.4; 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
- Requires-Dist: pytest==8.3.3; extra == "test"
34
- Requires-Dist: pytest-asyncio==0.24.0; extra == "test"
35
- Requires-Dist: ruff==0.7.1; extra == "test"
34
+ Requires-Dist: pytest==8.3.4; 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
- Requires-Dist: sphinx_rtd_theme==3.0.1; extra == "docs"
41
+ Requires-Dist: sphinx_rtd_theme==3.0.2; extra == "docs"
41
42
  Requires-Dist: readthedocs-sphinx-search==0.3.2; extra == "docs"
42
43
  Provides-Extra: dev
43
44
  Requires-Dist: pyplumio[docs,test]; extra == "dev"
44
45
  Requires-Dist: pre-commit==4.0.1; extra == "dev"
45
- Requires-Dist: tomli==2.0.2; extra == "dev"
46
+ Requires-Dist: tomli==2.2.1; extra == "dev"
46
47
 
47
48
  # PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
48
49
  [![PyPI version](https://badge.fury.io/py/PyPlumIO.svg)](https://badge.fury.io/py/PyPlumIO)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: PyPlumIO
3
- Version: 0.5.29
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,22 +28,22 @@ 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.4; 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
- Requires-Dist: pytest==8.3.3; extra == "test"
34
- Requires-Dist: pytest-asyncio==0.24.0; extra == "test"
35
- Requires-Dist: ruff==0.7.1; extra == "test"
34
+ Requires-Dist: pytest==8.3.4; 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
- Requires-Dist: sphinx_rtd_theme==3.0.1; extra == "docs"
41
+ Requires-Dist: sphinx_rtd_theme==3.0.2; extra == "docs"
41
42
  Requires-Dist: readthedocs-sphinx-search==0.3.2; extra == "docs"
42
43
  Provides-Extra: dev
43
44
  Requires-Dist: pyplumio[docs,test]; extra == "dev"
44
45
  Requires-Dist: pre-commit==4.0.1; extra == "dev"
45
- Requires-Dist: tomli==2.0.2; extra == "dev"
46
+ Requires-Dist: tomli==2.2.1; extra == "dev"
46
47
 
47
48
  # PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
48
49
  [![PyPI version](https://badge.fury.io/py/PyPlumIO.svg)](https://badge.fury.io/py/PyPlumIO)
@@ -5,20 +5,20 @@ typing-extensions==4.12.2
5
5
  [dev]
6
6
  pyplumio[docs,test]
7
7
  pre-commit==4.0.1
8
- tomli==2.0.2
8
+ tomli==2.2.1
9
9
 
10
10
  [docs]
11
11
  sphinx==8.1.3
12
- sphinx_rtd_theme==3.0.1
12
+ sphinx_rtd_theme==3.0.2
13
13
  readthedocs-sphinx-search==0.3.2
14
14
 
15
15
  [test]
16
16
  codespell==2.3.0
17
- coverage==7.6.4
18
- mypy==1.13.0
17
+ coverage==7.6.10
18
+ mypy==1.14.1
19
19
  pyserial-asyncio-fast==0.14
20
- pytest==8.3.3
21
- pytest-asyncio==0.24.0
22
- ruff==0.7.1
20
+ pytest==8.3.4
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
 
@@ -69,18 +69,22 @@ working with device classes and queues.
69
69
  async def main():
70
70
  """Open a connection and request alerts."""
71
71
  async with pyplumio.open_tcp_connection(
72
- host="localhost", port=8899, protocol=pyplumio.DummyProtocol
72
+ host="localhost", port=8899, protocol=pyplumio.DummyProtocol()
73
73
  ) as connection:
74
74
  await connection.writer.write(
75
75
  requests.AlertsRequest(recipient=DeviceType.ECOMAX, start=0, count=5)
76
76
  )
77
77
 
78
78
  while connection.connected:
79
- if isinstance(
80
- (frame := await connection.reader.read()), responses.AlertsResponse
81
- ):
82
- print(frame.data)
83
- break
79
+ try:
80
+ if isinstance(
81
+ (frame := await connection.reader.read()), responses.AlertsResponse
82
+ ):
83
+ print(frame.data)
84
+ break
85
+ except pyplumio.ProtocolError:
86
+ # Skip protocol errors and read the next frame.
87
+ pass
84
88
 
85
89
 
86
90
  asyncio.run(main())
@@ -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.29'
16
- __version_tuple__ = version_tuple = (0, 5, 29)
15
+ __version__ = version = '0.5.31'
16
+ __version_tuple__ = version_tuple = (0, 5, 31)
@@ -5,17 +5,21 @@ from __future__ import annotations
5
5
  from abc import ABC
6
6
  import asyncio
7
7
  from functools import cache
8
+ import logging
8
9
  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
13
+ from pyplumio.frames import DataFrameDescription, Frame, Request, is_known_frame_type
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 Parameter, ParameterValue
17
+ from pyplumio.structures.frame_versions import ATTR_FRAME_VERSIONS
16
18
  from pyplumio.structures.network_info import NetworkInfo
17
19
  from pyplumio.utils import to_camelcase
18
20
 
21
+ _LOGGER = logging.getLogger(__name__)
22
+
19
23
 
20
24
  @cache
21
25
  def is_known_device_type(device_type: int) -> bool:
@@ -103,9 +107,6 @@ class Device(ABC, EventManager):
103
107
  this value is used to determine failure when
104
108
  retrying and doesn't block, defaults to `None`
105
109
  :type timeout: float, optional
106
- :return: `True` if parameter was successfully set, `False`
107
- otherwise.
108
- :rtype: bool
109
110
  """
110
111
  self.create_task(self.set(name, value, retries, timeout))
111
112
 
@@ -125,11 +126,44 @@ class PhysicalDevice(Device, ABC):
125
126
  address: ClassVar[int]
126
127
  _network: NetworkInfo
127
128
  _setup_frames: tuple[DataFrameDescription, ...]
129
+ _frame_versions: dict[int, int]
128
130
 
129
131
  def __init__(self, queue: asyncio.Queue[Frame], network: NetworkInfo) -> None:
130
132
  """Initialize a new physical device."""
131
133
  super().__init__(queue)
132
134
  self._network = network
135
+ self._frame_versions = {}
136
+
137
+ async def update_frame_versions(versions: dict[int, int]) -> None:
138
+ """Check frame versions and update outdated frames."""
139
+ for frame_type, version in versions.items():
140
+ if (
141
+ is_known_frame_type(frame_type)
142
+ and self.supports_frame_type(frame_type)
143
+ and not self.has_frame_version(frame_type, version)
144
+ ):
145
+ _LOGGER.debug(
146
+ "Updating frame %s to version %i", repr(frame_type), version
147
+ )
148
+ request = await Request.create(frame_type, recipient=self.address)
149
+ self.queue.put_nowait(request)
150
+ self._frame_versions[frame_type] = version
151
+
152
+ self.subscribe(ATTR_FRAME_VERSIONS, update_frame_versions)
153
+
154
+ def has_frame_version(self, frame_type: int, version: int | None = None) -> bool:
155
+ """Return True if frame data is up to date, False otherwise."""
156
+ if frame_type not in self._frame_versions:
157
+ return False
158
+
159
+ if version is None or self._frame_versions[frame_type] == version:
160
+ return True
161
+
162
+ return False
163
+
164
+ def supports_frame_type(self, frame_type: int) -> bool:
165
+ """Check if frame type is supported by the device."""
166
+ return frame_type not in self.data.get(ATTR_FRAME_ERRORS, [])
133
167
 
134
168
  def handle_frame(self, frame: Frame) -> None:
135
169
  """Handle frame received from the device."""
@@ -9,7 +9,6 @@ import time
9
9
  from typing import Any, Final
10
10
 
11
11
  from pyplumio.const import (
12
- ATTR_FRAME_ERRORS,
13
12
  ATTR_PASSWORD,
14
13
  ATTR_SENSORS,
15
14
  ATTR_STATE,
@@ -21,7 +20,7 @@ from pyplumio.devices import PhysicalDevice
21
20
  from pyplumio.devices.mixer import Mixer
22
21
  from pyplumio.devices.thermostat import Thermostat
23
22
  from pyplumio.filters import on_change
24
- from pyplumio.frames import DataFrameDescription, Frame, Request, is_known_frame_type
23
+ from pyplumio.frames import DataFrameDescription, Frame, Request
25
24
  from pyplumio.helpers.parameter import ParameterValues
26
25
  from pyplumio.helpers.schedule import Schedule, ScheduleDay
27
26
  from pyplumio.structures.alerts import ATTR_TOTAL_ALERTS
@@ -35,7 +34,6 @@ from pyplumio.structures.ecomax_parameters import (
35
34
  EcomaxSwitch,
36
35
  EcomaxSwitchDescription,
37
36
  )
38
- from pyplumio.structures.frame_versions import ATTR_FRAME_VERSIONS
39
37
  from pyplumio.structures.fuel_consumption import ATTR_FUEL_CONSUMPTION
40
38
  from pyplumio.structures.mixer_parameters import ATTR_MIXER_PARAMETERS
41
39
  from pyplumio.structures.mixer_sensors import ATTR_MIXER_SENSORS
@@ -106,17 +104,14 @@ class EcoMAX(PhysicalDevice):
106
104
 
107
105
  address = DeviceType.ECOMAX
108
106
 
109
- _frame_versions: dict[int, int]
110
107
  _fuel_burned_timestamp_ns: int
111
108
  _setup_frames = SETUP_FRAME_TYPES
112
109
 
113
110
  def __init__(self, queue: asyncio.Queue[Frame], network: NetworkInfo) -> None:
114
111
  """Initialize a new ecoMAX controller."""
115
112
  super().__init__(queue, network)
116
- self._frame_versions = {}
117
113
  self._fuel_burned_timestamp_ns = time.perf_counter_ns()
118
114
  self.subscribe(ATTR_ECOMAX_PARAMETERS, self._handle_ecomax_parameters)
119
- self.subscribe(ATTR_FRAME_VERSIONS, self._update_frame_versions)
120
115
  self.subscribe(ATTR_FUEL_CONSUMPTION, self._add_burned_fuel_counter)
121
116
  self.subscribe(ATTR_MIXER_PARAMETERS, self._handle_mixer_parameters)
122
117
  self.subscribe(ATTR_MIXER_SENSORS, self._handle_mixer_sensors)
@@ -142,17 +137,6 @@ class EcoMAX(PhysicalDevice):
142
137
 
143
138
  super().handle_frame(frame)
144
139
 
145
- def _has_frame_version(self, frame_type: FrameType | int, version: int) -> bool:
146
- """Check if ecoMAX controller has this version of the frame."""
147
- return (
148
- frame_type in self._frame_versions
149
- and self._frame_versions[frame_type] == version
150
- )
151
-
152
- def _frame_is_supported(self, frame_type: FrameType | int) -> bool:
153
- """Check if frame is supported by the device."""
154
- return frame_type not in self.data.get(ATTR_FRAME_ERRORS, [])
155
-
156
140
  def _mixers(self, indexes: Iterable[int]) -> Generator[Mixer, None, None]:
157
141
  """Iterate through the mixer indexes.
158
142
 
@@ -224,19 +208,6 @@ class EcoMAX(PhysicalDevice):
224
208
  await asyncio.gather(*_ecomax_parameter_events())
225
209
  return True
226
210
 
227
- async def _update_frame_versions(self, versions: dict[int, int]) -> None:
228
- """Check frame versions and update outdated frames."""
229
- for frame_type, version in versions.items():
230
- if (
231
- is_known_frame_type(frame_type)
232
- and self._frame_is_supported(frame_type)
233
- and not self._has_frame_version(frame_type, version)
234
- ):
235
- # We don't have this frame or it's version has changed.
236
- request = await Request.create(frame_type, recipient=self.address)
237
- self.queue.put_nowait(request)
238
- self._frame_versions[frame_type] = version
239
-
240
211
  async def _add_burned_fuel_counter(self, fuel_consumption: float) -> None:
241
212
  """Calculate fuel burned since last sensor's data message."""
242
213
  current_timestamp_ns = time.perf_counter_ns()