PyPlumIO 0.6.8__tar.gz → 0.7.0__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 (161) hide show
  1. {pyplumio-0.6.8 → pyplumio-0.7.0}/.gitignore +6 -2
  2. {pyplumio-0.6.8 → pyplumio-0.7.0}/PKG-INFO +64 -36
  3. {pyplumio-0.6.8 → pyplumio-0.7.0}/README.md +54 -3
  4. {pyplumio-0.6.8 → pyplumio-0.7.0}/docs/source/callbacks.rst +7 -3
  5. {pyplumio-0.6.8 → pyplumio-0.7.0}/docs/source/connecting.rst +29 -38
  6. {pyplumio-0.6.8 → pyplumio-0.7.0}/docs/source/frames.rst +94 -0
  7. {pyplumio-0.6.8 → pyplumio-0.7.0}/docs/source/index.rst +4 -2
  8. {pyplumio-0.6.8 → pyplumio-0.7.0}/docs/source/mixers_thermostats.rst +2 -2
  9. {pyplumio-0.6.8 → pyplumio-0.7.0}/docs/source/reading.rst +2 -2
  10. {pyplumio-0.6.8 → pyplumio-0.7.0}/docs/source/schedules.rst +4 -4
  11. {pyplumio-0.6.8 → pyplumio-0.7.0}/docs/source/writing.rst +4 -4
  12. pyplumio-0.7.0/pyplumio/__init__.py +120 -0
  13. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/__main__.py +1 -1
  14. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/connection.py +15 -59
  15. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/const.py +6 -0
  16. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/data_types.py +1 -1
  17. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/devices/__init__.py +2 -2
  18. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/devices/ecomax.py +63 -15
  19. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/filters.py +19 -8
  20. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/frames/__init__.py +30 -28
  21. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/frames/messages.py +1 -1
  22. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/frames/requests.py +65 -35
  23. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/frames/responses.py +39 -10
  24. pyplumio-0.7.0/pyplumio/helpers/__init__.py +3 -0
  25. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/helpers/async_cache.py +2 -0
  26. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/helpers/event_manager.py +2 -2
  27. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/parameters/__init__.py +4 -4
  28. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/parameters/custom/ecomax_860d3_hb.py +2 -0
  29. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/parameters/ecomax.py +311 -3
  30. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/parameters/mixer.py +1 -3
  31. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/parameters/thermostat.py +3 -0
  32. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/protocol.py +1 -1
  33. pyplumio-0.7.0/pyplumio/py.typed +1 -0
  34. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/stream.py +3 -3
  35. pyplumio-0.7.0/pyplumio/structures/__init__.py +52 -0
  36. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/structures/alerts.py +10 -24
  37. pyplumio-0.7.0/pyplumio/structures/ecomax_extended_parameters.py +50 -0
  38. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/structures/ecomax_parameters.py +3 -20
  39. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/structures/frame_versions.py +4 -20
  40. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/structures/mixer_parameters.py +6 -21
  41. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/structures/network_info.py +25 -33
  42. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/structures/product_info.py +9 -20
  43. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/structures/program_version.py +25 -24
  44. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/structures/regulator_data.py +16 -25
  45. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/structures/regulator_data_schema.py +6 -23
  46. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/structures/schedules.py +8 -17
  47. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/structures/sensor_data.py +32 -57
  48. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/structures/thermostat_parameters.py +8 -22
  49. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/utils.py +2 -15
  50. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyproject.toml +56 -96
  51. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/conftest.py +10 -2
  52. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/devices/test_ecomax.py +250 -6
  53. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/devices/test_init.py +1 -1
  54. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/frames/test_init.py +11 -11
  55. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/frames/test_messages.py +6 -5
  56. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/frames/test_requests.py +34 -0
  57. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/frames/test_responses.py +11 -0
  58. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/helpers/test_uid.py +3 -3
  59. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/parameters/test_ecomax.py +38 -1
  60. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/parameters/test_init.py +41 -8
  61. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/structures/test_alerts.py +9 -8
  62. pyplumio-0.7.0/tests/structures/test_ecomax_extended_parameters.py +51 -0
  63. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/structures/test_ecomax_parameters.py +6 -3
  64. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/structures/test_frame_versions.py +6 -3
  65. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/structures/test_mixer_parameters.py +6 -3
  66. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/structures/test_product_info.py +3 -3
  67. pyplumio-0.7.0/tests/structures/test_program_version.py +45 -0
  68. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/test_connection.py +51 -75
  69. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/test_data_types.py +14 -14
  70. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/test_filters.py +104 -1
  71. pyplumio-0.7.0/tests/test_init.py +37 -0
  72. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/test_main.py +2 -4
  73. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/test_stream.py +7 -5
  74. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/test_utils.py +4 -17
  75. pyplumio-0.7.0/tests/testdata/requests/ecomax_extended_parameters.json +15 -0
  76. pyplumio-0.7.0/tests/testdata/requests/set_ecomax_extended_parameter.json +54 -0
  77. pyplumio-0.7.0/tests/testdata/responses/ecomax_extended_parameters.json +3036 -0
  78. pyplumio-0.6.8/.gitattributes +0 -2
  79. pyplumio-0.6.8/.github/CODE_OF_CONDUCT.md +0 -132
  80. pyplumio-0.6.8/.github/ISSUE_TEMPLATE/bug_report.yml +0 -82
  81. pyplumio-0.6.8/.github/ISSUE_TEMPLATE/feature_request.yml +0 -71
  82. pyplumio-0.6.8/.github/dependabot.yml +0 -7
  83. pyplumio-0.6.8/.github/workflows/ci.yml +0 -50
  84. pyplumio-0.6.8/.github/workflows/codeql.yml +0 -103
  85. pyplumio-0.6.8/.github/workflows/deploy.yml +0 -41
  86. pyplumio-0.6.8/.github/workflows/documentation.yml +0 -44
  87. pyplumio-0.6.8/.pre-commit-config.yaml +0 -18
  88. pyplumio-0.6.8/.qlty/qlty.toml +0 -99
  89. pyplumio-0.6.8/.vscode/settings.json +0 -16
  90. pyplumio-0.6.8/MANIFEST.in +0 -4
  91. pyplumio-0.6.8/PyPlumIO.egg-info/PKG-INFO +0 -132
  92. pyplumio-0.6.8/PyPlumIO.egg-info/SOURCES.txt +0 -148
  93. pyplumio-0.6.8/PyPlumIO.egg-info/dependency_links.txt +0 -1
  94. pyplumio-0.6.8/PyPlumIO.egg-info/requires.txt +0 -24
  95. pyplumio-0.6.8/PyPlumIO.egg-info/top_level.txt +0 -1
  96. pyplumio-0.6.8/images/ecomax.png +0 -0
  97. pyplumio-0.6.8/images/rs485.png +0 -0
  98. pyplumio-0.6.8/pyplumio/__init__.py +0 -117
  99. pyplumio-0.6.8/pyplumio/_version.py +0 -34
  100. pyplumio-0.6.8/pyplumio/helpers/__init__.py +0 -1
  101. pyplumio-0.6.8/pyplumio/py.typed +0 -0
  102. pyplumio-0.6.8/pyplumio/structures/__init__.py +0 -52
  103. pyplumio-0.6.8/requirements.txt +0 -1
  104. pyplumio-0.6.8/requirements_docs.txt +0 -3
  105. pyplumio-0.6.8/requirements_test.txt +0 -13
  106. pyplumio-0.6.8/setup.cfg +0 -4
  107. pyplumio-0.6.8/tests/test_init.py +0 -25
  108. {pyplumio-0.6.8 → pyplumio-0.7.0}/LICENSE +0 -0
  109. {pyplumio-0.6.8 → pyplumio-0.7.0}/docs/Makefile +0 -0
  110. {pyplumio-0.6.8 → pyplumio-0.7.0}/docs/make.bat +0 -0
  111. {pyplumio-0.6.8 → pyplumio-0.7.0}/docs/source/conf.py +0 -0
  112. {pyplumio-0.6.8 → pyplumio-0.7.0}/docs/source/protocol.rst +0 -0
  113. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/devices/ecoster.py +0 -0
  114. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/devices/mixer.py +0 -0
  115. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/devices/thermostat.py +0 -0
  116. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/exceptions.py +0 -0
  117. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/helpers/task_manager.py +0 -0
  118. {pyplumio-0.6.8 → pyplumio-0.7.0}/pyplumio/parameters/custom/__init__.py +0 -0
  119. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/__init__.py +0 -0
  120. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/devices/__init__.py +0 -0
  121. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/devices/test_ecoster.py +0 -0
  122. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/devices/test_mixer.py +0 -0
  123. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/devices/test_thermostat.py +0 -0
  124. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/helpers/__init__.py +0 -0
  125. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/helpers/test_async_cache.py +0 -0
  126. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/helpers/test_event_manager.py +0 -0
  127. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/helpers/test_task_manager.py +0 -0
  128. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/parameters/__init__.py +0 -0
  129. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/parameters/custom/__init__.py +0 -0
  130. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/parameters/custom/test_ecomax_860d3_hb.py +0 -0
  131. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/parameters/custom/test_init.py +0 -0
  132. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/parameters/test_mixers.py +0 -0
  133. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/parameters/test_thermostats.py +0 -0
  134. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/ruff.toml +0 -0
  135. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/structures/__init__.py +0 -0
  136. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/structures/test_schedules.py +0 -0
  137. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/test_protocol.py +0 -0
  138. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/messages/regulator_data.json +0 -0
  139. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/messages/sensor_data.json +0 -0
  140. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/parameters/ecomax_860d3_hb.json +0 -0
  141. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/requests/alerts.json +0 -0
  142. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/requests/ecomax_control.json +0 -0
  143. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/requests/ecomax_parameters.json +0 -0
  144. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/requests/mixer_parameters.json +0 -0
  145. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/requests/set_ecomax_parameter.json +0 -0
  146. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/requests/set_mixer_parameter.json +0 -0
  147. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/requests/set_schedule.json +0 -0
  148. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/requests/set_thermostat_parameter.json +0 -0
  149. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/requests/thermostat_parameters.json +0 -0
  150. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/responses/alerts.json +0 -0
  151. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/responses/device_available.json +0 -0
  152. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/responses/ecomax_parameters.json +0 -0
  153. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/responses/mixer_parameters.json +0 -0
  154. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/responses/password.json +0 -0
  155. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/responses/program_version.json +0 -0
  156. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/responses/regulator_data_schema.json +0 -0
  157. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/responses/schedules.json +0 -0
  158. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/responses/thermostat_parameters.json +0 -0
  159. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/responses/uid.json +0 -0
  160. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/unknown/unknown_ecomax_parameter.json +0 -0
  161. {pyplumio-0.6.8 → pyplumio-0.7.0}/tests/testdata/unknown/unknown_mixer_parameter.json +0 -0
@@ -113,5 +113,9 @@ dmypy.json
113
113
  # Pyre type checker
114
114
  .pyre/
115
115
 
116
- # setuptools-scm
117
- _version.py
116
+ # Complexipy
117
+ .complexipy_cache/
118
+
119
+ # QLTY
120
+ .qlty/results/
121
+ .qlty/out/
@@ -1,61 +1,39 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: PyPlumIO
3
- Version: 0.6.8
3
+ Version: 0.7.0
4
4
  Summary: PyPlumIO is a native ecoNET library for Plum ecoMAX controllers.
5
- Author-email: Denis Paavilainen <denpa@denpa.pro>
6
- License: MIT License
7
5
  Project-URL: Documentation, https://pyplumio.denpa.pro
8
6
  Project-URL: Source Code, https://github.com/denpamusic/PyPlumIO
9
7
  Project-URL: Bug Tracker, https://github.com/denpamusic/PyPlumIO/issues
10
- Keywords: home,automation,heating
11
- Platform: any
8
+ Author-email: Denis Paavilainen <denpa@denpa.pro>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: automation,heating,home
12
12
  Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Intended Audience :: Developers
14
- Classifier: License :: OSI Approved :: MIT License
15
14
  Classifier: Operating System :: OS Independent
16
- Classifier: Programming Language :: Python :: 3.10
17
15
  Classifier: Programming Language :: Python :: 3.11
18
16
  Classifier: Programming Language :: Python :: 3.12
19
17
  Classifier: Programming Language :: Python :: 3.13
20
18
  Classifier: Programming Language :: Python :: 3.14
21
- Classifier: Topic :: Software Development :: Libraries
22
19
  Classifier: Topic :: Home Automation
23
- Requires-Python: >=3.10
20
+ Classifier: Topic :: Software Development :: Libraries
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.11
23
+ Requires-Dist: serialx>=1.14.0
24
24
  Description-Content-Type: text/markdown
25
- License-File: LICENSE
26
- Requires-Dist: pyserial-asyncio-fast==0.16
27
- Provides-Extra: test
28
- Requires-Dist: codespell==2.4.1; extra == "test"
29
- Requires-Dist: coverage==7.13.0; extra == "test"
30
- Requires-Dist: freezegun==1.5.5; extra == "test"
31
- Requires-Dist: mypy==1.19.1; extra == "test"
32
- Requires-Dist: numpy<3.0.0,>=2.0.0; extra == "test"
33
- Requires-Dist: pyserial-asyncio-fast==0.16; extra == "test"
34
- Requires-Dist: pytest==9.0.2; extra == "test"
35
- Requires-Dist: pytest-asyncio==1.3.0; extra == "test"
36
- Requires-Dist: ruff==0.14.10; extra == "test"
37
- Requires-Dist: tox==4.32.0; extra == "test"
38
- Requires-Dist: types-pyserial==3.5.0.20251001; extra == "test"
39
- Provides-Extra: docs
40
- Requires-Dist: sphinx==8.1.3; extra == "docs"
41
- Requires-Dist: sphinx_rtd_theme==3.0.2; extra == "docs"
42
- Requires-Dist: readthedocs-sphinx-search==0.3.2; extra == "docs"
43
- Provides-Extra: dev
44
- Requires-Dist: pyplumio[docs,test]; extra == "dev"
45
- Requires-Dist: pre-commit==4.5.1; extra == "dev"
46
- Requires-Dist: tomli==2.3.0; extra == "dev"
47
- Dynamic: license-file
48
25
 
49
26
  # PyPlumIO is a native ecoNET library for Plum ecoMAX controllers
50
27
 
51
28
  [![PyPI version](https://badge.fury.io/py/PyPlumIO.svg)](https://badge.fury.io/py/PyPlumIO)
52
29
  [![PyPI Supported Python Versions](https://img.shields.io/pypi/pyversions/pyplumio.svg)](https://pypi.python.org/pypi/pyplumio/)
53
- [![PyPlumIO CI](https://github.com/denpamusic/PyPlumIO/actions/workflows/ci.yml/badge.svg)](https://github.com/denpamusic/PyPlumIO/actions/workflows/ci.yml)
54
30
  [![Maintainability](https://qlty.sh/gh/denpamusic/projects/PyPlumIO/maintainability.svg)](https://qlty.sh/gh/denpamusic/projects/PyPlumIO)
55
31
  [![Code Coverage](https://qlty.sh/gh/denpamusic/projects/PyPlumIO/coverage.svg)](https://qlty.sh/gh/denpamusic/projects/PyPlumIO)
56
32
  [![stability-release-candidate](https://img.shields.io/badge/stability-pre--release-48c9b0.svg)](https://guidelines.denpa.pro/stability#release-candidate)
57
33
  [![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)
58
34
 
35
+ ---
36
+
59
37
  ## Overview
60
38
 
61
39
  This package aims to provide complete and easy to use solution for
@@ -89,16 +67,18 @@ through network by using RS-485 to Ethernet/WiFi converter.
89
67
 
90
68
  ## Quickstart
91
69
 
92
- 1. To use PyPlumIO, first install it using pip:
70
+ 1. To use PyPlumIO, first install it using [uv](https://docs.astral.sh/uv/) or pip:
93
71
 
94
72
  ```bash
73
+ uv add pyplumio
74
+ # or
95
75
  pip install pyplumio
96
76
  ```
97
77
 
98
78
  2. Connect to the ecoMAX controller:
99
79
 
100
80
  ```python
101
- >>> connection = pyplumio.open_serial_connection("/dev/ttyUSB0")
81
+ >>> connection = pyplumio.serial_for_url("/dev/ttyUSB0")
102
82
  >>> await connection.connect()
103
83
  >>> ecomax = await connection.get("ecomax")
104
84
  ```
@@ -122,6 +102,54 @@ this package and depends on it. Click button below to check it out.
122
102
 
123
103
  [![Plum ecoMAX for Home Assistant](https://img.shields.io/badge/Plum%20ecoMAX%20for%20Home%20Assistant-41bdf5)](https://github.com/denpamusic/homeassistant-plum-ecomax)
124
104
 
105
+ ## Development
106
+
107
+ This project uses [uv](https://docs.astral.sh/uv/) for project and dependency management, and [prek](https://github.com/j178/prek) for Git hooks.
108
+
109
+ ### Setup
110
+
111
+ Install the project and development dependencies:
112
+
113
+ ```bash
114
+ uv sync
115
+ ```
116
+
117
+ ### Running Tests
118
+
119
+ Run the test suite with coverage enforcement (100% required):
120
+
121
+ ```bash
122
+ uv run pytest
123
+ ```
124
+
125
+ ### Code Quality & Formatting
126
+
127
+ Run linters, format checks, and cognitive complexity analysis:
128
+
129
+ ```bash
130
+ uv run codespell
131
+ uv run ruff check .
132
+ uv run ruff format --check .
133
+ uv run complexipy . --max-complexity-allowed 15
134
+ ```
135
+
136
+ ### Type Checking
137
+
138
+ Run type checking with mypy:
139
+
140
+ ```bash
141
+ uv run mypy pyplumio tests
142
+ ```
143
+
144
+ ### Git Hooks
145
+
146
+ Install and run Git hooks via prek:
147
+
148
+ ```bash
149
+ prek install
150
+ prek run --all-files
151
+ ```
152
+
125
153
  ## Attribution
126
154
 
127
155
  Special thanks to [econetanalyze](https://github.com/twkrol/econetanalyze)
@@ -2,12 +2,13 @@
2
2
 
3
3
  [![PyPI version](https://badge.fury.io/py/PyPlumIO.svg)](https://badge.fury.io/py/PyPlumIO)
4
4
  [![PyPI Supported Python Versions](https://img.shields.io/pypi/pyversions/pyplumio.svg)](https://pypi.python.org/pypi/pyplumio/)
5
- [![PyPlumIO CI](https://github.com/denpamusic/PyPlumIO/actions/workflows/ci.yml/badge.svg)](https://github.com/denpamusic/PyPlumIO/actions/workflows/ci.yml)
6
5
  [![Maintainability](https://qlty.sh/gh/denpamusic/projects/PyPlumIO/maintainability.svg)](https://qlty.sh/gh/denpamusic/projects/PyPlumIO)
7
6
  [![Code Coverage](https://qlty.sh/gh/denpamusic/projects/PyPlumIO/coverage.svg)](https://qlty.sh/gh/denpamusic/projects/PyPlumIO)
8
7
  [![stability-release-candidate](https://img.shields.io/badge/stability-pre--release-48c9b0.svg)](https://guidelines.denpa.pro/stability#release-candidate)
9
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)
10
9
 
10
+ ---
11
+
11
12
  ## Overview
12
13
 
13
14
  This package aims to provide complete and easy to use solution for
@@ -41,16 +42,18 @@ through network by using RS-485 to Ethernet/WiFi converter.
41
42
 
42
43
  ## Quickstart
43
44
 
44
- 1. To use PyPlumIO, first install it using pip:
45
+ 1. To use PyPlumIO, first install it using [uv](https://docs.astral.sh/uv/) or pip:
45
46
 
46
47
  ```bash
48
+ uv add pyplumio
49
+ # or
47
50
  pip install pyplumio
48
51
  ```
49
52
 
50
53
  2. Connect to the ecoMAX controller:
51
54
 
52
55
  ```python
53
- >>> connection = pyplumio.open_serial_connection("/dev/ttyUSB0")
56
+ >>> connection = pyplumio.serial_for_url("/dev/ttyUSB0")
54
57
  >>> await connection.connect()
55
58
  >>> ecomax = await connection.get("ecomax")
56
59
  ```
@@ -74,6 +77,54 @@ this package and depends on it. Click button below to check it out.
74
77
 
75
78
  [![Plum ecoMAX for Home Assistant](https://img.shields.io/badge/Plum%20ecoMAX%20for%20Home%20Assistant-41bdf5)](https://github.com/denpamusic/homeassistant-plum-ecomax)
76
79
 
80
+ ## Development
81
+
82
+ This project uses [uv](https://docs.astral.sh/uv/) for project and dependency management, and [prek](https://github.com/j178/prek) for Git hooks.
83
+
84
+ ### Setup
85
+
86
+ Install the project and development dependencies:
87
+
88
+ ```bash
89
+ uv sync
90
+ ```
91
+
92
+ ### Running Tests
93
+
94
+ Run the test suite with coverage enforcement (100% required):
95
+
96
+ ```bash
97
+ uv run pytest
98
+ ```
99
+
100
+ ### Code Quality & Formatting
101
+
102
+ Run linters, format checks, and cognitive complexity analysis:
103
+
104
+ ```bash
105
+ uv run codespell
106
+ uv run ruff check .
107
+ uv run ruff format --check .
108
+ uv run complexipy . --max-complexity-allowed 15
109
+ ```
110
+
111
+ ### Type Checking
112
+
113
+ Run type checking with mypy:
114
+
115
+ ```bash
116
+ uv run mypy pyplumio tests
117
+ ```
118
+
119
+ ### Git Hooks
120
+
121
+ Install and run Git hooks via prek:
122
+
123
+ ```bash
124
+ prek install
125
+ prek run --all-files
126
+ ```
127
+
77
128
  ## Attribution
78
129
 
79
130
  Special thanks to [econetanalyze](https://github.com/twkrol/econetanalyze)
@@ -96,6 +96,10 @@ value is changed.
96
96
  This filter await the callback on signifacant changes only.
97
97
  Significance is defined by ``tolerance`` argument (i. e. tolerance=0.1
98
98
  will only await callback when value is changed by more that 0.1).
99
+ For Number parameters a change of min or max value always awaits the
100
+ callback; tolerance applies to the value only.
101
+ The ``on_change``, ``debounce`` and ``delta`` filters also treat a min or
102
+ max change as a change, so ``delta`` may await the callback with 0.
99
103
 
100
104
  .. code-block:: python
101
105
 
@@ -177,7 +181,7 @@ processing.
177
181
 
178
182
  async def main():
179
183
  """Subscribes callback to the current heating temperature."""
180
- async with pyplumio.open_tcp_connection("localhost", 8899) as conn:
184
+ async with pyplumio.serial_for_url("socket://localhost:8899") as conn:
181
185
  async with conn.device("ecomax") as ecomax:
182
186
  # Await the callback on value change but no faster than
183
187
  # once per 5 seconds.
@@ -207,7 +211,7 @@ current heating temperature on every SensorDataMessage.
207
211
 
208
212
  async def main():
209
213
  """Subscribes callback to the current heating temperature."""
210
- async with pyplumio.open_tcp_connection("localhost", 8899) as conn:
214
+ async with pyplumio.serial_for_url("socket://localhost:8899") as conn:
211
215
  async with conn.device("ecomax") as ecomax:
212
216
  # Subscribe my_callback to heating_temp event.
213
217
  ecomax.subscribe("heating_temp", my_callback)
@@ -240,7 +244,7 @@ when heating temperature will change more the 0.1 degrees Celsius.
240
244
 
241
245
  async def main():
242
246
  """Subscribes callback to the current heating temperature."""
243
- async with pyplumio.open_tcp_connection("localhost", 8899) as conn:
247
+ async with pyplumio.serial_for_url("socket://localhost:8899") as conn:
244
248
  async with conn.device("ecomax") as ecomax:
245
249
  # Subscribe my_callback to heating_target_temp event.
246
250
  ecomax.subscribe_once("heating_target_temp", my_callback)
@@ -4,41 +4,30 @@ Connecting
4
4
  Opening the connection
5
5
  ----------------------
6
6
 
7
- .. autofunction:: pyplumio.open_tcp_connection
7
+ .. autofunction:: pyplumio.serial_for_url
8
8
 
9
- With this you can connect to the controller remotely via RS-485 to
10
- Ethernet/WiFi converters, which are readily available online or
11
- can be custom built using wired connection and ser2net software.
9
+ You can connect to the ecoMAX controller via physical connection through
10
+ RS-485 to USB or RS-485 to TTL adapters, that are connected directly
11
+ to the device running PyPlumIO or wirelessly through tcp socket.
12
+
13
+ You MUST not connect RS-485 lines directly to the UART outputs of your
14
+ PC or you'll risk damaging your PC/controller or the ecoMAX controller itself.
12
15
 
13
16
  .. code-block:: python
14
17
 
15
18
  import pyplumio
16
19
 
17
- async with pyplumio.open_tcp_connection("localhost", port=8899) as conn:
20
+ async with pyplumio.serial_for_url("/dev/ttyUSB0") as conn:
21
+ ...
22
+
23
+ async with pyplumio.serial_for_url("socket://localhost:8899") as conn:
18
24
  ...
19
-
20
25
 
21
26
  .. note::
22
27
 
23
28
  Although **async with** syntax is preferred, you can initiate connection without it.
24
29
  See following :ref:`examples<Connection Examples>` for more information.
25
30
 
26
- .. autofunction:: pyplumio.open_serial_connection
27
-
28
- You can connect to the ecoMAX controller via wired connection through
29
- RS-485 to USB or RS-485 to TTL adapters, that are connected directly
30
- to the device running PyPlumIO.
31
-
32
- You MUST not connect RS-485 lines directly to the UART outputs of your
33
- PC or you'll risk damaging your PC/controller or the ecoMAX controller itself.
34
-
35
- .. code-block:: python
36
-
37
- import pyplumio
38
-
39
- async with pyplumio.open_serial_connection("/dev/ttyUSB0", baudrate=115200) as conn:
40
- ...
41
-
42
31
  Protocols
43
32
  ---------
44
33
 
@@ -62,14 +51,12 @@ working with device classes and queues.
62
51
  import asyncio
63
52
 
64
53
  import pyplumio
65
- from pyplumio.const import DeviceType
66
- from pyplumio.frames import requests, responses
54
+ from pyplumio import DeviceType, DummyProtocol, requests, responses
67
55
 
68
56
 
69
57
  async def main():
70
58
  """Open a connection and request alerts."""
71
- async with pyplumio.open_tcp_connection(
72
- host="localhost", port=8899, protocol=pyplumio.DummyProtocol()
59
+ async with pyplumio.serial_for_url("socket://localhost:8899", protocol=DummyProtocol()
73
60
  ) as conn:
74
61
  await conn.writer.write(
75
62
  requests.AlertsRequest(recipient=DeviceType.ECOMAX, start=0, count=5)
@@ -116,21 +103,25 @@ In the example below, we'll set both ethernet and wireless parameters.
116
103
  .. code-block:: python
117
104
 
118
105
  import pyplumio
119
- from pyplumio.const import EncryptionType
106
+ from pyplumio import (
107
+ AsyncProtocol,
108
+ EthernetParameters,
109
+ WirelessParameters,
110
+ EncryptionType
111
+ )
120
112
 
121
113
 
122
114
  async def main():
123
115
  """Initialize a connection with network parameters."""
124
- async with pyplumio.open_tcp_connection(
125
- host="localhost",
126
- port=8899,
127
- protocol=pyplumio.AsyncProtocol(
128
- ethernet_parameters=pyplumio.EthernetParameters(
116
+ async with pyplumio.serial_for_url(
117
+ "socket://localhost:8899",
118
+ protocol=AsyncProtocol(
119
+ ethernet_parameters=EthernetParameters(
129
120
  ip="10.10.1.100",
130
121
  netmask="255.255.255.0",
131
122
  gateway="10.10.1.1",
132
123
  ),
133
- wireless_parameters=pyplumio.WirelessParameters(
124
+ wireless_parameters=WirelessParameters(
134
125
  ip="10.10.2.100",
135
126
  netmask="255.255.255.0",
136
127
  gateway="10.10.2.1",
@@ -176,7 +167,7 @@ connection.
176
167
 
177
168
  async def main():
178
169
  """Print connection statistics."""
179
- async with pyplumio.open_tcp_connection("localhost", 8899) as conn:
170
+ async with pyplumio.serial_for_url("socket://localhost:8899") as conn:
180
171
  print(conn.statistics)
181
172
  ...
182
173
 
@@ -199,12 +190,12 @@ context manager.
199
190
 
200
191
  async def main():
201
192
  """Opens the connection and gets the ecoMAX device."""
202
- async with pyplumio.open_tcp_connection("localhost", port=8899) as conn:
193
+ async with pyplumio.serial_for_url("socket://localhost:8899") as conn:
203
194
  try:
204
195
  # Get the ecoMAX device within 10 seconds or timeout.
205
196
  async with conn.device("ecomax", timeout=10) as ecomax:
206
197
  ...
207
- except asyncio.TimeoutError:
198
+ except TimeoutError:
208
199
  # If device times out, log the error.
209
200
  _LOGGER.error("Failed to get the device within 10 seconds")
210
201
 
@@ -228,7 +219,7 @@ using Python's context manager.
228
219
 
229
220
  async def main():
230
221
  """Opens the connection and gets the ecoMAX device."""
231
- connection = pyplumio.open_tcp_connection("localhost", port=8899)
222
+ connection = pyplumio.serial_for_url("socket://localhost:8899")
232
223
 
233
224
  # Connect to the device.
234
225
  await connection.connect()
@@ -237,7 +228,7 @@ using Python's context manager.
237
228
  # Get the ecoMAX device within 10 seconds or timeout.
238
229
  async with conn.device("ecomax", timeout=10) as ecomax:
239
230
  ...
240
- except asyncio.TimeoutError:
231
+ except TimeoutError:
241
232
  # If device times out, log the error.
242
233
  _LOGGER.error("Failed to get the device within 10 seconds")
243
234
 
@@ -99,6 +99,60 @@ Handler
99
99
 
100
100
  .. autoclass:: pyplumio.structures.ecomax_parameters.EcomaxParametersStructure
101
101
 
102
+ EcomaxExtendedParameters
103
+ ------------------------
104
+
105
+ Get the ecoMAX controller editable parameters from the extended parameter
106
+ table. Newer controllers, such as ecoMAX 860P4-NM TOUCH, keep part of their
107
+ settings only in this table.
108
+
109
+ PyPlumIO requests this table only if the controller announces frame type 97
110
+ in the frame version table, and requests it again each time its version
111
+ changes.
112
+
113
+ Parameters are represented in the same way as in :ref:`ecomaxparameters`.
114
+ Raw parameters are available under the ``ecomax_extended_parameters`` key.
115
+ Parameters with known meaning are also available by name for verified
116
+ products only.
117
+
118
+ Request
119
+ ^^^^^^^
120
+
121
+ .. autoattribute:: pyplumio.const.FrameType.REQUEST_ECOMAX_EXTENDED_PARAMETERS
122
+
123
+ .. list-table::
124
+ :widths: 20 65 10
125
+ :header-rows: 1
126
+
127
+ * - Name
128
+ - Description
129
+ - Default
130
+ * - start
131
+ - Parameter index to start on.
132
+ - 0
133
+ * - count
134
+ - Number of parameters to request.
135
+ - 255
136
+
137
+ Response
138
+ ^^^^^^^^
139
+
140
+ .. autoattribute:: pyplumio.const.FrameType.RESPONSE_ECOMAX_EXTENDED_PARAMETERS
141
+
142
+ .. list-table::
143
+ :widths: 50 50
144
+ :header-rows: 1
145
+
146
+ * - Name
147
+ - Description
148
+ * - ecomax_extended_parameters
149
+ - Contains a list of ecomax extended parameters.
150
+
151
+ Handler
152
+ ^^^^^^^
153
+
154
+ .. autoclass:: pyplumio.structures.ecomax_extended_parameters.EcomaxExtendedParametersStructure
155
+
102
156
  MixerParameters
103
157
  ---------------
104
158
 
@@ -174,6 +228,46 @@ Response
174
228
 
175
229
  .. autoattribute:: pyplumio.const.FrameType.RESPONSE_SET_ECOMAX_PARAMETER
176
230
 
231
+ SetEcomaxExtendedParameter
232
+ --------------------------
233
+
234
+ Set the editable parameter value in the extended parameter table.
235
+
236
+ The message is the number of values, the index of the first parameter and
237
+ the new raw values for consecutive indexes. In a panel capture, a single
238
+ write was ``01 <index> <value>`` and one frame ``05 3b 47 16 1e 0a 46`` set
239
+ indexes 0x3b to 0x3f to 71, 22, 30, 10 and 70. PyPlumIO always sets one
240
+ parameter per frame.
241
+
242
+ In captures, after every panel write the controller increased the frame
243
+ type 97 version in the regulator data frame version table within 1-3 seconds
244
+ (6, 7, 8, 9, 10, 11 for five writes), so PyPlumIO re-reads the extended
245
+ parameter table and confirms the set from it.
246
+
247
+ Request
248
+ ^^^^^^^
249
+
250
+ .. autoattribute:: pyplumio.const.FrameType.REQUEST_SET_ECOMAX_EXTENDED_PARAMETER
251
+
252
+ .. list-table::
253
+ :widths: 20 65 10
254
+ :header-rows: 1
255
+
256
+ * - Name
257
+ - Description
258
+ - Default
259
+ * - index
260
+ - Parameter index.
261
+ -
262
+ * - value
263
+ - Value to set parameter to.
264
+ -
265
+
266
+ Response
267
+ ^^^^^^^^
268
+
269
+ .. autoattribute:: pyplumio.const.FrameType.RESPONSE_SET_ECOMAX_EXTENDED_PARAMETER
270
+
177
271
  SetMixerParameter
178
272
  -----------------
179
273
 
@@ -19,15 +19,17 @@ RS-485 to Ethernet/WiFi converter.
19
19
 
20
20
  Quickstart
21
21
  ==========
22
- 1. To use PyPlumIO, first install it using pip:
22
+ 1. To use PyPlumIO, first install it using uv or pip:
23
23
 
24
24
  .. code-block:: console
25
25
 
26
+ $ uv add pyplumio
27
+ # or
26
28
  (.venv) $ pip install pyplumio
27
29
 
28
30
  2. Connect to the ecoMAX controller:
29
31
 
30
- >>> connection = pyplumio.open_serial_connection("/dev/ttyUSB0")
32
+ >>> connection = pyplumio.serial_for_url("/dev/ttyUSB0")
31
33
  >>> await connection.connect()
32
34
  >>> ecomax = await connection.get("ecomax")
33
35
 
@@ -32,7 +32,7 @@ get it's current_temp property and set it's target temperature to
32
32
 
33
33
  .. code-block:: python
34
34
 
35
- from pyplumio.devices import Mixer
35
+ from pyplumio import Mixer
36
36
 
37
37
  async with conn.device("ecomax") as ecomax:
38
38
  # Get connected mixers.
@@ -56,7 +56,7 @@ degrees Celsius.
56
56
 
57
57
  .. code-block:: python
58
58
 
59
- from pyplumio.device import Thermostat
59
+ from pyplumio import Thermostat
60
60
 
61
61
  async with conn.device("ecomax") as ecomax:
62
62
  # Get connected thermostats.
@@ -34,7 +34,7 @@ and close the connection.
34
34
 
35
35
  async def main():
36
36
  """Read the current heating temperature."""
37
- async with pyplumio.open_tcp_connection("localhost", 8899) as conn:
37
+ async with pyplumio.serial_for_url("socket://localhost:8899") as conn:
38
38
  # Get the ecoMAX device.
39
39
  async with conn.device("ecomax") as ecomax:
40
40
  # Get the heating temperature.
@@ -119,7 +119,7 @@ to the terminal.
119
119
  """Read current temp, target temp and device state from
120
120
  the regdata.
121
121
  """
122
- async with pyplumio.open_tcp_connection("localhost", 8899) as conn:
122
+ async with pyplumio.serial_for_url("socket://localhost:8899") as conn:
123
123
  # Get the ecoMAX device.
124
124
  async with conn.device("ecomax") as ecomax:
125
125
  # Get heating temperature, if it's available otherwise
@@ -60,11 +60,11 @@ switches back to daytime mode from 07:00 to 00:00.
60
60
  await heating_schedule.commit()
61
61
 
62
62
  For clarity sake, you might want to use ``STATE_OFF`` and
63
- ``STATE_ON`` constants from ``pyplumio.const`` module.
63
+ ``STATE_ON`` constants.
64
64
 
65
65
  .. code-block:: python
66
66
 
67
- from pyplumio.const import STATE_OFF
67
+ from pyplumio import STATE_OFF
68
68
 
69
69
  heating_schedule.monday["18:00"] = STATE_OFF
70
70
  heating_schedule.monday.set_state(STATE_OFF, "00:00", "07:00")
@@ -110,12 +110,12 @@ Schedule Examples
110
110
  .. code-block:: python
111
111
 
112
112
  import pyplumio
113
- from pyplumio.const import STATE_ON, STATE_OFF
113
+ from pyplumio import STATE_ON, STATE_OFF
114
114
 
115
115
 
116
116
  async def main():
117
117
  """Set a device schedule."""
118
- async with pyplumio.open_tcp_connection("localhost", 8899) as conn:
118
+ async with pyplumio.serial_for_url("socket://localhost:8899") as conn:
119
119
  async with conn.device("ecomax") as ecomax:
120
120
  schedules = await ecomax.get("schedules")
121
121
  heating_schedule = schedules["heating"]
@@ -45,7 +45,7 @@ Numbers are parameters that have numerical value associated with them.
45
45
 
46
46
  .. code-block:: python
47
47
 
48
- from pyplumio.parameters import Number
48
+ from pyplumio import Number
49
49
 
50
50
  async with conn.device("ecomax") as ecomax:
51
51
  heating_target: Number = ecomax.get("heating_target_temp")
@@ -61,7 +61,7 @@ Please note, that both values are **inclusive**.
61
61
 
62
62
  .. code-block:: python
63
63
 
64
- from pyplumio.parameters import Number
64
+ from pyplumio import Number
65
65
 
66
66
  async with conn.device("ecomax") as ecomax:
67
67
  target_temp: Number = await ecomax.get("heating_target_temp")
@@ -95,7 +95,7 @@ object.
95
95
 
96
96
  .. code-block:: python
97
97
 
98
- from pyplumio.parameters import Switch
98
+ from pyplumio import Switch
99
99
 
100
100
  # Get an ecomax_control parameter and turn it on.
101
101
  ecomax_control: Switch = await ecomax.get("ecomax_control")
@@ -127,7 +127,7 @@ outputting the result to the terminal.
127
127
 
128
128
  async def main():
129
129
  """Turn on the controller and set target temperature."""
130
- async with pyplumio.open_tcp_connection("localhost", 8899) as conn:
130
+ async with pyplumio.serial_for_url("socket://localhost:8899") as conn:
131
131
  # Get the ecoMAX device.
132
132
  async with conn.device("ecomax") as ecomax:
133
133
  # Turn on controller without waiting for the result.