python-bsblan 3.0.0__tar.gz → 3.1.0__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.0.0 → python_bsblan-3.1.0}/.github/workflows/codeql.yaml +3 -3
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/workflows/labels.yaml +1 -1
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/workflows/linting.yaml +6 -6
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/workflows/release.yaml +1 -1
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/workflows/tests.yaml +3 -3
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/workflows/typing.yaml +1 -1
- python_bsblan-3.1.0/.nvmrc +1 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/PKG-INFO +1 -1
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/examples/control.py +9 -9
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/pyproject.toml +21 -8
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/src/bsblan/bsblan.py +11 -9
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/fixtures/thermostat_hvac.json +1 -1
- python_bsblan-3.1.0/tests/test_constants.py +152 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_hotwater_state.py +5 -8
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_set_hotwater.py +10 -10
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_thermostat.py +2 -2
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/uv.lock +508 -482
- python_bsblan-3.0.0/.nvmrc +0 -1
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.editorconfig +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.gitattributes +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/CODE_OF_CONDUCT.md +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/CONTRIBUTING.md +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/LICENSE.md +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/labels.yml +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/release-drafter.yml +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/renovate.json +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/workflows/lock.yaml +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/workflows/pr-labels.yaml +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/workflows/release-drafter.yaml +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.github/workflows/stale.yaml +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.gitignore +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.pre-commit-config.yaml +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.prettierignore +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/.yamllint +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/README.md +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/examples/ruff.toml +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/package-lock.json +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/package.json +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/sonar-project.properties +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/src/bsblan/__init__.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/src/bsblan/constants.py +1 -1
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/src/bsblan/exceptions.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/src/bsblan/models.py +1 -1
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/src/bsblan/py.typed +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/src/bsblan/utility.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/__init__.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/conftest.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/fixtures/device.json +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/fixtures/dict_version.json +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/fixtures/hot_water_state.json +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/fixtures/info.json +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/fixtures/password.txt +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/fixtures/sensor.json +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/fixtures/state.json +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/fixtures/static_state.json +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/fixtures/thermostat_temp.json +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/fixtures/time.json +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/ruff.toml +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_api_initialization.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_api_validation.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_auth.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_bsblan.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_bsblan_edge_cases.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_configuration.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_context_manager.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_device.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_dhw_time_switch.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_entity_info.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_hot_water_additional.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_info.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_initialization.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_reset_validation.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_sensor.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_state.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_static_state.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_temperature_unit.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_temperature_validation.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_time.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_utility.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_utility_additional.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_utility_edge_cases.py +0 -0
- {python_bsblan-3.0.0 → python_bsblan-3.1.0}/tests/test_version_errors.py +0 -0
|
@@ -17,8 +17,8 @@ jobs:
|
|
|
17
17
|
runs-on: ubuntu-latest
|
|
18
18
|
steps:
|
|
19
19
|
- name: ⤵️ Check out code from GitHub
|
|
20
|
-
uses: actions/checkout@
|
|
20
|
+
uses: actions/checkout@v5.0.0
|
|
21
21
|
- name: 🏗 Initialize CodeQL
|
|
22
|
-
uses: github/codeql-action/init@v3.
|
|
22
|
+
uses: github/codeql-action/init@v3.30.3
|
|
23
23
|
- name: 🚀 Perform CodeQL Analysis
|
|
24
|
-
uses: github/codeql-action/analyze@v3.
|
|
24
|
+
uses: github/codeql-action/analyze@v3.30.3
|
|
@@ -16,7 +16,7 @@ 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@v5.0.0
|
|
20
20
|
- name: 🏗 Set up uv
|
|
21
21
|
uses: astral-sh/setup-uv@v6
|
|
22
22
|
with:
|
|
@@ -36,7 +36,7 @@ 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@v5.0.0
|
|
40
40
|
- name: 🏗 Set up uv
|
|
41
41
|
uses: astral-sh/setup-uv@v6
|
|
42
42
|
with:
|
|
@@ -58,7 +58,7 @@ 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@v5.0.0
|
|
62
62
|
- name: 🏗 Set up uv
|
|
63
63
|
uses: astral-sh/setup-uv@v6
|
|
64
64
|
with:
|
|
@@ -102,7 +102,7 @@ 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@v5.0.0
|
|
106
106
|
- name: 🏗 Set up uv
|
|
107
107
|
uses: astral-sh/setup-uv@v6
|
|
108
108
|
with:
|
|
@@ -122,7 +122,7 @@ 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@v5.0.0
|
|
126
126
|
- name: 🏗 Set up uv
|
|
127
127
|
uses: astral-sh/setup-uv@v6
|
|
128
128
|
with:
|
|
@@ -142,7 +142,7 @@ 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@v5.0.0
|
|
146
146
|
- name: 🏗 Set up uv
|
|
147
147
|
uses: astral-sh/setup-uv@v6
|
|
148
148
|
with:
|
|
@@ -19,7 +19,7 @@ jobs:
|
|
|
19
19
|
python: ["3.11", "3.12"]
|
|
20
20
|
steps:
|
|
21
21
|
- name: ⤵️ Check out code from GitHub
|
|
22
|
-
uses: actions/checkout@
|
|
22
|
+
uses: actions/checkout@v5.0.0
|
|
23
23
|
- name: 🏗 Set up uv
|
|
24
24
|
uses: astral-sh/setup-uv@v6
|
|
25
25
|
with:
|
|
@@ -45,7 +45,7 @@ jobs:
|
|
|
45
45
|
needs: pytest
|
|
46
46
|
steps:
|
|
47
47
|
- name: ⤵️ Check out code from GitHub
|
|
48
|
-
uses: actions/checkout@
|
|
48
|
+
uses: actions/checkout@v5.0.0
|
|
49
49
|
with:
|
|
50
50
|
fetch-depth: 0
|
|
51
51
|
- name: ⬇️ Download coverage data
|
|
@@ -66,7 +66,7 @@ jobs:
|
|
|
66
66
|
uv run coverage combine coverage*/.coverage*
|
|
67
67
|
uv run coverage xml -i
|
|
68
68
|
- name: 🚀 Upload coverage report
|
|
69
|
-
uses: codecov/codecov-action@v5.
|
|
69
|
+
uses: codecov/codecov-action@v5.5.1
|
|
70
70
|
with:
|
|
71
71
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
72
72
|
- name: SonarCloud Scan
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
22.19.0
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"""Asynchronous Python client for BSBLan.
|
|
3
3
|
|
|
4
4
|
This example demonstrates the optimized hot water functionality:
|
|
5
|
-
- HotWaterState: Essential parameters for frequent polling (
|
|
6
|
-
- HotWaterConfig: Configuration parameters checked less frequently (
|
|
5
|
+
- HotWaterState: Essential parameters for frequent polling (5 fields)
|
|
6
|
+
- HotWaterConfig: Configuration parameters checked less frequently (16 fields)
|
|
7
7
|
- HotWaterSchedule: Time program schedules checked occasionally (8 fields)
|
|
8
8
|
|
|
9
9
|
This three-tier approach reduces API calls by 79% for regular monitoring.
|
|
@@ -169,9 +169,6 @@ async def print_hot_water_state(hot_water_state: HotWaterState) -> None:
|
|
|
169
169
|
"Nominal Setpoint": await get_attribute(
|
|
170
170
|
hot_water_state.nominal_setpoint, "value", "N/A"
|
|
171
171
|
),
|
|
172
|
-
"Reduced Setpoint": await get_attribute(
|
|
173
|
-
hot_water_state.reduced_setpoint, "value", "N/A"
|
|
174
|
-
),
|
|
175
172
|
"Release": await get_attribute(hot_water_state.release, "desc", "N/A"),
|
|
176
173
|
"Current Temperature": await get_attribute(
|
|
177
174
|
hot_water_state.dhw_actual_value_top_temperature, "value", "N/A"
|
|
@@ -195,6 +192,9 @@ async def print_hot_water_config(hot_water_config: HotWaterConfig) -> None:
|
|
|
195
192
|
"Nominal Setpoint Max": await get_attribute(
|
|
196
193
|
hot_water_config.nominal_setpoint_max, "value", "N/A"
|
|
197
194
|
),
|
|
195
|
+
"Reduced Setpoint": await get_attribute(
|
|
196
|
+
hot_water_config.reduced_setpoint, "value", "N/A"
|
|
197
|
+
),
|
|
198
198
|
"Legionella Function": await get_attribute(
|
|
199
199
|
hot_water_config.legionella_function, "desc", "N/A"
|
|
200
200
|
),
|
|
@@ -300,14 +300,14 @@ async def main() -> None:
|
|
|
300
300
|
try:
|
|
301
301
|
hot_water_config: HotWaterConfig = await bsblan.hot_water_config()
|
|
302
302
|
await print_hot_water_config(hot_water_config)
|
|
303
|
-
except Exception as e: # noqa: BLE001
|
|
303
|
+
except Exception as e: # noqa: BLE001 - Broad exception for demo purposes
|
|
304
304
|
print(f"Hot water configuration not available: {e}")
|
|
305
305
|
|
|
306
306
|
# Get hot water schedule (time programs)
|
|
307
307
|
try:
|
|
308
308
|
hot_water_schedule: HotWaterSchedule = await bsblan.hot_water_schedule()
|
|
309
309
|
await print_hot_water_schedule(hot_water_schedule)
|
|
310
|
-
except Exception as e: # noqa: BLE001
|
|
310
|
+
except Exception as e: # noqa: BLE001 - Broad exception for demo purposes
|
|
311
311
|
print(f"Hot water schedule not available: {e}")
|
|
312
312
|
|
|
313
313
|
# Example: Set DHW time program for Monday
|
|
@@ -321,8 +321,8 @@ async def main() -> None:
|
|
|
321
321
|
# Example: Set device time
|
|
322
322
|
print("\nSetting device time to current system time")
|
|
323
323
|
# Get current local system time and format it for BSB-LAN (DD.MM.YYYY HH:MM:SS)
|
|
324
|
-
# Note: Using local time intentionally to sync BSB-LAN
|
|
325
|
-
current_time = datetime.now().replace(microsecond=0) # noqa: DTZ005
|
|
324
|
+
# Note: Using local time intentionally for this demo to sync BSB-LAN
|
|
325
|
+
current_time = datetime.now().replace(microsecond=0) # noqa: DTZ005 - Demo uses local time
|
|
326
326
|
formatted_time = current_time.strftime("%d.%m.%Y %H:%M:%S")
|
|
327
327
|
print(f"Current system time: {formatted_time}")
|
|
328
328
|
await bsblan.set_time(formatted_time)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-bsblan"
|
|
3
|
-
version = "3.
|
|
3
|
+
version = "3.1.0"
|
|
4
4
|
description = "Asynchronous Python client for BSBLAN API"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Willem-Jan van Rootselaar", email = "liudgervr@gmail.com"}
|
|
@@ -46,11 +46,24 @@ packages = ["src/bsblan"]
|
|
|
46
46
|
|
|
47
47
|
[tool.coverage.run]
|
|
48
48
|
plugins = ["covdefaults"]
|
|
49
|
-
source = ["bsblan"]
|
|
49
|
+
source = ["src/bsblan"]
|
|
50
|
+
omit = [
|
|
51
|
+
"tests/*",
|
|
52
|
+
"*/tests/*",
|
|
53
|
+
"test_*",
|
|
54
|
+
"*test*"
|
|
55
|
+
]
|
|
50
56
|
|
|
51
57
|
[tool.coverage.report]
|
|
52
58
|
show_missing = true
|
|
53
59
|
fail_under = 53
|
|
60
|
+
exclude_lines = [
|
|
61
|
+
"pragma: no cover",
|
|
62
|
+
"def __repr__",
|
|
63
|
+
"raise AssertionError",
|
|
64
|
+
"raise NotImplementedError",
|
|
65
|
+
"if __name__ == .__main__.:",
|
|
66
|
+
]
|
|
54
67
|
|
|
55
68
|
[tool.mypy]
|
|
56
69
|
# Specify the target platform details in config, so your developers are
|
|
@@ -165,26 +178,26 @@ dev = [
|
|
|
165
178
|
"aresponses==3.0.0",
|
|
166
179
|
"bandit==1.8.6",
|
|
167
180
|
"black==25.1.0",
|
|
168
|
-
"blacken-docs==1.
|
|
181
|
+
"blacken-docs==1.20.0",
|
|
169
182
|
"codespell==2.4.1",
|
|
170
183
|
"covdefaults==2.3.0",
|
|
171
|
-
"coverage==7.10.
|
|
184
|
+
"coverage==7.10.6",
|
|
172
185
|
"darglint==1.8.1",
|
|
173
186
|
"flake8==7.3.0",
|
|
174
187
|
"flake8-simplify==0.22.0",
|
|
175
188
|
# hatch is required to support type hinting and proper packaging of the py.typed file.
|
|
176
189
|
"hatch>=1.14.1",
|
|
177
190
|
"isort==6.0.1",
|
|
178
|
-
"mypy==1.
|
|
191
|
+
"mypy==1.18.1",
|
|
179
192
|
"pre-commit==4.3.0",
|
|
180
193
|
"pre-commit-hooks==6.0.0",
|
|
181
194
|
"pylint==3.3.8",
|
|
182
195
|
"pytest>=8.3.5",
|
|
183
196
|
"pytest-asyncio==1.1.0",
|
|
184
|
-
"pytest-cov==6.
|
|
197
|
+
"pytest-cov==6.3.0",
|
|
185
198
|
"pyupgrade==3.20.0",
|
|
186
|
-
"ruff==0.
|
|
187
|
-
"safety==3.6.
|
|
199
|
+
"ruff==0.13.0",
|
|
200
|
+
"safety==3.6.1",
|
|
188
201
|
"vulture==2.14",
|
|
189
202
|
"yamllint==1.37.1",
|
|
190
203
|
]
|
|
@@ -358,8 +358,10 @@ class BSBLAN:
|
|
|
358
358
|
raise BSBLANConnectionError(BSBLANConnectionError.message_error) from e
|
|
359
359
|
except aiohttp.ClientError as e:
|
|
360
360
|
raise BSBLANConnectionError(BSBLANConnectionError.message_error) from e
|
|
361
|
-
except ValueError as e:
|
|
362
|
-
|
|
361
|
+
except (ValueError, UnicodeDecodeError) as e:
|
|
362
|
+
# Handle JSON decode errors and other parsing issues
|
|
363
|
+
error_msg = f"Invalid response format from BSB-LAN device: {e!s}"
|
|
364
|
+
raise BSBLANError(error_msg) from e
|
|
363
365
|
|
|
364
366
|
def _process_response(
|
|
365
367
|
self, response_data: dict[str, Any], base_path: str
|
|
@@ -610,7 +612,7 @@ class BSBLAN:
|
|
|
610
612
|
state.update(
|
|
611
613
|
{
|
|
612
614
|
"Parameter": "700",
|
|
613
|
-
"
|
|
615
|
+
"Value": str(HVAC_MODE_DICT_REVERSE[hvac_mode]),
|
|
614
616
|
"Type": "1",
|
|
615
617
|
},
|
|
616
618
|
)
|
|
@@ -946,7 +948,7 @@ class BSBLAN:
|
|
|
946
948
|
state.update(
|
|
947
949
|
{
|
|
948
950
|
"Parameter": "1600",
|
|
949
|
-
"
|
|
951
|
+
"Value": str(operating_mode),
|
|
950
952
|
"Type": "1",
|
|
951
953
|
},
|
|
952
954
|
)
|
|
@@ -954,7 +956,7 @@ class BSBLAN:
|
|
|
954
956
|
state.update(
|
|
955
957
|
{
|
|
956
958
|
"Parameter": "1601",
|
|
957
|
-
"
|
|
959
|
+
"Value": str(eco_mode_selection),
|
|
958
960
|
"Type": "1",
|
|
959
961
|
},
|
|
960
962
|
)
|
|
@@ -962,7 +964,7 @@ class BSBLAN:
|
|
|
962
964
|
state.update(
|
|
963
965
|
{
|
|
964
966
|
"Parameter": "1630",
|
|
965
|
-
"
|
|
967
|
+
"Value": str(dhw_charging_priority),
|
|
966
968
|
"Type": "1",
|
|
967
969
|
},
|
|
968
970
|
)
|
|
@@ -978,7 +980,7 @@ class BSBLAN:
|
|
|
978
980
|
state.update(
|
|
979
981
|
{
|
|
980
982
|
"Parameter": "1647",
|
|
981
|
-
"
|
|
983
|
+
"Value": str(legionella_circulation_pump),
|
|
982
984
|
"Type": "1",
|
|
983
985
|
},
|
|
984
986
|
)
|
|
@@ -994,7 +996,7 @@ class BSBLAN:
|
|
|
994
996
|
state.update(
|
|
995
997
|
{
|
|
996
998
|
"Parameter": "1660",
|
|
997
|
-
"
|
|
999
|
+
"Value": str(dhw_circulation_pump_release),
|
|
998
1000
|
"Type": "1",
|
|
999
1001
|
},
|
|
1000
1002
|
)
|
|
@@ -1018,7 +1020,7 @@ class BSBLAN:
|
|
|
1018
1020
|
state.update(
|
|
1019
1021
|
{
|
|
1020
1022
|
"Parameter": "1680",
|
|
1021
|
-
"
|
|
1023
|
+
"Value": str(operating_mode_changeover),
|
|
1022
1024
|
"Type": "1",
|
|
1023
1025
|
},
|
|
1024
1026
|
)
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Test BSBLAN constants module."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from bsblan.constants import (
|
|
6
|
+
API_V1,
|
|
7
|
+
API_V3,
|
|
8
|
+
BASE_HOT_WATER_PARAMS,
|
|
9
|
+
HOT_WATER_CONFIG_PARAMS,
|
|
10
|
+
HOT_WATER_ESSENTIAL_PARAMS,
|
|
11
|
+
HOT_WATER_SCHEDULE_PARAMS,
|
|
12
|
+
build_api_config,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.parametrize(
|
|
17
|
+
("version", "expected_includes", "expected_excludes"),
|
|
18
|
+
[
|
|
19
|
+
(
|
|
20
|
+
"v1",
|
|
21
|
+
{"700", "710", "714", "730"}, # hvac_mode, target_temp, min_temp, v1_max
|
|
22
|
+
{"770", "716"}, # v3_boost, v3_max_temp
|
|
23
|
+
),
|
|
24
|
+
(
|
|
25
|
+
"v3",
|
|
26
|
+
{"700", "710", "714", "770", "716"}, # base + v3 extensions
|
|
27
|
+
{"730"}, # v1_max_temp
|
|
28
|
+
),
|
|
29
|
+
(
|
|
30
|
+
"v5", # Unknown version
|
|
31
|
+
{"700", "710", "714"}, # only base parameters
|
|
32
|
+
{"770", "730", "716"}, # no extensions
|
|
33
|
+
),
|
|
34
|
+
],
|
|
35
|
+
)
|
|
36
|
+
def test_build_api_config_versions(
|
|
37
|
+
version: str,
|
|
38
|
+
expected_includes: set[str],
|
|
39
|
+
expected_excludes: set[str],
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Test building API config for different versions."""
|
|
42
|
+
config = build_api_config(version)
|
|
43
|
+
|
|
44
|
+
# Check expected parameters are included
|
|
45
|
+
for param_id in expected_includes:
|
|
46
|
+
assert param_id in (
|
|
47
|
+
config["heating"]
|
|
48
|
+
| config["staticValues"]
|
|
49
|
+
| config["device"]
|
|
50
|
+
| config["sensor"]
|
|
51
|
+
| config["hot_water"]
|
|
52
|
+
), f"Parameter {param_id} missing in {version} config"
|
|
53
|
+
|
|
54
|
+
# Check excluded parameters are not included
|
|
55
|
+
for param_id in expected_excludes:
|
|
56
|
+
assert param_id not in (config["heating"] | config["staticValues"]), (
|
|
57
|
+
f"Parameter {param_id} should not be in {version} config"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@pytest.mark.parametrize(
|
|
62
|
+
("api_config", "should_have", "should_not_have"),
|
|
63
|
+
[
|
|
64
|
+
(API_V1, {"730"}, {"770", "716"}), # V1 has 730, not 770/716
|
|
65
|
+
(API_V3, {"770", "716"}, {"730"}), # V3 has 770/716, not 730
|
|
66
|
+
],
|
|
67
|
+
)
|
|
68
|
+
def test_pre_built_api_configurations(
|
|
69
|
+
api_config: dict[str, dict[str, str]],
|
|
70
|
+
should_have: set[str],
|
|
71
|
+
should_not_have: set[str],
|
|
72
|
+
) -> None:
|
|
73
|
+
"""Test that pre-built API configurations are correct."""
|
|
74
|
+
all_params = set(api_config["heating"].keys()) | set(
|
|
75
|
+
api_config["staticValues"].keys()
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
for param_id in should_have:
|
|
79
|
+
assert param_id in all_params
|
|
80
|
+
|
|
81
|
+
for param_id in should_not_have:
|
|
82
|
+
assert param_id not in all_params
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_hot_water_parameter_groups_completeness() -> None:
|
|
86
|
+
"""Test that hot water parameter groups cover all parameters."""
|
|
87
|
+
all_grouped_params = (
|
|
88
|
+
HOT_WATER_ESSENTIAL_PARAMS | HOT_WATER_CONFIG_PARAMS | HOT_WATER_SCHEDULE_PARAMS
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# All BASE_HOT_WATER_PARAMS should be categorized into one of the groups
|
|
92
|
+
all_base_param_ids = set(BASE_HOT_WATER_PARAMS.keys())
|
|
93
|
+
assert all_grouped_params == all_base_param_ids
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@pytest.mark.parametrize(
|
|
97
|
+
("group1", "group2"),
|
|
98
|
+
[
|
|
99
|
+
(HOT_WATER_ESSENTIAL_PARAMS, HOT_WATER_CONFIG_PARAMS),
|
|
100
|
+
(HOT_WATER_ESSENTIAL_PARAMS, HOT_WATER_SCHEDULE_PARAMS),
|
|
101
|
+
(HOT_WATER_CONFIG_PARAMS, HOT_WATER_SCHEDULE_PARAMS),
|
|
102
|
+
],
|
|
103
|
+
)
|
|
104
|
+
def test_hot_water_parameter_groups_no_overlap(
|
|
105
|
+
group1: set[str],
|
|
106
|
+
group2: set[str],
|
|
107
|
+
) -> None:
|
|
108
|
+
"""Test that hot water parameter groups don't overlap."""
|
|
109
|
+
assert not (group1 & group2), f"Groups should not overlap: {group1 & group2}"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@pytest.mark.parametrize(
|
|
113
|
+
("group", "expected_count"),
|
|
114
|
+
[
|
|
115
|
+
(HOT_WATER_ESSENTIAL_PARAMS, 5), # Current optimized count
|
|
116
|
+
(HOT_WATER_CONFIG_PARAMS, 16), # Configuration parameters
|
|
117
|
+
(HOT_WATER_SCHEDULE_PARAMS, 8), # Time program parameters
|
|
118
|
+
],
|
|
119
|
+
)
|
|
120
|
+
def test_hot_water_parameter_groups_expected_counts(
|
|
121
|
+
group: set[str],
|
|
122
|
+
expected_count: int,
|
|
123
|
+
) -> None:
|
|
124
|
+
"""Test that hot water parameter groups have expected counts."""
|
|
125
|
+
assert len(group) == expected_count
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_hot_water_parameter_groups_total_count() -> None:
|
|
129
|
+
"""Test that total grouped parameters match base parameters."""
|
|
130
|
+
total_grouped = (
|
|
131
|
+
len(HOT_WATER_ESSENTIAL_PARAMS)
|
|
132
|
+
+ len(HOT_WATER_CONFIG_PARAMS)
|
|
133
|
+
+ len(HOT_WATER_SCHEDULE_PARAMS)
|
|
134
|
+
)
|
|
135
|
+
assert total_grouped == len(BASE_HOT_WATER_PARAMS)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@pytest.mark.parametrize("version", ["v1", "v3"])
|
|
139
|
+
def test_api_config_structure(version: str) -> None:
|
|
140
|
+
"""Test that API config has required structure."""
|
|
141
|
+
config = build_api_config(version)
|
|
142
|
+
|
|
143
|
+
# Check all required sections exist
|
|
144
|
+
required_sections = {"heating", "staticValues", "device", "sensor", "hot_water"}
|
|
145
|
+
assert set(config.keys()) == required_sections
|
|
146
|
+
|
|
147
|
+
# Check that each section is a dict with string keys and values
|
|
148
|
+
for section_name, section in config.items():
|
|
149
|
+
assert isinstance(section, dict)
|
|
150
|
+
for key, value in section.items():
|
|
151
|
+
assert isinstance(key, str), f"Key {key} in {section_name} not string"
|
|
152
|
+
assert isinstance(value, str), f"Value {value} in {section_name} not string"
|
|
@@ -12,7 +12,7 @@ import aiohttp
|
|
|
12
12
|
import pytest
|
|
13
13
|
|
|
14
14
|
from bsblan import BSBLAN, BSBLANConfig, HotWaterState
|
|
15
|
-
from bsblan.constants import API_V3
|
|
15
|
+
from bsblan.constants import API_V3, APIConfig
|
|
16
16
|
from bsblan.utility import APIValidator
|
|
17
17
|
|
|
18
18
|
from . import load_fixture
|
|
@@ -32,7 +32,7 @@ async def test_hot_water_state(
|
|
|
32
32
|
monkeypatch.setattr(bsblan, "_api_version", "v3")
|
|
33
33
|
|
|
34
34
|
# Create a modified API_V3 excluding the time switch parameters
|
|
35
|
-
test_api_v3 = {
|
|
35
|
+
test_api_v3: APIConfig = {
|
|
36
36
|
"heating": API_V3["heating"].copy(),
|
|
37
37
|
"staticValues": API_V3["staticValues"].copy(),
|
|
38
38
|
"device": API_V3["device"].copy(),
|
|
@@ -54,12 +54,12 @@ async def test_hot_water_state(
|
|
|
54
54
|
hot_water_cache = {
|
|
55
55
|
"1600": "operating_mode",
|
|
56
56
|
"1610": "nominal_setpoint",
|
|
57
|
-
"1612": "reduced_setpoint",
|
|
58
57
|
"1620": "release",
|
|
59
58
|
"8830": "dhw_actual_value_top_temperature",
|
|
60
59
|
"8820": "state_dhw_pump",
|
|
61
60
|
# Add other parameters that would be in the full cache
|
|
62
61
|
"1601": "eco_mode_selection",
|
|
62
|
+
"1612": "reduced_setpoint", # Now in config
|
|
63
63
|
"1614": "nominal_setpoint_max",
|
|
64
64
|
}
|
|
65
65
|
bsblan.set_hot_water_cache(hot_water_cache)
|
|
@@ -92,23 +92,20 @@ async def test_hot_water_state(
|
|
|
92
92
|
assert hot_water_state.operating_mode.value == 1
|
|
93
93
|
assert hot_water_state.nominal_setpoint is not None
|
|
94
94
|
assert hot_water_state.nominal_setpoint.value == 50.0
|
|
95
|
-
assert hot_water_state.reduced_setpoint is not None
|
|
96
|
-
assert hot_water_state.reduced_setpoint.value == 10.0
|
|
97
95
|
assert hot_water_state.release is not None
|
|
98
96
|
assert hot_water_state.release.value == 2
|
|
99
97
|
assert hot_water_state.dhw_actual_value_top_temperature is not None
|
|
100
98
|
assert hot_water_state.dhw_actual_value_top_temperature.value == 36.5
|
|
101
99
|
assert hot_water_state.state_dhw_pump is not None
|
|
102
100
|
assert hot_water_state.state_dhw_pump.value == 255
|
|
103
|
-
# The Parameter string should only include the
|
|
101
|
+
# The Parameter string should only include the 5 essential parameters
|
|
104
102
|
request_mock.assert_called_once()
|
|
105
103
|
params = request_mock.call_args[1]["params"]["Parameter"].split(",")
|
|
106
|
-
assert len(params) ==
|
|
104
|
+
assert len(params) == 5
|
|
107
105
|
|
|
108
106
|
expected_essential_params = [
|
|
109
107
|
"1600", # operating_mode
|
|
110
108
|
"1610", # nominal_setpoint
|
|
111
|
-
"1612", # reduced_setpoint
|
|
112
109
|
"1620", # release
|
|
113
110
|
"8830", # dhw_actual_value_top_temperature
|
|
114
111
|
"8820", # state_dhw_pump
|
|
@@ -53,7 +53,7 @@ async def test_set_hot_water(mock_bsblan: BSBLAN) -> None:
|
|
|
53
53
|
base_path="/JS",
|
|
54
54
|
data={
|
|
55
55
|
"Parameter": "1601",
|
|
56
|
-
"
|
|
56
|
+
"Value": "1",
|
|
57
57
|
"Type": "1",
|
|
58
58
|
},
|
|
59
59
|
)
|
|
@@ -63,7 +63,7 @@ async def test_set_hot_water(mock_bsblan: BSBLAN) -> None:
|
|
|
63
63
|
base_path="/JS",
|
|
64
64
|
data={
|
|
65
65
|
"Parameter": "1630",
|
|
66
|
-
"
|
|
66
|
+
"Value": "1",
|
|
67
67
|
"Type": "1",
|
|
68
68
|
},
|
|
69
69
|
)
|
|
@@ -83,7 +83,7 @@ async def test_set_hot_water(mock_bsblan: BSBLAN) -> None:
|
|
|
83
83
|
base_path="/JS",
|
|
84
84
|
data={
|
|
85
85
|
"Parameter": "1647",
|
|
86
|
-
"
|
|
86
|
+
"Value": "1",
|
|
87
87
|
"Type": "1",
|
|
88
88
|
},
|
|
89
89
|
)
|
|
@@ -103,7 +103,7 @@ async def test_set_hot_water(mock_bsblan: BSBLAN) -> None:
|
|
|
103
103
|
base_path="/JS",
|
|
104
104
|
data={
|
|
105
105
|
"Parameter": "1660",
|
|
106
|
-
"
|
|
106
|
+
"Value": "2",
|
|
107
107
|
"Type": "1",
|
|
108
108
|
},
|
|
109
109
|
)
|
|
@@ -133,7 +133,7 @@ async def test_set_hot_water(mock_bsblan: BSBLAN) -> None:
|
|
|
133
133
|
base_path="/JS",
|
|
134
134
|
data={
|
|
135
135
|
"Parameter": "1680",
|
|
136
|
-
"
|
|
136
|
+
"Value": "1",
|
|
137
137
|
"Type": "1",
|
|
138
138
|
},
|
|
139
139
|
)
|
|
@@ -188,7 +188,7 @@ async def test_prepare_hot_water_state(mock_bsblan: BSBLAN) -> None:
|
|
|
188
188
|
|
|
189
189
|
assert state == {
|
|
190
190
|
"Parameter": "1600",
|
|
191
|
-
"
|
|
191
|
+
"Value": "3",
|
|
192
192
|
"Type": "1",
|
|
193
193
|
}
|
|
194
194
|
|
|
@@ -201,7 +201,7 @@ async def test_prepare_hot_water_state(mock_bsblan: BSBLAN) -> None:
|
|
|
201
201
|
)
|
|
202
202
|
assert state == {
|
|
203
203
|
"Parameter": "1601",
|
|
204
|
-
"
|
|
204
|
+
"Value": "1",
|
|
205
205
|
"Type": "1",
|
|
206
206
|
}
|
|
207
207
|
|
|
@@ -213,7 +213,7 @@ async def test_prepare_hot_water_state(mock_bsblan: BSBLAN) -> None:
|
|
|
213
213
|
)
|
|
214
214
|
assert state == {
|
|
215
215
|
"Parameter": "1630",
|
|
216
|
-
"
|
|
216
|
+
"Value": "1",
|
|
217
217
|
"Type": "1",
|
|
218
218
|
}
|
|
219
219
|
|
|
@@ -237,7 +237,7 @@ async def test_prepare_hot_water_state(mock_bsblan: BSBLAN) -> None:
|
|
|
237
237
|
)
|
|
238
238
|
assert state == {
|
|
239
239
|
"Parameter": "1647",
|
|
240
|
-
"
|
|
240
|
+
"Value": "1",
|
|
241
241
|
"Type": "1",
|
|
242
242
|
}
|
|
243
243
|
|
|
@@ -254,7 +254,7 @@ async def test_set_hot_water_state(
|
|
|
254
254
|
"""
|
|
255
255
|
state = {
|
|
256
256
|
"Parameter": "1600",
|
|
257
|
-
"
|
|
257
|
+
"Value": "3",
|
|
258
258
|
"Type": "1",
|
|
259
259
|
}
|
|
260
260
|
await mock_bsblan._set_hot_water_state(state)
|
|
@@ -71,7 +71,7 @@ def create_response_handler(expected_data: dict[str, Any]) -> Response:
|
|
|
71
71
|
|
|
72
72
|
for key, value in expected_data.items():
|
|
73
73
|
assert key in actual_data, f"Expected key '{key}' not found in actual data"
|
|
74
|
-
if key == "
|
|
74
|
+
if key == "Value":
|
|
75
75
|
# Allow both string and integer representations
|
|
76
76
|
assert str(actual_data[key]) == str(value)
|
|
77
77
|
else:
|
|
@@ -116,7 +116,7 @@ async def test_change_hvac_mode(
|
|
|
116
116
|
"""Test changing BSBLAN HVAC mode."""
|
|
117
117
|
expected_data = {
|
|
118
118
|
"Parameter": "700",
|
|
119
|
-
"
|
|
119
|
+
"Value": "1", # 1 corresponds to "auto" mode
|
|
120
120
|
"Type": "1",
|
|
121
121
|
}
|
|
122
122
|
mock_aresponses.add(
|