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