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