PyPlumIO 0.6.0__tar.gz → 0.6.1__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 (172) hide show
  1. {pyplumio-0.6.0 → pyplumio-0.6.1}/PKG-INFO +6 -7
  2. {pyplumio-0.6.0 → pyplumio-0.6.1}/PyPlumIO.egg-info/PKG-INFO +6 -7
  3. {pyplumio-0.6.0 → pyplumio-0.6.1}/PyPlumIO.egg-info/SOURCES.txt +0 -1
  4. {pyplumio-0.6.0 → pyplumio-0.6.1}/PyPlumIO.egg-info/requires.txt +5 -5
  5. {pyplumio-0.6.0 → pyplumio-0.6.1}/README.md +0 -1
  6. {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/connecting.rst +37 -0
  7. {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/index.rst +0 -1
  8. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/_version.py +3 -3
  9. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/connection.py +0 -36
  10. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/protocol.py +50 -40
  11. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyproject.toml +5 -5
  12. {pyplumio-0.6.0 → pyplumio-0.6.1}/requirements_test.txt +5 -5
  13. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/test_connection.py +0 -15
  14. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/test_protocol.py +14 -15
  15. pyplumio-0.6.0/docs/source/statistics.rst +0 -47
  16. {pyplumio-0.6.0 → pyplumio-0.6.1}/.gitattributes +0 -0
  17. {pyplumio-0.6.0 → pyplumio-0.6.1}/.github/CODE_OF_CONDUCT.md +0 -0
  18. {pyplumio-0.6.0 → pyplumio-0.6.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  19. {pyplumio-0.6.0 → pyplumio-0.6.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  20. {pyplumio-0.6.0 → pyplumio-0.6.1}/.github/dependabot.yml +0 -0
  21. {pyplumio-0.6.0 → pyplumio-0.6.1}/.github/workflows/ci.yml +0 -0
  22. {pyplumio-0.6.0 → pyplumio-0.6.1}/.github/workflows/codeql.yml +0 -0
  23. {pyplumio-0.6.0 → pyplumio-0.6.1}/.github/workflows/deploy.yml +0 -0
  24. {pyplumio-0.6.0 → pyplumio-0.6.1}/.github/workflows/documentation.yml +0 -0
  25. {pyplumio-0.6.0 → pyplumio-0.6.1}/.gitignore +0 -0
  26. {pyplumio-0.6.0 → pyplumio-0.6.1}/.pre-commit-config.yaml +0 -0
  27. {pyplumio-0.6.0 → pyplumio-0.6.1}/.qlty/qlty.toml +0 -0
  28. {pyplumio-0.6.0 → pyplumio-0.6.1}/.vscode/settings.json +0 -0
  29. {pyplumio-0.6.0 → pyplumio-0.6.1}/LICENSE +0 -0
  30. {pyplumio-0.6.0 → pyplumio-0.6.1}/MANIFEST.in +0 -0
  31. {pyplumio-0.6.0 → pyplumio-0.6.1}/PyPlumIO.egg-info/dependency_links.txt +0 -0
  32. {pyplumio-0.6.0 → pyplumio-0.6.1}/PyPlumIO.egg-info/top_level.txt +0 -0
  33. {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/Makefile +0 -0
  34. {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/make.bat +0 -0
  35. {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/callbacks.rst +0 -0
  36. {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/conf.py +0 -0
  37. {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/frames.rst +0 -0
  38. {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/mixers_thermostats.rst +0 -0
  39. {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/protocol.rst +0 -0
  40. {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/reading.rst +0 -0
  41. {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/schedules.rst +0 -0
  42. {pyplumio-0.6.0 → pyplumio-0.6.1}/docs/source/writing.rst +0 -0
  43. {pyplumio-0.6.0 → pyplumio-0.6.1}/images/ecomax.png +0 -0
  44. {pyplumio-0.6.0 → pyplumio-0.6.1}/images/rs485.png +0 -0
  45. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/__init__.py +0 -0
  46. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/__main__.py +0 -0
  47. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/const.py +0 -0
  48. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/data_types.py +0 -0
  49. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/devices/__init__.py +0 -0
  50. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/devices/ecomax.py +0 -0
  51. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/devices/ecoster.py +0 -0
  52. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/devices/mixer.py +0 -0
  53. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/devices/thermostat.py +0 -0
  54. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/exceptions.py +0 -0
  55. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/filters.py +0 -0
  56. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/frames/__init__.py +0 -0
  57. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/frames/messages.py +0 -0
  58. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/frames/requests.py +0 -0
  59. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/frames/responses.py +0 -0
  60. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/helpers/__init__.py +0 -0
  61. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/helpers/async_cache.py +0 -0
  62. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/helpers/event_manager.py +0 -0
  63. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/helpers/factory.py +0 -0
  64. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/helpers/task_manager.py +0 -0
  65. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/parameters/__init__.py +0 -0
  66. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/parameters/custom/__init__.py +0 -0
  67. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/parameters/custom/ecomax_860d3_hb.py +0 -0
  68. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/parameters/ecomax.py +0 -0
  69. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/parameters/mixer.py +0 -0
  70. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/parameters/thermostat.py +0 -0
  71. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/py.typed +0 -0
  72. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/stream.py +0 -0
  73. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/__init__.py +0 -0
  74. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/alerts.py +0 -0
  75. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/boiler_load.py +0 -0
  76. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/boiler_power.py +0 -0
  77. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/ecomax_parameters.py +0 -0
  78. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/fan_power.py +0 -0
  79. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/frame_versions.py +0 -0
  80. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/fuel_consumption.py +0 -0
  81. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/fuel_level.py +0 -0
  82. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/lambda_sensor.py +0 -0
  83. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/mixer_parameters.py +0 -0
  84. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/mixer_sensors.py +0 -0
  85. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/modules.py +0 -0
  86. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/network_info.py +0 -0
  87. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/output_flags.py +0 -0
  88. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/outputs.py +0 -0
  89. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/pending_alerts.py +0 -0
  90. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/product_info.py +0 -0
  91. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/program_version.py +0 -0
  92. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/regulator_data.py +0 -0
  93. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/regulator_data_schema.py +0 -0
  94. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/schedules.py +0 -0
  95. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/statuses.py +0 -0
  96. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/temperatures.py +0 -0
  97. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/thermostat_parameters.py +0 -0
  98. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/structures/thermostat_sensors.py +0 -0
  99. {pyplumio-0.6.0 → pyplumio-0.6.1}/pyplumio/utils.py +0 -0
  100. {pyplumio-0.6.0 → pyplumio-0.6.1}/requirements.txt +0 -0
  101. {pyplumio-0.6.0 → pyplumio-0.6.1}/requirements_docs.txt +0 -0
  102. {pyplumio-0.6.0 → pyplumio-0.6.1}/setup.cfg +0 -0
  103. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/__init__.py +0 -0
  104. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/conftest.py +0 -0
  105. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/devices/__init__.py +0 -0
  106. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/devices/test_ecomax.py +0 -0
  107. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/devices/test_ecoster.py +0 -0
  108. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/devices/test_init.py +0 -0
  109. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/devices/test_mixer.py +0 -0
  110. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/devices/test_thermostat.py +0 -0
  111. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/frames/test_init.py +0 -0
  112. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/frames/test_messages.py +0 -0
  113. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/frames/test_requests.py +0 -0
  114. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/frames/test_responses.py +0 -0
  115. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/helpers/__init__.py +0 -0
  116. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/helpers/test_async_cache.py +0 -0
  117. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/helpers/test_event_manager.py +0 -0
  118. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/helpers/test_factory.py +0 -0
  119. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/helpers/test_task_manager.py +0 -0
  120. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/helpers/test_uid.py +0 -0
  121. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/parameters/__init__.py +0 -0
  122. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/parameters/custom/__init__.py +0 -0
  123. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/parameters/custom/test_ecomax_860d3_hb.py +0 -0
  124. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/parameters/custom/test_init.py +0 -0
  125. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/parameters/test_ecomax.py +0 -0
  126. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/parameters/test_init.py +0 -0
  127. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/parameters/test_mixers.py +0 -0
  128. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/parameters/test_thermostats.py +0 -0
  129. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/ruff.toml +0 -0
  130. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/__init__.py +0 -0
  131. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_alerts.py +0 -0
  132. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_boiler_load.py +0 -0
  133. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_boiler_power.py +0 -0
  134. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_ecomax_parameters.py +0 -0
  135. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_fan_power.py +0 -0
  136. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_frame_versions.py +0 -0
  137. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_fuel_consumption.py +0 -0
  138. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_fuel_level.py +0 -0
  139. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_lambda_sensor.py +0 -0
  140. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_mixer_parameters.py +0 -0
  141. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_product_info.py +0 -0
  142. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/structures/test_schedules.py +0 -0
  143. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/test_data_types.py +0 -0
  144. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/test_filters.py +0 -0
  145. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/test_init.py +0 -0
  146. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/test_main.py +0 -0
  147. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/test_stream.py +0 -0
  148. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/test_utils.py +0 -0
  149. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/messages/regulator_data.json +0 -0
  150. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/messages/sensor_data.json +0 -0
  151. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/parameters/ecomax_860d3_hb.json +0 -0
  152. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/alerts.json +0 -0
  153. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/ecomax_control.json +0 -0
  154. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/ecomax_parameters.json +0 -0
  155. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/mixer_parameters.json +0 -0
  156. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/set_ecomax_parameter.json +0 -0
  157. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/set_mixer_parameter.json +0 -0
  158. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/set_schedule.json +0 -0
  159. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/set_thermostat_parameter.json +0 -0
  160. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/requests/thermostat_parameters.json +0 -0
  161. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/alerts.json +0 -0
  162. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/device_available.json +0 -0
  163. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/ecomax_parameters.json +0 -0
  164. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/mixer_parameters.json +0 -0
  165. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/password.json +0 -0
  166. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/program_version.json +0 -0
  167. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/regulator_data_schema.json +0 -0
  168. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/schedules.json +0 -0
  169. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/thermostat_parameters.json +0 -0
  170. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/responses/uid.json +0 -0
  171. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/unknown/unknown_ecomax_parameter.json +0 -0
  172. {pyplumio-0.6.0 → pyplumio-0.6.1}/tests/testdata/unknown/unknown_mixer_parameter.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPlumIO
3
- Version: 0.6.0
3
+ Version: 0.6.1
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
@@ -25,16 +25,16 @@ License-File: LICENSE
25
25
  Requires-Dist: pyserial-asyncio==0.6
26
26
  Provides-Extra: test
27
27
  Requires-Dist: codespell==2.4.1; extra == "test"
28
- Requires-Dist: coverage==7.10.6; extra == "test"
28
+ Requires-Dist: coverage==7.10.7; extra == "test"
29
29
  Requires-Dist: freezegun==1.5.5; extra == "test"
30
- Requires-Dist: mypy==1.18.1; extra == "test"
30
+ Requires-Dist: mypy==1.18.2; extra == "test"
31
31
  Requires-Dist: numpy<3.0.0,>=2.0.0; extra == "test"
32
32
  Requires-Dist: pyserial-asyncio-fast==0.16; extra == "test"
33
33
  Requires-Dist: pytest==8.4.2; extra == "test"
34
- Requires-Dist: pytest-asyncio==1.1.0; extra == "test"
35
- Requires-Dist: ruff==0.13.0; extra == "test"
34
+ Requires-Dist: pytest-asyncio==1.2.0; extra == "test"
35
+ Requires-Dist: ruff==0.13.2; extra == "test"
36
36
  Requires-Dist: tox==4.30.2; extra == "test"
37
- Requires-Dist: types-pyserial==3.5.0.20250822; extra == "test"
37
+ Requires-Dist: types-pyserial==3.5.0.20250919; extra == "test"
38
38
  Provides-Extra: docs
39
39
  Requires-Dist: sphinx==8.1.3; extra == "docs"
40
40
  Requires-Dist: sphinx_rtd_theme==3.0.2; extra == "docs"
@@ -79,7 +79,6 @@ through network by using RS-485 to Ethernet/WiFi converter.
79
79
  - [Callbacks](https://pyplumio.denpa.pro/callbacks.html)
80
80
  - [Mixers/Thermostats](https://pyplumio.denpa.pro/mixers_thermostats.html)
81
81
  - [Schedules](https://pyplumio.denpa.pro/schedules.html)
82
- - [Statistics](https://pyplumio.denpa.pro/statistics.html)
83
82
  - [Protocol](https://pyplumio.denpa.pro/protocol.html)
84
83
  - [Frame Structure](https://pyplumio.denpa.pro/protocol.html#frame-structure)
85
84
  - [Requests and Responses](https://pyplumio.denpa.pro/protocol.html#requests-and-responses)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyPlumIO
3
- Version: 0.6.0
3
+ Version: 0.6.1
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
@@ -25,16 +25,16 @@ License-File: LICENSE
25
25
  Requires-Dist: pyserial-asyncio==0.6
26
26
  Provides-Extra: test
27
27
  Requires-Dist: codespell==2.4.1; extra == "test"
28
- Requires-Dist: coverage==7.10.6; extra == "test"
28
+ Requires-Dist: coverage==7.10.7; extra == "test"
29
29
  Requires-Dist: freezegun==1.5.5; extra == "test"
30
- Requires-Dist: mypy==1.18.1; extra == "test"
30
+ Requires-Dist: mypy==1.18.2; extra == "test"
31
31
  Requires-Dist: numpy<3.0.0,>=2.0.0; extra == "test"
32
32
  Requires-Dist: pyserial-asyncio-fast==0.16; extra == "test"
33
33
  Requires-Dist: pytest==8.4.2; extra == "test"
34
- Requires-Dist: pytest-asyncio==1.1.0; extra == "test"
35
- Requires-Dist: ruff==0.13.0; extra == "test"
34
+ Requires-Dist: pytest-asyncio==1.2.0; extra == "test"
35
+ Requires-Dist: ruff==0.13.2; extra == "test"
36
36
  Requires-Dist: tox==4.30.2; extra == "test"
37
- Requires-Dist: types-pyserial==3.5.0.20250822; extra == "test"
37
+ Requires-Dist: types-pyserial==3.5.0.20250919; extra == "test"
38
38
  Provides-Extra: docs
39
39
  Requires-Dist: sphinx==8.1.3; extra == "docs"
40
40
  Requires-Dist: sphinx_rtd_theme==3.0.2; extra == "docs"
@@ -79,7 +79,6 @@ through network by using RS-485 to Ethernet/WiFi converter.
79
79
  - [Callbacks](https://pyplumio.denpa.pro/callbacks.html)
80
80
  - [Mixers/Thermostats](https://pyplumio.denpa.pro/mixers_thermostats.html)
81
81
  - [Schedules](https://pyplumio.denpa.pro/schedules.html)
82
- - [Statistics](https://pyplumio.denpa.pro/statistics.html)
83
82
  - [Protocol](https://pyplumio.denpa.pro/protocol.html)
84
83
  - [Frame Structure](https://pyplumio.denpa.pro/protocol.html#frame-structure)
85
84
  - [Requests and Responses](https://pyplumio.denpa.pro/protocol.html#requests-and-responses)
@@ -34,7 +34,6 @@ docs/source/mixers_thermostats.rst
34
34
  docs/source/protocol.rst
35
35
  docs/source/reading.rst
36
36
  docs/source/schedules.rst
37
- docs/source/statistics.rst
38
37
  docs/source/writing.rst
39
38
  images/ecomax.png
40
39
  images/rs485.png
@@ -12,13 +12,13 @@ readthedocs-sphinx-search==0.3.2
12
12
 
13
13
  [test]
14
14
  codespell==2.4.1
15
- coverage==7.10.6
15
+ coverage==7.10.7
16
16
  freezegun==1.5.5
17
- mypy==1.18.1
17
+ mypy==1.18.2
18
18
  numpy<3.0.0,>=2.0.0
19
19
  pyserial-asyncio-fast==0.16
20
20
  pytest==8.4.2
21
- pytest-asyncio==1.1.0
22
- ruff==0.13.0
21
+ pytest-asyncio==1.2.0
22
+ ruff==0.13.2
23
23
  tox==4.30.2
24
- types-pyserial==3.5.0.20250822
24
+ types-pyserial==3.5.0.20250919
@@ -32,7 +32,6 @@ through network by using RS-485 to Ethernet/WiFi converter.
32
32
  - [Callbacks](https://pyplumio.denpa.pro/callbacks.html)
33
33
  - [Mixers/Thermostats](https://pyplumio.denpa.pro/mixers_thermostats.html)
34
34
  - [Schedules](https://pyplumio.denpa.pro/schedules.html)
35
- - [Statistics](https://pyplumio.denpa.pro/statistics.html)
36
35
  - [Protocol](https://pyplumio.denpa.pro/protocol.html)
37
36
  - [Frame Structure](https://pyplumio.denpa.pro/protocol.html#frame-structure)
38
37
  - [Requests and Responses](https://pyplumio.denpa.pro/protocol.html#requests-and-responses)
@@ -142,6 +142,43 @@ In the example below, we'll set both ethernet and wireless parameters.
142
142
  ) as conn:
143
143
  ...
144
144
 
145
+ Statistics
146
+ ----------
147
+
148
+ Since PyPlumIO v0.5.56, you can access statistics via following property.
149
+
150
+ .. autoattribute:: pyplumio.protocol.AsyncProtocol.statistics
151
+
152
+ Statistics contain transfer data consisting of number of received/sent frames and bytes
153
+ as well as datetime of when connection was established, when connection was lost and
154
+ number of connection loss event.
155
+
156
+ .. autoclass:: pyplumio.protocol.Statistics
157
+ :members:
158
+ :exclude-members: update_sent, update_received, update_connection_lost, update_devices, reset_transfer_statistics
159
+
160
+ The `devices` property of statistics class of also contains a list of
161
+ device statistics objects. Those statistics include time the device was initially
162
+ connected as well as time, when device was last seen (sent an :ref:`RegulatorData`
163
+ message).
164
+
165
+ .. autoclass:: pyplumio.protocol.DeviceStatistics
166
+ :members:
167
+ :exclude-members: __hash__, update_last_seen
168
+
169
+ In the following example we'll print connection statistics after establishing a
170
+ connection.
171
+
172
+ .. code-block:: python
173
+
174
+ import pyplumio
175
+
176
+
177
+ async def main():
178
+ """Print connection statistics."""
179
+ async with pyplumio.open_tcp_connection("localhost", 8899) as conn:
180
+ print(conn.statistics)
181
+ ...
145
182
 
146
183
  Connection Examples
147
184
  -------------------
@@ -51,7 +51,6 @@ Documentation
51
51
  callbacks
52
52
  mixers_thermostats
53
53
  schedules
54
- statistics
55
54
  protocol
56
55
  frames
57
56
 
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.6.0'
32
- __version_tuple__ = version_tuple = (0, 6, 0)
31
+ __version__ = version = '0.6.1'
32
+ __version_tuple__ = version_tuple = (0, 6, 1)
33
33
 
34
- __commit_id__ = commit_id = 'gfb1c17ddb'
34
+ __commit_id__ = commit_id = 'g7cf7d589c'
@@ -116,42 +116,6 @@ class Connection(ABC, TaskManager):
116
116
 
117
117
  yield await self.protocol.get(name, timeout=timeout)
118
118
 
119
- @property
120
- def get(self): # type: ignore[no-untyped-def]
121
- """Access the remote device.
122
-
123
- Raise NotImplementedError when using protocol
124
- different from AsyncProtocol.
125
- """
126
- if isinstance(self.protocol, AsyncProtocol):
127
- return self.protocol.get
128
-
129
- raise NotImplementedError
130
-
131
- @property
132
- def get_nowait(self): # type: ignore[no-untyped-def]
133
- """Access the remote device without waiting.
134
-
135
- Raise NotImplementedError when using protocol
136
- different from AsyncProtocol.
137
- """
138
- if isinstance(self.protocol, AsyncProtocol):
139
- return self.protocol.get_nowait
140
-
141
- raise NotImplementedError
142
-
143
- @property
144
- def wait_for(self): # type: ignore[no-untyped-def]
145
- """Wait for the remote device to become available.
146
-
147
- Raise NotImplementedError when using protocol
148
- different from AsyncProtocol.
149
- """
150
- if isinstance(self.protocol, AsyncProtocol):
151
- return self.protocol.wait_for
152
-
153
- raise NotImplementedError
154
-
155
119
  @property
156
120
  def protocol(self) -> Protocol:
157
121
  """Return the protocol object."""
@@ -145,25 +145,29 @@ class Statistics:
145
145
  connection_losses: int = 0
146
146
 
147
147
  #: List of statistics for connected devices
148
- devices: list[DeviceStatistics] = field(default_factory=list)
148
+ devices: set[DeviceStatistics] = field(default_factory=set)
149
149
 
150
- def update_transfer_statistics(
151
- self, sent: Frame | None = None, received: Frame | None = None
152
- ) -> None:
153
- """Update transfer statistics."""
154
- if sent:
155
- self.sent_bytes += sent.length
156
- self.sent_frames += 1
150
+ def update_sent(self, frame: Frame) -> None:
151
+ """Update sent frames statistics."""
152
+ self.sent_bytes += frame.length
153
+ self.sent_frames += 1
157
154
 
158
- if received:
159
- self.received_bytes += received.length
160
- self.received_frames += 1
155
+ def update_received(self, frame: Frame) -> None:
156
+ """Update received frames statistics."""
157
+ self.received_bytes += frame.length
158
+ self.received_frames += 1
161
159
 
162
- def track_connection_loss(self) -> None:
163
- """Increase connection loss counter and store the datetime."""
160
+ def update_connection_lost(self) -> None:
161
+ """Update connection lost counter."""
164
162
  self.connection_losses += 1
165
163
  self.connection_loss_at = datetime.now()
166
164
 
165
+ def update_devices(self, device: PhysicalDevice) -> None:
166
+ """Update connected devices."""
167
+ device_statistics = DeviceStatistics(address=device.address)
168
+ device.subscribe(ATTR_REGDATA, device_statistics.update_last_seen)
169
+ self.devices.add(device_statistics)
170
+
167
171
  def reset_transfer_statistics(self) -> None:
168
172
  """Reset transfer statistics."""
169
173
  self.sent_bytes = 0
@@ -173,18 +177,22 @@ class Statistics:
173
177
  self.failed_frames = 0
174
178
 
175
179
 
176
- @dataclass(slots=True)
180
+ @dataclass(slots=True, kw_only=True)
177
181
  class DeviceStatistics:
178
182
  """Represents a device statistics."""
179
183
 
180
- #: Device name
181
- name: str
184
+ #: Device address
185
+ address: int
182
186
 
183
187
  #: Datetime object representing connection time
184
- connected_since: datetime | Literal["never"] = NEVER
188
+ connected_since: datetime = field(default_factory=datetime.now)
185
189
 
186
190
  #: Datetime object representing time when device was last seen
187
- last_seen: datetime | Literal["never"] = NEVER
191
+ last_seen: datetime = field(default_factory=datetime.now)
192
+
193
+ def __hash__(self) -> int:
194
+ """Return a hash of the statistics based on unique address."""
195
+ return self.address
188
196
 
189
197
  async def update_last_seen(self, _: Any) -> None:
190
198
  """Update last seen property."""
@@ -241,10 +249,10 @@ class AsyncProtocol(Protocol, EventManager[PhysicalDevice]):
241
249
  self.frame_producer(self._queues, reader=self.reader, writer=self.writer),
242
250
  name="frame_producer_task",
243
251
  )
244
- for consumer in range(self.consumers_count):
252
+ for consumer_id in range(self.consumers_count):
245
253
  self.create_task(
246
254
  self.frame_consumer(self._queues.read),
247
- name=f"frame_consumer_task ({consumer})",
255
+ name=f"frame_consumer_task ({consumer_id})",
248
256
  )
249
257
 
250
258
  for device in self.data.values():
@@ -277,30 +285,39 @@ class AsyncProtocol(Protocol, EventManager[PhysicalDevice]):
277
285
  await self._connection_close()
278
286
  await asyncio.gather(*(device.shutdown() for device in self.data.values()))
279
287
 
288
+ async def _write_from_queue(
289
+ self, writer: FrameWriter, queue: asyncio.Queue[Frame]
290
+ ) -> None:
291
+ """Send frame from the queue to the remote device."""
292
+ frame = await queue.get()
293
+ await writer.write(frame)
294
+ queue.task_done()
295
+ self.statistics.update_sent(frame)
296
+
297
+ async def _read_into_queue(
298
+ self, reader: FrameReader, queue: asyncio.Queue[Frame]
299
+ ) -> None:
300
+ """Read frame from the remote device into the queue."""
301
+ if frame := await reader.read():
302
+ queue.put_nowait(frame)
303
+ self.statistics.update_received(frame)
304
+
280
305
  async def frame_producer(
281
306
  self, queues: Queues, reader: FrameReader, writer: FrameWriter
282
307
  ) -> None:
283
308
  """Handle frame reads and writes."""
284
- statistics = self.statistics
285
309
  await self.connected.wait()
286
310
  while self.connected.is_set():
287
311
  try:
288
- request = None
289
312
  if not queues.write.empty():
290
- request = await queues.write.get()
291
- await writer.write(request)
292
- queues.write.task_done()
293
-
294
- if response := await reader.read():
295
- queues.read.put_nowait(response)
296
-
297
- statistics.update_transfer_statistics(request, response)
313
+ await self._write_from_queue(writer, queues.write)
298
314
 
315
+ await self._read_into_queue(reader, queues.read)
299
316
  except ProtocolError as e:
300
- statistics.failed_frames += 1
317
+ self.statistics.failed_frames += 1
301
318
  _LOGGER.debug("Can't process received frame: %s", e)
302
319
  except (OSError, asyncio.TimeoutError):
303
- statistics.track_connection_loss()
320
+ self.statistics.update_connection_lost()
304
321
  self.create_task(self.connection_lost())
305
322
  break
306
323
  except Exception:
@@ -327,14 +344,7 @@ class AsyncProtocol(Protocol, EventManager[PhysicalDevice]):
327
344
  device.dispatch_nowait(ATTR_CONNECTED, True)
328
345
  device.dispatch_nowait(ATTR_SETUP, True)
329
346
  await self.dispatch(name, device)
330
- self.statistics.devices.append(
331
- device_statistics := DeviceStatistics(
332
- name=name,
333
- connected_since=datetime.now(),
334
- last_seen=datetime.now(),
335
- )
336
- )
337
- device.subscribe(ATTR_REGDATA, device_statistics.update_last_seen)
347
+ self.statistics.update_devices(device)
338
348
 
339
349
  return self.data[name]
340
350
 
@@ -35,16 +35,16 @@ dynamic = ["version"]
35
35
  [project.optional-dependencies]
36
36
  test = [
37
37
  "codespell==2.4.1",
38
- "coverage==7.10.6",
38
+ "coverage==7.10.7",
39
39
  "freezegun==1.5.5",
40
- "mypy==1.18.1",
40
+ "mypy==1.18.2",
41
41
  "numpy<3.0.0,>=2.0.0",
42
42
  "pyserial-asyncio-fast==0.16",
43
43
  "pytest==8.4.2",
44
- "pytest-asyncio==1.1.0",
45
- "ruff==0.13.0",
44
+ "pytest-asyncio==1.2.0",
45
+ "ruff==0.13.2",
46
46
  "tox==4.30.2",
47
- "types-pyserial==3.5.0.20250822"
47
+ "types-pyserial==3.5.0.20250919"
48
48
  ]
49
49
  docs = [
50
50
  "sphinx==8.1.3",
@@ -1,13 +1,13 @@
1
1
  codespell==2.4.1
2
- coverage==7.10.6
2
+ coverage==7.10.7
3
3
  freezegun==1.5.5
4
- mypy==1.18.1
4
+ mypy==1.18.2
5
5
  numpy<3.0.0,>=2.0.0
6
6
  pre-commit==4.3.0
7
7
  pyserial-asyncio-fast==0.16
8
- pytest-asyncio==1.1.0
8
+ pytest-asyncio==1.2.0
9
9
  pytest==8.4.2
10
- ruff==0.13.0
10
+ ruff==0.13.2
11
11
  tomli==2.2.1
12
12
  tox==4.30.2
13
- types-pyserial==3.5.0.20250822
13
+ types-pyserial==3.5.0.20250919
@@ -189,21 +189,6 @@ class TestConnection:
189
189
  mock_protocol.some_method = Mock(return_value="called")
190
190
  assert connection.some_method() == "called"
191
191
 
192
- @pytest.mark.parametrize("func", ("get", "get_nowait", "wait_for"))
193
- async def test_protocol_proxy_calls(self, func: str) -> None:
194
- """Test calls proxied to protocol instance."""
195
- mock_protocol = AsyncMock(spec=AsyncProtocol, autospec=True)
196
- connection = DummyConnection(protocol=mock_protocol)
197
- connection_func = getattr(connection, func)
198
- protocol_func = getattr(mock_protocol, func)
199
- assert connection_func is protocol_func
200
-
201
- # Test with error.
202
- mock_protocol = AsyncMock(spec=DummyProtocol, autospec=True)
203
- connection = DummyConnection(protocol=mock_protocol)
204
- with pytest.raises(NotImplementedError):
205
- getattr(connection, func)
206
-
207
192
 
208
193
  HOST: Final = "localhost"
209
194
  PORT: Final = 8899
@@ -109,25 +109,28 @@ async def test_dummy_protocol() -> None:
109
109
  @pytest.mark.usefixtures("frozen_time")
110
110
  def test_statistics() -> None:
111
111
  """Test statistics dataclass."""
112
- statistics = Statistics()
113
- statistics.update_transfer_statistics(sent=Request())
114
- statistics.update_transfer_statistics(received=Response())
115
- statistics.failed_frames = 1
112
+ statistics = Statistics(failed_frames=1)
116
113
  assert statistics.connected_since == "never"
114
+ assert statistics.failed_frames == 1
115
+ assert statistics.connection_losses == 0
116
+ assert statistics.connection_loss_at == "never"
117
+ assert statistics.sent_bytes == 0
118
+ assert statistics.sent_frames == 0
119
+ assert statistics.received_bytes == 0
120
+ assert statistics.received_frames == 0
121
+ statistics.update_sent(Request())
117
122
  assert statistics.sent_bytes == 10
118
123
  assert statistics.sent_frames == 1
124
+ statistics.update_received(Response())
119
125
  assert statistics.received_bytes == 10
120
126
  assert statistics.received_frames == 1
121
- assert statistics.failed_frames == 1
122
- assert statistics.connection_losses == 0
123
- assert statistics.connection_loss_at == "never"
124
127
  statistics.reset_transfer_statistics()
125
128
  assert statistics.sent_bytes == 0
126
129
  assert statistics.sent_frames == 0
127
130
  assert statistics.received_bytes == 0
128
131
  assert statistics.received_frames == 0
129
132
  assert statistics.failed_frames == 0
130
- statistics.track_connection_loss()
133
+ statistics.update_connection_lost()
131
134
  assert statistics.connection_losses == 1
132
135
  assert statistics.connection_loss_at == datetime.now()
133
136
 
@@ -376,7 +379,7 @@ async def test_async_protocol_frame_producer(
376
379
  "failed_frames": 5,
377
380
  "connection_losses": 1,
378
381
  "connection_loss_at": datetime.now(),
379
- "devices": [],
382
+ "devices": set(),
380
383
  }
381
384
 
382
385
 
@@ -453,12 +456,8 @@ async def test_async_protocol_frame_consumer(
453
456
  assert mock_read_queue.task_done.call_count == 2
454
457
 
455
458
  # Test statistics.
456
- connected_since = datetime.now()
457
459
  frozen_time.tick(timedelta(seconds=10))
458
460
  ecomax = cast(EcoMAX, async_protocol.get_nowait("ecomax"))
459
461
  await ecomax.dispatch(ATTR_REGDATA, True)
460
- assert asdict(async_protocol.statistics.devices[0]) == {
461
- "name": "ecomax",
462
- "connected_since": connected_since,
463
- "last_seen": datetime.now(),
464
- }
462
+ device_statistics = async_protocol.statistics.devices.pop()
463
+ assert device_statistics.address == DeviceType.ECOMAX
@@ -1,47 +0,0 @@
1
- Statistics
2
- ==========
3
-
4
- About Statistics
5
- ----------------
6
-
7
- Since PyPlumIO v0.5.56, you can access statistics via following property.
8
-
9
- .. autoattribute:: pyplumio.protocol.AsyncProtocol.statistics
10
-
11
- Statistics contain transfer data consisting of number of received/sent frames and bytes
12
- as well as datetime of when connection was established, when connection was lost and
13
- number of connection loss event.
14
-
15
- .. autoclass:: pyplumio.protocol.Statistics
16
- :members:
17
- :exclude-members: update_transfer_statistics, track_connection_loss, reset_transfer_statistics
18
-
19
- The `devices` property of statistics class of also contains a list of
20
- device statistics objects. Those statistics include time the device was initially
21
- connected as well as time, when device was last seen (sent an :ref:`RegulatorData`
22
- message).
23
-
24
- .. autoclass:: pyplumio.protocol.DeviceStatistics
25
- :members:
26
- :exclude-members: update_last_seen
27
-
28
- Statistics Examples
29
- -------------------
30
-
31
- You can easily access statistic object via proxy call through Connection object
32
- as in example below.
33
-
34
- .. code-block:: python
35
-
36
- import asyncio
37
-
38
- import pyplumio
39
-
40
- async def main():
41
- """Read the current heating temperature."""
42
- async with pyplumio.open_tcp_connection("localhost", 8899) as conn:
43
- print(conn.statistics)
44
-
45
-
46
- asyncio.run(main())
47
-
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes