python-bsblan 3.1.2__tar.gz → 3.1.4__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.
- python_bsblan-3.1.4/.github/workflows/auto-approve-renovate.yml +20 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/codeql.yaml +1 -1
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/labels.yaml +1 -1
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/linting.yaml +13 -13
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/release.yaml +3 -3
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/stale.yaml +1 -1
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/tests.yaml +6 -6
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/typing.yaml +2 -2
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/PKG-INFO +1 -1
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/package-lock.json +4 -4
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/package.json +1 -1
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/pyproject.toml +6 -5
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/src/bsblan/__init__.py +8 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/src/bsblan/bsblan.py +93 -11
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/src/bsblan/constants.py +1 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/src/bsblan/models.py +169 -3
- python_bsblan-3.1.4/tests/test_backoff_retry.py +213 -0
- python_bsblan-3.1.4/tests/test_schedule_models.py +229 -0
- python_bsblan-3.1.4/tests/test_sensor.py +107 -0
- python_bsblan-3.1.4/tests/test_set_hot_water_schedule.py +174 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/uv.lock +123 -46
- python_bsblan-3.1.2/tests/test_sensor.py +0 -52
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.editorconfig +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.gitattributes +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/CODE_OF_CONDUCT.md +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/CONTRIBUTING.md +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/LICENSE.md +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/copilot-instructions.md +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/labels.yml +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/release-drafter.yml +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/renovate.json +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/lock.yaml +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/pr-labels.yaml +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.github/workflows/release-drafter.yaml +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.gitignore +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.nvmrc +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.pre-commit-config.yaml +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.prettierignore +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/.yamllint +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/AGENTS.md +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/CLAUDE.md +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/README.md +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/examples/control.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/examples/ruff.toml +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/sonar-project.properties +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/src/bsblan/exceptions.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/src/bsblan/py.typed +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/src/bsblan/utility.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/__init__.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/conftest.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/device.json +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/dict_version.json +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/hot_water_state.json +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/info.json +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/password.txt +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/sensor.json +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/state.json +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/static_state.json +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/thermostat_hvac.json +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/thermostat_temp.json +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/fixtures/time.json +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/ruff.toml +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_api_initialization.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_api_validation.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_auth.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_bsblan.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_bsblan_edge_cases.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_configuration.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_constants.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_context_manager.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_device.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_dhw_time_switch.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_entity_info.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_hot_water_additional.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_hotwater_state.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_info.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_initialization.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_reset_validation.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_set_hotwater.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_state.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_static_state.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_temperature_unit.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_temperature_validation.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_thermostat.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_time.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_utility.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_utility_additional.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_utility_edge_cases.py +0 -0
- {python_bsblan-3.1.2 → python_bsblan-3.1.4}/tests/test_version_errors.py +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Auto-approve Renovate PRs
|
|
3
|
+
|
|
4
|
+
# yamllint disable-line rule:truthy
|
|
5
|
+
on:
|
|
6
|
+
pull_request_target:
|
|
7
|
+
types:
|
|
8
|
+
- opened
|
|
9
|
+
- synchronize
|
|
10
|
+
- reopened
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
pull-requests: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
auto-approve:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
if: github.actor == 'renovate[bot]'
|
|
19
|
+
steps:
|
|
20
|
+
- uses: hmarr/auto-approve-action@v4
|
|
@@ -16,14 +16,14 @@ jobs:
|
|
|
16
16
|
runs-on: ubuntu-latest
|
|
17
17
|
steps:
|
|
18
18
|
- name: ⤵️ Check out code from GitHub
|
|
19
|
-
uses: actions/checkout@
|
|
19
|
+
uses: actions/checkout@v6.0.1
|
|
20
20
|
- name: 🏗 Set up uv
|
|
21
21
|
uses: astral-sh/setup-uv@v6
|
|
22
22
|
with:
|
|
23
23
|
enable-cache: true
|
|
24
24
|
- name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
25
25
|
id: python
|
|
26
|
-
uses: actions/setup-python@v6.
|
|
26
|
+
uses: actions/setup-python@v6.1.0
|
|
27
27
|
with:
|
|
28
28
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
29
29
|
- name: 🏗 Install Python dependencies
|
|
@@ -36,14 +36,14 @@ jobs:
|
|
|
36
36
|
runs-on: ubuntu-latest
|
|
37
37
|
steps:
|
|
38
38
|
- name: ⤵️ Check out code from GitHub
|
|
39
|
-
uses: actions/checkout@
|
|
39
|
+
uses: actions/checkout@v6.0.1
|
|
40
40
|
- name: 🏗 Set up uv
|
|
41
41
|
uses: astral-sh/setup-uv@v6
|
|
42
42
|
with:
|
|
43
43
|
enable-cache: true
|
|
44
44
|
- name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
45
45
|
id: python
|
|
46
|
-
uses: actions/setup-python@v6.
|
|
46
|
+
uses: actions/setup-python@v6.1.0
|
|
47
47
|
with:
|
|
48
48
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
49
49
|
- name: 🏗 Install Python dependencies
|
|
@@ -58,14 +58,14 @@ jobs:
|
|
|
58
58
|
runs-on: ubuntu-latest
|
|
59
59
|
steps:
|
|
60
60
|
- name: ⤵️ Check out code from GitHub
|
|
61
|
-
uses: actions/checkout@
|
|
61
|
+
uses: actions/checkout@v6.0.1
|
|
62
62
|
- name: 🏗 Set up uv
|
|
63
63
|
uses: astral-sh/setup-uv@v6
|
|
64
64
|
with:
|
|
65
65
|
enable-cache: true
|
|
66
66
|
- name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
67
67
|
id: python
|
|
68
|
-
uses: actions/setup-python@v6.
|
|
68
|
+
uses: actions/setup-python@v6.1.0
|
|
69
69
|
with:
|
|
70
70
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
71
71
|
- name: 🏗 Install Python dependencies
|
|
@@ -102,14 +102,14 @@ jobs:
|
|
|
102
102
|
runs-on: ubuntu-latest
|
|
103
103
|
steps:
|
|
104
104
|
- name: ⤵️ Check out code from GitHub
|
|
105
|
-
uses: actions/checkout@
|
|
105
|
+
uses: actions/checkout@v6.0.1
|
|
106
106
|
- name: 🏗 Set up uv
|
|
107
107
|
uses: astral-sh/setup-uv@v6
|
|
108
108
|
with:
|
|
109
109
|
enable-cache: true
|
|
110
110
|
- name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
111
111
|
id: python
|
|
112
|
-
uses: actions/setup-python@v6.
|
|
112
|
+
uses: actions/setup-python@v6.1.0
|
|
113
113
|
with:
|
|
114
114
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
115
115
|
- name: 🏗 Install Python dependencies
|
|
@@ -122,14 +122,14 @@ jobs:
|
|
|
122
122
|
runs-on: ubuntu-latest
|
|
123
123
|
steps:
|
|
124
124
|
- name: ⤵️ Check out code from GitHub
|
|
125
|
-
uses: actions/checkout@
|
|
125
|
+
uses: actions/checkout@v6.0.1
|
|
126
126
|
- name: 🏗 Set up uv
|
|
127
127
|
uses: astral-sh/setup-uv@v6
|
|
128
128
|
with:
|
|
129
129
|
enable-cache: true
|
|
130
130
|
- name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
131
131
|
id: python
|
|
132
|
-
uses: actions/setup-python@v6.
|
|
132
|
+
uses: actions/setup-python@v6.1.0
|
|
133
133
|
with:
|
|
134
134
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
135
135
|
- name: 🏗 Install Python dependencies
|
|
@@ -142,20 +142,20 @@ jobs:
|
|
|
142
142
|
runs-on: ubuntu-latest
|
|
143
143
|
steps:
|
|
144
144
|
- name: ⤵️ Check out code from GitHub
|
|
145
|
-
uses: actions/checkout@
|
|
145
|
+
uses: actions/checkout@v6.0.1
|
|
146
146
|
- name: 🏗 Set up uv
|
|
147
147
|
uses: astral-sh/setup-uv@v6
|
|
148
148
|
with:
|
|
149
149
|
enable-cache: true
|
|
150
150
|
- name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
151
151
|
id: python
|
|
152
|
-
uses: actions/setup-python@v6.
|
|
152
|
+
uses: actions/setup-python@v6.1.0
|
|
153
153
|
with:
|
|
154
154
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
155
155
|
- name: 🏗 Install Python dependencies
|
|
156
156
|
run: uv sync --dev
|
|
157
157
|
- name: 🏗 Set up Node.js
|
|
158
|
-
uses: actions/setup-node@
|
|
158
|
+
uses: actions/setup-node@v6.1.0
|
|
159
159
|
with:
|
|
160
160
|
node-version-file: ".nvmrc"
|
|
161
161
|
cache: "npm"
|
|
@@ -22,14 +22,14 @@ jobs:
|
|
|
22
22
|
id-token: write
|
|
23
23
|
steps:
|
|
24
24
|
- name: ⤵️ Check out code from GitHub
|
|
25
|
-
uses: actions/checkout@
|
|
25
|
+
uses: actions/checkout@v6.0.1
|
|
26
26
|
- name: 🏗 Set up uv
|
|
27
27
|
uses: astral-sh/setup-uv@v6
|
|
28
28
|
with:
|
|
29
29
|
enable-cache: true
|
|
30
30
|
- name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
31
31
|
id: python
|
|
32
|
-
uses: actions/setup-python@v6.
|
|
32
|
+
uses: actions/setup-python@v6.1.0
|
|
33
33
|
with:
|
|
34
34
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
35
35
|
- name: 🏗 Install dependencies
|
|
@@ -48,7 +48,7 @@ jobs:
|
|
|
48
48
|
verbose: true
|
|
49
49
|
print-hash: true
|
|
50
50
|
- name: ✍️ Sign published artifacts
|
|
51
|
-
uses: sigstore/gh-action-sigstore-python@v3.
|
|
51
|
+
uses: sigstore/gh-action-sigstore-python@v3.2.0
|
|
52
52
|
with:
|
|
53
53
|
inputs: ./dist/*.tar.gz ./dist/*.whl
|
|
54
54
|
release-signing-artifacts: false
|
|
@@ -19,14 +19,14 @@ jobs:
|
|
|
19
19
|
python: ["3.12", "3.13", "3.14"]
|
|
20
20
|
steps:
|
|
21
21
|
- name: ⤵️ Check out code from GitHub
|
|
22
|
-
uses: actions/checkout@
|
|
22
|
+
uses: actions/checkout@v6.0.1
|
|
23
23
|
- name: 🏗 Set up uv
|
|
24
24
|
uses: astral-sh/setup-uv@v6
|
|
25
25
|
with:
|
|
26
26
|
enable-cache: true
|
|
27
27
|
- name: 🏗 Set up Python ${{ matrix.python }}
|
|
28
28
|
id: python
|
|
29
|
-
uses: actions/setup-python@v6.
|
|
29
|
+
uses: actions/setup-python@v6.1.0
|
|
30
30
|
with:
|
|
31
31
|
python-version: ${{ matrix.python }}
|
|
32
32
|
- name: 🏗 Install dependencies
|
|
@@ -34,7 +34,7 @@ jobs:
|
|
|
34
34
|
- name: 🚀 Run pytest
|
|
35
35
|
run: uv run pytest --cov=bsblan tests
|
|
36
36
|
- name: ⬆️ Upload coverage artifact
|
|
37
|
-
uses: actions/upload-artifact@
|
|
37
|
+
uses: actions/upload-artifact@v5.0.0
|
|
38
38
|
with:
|
|
39
39
|
name: coverage-${{ matrix.python }}
|
|
40
40
|
include-hidden-files: true
|
|
@@ -45,18 +45,18 @@ jobs:
|
|
|
45
45
|
needs: pytest
|
|
46
46
|
steps:
|
|
47
47
|
- name: ⤵️ Check out code from GitHub
|
|
48
|
-
uses: actions/checkout@
|
|
48
|
+
uses: actions/checkout@v6.0.1
|
|
49
49
|
with:
|
|
50
50
|
fetch-depth: 0
|
|
51
51
|
- name: ⬇️ Download coverage data
|
|
52
|
-
uses: actions/download-artifact@
|
|
52
|
+
uses: actions/download-artifact@v6.0.0
|
|
53
53
|
- name: 🏗 Set up uv
|
|
54
54
|
uses: astral-sh/setup-uv@v6
|
|
55
55
|
with:
|
|
56
56
|
enable-cache: true
|
|
57
57
|
- name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
58
58
|
id: python
|
|
59
|
-
uses: actions/setup-python@v6.
|
|
59
|
+
uses: actions/setup-python@v6.1.0
|
|
60
60
|
with:
|
|
61
61
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
62
62
|
- name: 🏗 Install dependencies
|
|
@@ -16,14 +16,14 @@ jobs:
|
|
|
16
16
|
runs-on: ubuntu-latest
|
|
17
17
|
steps:
|
|
18
18
|
- name: ⤵️ Check out code from GitHub
|
|
19
|
-
uses: actions/checkout@
|
|
19
|
+
uses: actions/checkout@v6.0.1
|
|
20
20
|
- name: 🏗 Set up uv
|
|
21
21
|
uses: astral-sh/setup-uv@v6
|
|
22
22
|
with:
|
|
23
23
|
enable-cache: true
|
|
24
24
|
- name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
25
25
|
id: python
|
|
26
|
-
uses: actions/setup-python@v6.
|
|
26
|
+
uses: actions/setup-python@v6.1.0
|
|
27
27
|
with:
|
|
28
28
|
python-version: ${{ env.DEFAULT_PYTHON }}
|
|
29
29
|
- name: 🏗 Install dependencies
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
"url-parse": "1.5.10"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"prettier": "3.
|
|
15
|
+
"prettier": "3.7.4"
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
"node_modules/prettier": {
|
|
19
|
-
"version": "3.
|
|
20
|
-
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.
|
|
21
|
-
"integrity": "sha512-
|
|
19
|
+
"version": "3.7.4",
|
|
20
|
+
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz",
|
|
21
|
+
"integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==",
|
|
22
22
|
"dev": true,
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"bin": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-bsblan"
|
|
3
|
-
version = "3.1.
|
|
3
|
+
version = "3.1.4"
|
|
4
4
|
description = "Asynchronous Python client for BSBLAN API"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Willem-Jan van Rootselaar", email = "liudgervr@gmail.com"}
|
|
@@ -71,7 +71,7 @@ exclude_lines = [
|
|
|
71
71
|
# free to run mypy on Windows, Linux, or macOS and get consistent
|
|
72
72
|
# results.
|
|
73
73
|
platform = "linux"
|
|
74
|
-
python_version = "3.
|
|
74
|
+
python_version = "3.12"
|
|
75
75
|
|
|
76
76
|
# show error messages from unrelated files
|
|
77
77
|
follow_imports = "normal"
|
|
@@ -135,7 +135,7 @@ min-similarity-lines = 4 # Minimum lines number of a similarity.
|
|
|
135
135
|
max-line-length=88
|
|
136
136
|
|
|
137
137
|
[tool.pytest.ini_options]
|
|
138
|
-
addopts = "--cov"
|
|
138
|
+
addopts = "--cov -n auto"
|
|
139
139
|
asyncio_mode = "auto"
|
|
140
140
|
|
|
141
141
|
[tool.ruff.lint]
|
|
@@ -189,15 +189,16 @@ dev = [
|
|
|
189
189
|
# hatch is required to support type hinting and proper packaging of the py.typed file.
|
|
190
190
|
"hatch>=1.14.1",
|
|
191
191
|
"isort==6.1.0",
|
|
192
|
-
"mypy==1.
|
|
192
|
+
"mypy==1.19.0",
|
|
193
193
|
"pre-commit==4.5.0",
|
|
194
194
|
"pre-commit-hooks==6.0.0",
|
|
195
195
|
"pylint==3.3.9",
|
|
196
196
|
"pytest>=8.3.5",
|
|
197
197
|
"pytest-asyncio==1.3.0",
|
|
198
198
|
"pytest-cov==7.0.0",
|
|
199
|
+
"pytest-xdist>=3.8.0",
|
|
199
200
|
"pyupgrade==3.21.2",
|
|
200
|
-
"ruff==0.14.
|
|
201
|
+
"ruff==0.14.8",
|
|
201
202
|
"safety==3.7.0",
|
|
202
203
|
"vulture==2.14",
|
|
203
204
|
"yamllint==1.37.1",
|
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
from .bsblan import BSBLAN, BSBLANConfig
|
|
4
4
|
from .exceptions import BSBLANAuthError, BSBLANConnectionError, BSBLANError
|
|
5
5
|
from .models import (
|
|
6
|
+
DaySchedule,
|
|
6
7
|
Device,
|
|
7
8
|
DeviceTime,
|
|
9
|
+
DHWSchedule,
|
|
8
10
|
DHWTimeSwitchPrograms,
|
|
11
|
+
EntityInfo,
|
|
9
12
|
HotWaterConfig,
|
|
10
13
|
HotWaterSchedule,
|
|
11
14
|
HotWaterState,
|
|
@@ -14,6 +17,7 @@ from .models import (
|
|
|
14
17
|
SetHotWaterParam,
|
|
15
18
|
State,
|
|
16
19
|
StaticState,
|
|
20
|
+
TimeSlot,
|
|
17
21
|
)
|
|
18
22
|
|
|
19
23
|
__all__ = [
|
|
@@ -22,9 +26,12 @@ __all__ = [
|
|
|
22
26
|
"BSBLANConfig",
|
|
23
27
|
"BSBLANConnectionError",
|
|
24
28
|
"BSBLANError",
|
|
29
|
+
"DHWSchedule",
|
|
25
30
|
"DHWTimeSwitchPrograms",
|
|
31
|
+
"DaySchedule",
|
|
26
32
|
"Device",
|
|
27
33
|
"DeviceTime",
|
|
34
|
+
"EntityInfo",
|
|
28
35
|
"HotWaterConfig",
|
|
29
36
|
"HotWaterSchedule",
|
|
30
37
|
"HotWaterState",
|
|
@@ -33,4 +40,5 @@ __all__ = [
|
|
|
33
40
|
"SetHotWaterParam",
|
|
34
41
|
"State",
|
|
35
42
|
"StaticState",
|
|
43
|
+
"TimeSlot",
|
|
36
44
|
]
|
|
@@ -11,6 +11,7 @@ if TYPE_CHECKING:
|
|
|
11
11
|
from collections.abc import Mapping
|
|
12
12
|
|
|
13
13
|
import aiohttp
|
|
14
|
+
import backoff
|
|
14
15
|
from aiohttp.hdrs import METH_POST
|
|
15
16
|
from aiohttp.helpers import BasicAuth
|
|
16
17
|
from packaging import version as pkg_version
|
|
@@ -29,6 +30,7 @@ from .constants import (
|
|
|
29
30
|
MAX_VALID_YEAR,
|
|
30
31
|
MIN_VALID_YEAR,
|
|
31
32
|
MULTI_PARAMETER_ERROR_MSG,
|
|
33
|
+
NO_SCHEDULE_ERROR_MSG,
|
|
32
34
|
NO_STATE_ERROR_MSG,
|
|
33
35
|
SESSION_NOT_INITIALIZED_ERROR_MSG,
|
|
34
36
|
SETTABLE_HOT_WATER_PARAMS,
|
|
@@ -45,8 +47,10 @@ from .exceptions import (
|
|
|
45
47
|
BSBLANVersionError,
|
|
46
48
|
)
|
|
47
49
|
from .models import (
|
|
50
|
+
DaySchedule,
|
|
48
51
|
Device,
|
|
49
52
|
DeviceTime,
|
|
53
|
+
DHWSchedule,
|
|
50
54
|
HotWaterConfig,
|
|
51
55
|
HotWaterSchedule,
|
|
52
56
|
HotWaterState,
|
|
@@ -409,6 +413,9 @@ class BSBLAN:
|
|
|
409
413
|
) -> dict[str, Any]:
|
|
410
414
|
"""Handle a request to a BSBLAN device.
|
|
411
415
|
|
|
416
|
+
Uses exponential backoff to retry on transient connection errors.
|
|
417
|
+
Will not retry on authentication errors (401, 403) or not found (404).
|
|
418
|
+
|
|
412
419
|
Args:
|
|
413
420
|
method (str): The HTTP method to use for the request.
|
|
414
421
|
base_path (str): The base path for the URL.
|
|
@@ -420,9 +427,47 @@ class BSBLAN:
|
|
|
420
427
|
dict[str, Any]: The JSON response from the BSBLAN device.
|
|
421
428
|
|
|
422
429
|
Raises:
|
|
423
|
-
BSBLANConnectionError: If there is a connection error.
|
|
430
|
+
BSBLANConnectionError: If there is a connection error after retries.
|
|
431
|
+
BSBLANAuthError: If authentication fails (not retried).
|
|
424
432
|
BSBLANError: If there is an error with the request.
|
|
425
433
|
|
|
434
|
+
"""
|
|
435
|
+
try:
|
|
436
|
+
return await self._request_with_retry(method, base_path, data, params)
|
|
437
|
+
except TimeoutError as e:
|
|
438
|
+
raise BSBLANConnectionError(BSBLANConnectionError.message_timeout) from e
|
|
439
|
+
except aiohttp.ClientError as e:
|
|
440
|
+
raise BSBLANConnectionError(BSBLANConnectionError.message_error) from e
|
|
441
|
+
|
|
442
|
+
@backoff.on_exception(
|
|
443
|
+
backoff.expo,
|
|
444
|
+
(TimeoutError, aiohttp.ClientError),
|
|
445
|
+
max_tries=3,
|
|
446
|
+
max_time=30,
|
|
447
|
+
giveup=lambda e: isinstance(e, aiohttp.ClientResponseError) and e.status == 404,
|
|
448
|
+
logger=logger,
|
|
449
|
+
)
|
|
450
|
+
async def _request_with_retry(
|
|
451
|
+
self,
|
|
452
|
+
method: str,
|
|
453
|
+
base_path: str,
|
|
454
|
+
data: dict[str, object] | None,
|
|
455
|
+
params: Mapping[str, str | int] | str | None,
|
|
456
|
+
) -> dict[str, Any]:
|
|
457
|
+
"""Execute HTTP request with retry logic.
|
|
458
|
+
|
|
459
|
+
This internal method handles the actual HTTP request and is decorated
|
|
460
|
+
with backoff for automatic retries on transient failures.
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
method: The HTTP method to use.
|
|
464
|
+
base_path: The base path for the URL.
|
|
465
|
+
data: The data to send in the request body.
|
|
466
|
+
params: The query parameters to include.
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
dict[str, Any]: The JSON response from the BSBLAN device.
|
|
470
|
+
|
|
426
471
|
"""
|
|
427
472
|
if self.session is None:
|
|
428
473
|
raise BSBLANError(SESSION_NOT_INITIALIZED_ERROR_MSG)
|
|
@@ -443,14 +488,10 @@ class BSBLAN:
|
|
|
443
488
|
response.raise_for_status()
|
|
444
489
|
response_data = cast("dict[str, Any]", await response.json())
|
|
445
490
|
return self._process_response(response_data, base_path)
|
|
446
|
-
except TimeoutError as e:
|
|
447
|
-
raise BSBLANConnectionError(BSBLANConnectionError.message_timeout) from e
|
|
448
491
|
except aiohttp.ClientResponseError as e:
|
|
449
492
|
if e.status in (401, 403):
|
|
450
493
|
raise BSBLANAuthError from e
|
|
451
|
-
raise
|
|
452
|
-
except aiohttp.ClientError as e:
|
|
453
|
-
raise BSBLANConnectionError(BSBLANConnectionError.message_error) from e
|
|
494
|
+
raise
|
|
454
495
|
except (ValueError, UnicodeDecodeError) as e:
|
|
455
496
|
# Handle JSON decode errors and other parsing issues
|
|
456
497
|
error_msg = f"Invalid response format from BSB-LAN device: {e!s}"
|
|
@@ -589,9 +630,8 @@ class BSBLAN:
|
|
|
589
630
|
State: The current state of the BSBLAN device.
|
|
590
631
|
|
|
591
632
|
Note:
|
|
592
|
-
The hvac_mode.value is returned as a raw integer from the device
|
|
593
|
-
|
|
594
|
-
representation (e.g., "off", "auto", "eco", "heat").
|
|
633
|
+
The hvac_mode.value is returned as a raw integer from the device:
|
|
634
|
+
0=off, 1=auto, 2=eco, 3=heat.
|
|
595
635
|
|
|
596
636
|
"""
|
|
597
637
|
return await self._fetch_section_data("heating", State)
|
|
@@ -677,8 +717,6 @@ class BSBLAN:
|
|
|
677
717
|
target_temperature (str | None): The target temperature to set.
|
|
678
718
|
hvac_mode (int | None): The HVAC mode to set as raw integer value.
|
|
679
719
|
Valid values: 0=off, 1=auto, 2=eco, 3=heat.
|
|
680
|
-
Use HVAC_MODE_DICT_REVERSE from bsblan.constants to convert
|
|
681
|
-
string names to integers.
|
|
682
720
|
|
|
683
721
|
"""
|
|
684
722
|
await self._initialize_temperature_range()
|
|
@@ -930,6 +968,50 @@ class BSBLAN:
|
|
|
930
968
|
state = self._prepare_hot_water_state(params)
|
|
931
969
|
await self._set_device_state(state)
|
|
932
970
|
|
|
971
|
+
async def set_hot_water_schedule(self, schedule: DHWSchedule) -> None:
|
|
972
|
+
"""Set hot water time program schedules.
|
|
973
|
+
|
|
974
|
+
This method allows setting weekly DHW schedules using a type-safe
|
|
975
|
+
interface with TimeSlot and DaySchedule objects.
|
|
976
|
+
|
|
977
|
+
Example:
|
|
978
|
+
schedule = DHWSchedule(
|
|
979
|
+
monday=DaySchedule(slots=[
|
|
980
|
+
TimeSlot(time(6, 0), time(8, 0)),
|
|
981
|
+
TimeSlot(time(17, 0), time(21, 0)),
|
|
982
|
+
]),
|
|
983
|
+
tuesday=DaySchedule(slots=[
|
|
984
|
+
TimeSlot(time(6, 0), time(8, 0)),
|
|
985
|
+
])
|
|
986
|
+
)
|
|
987
|
+
await client.set_hot_water_schedule(schedule)
|
|
988
|
+
|
|
989
|
+
Args:
|
|
990
|
+
schedule: DHWSchedule object containing the weekly schedule.
|
|
991
|
+
|
|
992
|
+
Raises:
|
|
993
|
+
BSBLANError: If no schedule is provided.
|
|
994
|
+
|
|
995
|
+
"""
|
|
996
|
+
if not schedule.has_any_schedule():
|
|
997
|
+
raise BSBLANError(NO_SCHEDULE_ERROR_MSG)
|
|
998
|
+
|
|
999
|
+
# Invert DHW_TIME_PROGRAM_PARAMS to get day_name -> param_id mapping
|
|
1000
|
+
# Exclude standard_values as it's not a day of the week
|
|
1001
|
+
day_param_map = {
|
|
1002
|
+
v: k for k, v in DHW_TIME_PROGRAM_PARAMS.items() if v != "standard_values"
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
for day_name, param_id in day_param_map.items():
|
|
1006
|
+
day_schedule: DaySchedule | None = getattr(schedule, day_name)
|
|
1007
|
+
if day_schedule is not None:
|
|
1008
|
+
state = {
|
|
1009
|
+
"Parameter": param_id,
|
|
1010
|
+
"Value": day_schedule.to_bsblan_format(),
|
|
1011
|
+
"Type": "1",
|
|
1012
|
+
}
|
|
1013
|
+
await self._set_device_state(state)
|
|
1014
|
+
|
|
933
1015
|
def _prepare_hot_water_state(
|
|
934
1016
|
self,
|
|
935
1017
|
params: SetHotWaterParam,
|
|
@@ -133,6 +133,7 @@ VALID_HVAC_MODES: Final[set[int]] = {0, 1, 2, 3}
|
|
|
133
133
|
|
|
134
134
|
# Error Messages
|
|
135
135
|
NO_STATE_ERROR_MSG: Final[str] = "No state provided."
|
|
136
|
+
NO_SCHEDULE_ERROR_MSG: Final[str] = "No schedule provided."
|
|
136
137
|
VERSION_ERROR_MSG: Final[str] = "Version not supported"
|
|
137
138
|
FIRMWARE_VERSION_ERROR_MSG: Final[str] = "Firmware version not available"
|
|
138
139
|
TEMPERATURE_RANGE_ERROR_MSG: Final[str] = "Temperature range not initialized"
|