PyPlumIO 0.5.36__tar.gz → 0.5.37__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.36 → pyplumio-0.5.37}/.pre-commit-config.yaml +2 -2
  2. {pyplumio-0.5.36 → pyplumio-0.5.37}/PKG-INFO +7 -7
  3. {pyplumio-0.5.36 → pyplumio-0.5.37}/PyPlumIO.egg-info/PKG-INFO +7 -7
  4. {pyplumio-0.5.36 → pyplumio-0.5.37}/PyPlumIO.egg-info/requires.txt +4 -4
  5. {pyplumio-0.5.36 → pyplumio-0.5.37}/README.md +2 -2
  6. {pyplumio-0.5.36 → pyplumio-0.5.37}/docs/source/index.rst +3 -2
  7. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/_version.py +2 -2
  8. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/helpers/parameter.py +14 -5
  9. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/ecomax_parameters.py +3 -1
  10. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyproject.toml +7 -6
  11. {pyplumio-0.5.36 → pyplumio-0.5.37}/requirements_test.txt +4 -4
  12. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/helpers/test_parameter.py +53 -9
  13. {pyplumio-0.5.36 → pyplumio-0.5.37}/.gitattributes +0 -0
  14. {pyplumio-0.5.36 → pyplumio-0.5.37}/.github/CODE_OF_CONDUCT.md +0 -0
  15. {pyplumio-0.5.36 → pyplumio-0.5.37}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  16. {pyplumio-0.5.36 → pyplumio-0.5.37}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  17. {pyplumio-0.5.36 → pyplumio-0.5.37}/.github/dependabot.yml +0 -0
  18. {pyplumio-0.5.36 → pyplumio-0.5.37}/.github/workflows/ci.yml +0 -0
  19. {pyplumio-0.5.36 → pyplumio-0.5.37}/.github/workflows/codeql-analysis.yml +0 -0
  20. {pyplumio-0.5.36 → pyplumio-0.5.37}/.github/workflows/deploy.yml +0 -0
  21. {pyplumio-0.5.36 → pyplumio-0.5.37}/.github/workflows/documentation.yml +0 -0
  22. {pyplumio-0.5.36 → pyplumio-0.5.37}/.gitignore +0 -0
  23. {pyplumio-0.5.36 → pyplumio-0.5.37}/.vscode/settings.json +0 -0
  24. {pyplumio-0.5.36 → pyplumio-0.5.37}/LICENSE +0 -0
  25. {pyplumio-0.5.36 → pyplumio-0.5.37}/MANIFEST.in +0 -0
  26. {pyplumio-0.5.36 → pyplumio-0.5.37}/PyPlumIO.egg-info/SOURCES.txt +0 -0
  27. {pyplumio-0.5.36 → pyplumio-0.5.37}/PyPlumIO.egg-info/dependency_links.txt +0 -0
  28. {pyplumio-0.5.36 → pyplumio-0.5.37}/PyPlumIO.egg-info/top_level.txt +0 -0
  29. {pyplumio-0.5.36 → pyplumio-0.5.37}/docs/Makefile +0 -0
  30. {pyplumio-0.5.36 → pyplumio-0.5.37}/docs/make.bat +0 -0
  31. {pyplumio-0.5.36 → pyplumio-0.5.37}/docs/source/callbacks.rst +0 -0
  32. {pyplumio-0.5.36 → pyplumio-0.5.37}/docs/source/conf.py +0 -0
  33. {pyplumio-0.5.36 → pyplumio-0.5.37}/docs/source/connecting.rst +0 -0
  34. {pyplumio-0.5.36 → pyplumio-0.5.37}/docs/source/frames.rst +0 -0
  35. {pyplumio-0.5.36 → pyplumio-0.5.37}/docs/source/mixers_thermostats.rst +0 -0
  36. {pyplumio-0.5.36 → pyplumio-0.5.37}/docs/source/protocol.rst +0 -0
  37. {pyplumio-0.5.36 → pyplumio-0.5.37}/docs/source/reading.rst +0 -0
  38. {pyplumio-0.5.36 → pyplumio-0.5.37}/docs/source/schedules.rst +0 -0
  39. {pyplumio-0.5.36 → pyplumio-0.5.37}/docs/source/writing.rst +0 -0
  40. {pyplumio-0.5.36 → pyplumio-0.5.37}/images/ecomax.png +0 -0
  41. {pyplumio-0.5.36 → pyplumio-0.5.37}/images/rs485.png +0 -0
  42. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/__init__.py +0 -0
  43. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/__main__.py +0 -0
  44. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/connection.py +0 -0
  45. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/const.py +0 -0
  46. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/devices/__init__.py +0 -0
  47. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/devices/ecomax.py +0 -0
  48. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/devices/ecoster.py +0 -0
  49. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/devices/mixer.py +0 -0
  50. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/devices/thermostat.py +0 -0
  51. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/exceptions.py +0 -0
  52. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/filters.py +0 -0
  53. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/frames/__init__.py +0 -0
  54. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/frames/messages.py +0 -0
  55. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/frames/requests.py +0 -0
  56. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/frames/responses.py +0 -0
  57. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/helpers/__init__.py +0 -0
  58. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/helpers/data_types.py +0 -0
  59. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/helpers/event_manager.py +0 -0
  60. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/helpers/factory.py +0 -0
  61. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/helpers/schedule.py +0 -0
  62. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/helpers/task_manager.py +0 -0
  63. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/helpers/timeout.py +0 -0
  64. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/helpers/uid.py +0 -0
  65. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/protocol.py +0 -0
  66. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/py.typed +0 -0
  67. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/stream.py +0 -0
  68. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/__init__.py +0 -0
  69. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/alerts.py +0 -0
  70. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/boiler_load.py +0 -0
  71. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/boiler_power.py +0 -0
  72. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/fan_power.py +0 -0
  73. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/frame_versions.py +0 -0
  74. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/fuel_consumption.py +0 -0
  75. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/fuel_level.py +0 -0
  76. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/lambda_sensor.py +0 -0
  77. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/mixer_parameters.py +0 -0
  78. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/mixer_sensors.py +0 -0
  79. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/modules.py +0 -0
  80. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/network_info.py +0 -0
  81. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/output_flags.py +0 -0
  82. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/outputs.py +0 -0
  83. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/pending_alerts.py +0 -0
  84. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/product_info.py +0 -0
  85. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/program_version.py +0 -0
  86. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/regulator_data.py +0 -0
  87. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/regulator_data_schema.py +0 -0
  88. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/schedules.py +0 -0
  89. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/statuses.py +0 -0
  90. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/temperatures.py +0 -0
  91. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/thermostat_parameters.py +0 -0
  92. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/structures/thermostat_sensors.py +0 -0
  93. {pyplumio-0.5.36 → pyplumio-0.5.37}/pyplumio/utils.py +0 -0
  94. {pyplumio-0.5.36 → pyplumio-0.5.37}/requirements.txt +0 -0
  95. {pyplumio-0.5.36 → pyplumio-0.5.37}/requirements_docs.txt +0 -0
  96. {pyplumio-0.5.36 → pyplumio-0.5.37}/setup.cfg +0 -0
  97. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/__init__.py +0 -0
  98. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/conftest.py +0 -0
  99. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/frames/test_init.py +0 -0
  100. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/frames/test_messages.py +0 -0
  101. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/frames/test_requests.py +0 -0
  102. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/frames/test_responses.py +0 -0
  103. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/helpers/__init__.py +0 -0
  104. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/helpers/test_data_types.py +0 -0
  105. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/helpers/test_event_manager.py +0 -0
  106. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/helpers/test_factory.py +0 -0
  107. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/helpers/test_schedule.py +0 -0
  108. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/helpers/test_task_manager.py +0 -0
  109. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/helpers/test_timeout.py +0 -0
  110. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/helpers/test_uid.py +0 -0
  111. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/ruff.toml +0 -0
  112. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/test_connection.py +0 -0
  113. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/test_devices.py +0 -0
  114. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/test_filters.py +0 -0
  115. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/test_init.py +0 -0
  116. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/test_main.py +0 -0
  117. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/test_protocol.py +0 -0
  118. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/test_stream.py +0 -0
  119. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/test_utils.py +0 -0
  120. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/messages/regulator_data.json +0 -0
  121. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/messages/sensor_data.json +0 -0
  122. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/requests/alerts.json +0 -0
  123. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/requests/ecomax_control.json +0 -0
  124. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/requests/ecomax_parameters.json +0 -0
  125. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/requests/mixer_parameters.json +0 -0
  126. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/requests/set_ecomax_parameter.json +0 -0
  127. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/requests/set_mixer_parameter.json +0 -0
  128. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/requests/set_schedule.json +0 -0
  129. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/requests/set_thermostat_parameter.json +0 -0
  130. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/requests/thermostat_parameters.json +0 -0
  131. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/responses/alerts.json +0 -0
  132. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/responses/device_available.json +0 -0
  133. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/responses/ecomax_parameters.json +0 -0
  134. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/responses/mixer_parameters.json +0 -0
  135. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/responses/password.json +0 -0
  136. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/responses/program_version.json +0 -0
  137. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/responses/regulator_data_schema.json +0 -0
  138. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/responses/schedules.json +0 -0
  139. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/responses/thermostat_parameters.json +0 -0
  140. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/responses/uid.json +0 -0
  141. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/unknown/unknown_ecomax_parameter.json +0 -0
  142. {pyplumio-0.5.36 → pyplumio-0.5.37}/tests/testdata/unknown/unknown_mixer_parameter.json +0 -0
@@ -2,13 +2,13 @@
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.9.3
5
+ rev: v0.9.4
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.4.0
11
+ rev: v2.4.1
12
12
  hooks:
13
13
  - id: codespell
14
14
  - repo: https://github.com/pre-commit/mirrors-mypy
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: PyPlumIO
3
- Version: 0.5.36
3
+ Version: 0.5.37
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
@@ -27,15 +27,15 @@ Requires-Dist: dataslots==1.2.0
27
27
  Requires-Dist: pyserial-asyncio==0.6
28
28
  Requires-Dist: typing-extensions==4.12.2
29
29
  Provides-Extra: test
30
- Requires-Dist: codespell==2.4.0; extra == "test"
30
+ Requires-Dist: codespell==2.4.1; extra == "test"
31
31
  Requires-Dist: coverage==7.6.10; extra == "test"
32
32
  Requires-Dist: mypy==1.14.1; extra == "test"
33
33
  Requires-Dist: pyserial-asyncio-fast==0.14; extra == "test"
34
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.3; extra == "test"
35
+ Requires-Dist: pytest-asyncio==0.25.3; extra == "test"
36
+ Requires-Dist: ruff==0.9.4; extra == "test"
37
37
  Requires-Dist: tox==4.24.1; extra == "test"
38
- Requires-Dist: types-pyserial==3.5.0.20250124; extra == "test"
38
+ Requires-Dist: types-pyserial==3.5.0.20250130; extra == "test"
39
39
  Provides-Extra: docs
40
40
  Requires-Dist: sphinx==8.1.3; extra == "docs"
41
41
  Requires-Dist: sphinx_rtd_theme==3.0.2; extra == "docs"
@@ -49,8 +49,8 @@ Requires-Dist: tomli==2.2.1; extra == "dev"
49
49
  [![PyPI version](https://badge.fury.io/py/PyPlumIO.svg)](https://badge.fury.io/py/PyPlumIO)
50
50
  [![PyPI Supported Python Versions](https://img.shields.io/pypi/pyversions/pyplumio.svg)](https://pypi.python.org/pypi/pyplumio/)
51
51
  [![PyPlumIO CI](https://github.com/denpamusic/PyPlumIO/actions/workflows/ci.yml/badge.svg)](https://github.com/denpamusic/PyPlumIO/actions/workflows/ci.yml)
52
- [![Maintainability](https://api.codeclimate.com/v1/badges/9f275fbc50fe9082a909/maintainability)](https://codeclimate.com/github/denpamusic/PyPlumIO/maintainability)
53
- [![Test Coverage](https://api.codeclimate.com/v1/badges/9f275fbc50fe9082a909/test_coverage)](https://codeclimate.com/github/denpamusic/PyPlumIO/test_coverage)
52
+ [![Maintainability](https://api.codeclimate.com/v1/badges/e802127770476b7ba6fd/maintainability)](https://codeclimate.com/github/denpamusic/PyPlumIO/maintainability)
53
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/e802127770476b7ba6fd/test_coverage)](https://codeclimate.com/github/denpamusic/PyPlumIO/test_coverage)
54
54
  [![stability-release-candidate](https://img.shields.io/badge/stability-pre--release-48c9b0.svg)](https://guidelines.denpa.pro/stability#release-candidate)
55
55
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
56
56
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: PyPlumIO
3
- Version: 0.5.36
3
+ Version: 0.5.37
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
@@ -27,15 +27,15 @@ Requires-Dist: dataslots==1.2.0
27
27
  Requires-Dist: pyserial-asyncio==0.6
28
28
  Requires-Dist: typing-extensions==4.12.2
29
29
  Provides-Extra: test
30
- Requires-Dist: codespell==2.4.0; extra == "test"
30
+ Requires-Dist: codespell==2.4.1; extra == "test"
31
31
  Requires-Dist: coverage==7.6.10; extra == "test"
32
32
  Requires-Dist: mypy==1.14.1; extra == "test"
33
33
  Requires-Dist: pyserial-asyncio-fast==0.14; extra == "test"
34
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.3; extra == "test"
35
+ Requires-Dist: pytest-asyncio==0.25.3; extra == "test"
36
+ Requires-Dist: ruff==0.9.4; extra == "test"
37
37
  Requires-Dist: tox==4.24.1; extra == "test"
38
- Requires-Dist: types-pyserial==3.5.0.20250124; extra == "test"
38
+ Requires-Dist: types-pyserial==3.5.0.20250130; extra == "test"
39
39
  Provides-Extra: docs
40
40
  Requires-Dist: sphinx==8.1.3; extra == "docs"
41
41
  Requires-Dist: sphinx_rtd_theme==3.0.2; extra == "docs"
@@ -49,8 +49,8 @@ Requires-Dist: tomli==2.2.1; extra == "dev"
49
49
  [![PyPI version](https://badge.fury.io/py/PyPlumIO.svg)](https://badge.fury.io/py/PyPlumIO)
50
50
  [![PyPI Supported Python Versions](https://img.shields.io/pypi/pyversions/pyplumio.svg)](https://pypi.python.org/pypi/pyplumio/)
51
51
  [![PyPlumIO CI](https://github.com/denpamusic/PyPlumIO/actions/workflows/ci.yml/badge.svg)](https://github.com/denpamusic/PyPlumIO/actions/workflows/ci.yml)
52
- [![Maintainability](https://api.codeclimate.com/v1/badges/9f275fbc50fe9082a909/maintainability)](https://codeclimate.com/github/denpamusic/PyPlumIO/maintainability)
53
- [![Test Coverage](https://api.codeclimate.com/v1/badges/9f275fbc50fe9082a909/test_coverage)](https://codeclimate.com/github/denpamusic/PyPlumIO/test_coverage)
52
+ [![Maintainability](https://api.codeclimate.com/v1/badges/e802127770476b7ba6fd/maintainability)](https://codeclimate.com/github/denpamusic/PyPlumIO/maintainability)
53
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/e802127770476b7ba6fd/test_coverage)](https://codeclimate.com/github/denpamusic/PyPlumIO/test_coverage)
54
54
  [![stability-release-candidate](https://img.shields.io/badge/stability-pre--release-48c9b0.svg)](https://guidelines.denpa.pro/stability#release-candidate)
55
55
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
56
56
 
@@ -13,12 +13,12 @@ sphinx_rtd_theme==3.0.2
13
13
  readthedocs-sphinx-search==0.3.2
14
14
 
15
15
  [test]
16
- codespell==2.4.0
16
+ codespell==2.4.1
17
17
  coverage==7.6.10
18
18
  mypy==1.14.1
19
19
  pyserial-asyncio-fast==0.14
20
20
  pytest==8.3.4
21
- pytest-asyncio==0.25.2
22
- ruff==0.9.3
21
+ pytest-asyncio==0.25.3
22
+ ruff==0.9.4
23
23
  tox==4.24.1
24
- types-pyserial==3.5.0.20250124
24
+ types-pyserial==3.5.0.20250130
@@ -2,8 +2,8 @@
2
2
  [![PyPI version](https://badge.fury.io/py/PyPlumIO.svg)](https://badge.fury.io/py/PyPlumIO)
3
3
  [![PyPI Supported Python Versions](https://img.shields.io/pypi/pyversions/pyplumio.svg)](https://pypi.python.org/pypi/pyplumio/)
4
4
  [![PyPlumIO CI](https://github.com/denpamusic/PyPlumIO/actions/workflows/ci.yml/badge.svg)](https://github.com/denpamusic/PyPlumIO/actions/workflows/ci.yml)
5
- [![Maintainability](https://api.codeclimate.com/v1/badges/9f275fbc50fe9082a909/maintainability)](https://codeclimate.com/github/denpamusic/PyPlumIO/maintainability)
6
- [![Test Coverage](https://api.codeclimate.com/v1/badges/9f275fbc50fe9082a909/test_coverage)](https://codeclimate.com/github/denpamusic/PyPlumIO/test_coverage)
5
+ [![Maintainability](https://api.codeclimate.com/v1/badges/e802127770476b7ba6fd/maintainability)](https://codeclimate.com/github/denpamusic/PyPlumIO/maintainability)
6
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/e802127770476b7ba6fd/test_coverage)](https://codeclimate.com/github/denpamusic/PyPlumIO/test_coverage)
7
7
  [![stability-release-candidate](https://img.shields.io/badge/stability-pre--release-48c9b0.svg)](https://guidelines.denpa.pro/stability#release-candidate)
8
8
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
9
9
 
@@ -6,8 +6,9 @@
6
6
  Welcome to PyPlumIO's documentation!
7
7
  ====================================
8
8
 
9
- This project aims to provide complete and easy to use solution for communicating with
10
- climate devices by `Plum Sp. z o.o. <https://www.plum.pl/>`_
9
+ The `PyPlumIO <https://github.com/denpamusic/PyPlumIO/>`_ projects aims to
10
+ provide complete and easy to use solution for communicating with
11
+ climate devices manufactured by `Plum Sp. z o.o. <https://www.plum.pl/>`_
11
12
 
12
13
  Currently it supports reading and writing parameters of ecoMAX controllers by
13
14
  Plum Sp. z o.o., getting service password and sending network information to
@@ -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.36'
16
- __version_tuple__ = version_tuple = (0, 5, 36)
15
+ __version__ = version = '0.5.37'
16
+ __version_tuple__ = version_tuple = (0, 5, 37)
@@ -28,7 +28,7 @@ def unpack_parameter(
28
28
  data: bytearray, offset: int = 0, size: int = 1
29
29
  ) -> ParameterValues | None:
30
30
  """Unpack a device parameter."""
31
- if not validate_parameter(data[offset : offset + size * 3]):
31
+ if not is_valid_parameter(data[offset : offset + size * 3]):
32
32
  return None
33
33
 
34
34
  value = data[offset : offset + size]
@@ -42,7 +42,7 @@ def unpack_parameter(
42
42
  )
43
43
 
44
44
 
45
- def validate_parameter(data: bytearray) -> bool:
45
+ def is_valid_parameter(data: bytearray) -> bool:
46
46
  """Check if parameter contains any bytes besides 0xFF."""
47
47
  return any(x for x in data if x != BYTE_UNDEFINED)
48
48
 
@@ -75,6 +75,7 @@ class ParameterDescription:
75
75
  """Represents a parameter description."""
76
76
 
77
77
  name: str
78
+ optimistic: bool = False
78
79
 
79
80
 
80
81
  class Parameter(ABC):
@@ -204,13 +205,21 @@ class Parameter(ABC):
204
205
  self, value: int, retries: int = 5, timeout: float = 5.0
205
206
  ) -> bool:
206
207
  """Attempt to update a parameter value on the remote device."""
208
+ _LOGGER.debug(
209
+ "Attempting to update '%s' parameter to %d", self.description.name, value
210
+ )
207
211
  if value == self.values.value:
208
212
  # Value is unchanged
209
213
  return True
210
214
 
211
- self._pending_update = True
212
215
  self._values.value = value
213
- initial_retries = retries
216
+ request = await self.create_request()
217
+ if self.description.optimistic or not (initial_retries := retries):
218
+ # No retries
219
+ await self.device.queue.put(request)
220
+ return True
221
+
222
+ self._pending_update = True
214
223
  while self.pending_update:
215
224
  if retries <= 0:
216
225
  _LOGGER.warning(
@@ -220,7 +229,7 @@ class Parameter(ABC):
220
229
  )
221
230
  return False
222
231
 
223
- await self.device.queue.put(await self.create_request())
232
+ await self.device.queue.put(request)
224
233
  await asyncio.sleep(timeout)
225
234
  retries -= 1
226
235
 
@@ -825,7 +825,9 @@ ECOMAX_PARAMETERS: dict[ProductType, tuple[EcomaxParameterDescription, ...]] = {
825
825
  ),
826
826
  }
827
827
 
828
- ECOMAX_CONTROL_PARAMETER = EcomaxSwitchDescription(name=ATTR_ECOMAX_CONTROL)
828
+ ECOMAX_CONTROL_PARAMETER = EcomaxSwitchDescription(
829
+ name=ATTR_ECOMAX_CONTROL, optimistic=True
830
+ )
829
831
  THERMOSTAT_PROFILE_PARAMETER = EcomaxNumberDescription(name=ATTR_THERMOSTAT_PROFILE)
830
832
 
831
833
 
@@ -37,15 +37,15 @@ dynamic = ["version"]
37
37
 
38
38
  [project.optional-dependencies]
39
39
  test = [
40
- "codespell==2.4.0",
40
+ "codespell==2.4.1",
41
41
  "coverage==7.6.10",
42
42
  "mypy==1.14.1",
43
43
  "pyserial-asyncio-fast==0.14",
44
44
  "pytest==8.3.4",
45
- "pytest-asyncio==0.25.2",
46
- "ruff==0.9.3",
45
+ "pytest-asyncio==0.25.3",
46
+ "ruff==0.9.4",
47
47
  "tox==4.24.1",
48
- "types-pyserial==3.5.0.20250124"
48
+ "types-pyserial==3.5.0.20250130"
49
49
  ]
50
50
  docs = [
51
51
  "sphinx==8.1.3",
@@ -243,7 +243,7 @@ asyncio_default_fixture_loop_scope = "function"
243
243
  [tool.tox]
244
244
  legacy_tox_ini = """
245
245
  [tox]
246
- envlist = lint, type, py{39,310,311,312}
246
+ envlist = lint, type, py{39,310,311,312,313}
247
247
  isolated_build = True
248
248
  skip_missing_interpreters = True
249
249
  ignore_basepython_conflict = True
@@ -253,7 +253,8 @@ legacy_tox_ini = """
253
253
  3.9: py39
254
254
  3.10: py310
255
255
  3.11: py311
256
- 3.12: py312, lint, type
256
+ 3.12: py312
257
+ 3.13: py313, lint, type
257
258
 
258
259
  [testenv]
259
260
  deps =
@@ -1,11 +1,11 @@
1
- codespell==2.4.0
1
+ codespell==2.4.1
2
2
  coverage==7.6.10
3
3
  mypy==1.14.1
4
4
  pre-commit==4.1.0
5
5
  pyserial-asyncio-fast==0.14
6
- pytest-asyncio==0.25.2
6
+ pytest-asyncio==0.25.3
7
7
  pytest==8.3.4
8
- ruff==0.9.3
8
+ ruff==0.9.4
9
9
  tomli==2.2.1
10
10
  tox==4.24.1
11
- types-pyserial==3.5.0.20250124
11
+ types-pyserial==3.5.0.20250130
@@ -15,7 +15,7 @@ from pyplumio.helpers.parameter import (
15
15
  ParameterValues,
16
16
  Switch,
17
17
  SwitchDescription,
18
- validate_parameter,
18
+ is_valid_parameter,
19
19
  )
20
20
 
21
21
 
@@ -41,16 +41,16 @@ def fixture_switch(ecomax: EcoMAX) -> Switch:
41
41
  )
42
42
 
43
43
 
44
- def test_validate_parameter() -> None:
44
+ def test_is_valid_parameter() -> None:
45
45
  """Test checking if parameter is valid."""
46
- assert validate_parameter(
46
+ assert is_valid_parameter(
47
47
  bytearray([BYTE_UNDEFINED, 0xFE, BYTE_UNDEFINED, BYTE_UNDEFINED])
48
48
  )
49
49
 
50
50
 
51
- def test_validate_parameter_invalid() -> None:
51
+ def test_is_valid_parameter_invalid() -> None:
52
52
  """Test checking if parameter is invalid."""
53
- assert not validate_parameter(
53
+ assert not is_valid_parameter(
54
54
  bytearray([BYTE_UNDEFINED, BYTE_UNDEFINED, BYTE_UNDEFINED, BYTE_UNDEFINED])
55
55
  )
56
56
 
@@ -120,14 +120,36 @@ async def test_number_validate(number: Number) -> None:
120
120
  async def test_number_set(number: Number, bypass_asyncio_sleep) -> None:
121
121
  """Test setting a number."""
122
122
  await number.set(5)
123
+ assert number.pending_update
123
124
  number.update(ParameterValues(value=5, min_value=0, max_value=5))
124
125
  assert number == 5
125
126
  assert not number.pending_update
126
- with patch("pyplumio.helpers.parameter.Parameter.pending_update", False):
127
+ with patch("pyplumio.helpers.parameter.Parameter.pending_update", False): # type: ignore [unreachable]
127
128
  assert await number.set(3)
128
129
  assert number == 3
129
130
 
130
131
 
132
+ async def test_number_set_with_no_retries(number: Number, bypass_asyncio_sleep) -> None:
133
+ """Test setting a number with no retries."""
134
+ with patch("asyncio.Queue.put") as mock_put:
135
+ assert await number.set(5, retries=0)
136
+
137
+ mock_put.assert_awaited_once_with(await number.create_request())
138
+ assert number == 5
139
+ assert not number.pending_update
140
+
141
+
142
+ async def test_number_set_optimistic(number: Number, bypass_asyncio_sleep) -> None:
143
+ """Test setting a number optimistically."""
144
+ number.description.optimistic = True
145
+ with patch("asyncio.Queue.put") as mock_put:
146
+ assert await number.set(5)
147
+
148
+ mock_put.assert_awaited_once_with(await number.create_request())
149
+ assert number == 5
150
+ assert not number.pending_update
151
+
152
+
131
153
  async def test_switch_validate(switch: Switch) -> None:
132
154
  """Test the switch validation."""
133
155
  assert switch.validate(STATE_ON)
@@ -139,15 +161,37 @@ async def test_switch_validate(switch: Switch) -> None:
139
161
  async def test_switch_set(switch: Switch, bypass_asyncio_sleep) -> None:
140
162
  """Test setting a number."""
141
163
  await switch.set(STATE_ON)
164
+ assert switch.pending_update
142
165
  switch.update(ParameterValues(value=1, min_value=0, max_value=1))
143
166
  assert switch == 1
144
167
  assert not switch.pending_update
145
- with patch("pyplumio.helpers.parameter.Parameter.pending_update", False):
168
+ with patch("pyplumio.helpers.parameter.Parameter.pending_update", False): # type: ignore [unreachable]
146
169
  assert await switch.set(STATE_OFF)
147
170
 
148
171
  assert switch == 0
149
172
 
150
173
 
174
+ async def test_switch_set_with_no_retries(switch: Switch, bypass_asyncio_sleep) -> None:
175
+ """Test setting a switch with no retries."""
176
+ with patch("asyncio.Queue.put") as mock_put:
177
+ assert await switch.set(STATE_ON, retries=0)
178
+
179
+ mock_put.assert_awaited_once_with(await switch.create_request())
180
+ assert switch == 1
181
+ assert not switch.pending_update
182
+
183
+
184
+ async def test_switch_set_optimistic(switch: Switch, bypass_asyncio_sleep) -> None:
185
+ """Test setting a switch optimistically."""
186
+ switch.description.optimistic = True
187
+ with patch("asyncio.Queue.put") as mock_put:
188
+ assert await switch.set(STATE_ON)
189
+
190
+ mock_put.assert_awaited_once_with(await switch.create_request())
191
+ assert switch == 1
192
+ assert not switch.pending_update
193
+
194
+
151
195
  async def test_number_set_nowait(number: Number):
152
196
  """Test setting a number without waiting for result."""
153
197
  number.set_nowait(5)
@@ -241,7 +285,7 @@ def test_number_repr(number: Number) -> None:
241
285
  """Test a number representation."""
242
286
  assert repr(number) == (
243
287
  "Number(device=EcoMAX, "
244
- "description=NumberDescription(name='test_number', "
288
+ "description=NumberDescription(name='test_number', optimistic=False, "
245
289
  "unit_of_measurement=<UnitOfMeasurement.CELSIUS: '°C'>), "
246
290
  "values=ParameterValues(value=1, min_value=0, max_value=5), "
247
291
  "index=0)"
@@ -252,7 +296,7 @@ def test_switch_repr(switch: Switch) -> None:
252
296
  """Test a number representation."""
253
297
  assert repr(switch) == (
254
298
  "Switch(device=EcoMAX, "
255
- "description=SwitchDescription(name='test_switch'), "
299
+ "description=SwitchDescription(name='test_switch', optimistic=False), "
256
300
  "values=ParameterValues(value=0, min_value=0, max_value=1), "
257
301
  "index=0)"
258
302
  )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes