python-bsblan 5.1.5__tar.gz → 5.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/workflows/codeql.yaml +2 -2
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/workflows/docs.yaml +1 -1
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/workflows/linting.yaml +2 -2
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/workflows/release-drafter.yaml +1 -1
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/workflows/release.yaml +1 -1
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/workflows/scorecard.yml +1 -1
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/workflows/tests.yaml +3 -3
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/workflows/typing.yaml +1 -1
- python_bsblan-5.2.1/.nvmrc +1 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/PKG-INFO +1 -1
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/docs/api/client.md +2 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/docs/api/models.md +8 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/pyproject.toml +3 -3
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/src/bsblan/__init__.py +4 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/src/bsblan/bsblan.py +104 -42
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/src/bsblan/constants.py +28 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/src/bsblan/models.py +61 -19
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_circuit.py +49 -120
- python_bsblan-5.2.1/tests/test_heating_schedule.py +145 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_schedule_models.py +32 -1
- python_bsblan-5.2.1/tests/test_set_heating_schedule.py +150 -0
- python_bsblan-5.1.5/.nvmrc +0 -1
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.editorconfig +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.gitattributes +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/CODE_OF_CONDUCT.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/CONTRIBUTING.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/SECURITY.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/copilot-instructions.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/labels.yml +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/prompts/add-parameter.prompt.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/prompts/code-review.prompt.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/release-drafter.yml +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/renovate.json +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/skills/bsblan-parameters/SKILL.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/skills/bsblan-testing/SKILL.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/workflows/auto-approve-renovate.yml +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/workflows/dependency-review.yaml +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/workflows/labels.yaml +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/workflows/lock.yaml +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/workflows/pr-labels.yaml +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/workflows/stale.yaml +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.github/zizmor.yml +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.gitignore +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.pre-commit-config.yaml +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.prettierignore +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/.yamllint +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/AGENTS.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/CLAUDE.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/LICENSE.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/Makefile +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/README.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/docs/api/constants.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/docs/api/exceptions.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/docs/getting-started.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/docs/index.md +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/examples/control.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/examples/discovery.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/examples/fetch_param.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/examples/profile_init.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/examples/ruff.toml +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/examples/speed_test.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/mkdocs.yml +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/package-lock.json +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/package.json +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/sonar-project.properties +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/src/bsblan/exceptions.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/src/bsblan/py.typed +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/src/bsblan/utility.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/__init__.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/conftest.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/fixtures/device.json +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/fixtures/dict_version.json +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/fixtures/hot_water_state.json +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/fixtures/info.json +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/fixtures/password.txt +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/fixtures/sensor.json +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/fixtures/state.json +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/fixtures/state_circuit2.json +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/fixtures/static_state.json +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/fixtures/static_state_circuit2.json +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/fixtures/thermostat_hvac.json +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/fixtures/thermostat_temp.json +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/fixtures/time.json +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/ruff.toml +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_api_initialization.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_api_validation.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_auth.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_backoff_retry.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_bsblan.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_bsblan_edge_cases.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_configuration.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_constants.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_context_manager.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_device.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_dhw_time_switch.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_entity_info.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_entity_info_ha.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_hot_water_additional.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_hotwater_state.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_include_parameter.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_info.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_initialization.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_read_parameters.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_reset_validation.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_sensor.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_set_hot_water_schedule.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_set_hotwater.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_state.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_static_state.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_temperature_unit.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_temperature_validation.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_thermostat.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_time.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_utility.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_utility_additional.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_utility_edge_cases.py +0 -0
- {python_bsblan-5.1.5 → python_bsblan-5.2.1}/tests/test_version_errors.py +0 -0
|
@@ -27,6 +27,6 @@ jobs:
|
|
|
27
27
|
with:
|
|
28
28
|
persist-credentials: false
|
|
29
29
|
- name: 🏗 Initialize CodeQL
|
|
30
|
-
uses: github/codeql-action/init@
|
|
30
|
+
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
|
31
31
|
- name: 🚀 Perform CodeQL Analysis
|
|
32
|
-
uses: github/codeql-action/analyze@
|
|
32
|
+
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
|
@@ -27,7 +27,7 @@ jobs:
|
|
|
27
27
|
with:
|
|
28
28
|
persist-credentials: false
|
|
29
29
|
- name: 🏗 Set up uv
|
|
30
|
-
uses: astral-sh/setup-uv@
|
|
30
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
31
31
|
with:
|
|
32
32
|
enable-cache: true
|
|
33
33
|
- name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
@@ -25,7 +25,7 @@ jobs:
|
|
|
25
25
|
with:
|
|
26
26
|
persist-credentials: false
|
|
27
27
|
- name: 🏗 Set up uv
|
|
28
|
-
uses: astral-sh/setup-uv@
|
|
28
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
29
29
|
with:
|
|
30
30
|
enable-cache: true
|
|
31
31
|
- name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
@@ -49,7 +49,7 @@ jobs:
|
|
|
49
49
|
with:
|
|
50
50
|
persist-credentials: false
|
|
51
51
|
- name: 🏗 Set up uv
|
|
52
|
-
uses: astral-sh/setup-uv@
|
|
52
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
53
53
|
with:
|
|
54
54
|
enable-cache: true
|
|
55
55
|
- name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
@@ -36,7 +36,7 @@ jobs:
|
|
|
36
36
|
steps:
|
|
37
37
|
- name: 🚀 Run Release Drafter
|
|
38
38
|
# yamllint disable-line rule:line-length
|
|
39
|
-
uses: release-drafter/release-drafter@
|
|
39
|
+
uses: release-drafter/release-drafter@563bf132657a13ded0b01fcb723c5a58cdd824e2 # v7.2.1
|
|
40
40
|
with:
|
|
41
41
|
prerelease: ${{ github.event.inputs.prerelease == 'true' }}
|
|
42
42
|
prerelease-identifier: ${{ github.event.inputs.prerelease_identifier }}
|
|
@@ -29,7 +29,7 @@ jobs:
|
|
|
29
29
|
with:
|
|
30
30
|
persist-credentials: false
|
|
31
31
|
- name: 🏗 Set up uv
|
|
32
|
-
uses: astral-sh/setup-uv@
|
|
32
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
33
33
|
with:
|
|
34
34
|
enable-cache: false
|
|
35
35
|
- name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
@@ -66,6 +66,6 @@ jobs:
|
|
|
66
66
|
# Upload the results to GitHub's code scanning dashboard (optional).
|
|
67
67
|
- name: "Upload to code-scanning"
|
|
68
68
|
# yamllint disable-line rule:line-length
|
|
69
|
-
uses: github/codeql-action/upload-sarif@
|
|
69
|
+
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
|
70
70
|
with:
|
|
71
71
|
sarif_file: results.sarif
|
|
@@ -33,7 +33,7 @@ jobs:
|
|
|
33
33
|
with:
|
|
34
34
|
persist-credentials: false
|
|
35
35
|
- name: 🏗 Set up uv
|
|
36
|
-
uses: astral-sh/setup-uv@
|
|
36
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
37
37
|
with:
|
|
38
38
|
enable-cache: true
|
|
39
39
|
- name: 🏗 Set up Python ${{ matrix.python }}
|
|
@@ -68,7 +68,7 @@ jobs:
|
|
|
68
68
|
- name: ⬇️ Download coverage data
|
|
69
69
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
70
70
|
- name: 🏗 Set up uv
|
|
71
|
-
uses: astral-sh/setup-uv@
|
|
71
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
72
72
|
with:
|
|
73
73
|
enable-cache: true
|
|
74
74
|
- name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
@@ -93,7 +93,7 @@ jobs:
|
|
|
93
93
|
- name: SonarQube Cloud Scan
|
|
94
94
|
if: env.HAS_SONAR_TOKEN == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork)
|
|
95
95
|
# yamllint disable-line rule:line-length
|
|
96
|
-
uses: SonarSource/sonarqube-scan-action@
|
|
96
|
+
uses: SonarSource/sonarqube-scan-action@59db25f34e16620e48ab4bb9e4a5dce155cb5432 # v8.0
|
|
97
97
|
env:
|
|
98
98
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
99
99
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
@@ -27,7 +27,7 @@ jobs:
|
|
|
27
27
|
persist-credentials: false
|
|
28
28
|
- name: 🏗 Set up uv
|
|
29
29
|
# yamllint disable-line rule:line-length
|
|
30
|
-
uses: astral-sh/setup-uv@
|
|
30
|
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
31
31
|
with:
|
|
32
32
|
enable-cache: true
|
|
33
33
|
- name: 🏗 Set up Python ${{ env.DEFAULT_PYTHON }}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
24.15.0
|
|
@@ -26,6 +26,10 @@ Data models for BSB-LAN API responses.
|
|
|
26
26
|
|
|
27
27
|
::: bsblan.Sensor
|
|
28
28
|
|
|
29
|
+
### HeatingTimeSwitchPrograms
|
|
30
|
+
|
|
31
|
+
::: bsblan.HeatingTimeSwitchPrograms
|
|
32
|
+
|
|
29
33
|
## Hot water
|
|
30
34
|
|
|
31
35
|
### HotWaterState
|
|
@@ -46,6 +50,10 @@ Data models for BSB-LAN API responses.
|
|
|
46
50
|
|
|
47
51
|
## Schedules
|
|
48
52
|
|
|
53
|
+
### HeatingSchedule
|
|
54
|
+
|
|
55
|
+
::: bsblan.HeatingSchedule
|
|
56
|
+
|
|
49
57
|
### DHWSchedule
|
|
50
58
|
|
|
51
59
|
::: bsblan.DHWSchedule
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-bsblan"
|
|
3
|
-
version = "5.1
|
|
3
|
+
version = "5.2.1"
|
|
4
4
|
description = "Asynchronous Python client for BSBLAN API"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Willem-Jan van Rootselaar", email = "liudgervr@gmail.com"}
|
|
@@ -196,7 +196,7 @@ dev = [
|
|
|
196
196
|
# hatch is required to support type hinting and proper packaging of the py.typed file.
|
|
197
197
|
"hatch>=1.14.1",
|
|
198
198
|
"isort==8.0.1",
|
|
199
|
-
"ty==0.0.
|
|
199
|
+
"ty==0.0.34",
|
|
200
200
|
"prek>=0.3.3",
|
|
201
201
|
"pre-commit-hooks==6.0.0",
|
|
202
202
|
"pylint==4.0.5",
|
|
@@ -205,7 +205,7 @@ dev = [
|
|
|
205
205
|
"pytest-cov==7.1.0",
|
|
206
206
|
"pytest-xdist>=3.8.0",
|
|
207
207
|
"pyupgrade==3.21.2",
|
|
208
|
-
"ruff==0.15.
|
|
208
|
+
"ruff==0.15.12",
|
|
209
209
|
"safety==3.7.0",
|
|
210
210
|
"vulture==2.16",
|
|
211
211
|
"yamllint==1.38.0",
|
|
@@ -17,6 +17,8 @@ from .models import (
|
|
|
17
17
|
DHWTimeSwitchPrograms,
|
|
18
18
|
EntityInfo,
|
|
19
19
|
EntityValue,
|
|
20
|
+
HeatingSchedule,
|
|
21
|
+
HeatingTimeSwitchPrograms,
|
|
20
22
|
HotWaterConfig,
|
|
21
23
|
HotWaterSchedule,
|
|
22
24
|
HotWaterState,
|
|
@@ -45,6 +47,8 @@ __all__ = [
|
|
|
45
47
|
"EntityValue",
|
|
46
48
|
"HVACActionCategory",
|
|
47
49
|
"HeatingCircuitStatus",
|
|
50
|
+
"HeatingSchedule",
|
|
51
|
+
"HeatingTimeSwitchPrograms",
|
|
48
52
|
"HotWaterConfig",
|
|
49
53
|
"HotWaterSchedule",
|
|
50
54
|
"HotWaterState",
|
|
@@ -22,6 +22,7 @@ from .constants import (
|
|
|
22
22
|
APIConfig,
|
|
23
23
|
CircuitConfig,
|
|
24
24
|
ErrorMsg,
|
|
25
|
+
HeatingScheduleParams,
|
|
25
26
|
HotWaterParams,
|
|
26
27
|
Validation,
|
|
27
28
|
)
|
|
@@ -38,6 +39,8 @@ from .models import (
|
|
|
38
39
|
DeviceTime,
|
|
39
40
|
DHWSchedule,
|
|
40
41
|
EntityInfo,
|
|
42
|
+
HeatingSchedule,
|
|
43
|
+
HeatingTimeSwitchPrograms,
|
|
41
44
|
HotWaterConfig,
|
|
42
45
|
HotWaterSchedule,
|
|
43
46
|
HotWaterState,
|
|
@@ -149,13 +152,10 @@ class BSBLAN:
|
|
|
149
152
|
async def get_available_circuits(self) -> list[int]:
|
|
150
153
|
"""Detect which heating circuits are available on the device.
|
|
151
154
|
|
|
152
|
-
Uses
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
circuit returns ``value="0"`` with ``desc="---"``.
|
|
157
|
-
|
|
158
|
-
A circuit is only considered available when both checks pass.
|
|
155
|
+
Uses the configured operating mode probe parameters from
|
|
156
|
+
CircuitConfig.PROBE_PARAMS as the only discovery signal. Status
|
|
157
|
+
parameters are not queried during discovery to keep setup lightweight
|
|
158
|
+
and avoid excluding valid circuits when status data is unavailable.
|
|
159
159
|
|
|
160
160
|
This is useful for integration setup flows (e.g., Home Assistant
|
|
161
161
|
config flow) to discover how many circuits the user's controller
|
|
@@ -176,46 +176,23 @@ class BSBLAN:
|
|
|
176
176
|
response = await self._request(
|
|
177
177
|
params={"Parameter": param_id},
|
|
178
178
|
)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
# Secondary check: query the status parameter.
|
|
185
|
-
# Inactive circuits either:
|
|
186
|
-
# - return value="0" and desc="---"
|
|
187
|
-
# - return an empty dict {} (param not supported)
|
|
188
|
-
status_id = CircuitConfig.STATUS_PARAMS[circuit]
|
|
189
|
-
status_resp = await self._request(
|
|
190
|
-
params={"Parameter": status_id},
|
|
179
|
+
except BSBLANError:
|
|
180
|
+
logger.debug(
|
|
181
|
+
"Circuit %d not available (operating mode request failed)",
|
|
182
|
+
circuit,
|
|
191
183
|
)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
# Empty response means the parameter doesn't exist
|
|
195
|
-
if not status_data or not isinstance(status_data, dict):
|
|
196
|
-
logger.debug(
|
|
197
|
-
"Circuit %d has no status data (not supported)",
|
|
198
|
-
circuit,
|
|
199
|
-
)
|
|
200
|
-
continue
|
|
184
|
+
continue
|
|
201
185
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
and str(status_data.get("value", "")) == "0"
|
|
206
|
-
):
|
|
207
|
-
logger.debug(
|
|
208
|
-
"Circuit %d has status '---' (inactive)",
|
|
209
|
-
circuit,
|
|
210
|
-
)
|
|
211
|
-
continue
|
|
212
|
-
|
|
213
|
-
available.append(circuit)
|
|
214
|
-
except BSBLANError:
|
|
186
|
+
# A circuit exists if the response contains the operating mode key
|
|
187
|
+
# with actual data (not an empty dict).
|
|
188
|
+
if not response.get(param_id):
|
|
215
189
|
logger.debug(
|
|
216
|
-
"Circuit %d
|
|
190
|
+
"Circuit %d has no operating mode data (not supported)",
|
|
217
191
|
circuit,
|
|
218
192
|
)
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
available.append(circuit)
|
|
219
196
|
return sorted(available)
|
|
220
197
|
|
|
221
198
|
async def _setup_api_validator(self) -> None:
|
|
@@ -1380,6 +1357,91 @@ class BSBLAN:
|
|
|
1380
1357
|
include=include,
|
|
1381
1358
|
)
|
|
1382
1359
|
|
|
1360
|
+
async def heating_schedule(
|
|
1361
|
+
self,
|
|
1362
|
+
include: list[str] | None = None,
|
|
1363
|
+
circuit: int = 1,
|
|
1364
|
+
) -> HeatingTimeSwitchPrograms:
|
|
1365
|
+
"""Get heating time switch programs for a specific circuit.
|
|
1366
|
+
|
|
1367
|
+
Args:
|
|
1368
|
+
include: Optional list of day names to fetch. If None,
|
|
1369
|
+
fetches all schedule parameters. Valid names include:
|
|
1370
|
+
monday, tuesday, wednesday, thursday,
|
|
1371
|
+
friday, saturday, sunday, standard_values.
|
|
1372
|
+
circuit: The heating circuit number (1 or 2). Defaults to 1.
|
|
1373
|
+
|
|
1374
|
+
Returns:
|
|
1375
|
+
HeatingTimeSwitchPrograms: Heating schedule information.
|
|
1376
|
+
|
|
1377
|
+
"""
|
|
1378
|
+
self._validate_circuit(circuit)
|
|
1379
|
+
time_program_params = HeatingScheduleParams.TIME_PROGRAMS[circuit]
|
|
1380
|
+
|
|
1381
|
+
filtered_params = time_program_params
|
|
1382
|
+
if include is not None:
|
|
1383
|
+
if not include:
|
|
1384
|
+
raise BSBLANError(ErrorMsg.EMPTY_INCLUDE_LIST)
|
|
1385
|
+
filtered_params = {
|
|
1386
|
+
param_id: name
|
|
1387
|
+
for param_id, name in time_program_params.items()
|
|
1388
|
+
if name in include
|
|
1389
|
+
}
|
|
1390
|
+
if not filtered_params:
|
|
1391
|
+
raise BSBLANError(ErrorMsg.INVALID_INCLUDE_PARAMS)
|
|
1392
|
+
|
|
1393
|
+
params = self._extract_params_summary(filtered_params)
|
|
1394
|
+
data = await self._request(params={"Parameter": params["string_par"]})
|
|
1395
|
+
mapped_data = {
|
|
1396
|
+
name: data[param_id]
|
|
1397
|
+
for param_id, name in filtered_params.items()
|
|
1398
|
+
if param_id in data
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
if not mapped_data:
|
|
1402
|
+
raise BSBLANError(ErrorMsg.NO_HEATING_SCHEDULE_PARAMS)
|
|
1403
|
+
|
|
1404
|
+
return HeatingTimeSwitchPrograms.model_validate(mapped_data)
|
|
1405
|
+
|
|
1406
|
+
async def set_heating_schedule(
|
|
1407
|
+
self,
|
|
1408
|
+
schedule: HeatingSchedule,
|
|
1409
|
+
circuit: int = 1,
|
|
1410
|
+
) -> None:
|
|
1411
|
+
"""Set heating time switch programs for a specific circuit.
|
|
1412
|
+
|
|
1413
|
+
This method allows setting weekly heating schedules using a type-safe
|
|
1414
|
+
interface with TimeSlot and DaySchedule objects.
|
|
1415
|
+
|
|
1416
|
+
Args:
|
|
1417
|
+
schedule: HeatingSchedule object containing the weekly schedule.
|
|
1418
|
+
circuit: The heating circuit number (1 or 2). Defaults to 1.
|
|
1419
|
+
|
|
1420
|
+
Raises:
|
|
1421
|
+
BSBLANError: If no schedule is provided.
|
|
1422
|
+
|
|
1423
|
+
"""
|
|
1424
|
+
self._validate_circuit(circuit)
|
|
1425
|
+
|
|
1426
|
+
if not schedule.has_any_schedule():
|
|
1427
|
+
raise BSBLANError(ErrorMsg.NO_SCHEDULE)
|
|
1428
|
+
|
|
1429
|
+
day_param_map = {
|
|
1430
|
+
v: k
|
|
1431
|
+
for k, v in HeatingScheduleParams.TIME_PROGRAMS[circuit].items()
|
|
1432
|
+
if v != "standard_values"
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
for day_name, param_id in day_param_map.items():
|
|
1436
|
+
day_schedule: DaySchedule | None = getattr(schedule, day_name)
|
|
1437
|
+
if day_schedule is not None:
|
|
1438
|
+
state = {
|
|
1439
|
+
"Parameter": param_id,
|
|
1440
|
+
"Value": day_schedule.to_bsblan_format(),
|
|
1441
|
+
"Type": "1",
|
|
1442
|
+
}
|
|
1443
|
+
await self._set_device_state(state)
|
|
1444
|
+
|
|
1383
1445
|
async def set_hot_water(self, params: SetHotWaterParam) -> None:
|
|
1384
1446
|
"""Change the state of the hot water system through BSB-Lan.
|
|
1385
1447
|
|
|
@@ -491,6 +491,7 @@ class ErrorMsg:
|
|
|
491
491
|
EMPTY_INCLUDE_LIST = (
|
|
492
492
|
"Empty include list provided. Use None to fetch all parameters."
|
|
493
493
|
)
|
|
494
|
+
NO_HEATING_SCHEDULE_PARAMS = "No heating schedule parameters available"
|
|
494
495
|
|
|
495
496
|
|
|
496
497
|
# Handle both ASCII and Unicode degree symbols
|
|
@@ -655,3 +656,30 @@ class HotWaterParams:
|
|
|
655
656
|
"567": "sunday",
|
|
656
657
|
"576": "standard_values",
|
|
657
658
|
}
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
class HeatingScheduleParams:
|
|
662
|
+
"""Heating schedule parameter mappings per circuit."""
|
|
663
|
+
|
|
664
|
+
TIME_PROGRAMS: Final[dict[int, dict[str, str]]] = {
|
|
665
|
+
1: {
|
|
666
|
+
"501": "monday",
|
|
667
|
+
"502": "tuesday",
|
|
668
|
+
"503": "wednesday",
|
|
669
|
+
"504": "thursday",
|
|
670
|
+
"505": "friday",
|
|
671
|
+
"506": "saturday",
|
|
672
|
+
"507": "sunday",
|
|
673
|
+
"516": "standard_values",
|
|
674
|
+
},
|
|
675
|
+
2: {
|
|
676
|
+
"521": "monday",
|
|
677
|
+
"522": "tuesday",
|
|
678
|
+
"523": "wednesday",
|
|
679
|
+
"524": "thursday",
|
|
680
|
+
"525": "friday",
|
|
681
|
+
"526": "saturday",
|
|
682
|
+
"527": "sunday",
|
|
683
|
+
"536": "standard_values",
|
|
684
|
+
},
|
|
685
|
+
}
|
|
@@ -141,23 +141,10 @@ class DaySchedule:
|
|
|
141
141
|
|
|
142
142
|
|
|
143
143
|
@dataclass
|
|
144
|
-
class
|
|
145
|
-
"""
|
|
144
|
+
class WeeklySchedule:
|
|
145
|
+
"""Base weekly schedule with optional day schedules.
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
Each day can have up to 3 time slots.
|
|
149
|
-
|
|
150
|
-
Example:
|
|
151
|
-
>>> schedule = DHWSchedule(
|
|
152
|
-
... monday=DaySchedule(slots=[
|
|
153
|
-
... TimeSlot(time(6, 0), time(8, 0)),
|
|
154
|
-
... TimeSlot(time(17, 0), time(21, 0)),
|
|
155
|
-
... ]),
|
|
156
|
-
... tuesday=DaySchedule(slots=[
|
|
157
|
-
... TimeSlot(time(6, 0), time(8, 0)),
|
|
158
|
-
... ])
|
|
159
|
-
... )
|
|
160
|
-
>>> await client.set_hot_water_schedule(schedule)
|
|
147
|
+
Each day can have up to 3 time slots (validated by DaySchedule).
|
|
161
148
|
|
|
162
149
|
"""
|
|
163
150
|
|
|
@@ -190,6 +177,38 @@ class DHWSchedule:
|
|
|
190
177
|
)
|
|
191
178
|
|
|
192
179
|
|
|
180
|
+
@dataclass
|
|
181
|
+
class DHWSchedule(WeeklySchedule):
|
|
182
|
+
"""Weekly hot water schedule for setting time programs.
|
|
183
|
+
|
|
184
|
+
Use this dataclass to set DHW time programs via set_hot_water_schedule().
|
|
185
|
+
Each day can have up to 3 time slots.
|
|
186
|
+
|
|
187
|
+
Example:
|
|
188
|
+
>>> schedule = DHWSchedule(
|
|
189
|
+
... monday=DaySchedule(slots=[
|
|
190
|
+
... TimeSlot(time(6, 0), time(8, 0)),
|
|
191
|
+
... TimeSlot(time(17, 0), time(21, 0)),
|
|
192
|
+
... ]),
|
|
193
|
+
... tuesday=DaySchedule(slots=[
|
|
194
|
+
... TimeSlot(time(6, 0), time(8, 0)),
|
|
195
|
+
... ])
|
|
196
|
+
... )
|
|
197
|
+
>>> await client.set_hot_water_schedule(schedule)
|
|
198
|
+
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@dataclass
|
|
203
|
+
class HeatingSchedule(WeeklySchedule):
|
|
204
|
+
"""Weekly heating schedule for setting time programs.
|
|
205
|
+
|
|
206
|
+
Use this dataclass to set heating time programs via set_heating_schedule().
|
|
207
|
+
Each day can have up to 3 time slots.
|
|
208
|
+
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
|
|
193
212
|
@dataclass
|
|
194
213
|
class DHWTimeSwitchPrograms:
|
|
195
214
|
"""Dataclass for DHW time switch programs."""
|
|
@@ -307,13 +326,13 @@ class EntityInfo(BaseModel, Generic[T]):
|
|
|
307
326
|
unit: str
|
|
308
327
|
desc: str
|
|
309
328
|
value: T | None = None
|
|
310
|
-
data_type: int = Field(
|
|
329
|
+
data_type: int = Field(validation_alias="dataType", default=0)
|
|
311
330
|
error: int = 0
|
|
312
331
|
readonly: int = 0
|
|
313
332
|
readwrite: int = 0
|
|
314
333
|
precision: float | None = None
|
|
315
|
-
data_type_name: str = Field(default="",
|
|
316
|
-
data_type_family: str = Field(default="",
|
|
334
|
+
data_type_name: str = Field(default="", validation_alias="dataType_name")
|
|
335
|
+
data_type_family: str = Field(default="", validation_alias="dataType_family")
|
|
317
336
|
|
|
318
337
|
@model_validator(mode="before")
|
|
319
338
|
@classmethod
|
|
@@ -554,6 +573,29 @@ class HotWaterSchedule(BaseModel):
|
|
|
554
573
|
dhw_time_program_standard_values: EntityInfo[int] | None = None
|
|
555
574
|
|
|
556
575
|
|
|
576
|
+
class HeatingTimeSwitchPrograms(BaseModel):
|
|
577
|
+
"""Heating time switch programs for a specific heating circuit (READ).
|
|
578
|
+
|
|
579
|
+
The daily time programs (Monday-Sunday) use BSB-LAN dataType 9
|
|
580
|
+
(TIMEPROG) and return schedule strings like
|
|
581
|
+
``"13:00-15:00 ##:##-##:## ##:##-##:##"`` where ``##:##`` marks
|
|
582
|
+
unused time slots.
|
|
583
|
+
|
|
584
|
+
``standard_values`` is a YESNO enum (0=No, 1=Yes) that resets
|
|
585
|
+
all daily schedules back to the controller's factory defaults.
|
|
586
|
+
|
|
587
|
+
"""
|
|
588
|
+
|
|
589
|
+
monday: EntityInfo[str | int] | None = None
|
|
590
|
+
tuesday: EntityInfo[str | int] | None = None
|
|
591
|
+
wednesday: EntityInfo[str | int] | None = None
|
|
592
|
+
thursday: EntityInfo[str | int] | None = None
|
|
593
|
+
friday: EntityInfo[str | int] | None = None
|
|
594
|
+
saturday: EntityInfo[str | int] | None = None
|
|
595
|
+
sunday: EntityInfo[str | int] | None = None
|
|
596
|
+
standard_values: EntityInfo[int] | None = None
|
|
597
|
+
|
|
598
|
+
|
|
557
599
|
class DeviceTime(BaseModel):
|
|
558
600
|
"""Object holds device time information."""
|
|
559
601
|
|