python-bsblan 5.2.0__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.2.0 → python_bsblan-5.2.1}/.github/workflows/codeql.yaml +2 -2
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/docs.yaml +1 -1
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/linting.yaml +2 -2
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/release-drafter.yaml +1 -1
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/release.yaml +1 -1
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/scorecard.yml +1 -1
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/tests.yaml +3 -3
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/typing.yaml +1 -1
- python_bsblan-5.2.1/.nvmrc +1 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/PKG-INFO +1 -1
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/pyproject.toml +3 -3
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/src/bsblan/bsblan.py +16 -42
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/src/bsblan/models.py +3 -3
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_circuit.py +49 -120
- python_bsblan-5.2.0/.nvmrc +0 -1
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.editorconfig +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.gitattributes +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/CODE_OF_CONDUCT.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/CONTRIBUTING.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/SECURITY.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/copilot-instructions.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/labels.yml +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/prompts/add-parameter.prompt.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/prompts/code-review.prompt.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/release-drafter.yml +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/renovate.json +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/skills/bsblan-parameters/SKILL.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/skills/bsblan-testing/SKILL.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/auto-approve-renovate.yml +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/dependency-review.yaml +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/labels.yaml +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/lock.yaml +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/pr-labels.yaml +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/workflows/stale.yaml +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.github/zizmor.yml +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.gitignore +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.pre-commit-config.yaml +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.prettierignore +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/.yamllint +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/AGENTS.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/CLAUDE.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/LICENSE.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/Makefile +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/README.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/docs/api/client.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/docs/api/constants.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/docs/api/exceptions.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/docs/api/models.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/docs/getting-started.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/docs/index.md +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/examples/control.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/examples/discovery.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/examples/fetch_param.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/examples/profile_init.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/examples/ruff.toml +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/examples/speed_test.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/mkdocs.yml +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/package-lock.json +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/package.json +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/sonar-project.properties +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/src/bsblan/__init__.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/src/bsblan/constants.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/src/bsblan/exceptions.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/src/bsblan/py.typed +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/src/bsblan/utility.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/__init__.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/conftest.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/device.json +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/dict_version.json +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/hot_water_state.json +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/info.json +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/password.txt +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/sensor.json +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/state.json +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/state_circuit2.json +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/static_state.json +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/static_state_circuit2.json +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/thermostat_hvac.json +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/thermostat_temp.json +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/fixtures/time.json +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/ruff.toml +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_api_initialization.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_api_validation.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_auth.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_backoff_retry.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_bsblan.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_bsblan_edge_cases.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_configuration.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_constants.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_context_manager.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_device.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_dhw_time_switch.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_entity_info.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_entity_info_ha.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_heating_schedule.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_hot_water_additional.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_hotwater_state.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_include_parameter.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_info.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_initialization.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_read_parameters.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_reset_validation.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_schedule_models.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_sensor.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_set_heating_schedule.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_set_hot_water_schedule.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_set_hotwater.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_state.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_static_state.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_temperature_unit.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_temperature_validation.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_thermostat.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_time.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_utility.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_utility_additional.py +0 -0
- {python_bsblan-5.2.0 → python_bsblan-5.2.1}/tests/test_utility_edge_cases.py +0 -0
- {python_bsblan-5.2.0 → 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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-bsblan"
|
|
3
|
-
version = "5.2.
|
|
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",
|
|
@@ -152,13 +152,10 @@ class BSBLAN:
|
|
|
152
152
|
async def get_available_circuits(self) -> list[int]:
|
|
153
153
|
"""Detect which heating circuits are available on the device.
|
|
154
154
|
|
|
155
|
-
Uses
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
circuit returns ``value="0"`` with ``desc="---"``.
|
|
160
|
-
|
|
161
|
-
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.
|
|
162
159
|
|
|
163
160
|
This is useful for integration setup flows (e.g., Home Assistant
|
|
164
161
|
config flow) to discover how many circuits the user's controller
|
|
@@ -179,46 +176,23 @@ class BSBLAN:
|
|
|
179
176
|
response = await self._request(
|
|
180
177
|
params={"Parameter": param_id},
|
|
181
178
|
)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
# Secondary check: query the status parameter.
|
|
188
|
-
# Inactive circuits either:
|
|
189
|
-
# - return value="0" and desc="---"
|
|
190
|
-
# - return an empty dict {} (param not supported)
|
|
191
|
-
status_id = CircuitConfig.STATUS_PARAMS[circuit]
|
|
192
|
-
status_resp = await self._request(
|
|
193
|
-
params={"Parameter": status_id},
|
|
179
|
+
except BSBLANError:
|
|
180
|
+
logger.debug(
|
|
181
|
+
"Circuit %d not available (operating mode request failed)",
|
|
182
|
+
circuit,
|
|
194
183
|
)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
# Empty response means the parameter doesn't exist
|
|
198
|
-
if not status_data or not isinstance(status_data, dict):
|
|
199
|
-
logger.debug(
|
|
200
|
-
"Circuit %d has no status data (not supported)",
|
|
201
|
-
circuit,
|
|
202
|
-
)
|
|
203
|
-
continue
|
|
204
|
-
|
|
205
|
-
# value="0" + desc="---" means inactive
|
|
206
|
-
if (
|
|
207
|
-
status_data.get("desc") == CircuitConfig.INACTIVE_MARKER
|
|
208
|
-
and str(status_data.get("value", "")) == "0"
|
|
209
|
-
):
|
|
210
|
-
logger.debug(
|
|
211
|
-
"Circuit %d has status '---' (inactive)",
|
|
212
|
-
circuit,
|
|
213
|
-
)
|
|
214
|
-
continue
|
|
184
|
+
continue
|
|
215
185
|
|
|
216
|
-
|
|
217
|
-
|
|
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):
|
|
218
189
|
logger.debug(
|
|
219
|
-
"Circuit %d
|
|
190
|
+
"Circuit %d has no operating mode data (not supported)",
|
|
220
191
|
circuit,
|
|
221
192
|
)
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
available.append(circuit)
|
|
222
196
|
return sorted(available)
|
|
223
197
|
|
|
224
198
|
async def _setup_api_validator(self) -> None:
|
|
@@ -326,13 +326,13 @@ class EntityInfo(BaseModel, Generic[T]):
|
|
|
326
326
|
unit: str
|
|
327
327
|
desc: str
|
|
328
328
|
value: T | None = None
|
|
329
|
-
data_type: int = Field(
|
|
329
|
+
data_type: int = Field(validation_alias="dataType", default=0)
|
|
330
330
|
error: int = 0
|
|
331
331
|
readonly: int = 0
|
|
332
332
|
readwrite: int = 0
|
|
333
333
|
precision: float | None = None
|
|
334
|
-
data_type_name: str = Field(default="",
|
|
335
|
-
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")
|
|
336
336
|
|
|
337
337
|
@model_validator(mode="before")
|
|
338
338
|
@classmethod
|
|
@@ -13,7 +13,7 @@ import pytest
|
|
|
13
13
|
from aresponses import Response, ResponsesMockServer
|
|
14
14
|
|
|
15
15
|
from bsblan import BSBLAN, BSBLANConfig, State, StaticState
|
|
16
|
-
from bsblan.constants import ErrorMsg, build_api_config
|
|
16
|
+
from bsblan.constants import CircuitConfig, ErrorMsg, build_api_config
|
|
17
17
|
from bsblan.exceptions import BSBLANError, BSBLANInvalidParameterError
|
|
18
18
|
from bsblan.utility import APIValidator
|
|
19
19
|
|
|
@@ -544,26 +544,20 @@ async def test_get_available_circuits_two_circuits(
|
|
|
544
544
|
# HC1 operating mode
|
|
545
545
|
if param_id == "700":
|
|
546
546
|
return {"700": {"value": "1", "unit": "", "desc": "Automatic"}}
|
|
547
|
-
# HC1 status - active
|
|
548
|
-
if param_id == "8000":
|
|
549
|
-
return {
|
|
550
|
-
"8000": {
|
|
551
|
-
"value": "114",
|
|
552
|
-
"desc": "Heating mode Comfort",
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
547
|
# HC2 operating mode
|
|
556
548
|
if param_id == "1000":
|
|
557
549
|
return {"1000": {"value": "1", "unit": "", "desc": "Automatic"}}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
return {"8001": {"value": "114", "desc": "Heating mode Comfort"}}
|
|
561
|
-
return {}
|
|
550
|
+
msg = f"Unexpected parameter probe: {param_id}"
|
|
551
|
+
raise AssertionError(msg)
|
|
562
552
|
|
|
563
|
-
|
|
553
|
+
request_mock = AsyncMock(side_effect=mock_request)
|
|
554
|
+
bsblan._request = request_mock # type: ignore[method-assign]
|
|
564
555
|
|
|
565
556
|
circuits = await bsblan.get_available_circuits()
|
|
566
557
|
assert circuits == [1, 2]
|
|
558
|
+
assert [
|
|
559
|
+
call.kwargs["params"]["Parameter"] for call in request_mock.await_args_list
|
|
560
|
+
] == ["700", "1000"]
|
|
567
561
|
|
|
568
562
|
|
|
569
563
|
@pytest.mark.asyncio
|
|
@@ -580,33 +574,27 @@ async def test_get_available_circuits_only_one(
|
|
|
580
574
|
param_id = params.get("Parameter", "")
|
|
581
575
|
if param_id == "700":
|
|
582
576
|
return {"700": {"value": "3", "unit": "", "desc": "Comfort"}}
|
|
583
|
-
if param_id == "8000":
|
|
584
|
-
return {
|
|
585
|
-
"8000": {
|
|
586
|
-
"value": "114",
|
|
587
|
-
"desc": "Heating mode Comfort",
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
577
|
# HC2 operating mode - return empty
|
|
591
578
|
if param_id == "1000":
|
|
592
579
|
return {param_id: {}}
|
|
593
|
-
|
|
580
|
+
msg = f"Unexpected parameter probe: {param_id}"
|
|
581
|
+
raise AssertionError(msg)
|
|
594
582
|
|
|
595
|
-
|
|
583
|
+
request_mock = AsyncMock(side_effect=mock_request)
|
|
584
|
+
bsblan._request = request_mock # type: ignore[method-assign]
|
|
596
585
|
|
|
597
586
|
circuits = await bsblan.get_available_circuits()
|
|
598
587
|
assert circuits == [1]
|
|
588
|
+
assert [
|
|
589
|
+
call.kwargs["params"]["Parameter"] for call in request_mock.await_args_list
|
|
590
|
+
] == ["700", "1000"]
|
|
599
591
|
|
|
600
592
|
|
|
601
593
|
@pytest.mark.asyncio
|
|
602
|
-
async def
|
|
594
|
+
async def test_get_available_circuits_does_not_probe_status_params(
|
|
603
595
|
mock_bsblan_circuit: BSBLAN,
|
|
604
596
|
) -> None:
|
|
605
|
-
"""Test
|
|
606
|
-
|
|
607
|
-
This is the real-world scenario: the device returns a valid operating
|
|
608
|
-
mode for all circuits, but status param shows '---' for HC2.
|
|
609
|
-
"""
|
|
597
|
+
"""Test discovery only queries probe params, not status params."""
|
|
610
598
|
bsblan = mock_bsblan_circuit
|
|
611
599
|
|
|
612
600
|
async def mock_request(
|
|
@@ -617,61 +605,20 @@ async def test_get_available_circuits_inactive_by_status(
|
|
|
617
605
|
# All circuits return valid operating mode
|
|
618
606
|
if param_id in {"700", "1000"}:
|
|
619
607
|
return {param_id: {"value": "1", "unit": "", "desc": "Automatic"}}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
return {
|
|
623
|
-
"8000": {
|
|
624
|
-
"value": "114",
|
|
625
|
-
"desc": "Heating mode Comfort",
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
# HC2 status - inactive (value=0, desc=---)
|
|
629
|
-
if param_id == "8001":
|
|
630
|
-
return {"8001": {"value": "0", "desc": "---"}}
|
|
631
|
-
return {}
|
|
608
|
+
msg = f"Unexpected status parameter probe: {param_id}"
|
|
609
|
+
raise AssertionError(msg)
|
|
632
610
|
|
|
633
|
-
|
|
611
|
+
request_mock = AsyncMock(side_effect=mock_request)
|
|
612
|
+
bsblan._request = request_mock # type: ignore[method-assign]
|
|
634
613
|
|
|
635
614
|
circuits = await bsblan.get_available_circuits()
|
|
636
|
-
assert circuits == [1]
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
"""Test that circuits with empty status response are inactive.
|
|
644
|
-
|
|
645
|
-
Some controllers return an empty dict for status params of circuits
|
|
646
|
-
that don't exist (e.g., HC2 status 8001 returns {}).
|
|
647
|
-
"""
|
|
648
|
-
bsblan = mock_bsblan_circuit
|
|
649
|
-
|
|
650
|
-
async def mock_request(
|
|
651
|
-
**kwargs: Any,
|
|
652
|
-
) -> dict[str, Any]:
|
|
653
|
-
params = kwargs.get("params", {})
|
|
654
|
-
param_id = params.get("Parameter", "")
|
|
655
|
-
# All circuits return valid operating mode
|
|
656
|
-
if param_id in {"700", "1000"}:
|
|
657
|
-
return {param_id: {"value": "1", "unit": "", "desc": "Automatic"}}
|
|
658
|
-
# HC1 status - active
|
|
659
|
-
if param_id == "8000":
|
|
660
|
-
return {
|
|
661
|
-
"8000": {
|
|
662
|
-
"value": "114",
|
|
663
|
-
"desc": "Heating mode Comfort",
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
# HC2 status - empty response (param not supported)
|
|
667
|
-
if param_id == "8001":
|
|
668
|
-
return {}
|
|
669
|
-
return {}
|
|
670
|
-
|
|
671
|
-
bsblan._request = AsyncMock(side_effect=mock_request) # type: ignore[method-assign]
|
|
672
|
-
|
|
673
|
-
circuits = await bsblan.get_available_circuits()
|
|
674
|
-
assert circuits == [1]
|
|
615
|
+
assert circuits == [1, 2]
|
|
616
|
+
assert {
|
|
617
|
+
call.kwargs["params"]["Parameter"] for call in request_mock.await_args_list
|
|
618
|
+
} == {
|
|
619
|
+
"700",
|
|
620
|
+
"1000",
|
|
621
|
+
}
|
|
675
622
|
|
|
676
623
|
|
|
677
624
|
@pytest.mark.asyncio
|
|
@@ -681,27 +628,19 @@ async def test_get_available_circuits_request_failure(
|
|
|
681
628
|
"""Test circuit detection when some requests fail."""
|
|
682
629
|
bsblan = mock_bsblan_circuit
|
|
683
630
|
|
|
684
|
-
call_count = 0
|
|
685
|
-
|
|
686
631
|
async def mock_request(
|
|
687
632
|
**kwargs: Any,
|
|
688
633
|
) -> dict[str, Any]:
|
|
689
|
-
nonlocal call_count
|
|
690
|
-
call_count += 1
|
|
691
634
|
params = kwargs.get("params", {})
|
|
692
635
|
param_id = params.get("Parameter", "")
|
|
693
636
|
if param_id == "700":
|
|
694
637
|
return {"700": {"value": "1", "unit": "", "desc": "Automatic"}}
|
|
695
|
-
if param_id == "8000":
|
|
696
|
-
return {
|
|
697
|
-
"8000": {
|
|
698
|
-
"value": "114",
|
|
699
|
-
"desc": "Heating mode Comfort",
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
638
|
# HC2 fail with connection error
|
|
703
|
-
|
|
704
|
-
|
|
639
|
+
if param_id == "1000":
|
|
640
|
+
msg = "Connection failed"
|
|
641
|
+
raise BSBLANError(msg)
|
|
642
|
+
msg = f"Unexpected parameter probe: {param_id}"
|
|
643
|
+
raise AssertionError(msg)
|
|
705
644
|
|
|
706
645
|
bsblan._request = AsyncMock(side_effect=mock_request) # type: ignore[method-assign]
|
|
707
646
|
|
|
@@ -723,15 +662,11 @@ async def test_get_available_circuits_param_not_in_response(
|
|
|
723
662
|
param_id = params.get("Parameter", "")
|
|
724
663
|
if param_id == "700":
|
|
725
664
|
return {"700": {"value": "1", "unit": "", "desc": "Automatic"}}
|
|
726
|
-
if param_id == "8000":
|
|
727
|
-
return {
|
|
728
|
-
"8000": {
|
|
729
|
-
"value": "114",
|
|
730
|
-
"desc": "Heating mode Comfort",
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
665
|
# Returns a response but without the expected param key
|
|
734
|
-
|
|
666
|
+
if param_id == "1000":
|
|
667
|
+
return {"other_key": {"value": "1"}}
|
|
668
|
+
msg = f"Unexpected parameter probe: {param_id}"
|
|
669
|
+
raise AssertionError(msg)
|
|
735
670
|
|
|
736
671
|
bsblan._request = AsyncMock(side_effect=mock_request) # type: ignore[method-assign]
|
|
737
672
|
|
|
@@ -740,37 +675,31 @@ async def test_get_available_circuits_param_not_in_response(
|
|
|
740
675
|
|
|
741
676
|
|
|
742
677
|
@pytest.mark.asyncio
|
|
743
|
-
async def
|
|
678
|
+
async def test_get_available_circuits_all_probes_missing(
|
|
744
679
|
mock_bsblan_circuit: BSBLAN,
|
|
745
680
|
) -> None:
|
|
746
|
-
"""Test
|
|
747
|
-
|
|
748
|
-
If the operating mode returns valid data but the status request fails,
|
|
749
|
-
the circuit should be excluded (fail-safe).
|
|
750
|
-
"""
|
|
681
|
+
"""Test no circuits are detected when operating mode probes are missing."""
|
|
751
682
|
bsblan = mock_bsblan_circuit
|
|
683
|
+
expected_params = list(CircuitConfig.PROBE_PARAMS.values())
|
|
752
684
|
|
|
753
685
|
async def mock_request(
|
|
754
686
|
**kwargs: Any,
|
|
755
687
|
) -> dict[str, Any]:
|
|
756
688
|
params = kwargs.get("params", {})
|
|
757
689
|
param_id = params.get("Parameter", "")
|
|
758
|
-
if param_id
|
|
759
|
-
return {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
msg = "Connection failed"
|
|
763
|
-
raise BSBLANError(msg)
|
|
764
|
-
# HC2 return empty
|
|
765
|
-
if param_id == "1000":
|
|
766
|
-
return {param_id: {}}
|
|
767
|
-
return {}
|
|
690
|
+
if param_id in expected_params:
|
|
691
|
+
return {}
|
|
692
|
+
msg = f"Unexpected parameter probe: {param_id}"
|
|
693
|
+
raise AssertionError(msg)
|
|
768
694
|
|
|
769
|
-
|
|
695
|
+
request_mock = AsyncMock(side_effect=mock_request)
|
|
696
|
+
bsblan._request = request_mock # type: ignore[method-assign]
|
|
770
697
|
|
|
771
|
-
# HC1 status request fails -> entire circuit probe fails -> excluded
|
|
772
698
|
circuits = await bsblan.get_available_circuits()
|
|
773
699
|
assert circuits == []
|
|
700
|
+
assert [
|
|
701
|
+
call.kwargs["params"]["Parameter"] for call in request_mock.await_args_list
|
|
702
|
+
] == expected_params
|
|
774
703
|
|
|
775
704
|
|
|
776
705
|
@pytest.mark.asyncio
|
python_bsblan-5.2.0/.nvmrc
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
24.14.1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|