python-bsblan 0.6.4__tar.gz → 0.6.6__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-0.6.4 → python_bsblan-0.6.6}/PKG-INFO +6 -5
- {python_bsblan-0.6.4 → python_bsblan-0.6.6}/pyproject.toml +26 -23
- {python_bsblan-0.6.4 → python_bsblan-0.6.6}/src/bsblan/bsblan.py +194 -34
- {python_bsblan-0.6.4 → python_bsblan-0.6.6}/src/bsblan/constants.py +3 -0
- {python_bsblan-0.6.4 → python_bsblan-0.6.6}/src/bsblan/exceptions.py +18 -3
- {python_bsblan-0.6.4 → python_bsblan-0.6.6}/src/bsblan/models.py +3 -0
- {python_bsblan-0.6.4 → python_bsblan-0.6.6}/README.md +0 -0
- {python_bsblan-0.6.4 → python_bsblan-0.6.6}/src/bsblan/__init__.py +0 -0
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-bsblan
|
|
3
|
-
Version: 0.6.
|
|
4
|
-
Summary: Asynchronous Python client for BSBLAN
|
|
3
|
+
Version: 0.6.6
|
|
4
|
+
Summary: Asynchronous Python client for BSBLAN API
|
|
5
5
|
Home-page: https://github.com/liudger/python-bsblan
|
|
6
6
|
License: MIT
|
|
7
|
-
Keywords: bsblan,thermostat,client,api
|
|
7
|
+
Keywords: bsblan,thermostat,client,api,async
|
|
8
8
|
Author: Willem-Jan van Rootselaar
|
|
9
9
|
Author-email: liudgervr@gmail.com
|
|
10
10
|
Maintainer: Willem-Jan van Rootselaar
|
|
11
11
|
Maintainer-email: liudgervr@gmail.com
|
|
12
|
-
Requires-Python: >=3.
|
|
12
|
+
Requires-Python: >=3.11,<4.0
|
|
13
13
|
Classifier: Development Status :: 3 - Alpha
|
|
14
14
|
Classifier: Framework :: AsyncIO
|
|
15
15
|
Classifier: Intended Audience :: Developers
|
|
16
16
|
Classifier: License :: OSI Approved :: MIT License
|
|
17
17
|
Classifier: Natural Language :: English
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
23
|
Requires-Dist: aiohttp (>=3.8.1)
|
|
23
24
|
Requires-Dist: async-timeout (>=4.0.3,<5.0.0)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "python-bsblan"
|
|
3
|
-
version = "0.6.
|
|
4
|
-
description = "Asynchronous Python client for BSBLAN"
|
|
3
|
+
version = "0.6.6"
|
|
4
|
+
description = "Asynchronous Python client for BSBLAN API"
|
|
5
5
|
authors = ["Willem-Jan van Rootselaar <liudgervr@gmail.com>"]
|
|
6
6
|
maintainers = ["Willem-Jan van Rootselaar <liudgervr@gmail.com>"]
|
|
7
7
|
license = "MIT"
|
|
@@ -9,7 +9,7 @@ readme = "README.md"
|
|
|
9
9
|
homepage = "https://github.com/liudger/python-bsblan"
|
|
10
10
|
repository = "https://github.com/liudger/python-bsblan"
|
|
11
11
|
documentation = "https://github.com/liudger/python-bsblan"
|
|
12
|
-
keywords = ["bsblan", "thermostat", "client" , "api"]
|
|
12
|
+
keywords = ["bsblan", "thermostat", "client" , "api", "async"]
|
|
13
13
|
classifiers = [
|
|
14
14
|
"Development Status :: 3 - Alpha",
|
|
15
15
|
"Framework :: AsyncIO",
|
|
@@ -25,7 +25,7 @@ packages = [
|
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
[tool.poetry.dependencies]
|
|
28
|
-
python = "^3.
|
|
28
|
+
python = "^3.11"
|
|
29
29
|
aiohttp = ">=3.8.1"
|
|
30
30
|
yarl = ">=1.7.2"
|
|
31
31
|
packaging = ">=21.3"
|
|
@@ -34,9 +34,13 @@ async-timeout = "^4.0.3"
|
|
|
34
34
|
mashumaro = "^3.13.1"
|
|
35
35
|
orjson = "^3.9.10"
|
|
36
36
|
|
|
37
|
+
[tool.poetry.urls]
|
|
38
|
+
"Bug Tracker" = "https://github.com/liudger/python-bsblan/issues"
|
|
39
|
+
Changelog = "https://github.com/liudger/python-bsblan/releases"
|
|
40
|
+
|
|
37
41
|
[tool.poetry.dev-dependencies]
|
|
38
42
|
covdefaults = "^2.3.0"
|
|
39
|
-
ruff = "^0.
|
|
43
|
+
ruff = "^0.7.0"
|
|
40
44
|
aresponses = "^3.0.0"
|
|
41
45
|
black = "^24.0.0"
|
|
42
46
|
blacken-docs = "^1.13.0"
|
|
@@ -44,8 +48,8 @@ coverage = "^7.0.5"
|
|
|
44
48
|
flake8 = "^7.0.0"
|
|
45
49
|
isort = "^5.11.4"
|
|
46
50
|
mypy = "^1.0.0"
|
|
47
|
-
pre-commit = "^
|
|
48
|
-
pre-commit-hooks = "^
|
|
51
|
+
pre-commit = "^4.0.0"
|
|
52
|
+
pre-commit-hooks = "^5.0.0"
|
|
49
53
|
pylint = "^3.0.0"
|
|
50
54
|
pytest = "^8.0.0"
|
|
51
55
|
pytest-asyncio = "^0.24.0"
|
|
@@ -59,27 +63,21 @@ safety = "^3.0.0"
|
|
|
59
63
|
codespell = "^2.2.2"
|
|
60
64
|
bandit = "^1.7.4"
|
|
61
65
|
|
|
62
|
-
[tool.poetry.urls]
|
|
63
|
-
"Bug Tracker" = "https://github.com/liudger/python-bsblan/issues"
|
|
64
|
-
Changelog = "https://github.com/liudger/python-bsblan/releases"
|
|
65
|
-
|
|
66
|
-
[tool.coverage.report]
|
|
67
|
-
show_missing = true
|
|
68
|
-
fail_under = 53
|
|
69
66
|
|
|
70
67
|
[tool.coverage.run]
|
|
71
68
|
plugins = ["covdefaults"]
|
|
72
69
|
source = ["bsblan"]
|
|
73
70
|
|
|
74
|
-
[tool.
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
[tool.coverage.report]
|
|
72
|
+
show_missing = true
|
|
73
|
+
fail_under = 53
|
|
77
74
|
|
|
78
75
|
[tool.mypy]
|
|
79
76
|
# Specify the target platform details in config, so your developers are
|
|
80
77
|
# free to run mypy on Windows, Linux, or macOS and get consistent
|
|
81
78
|
# results.
|
|
82
79
|
platform = "linux"
|
|
80
|
+
python_version = "3.11"
|
|
83
81
|
|
|
84
82
|
# show error messages from unrelated files
|
|
85
83
|
follow_imports = "normal"
|
|
@@ -106,9 +104,6 @@ warn_unused_configs = true
|
|
|
106
104
|
warn_unused_ignores = true
|
|
107
105
|
|
|
108
106
|
[tool.pylint.MASTER]
|
|
109
|
-
extension-pkg-whitelist = [
|
|
110
|
-
"pydantic"
|
|
111
|
-
]
|
|
112
107
|
ignore= [
|
|
113
108
|
"tests"
|
|
114
109
|
]
|
|
@@ -128,11 +123,11 @@ good-names = [
|
|
|
128
123
|
]
|
|
129
124
|
|
|
130
125
|
[tool.pylint.DESIGN]
|
|
131
|
-
max-attributes =
|
|
126
|
+
max-attributes = 10
|
|
132
127
|
|
|
133
128
|
[tool.pylint."MESSAGES CONTROL"]
|
|
134
129
|
disable= [
|
|
135
|
-
"too-few-public-methods",
|
|
130
|
+
# "too-few-public-methods",
|
|
136
131
|
"duplicate-code",
|
|
137
132
|
"format",
|
|
138
133
|
"unsubscriptable-object",
|
|
@@ -153,11 +148,16 @@ asyncio_mode = "auto"
|
|
|
153
148
|
select = ["ALL"]
|
|
154
149
|
ignore = [
|
|
155
150
|
"ANN101", # Self... explanatory
|
|
151
|
+
"ANN102", # cls... Not classy enough
|
|
156
152
|
"ANN401", # Opinioated warning on disallowing dynamically typed expressions
|
|
157
153
|
"D203", # Conflicts with other rules
|
|
158
154
|
"D213", # Conflicts with other rules
|
|
159
155
|
"D417", # False positives in some occasions
|
|
160
156
|
"PLR2004", # Just annoying, not really useful
|
|
157
|
+
|
|
158
|
+
# Conflicts with the Ruff formatter
|
|
159
|
+
"COM812",
|
|
160
|
+
"ISC001",
|
|
161
161
|
]
|
|
162
162
|
|
|
163
163
|
[tool.ruff.lint.flake8-pytest-style]
|
|
@@ -167,9 +167,12 @@ fixture-parentheses = false
|
|
|
167
167
|
[tool.ruff.lint.isort]
|
|
168
168
|
known-first-party = ["bsblan"]
|
|
169
169
|
|
|
170
|
+
[tool.ruff.lint.flake8-type-checking]
|
|
171
|
+
runtime-evaluated-base-classes = ["mashumaro.mixins.orjson.DataClassORJSONMixin"]
|
|
172
|
+
|
|
170
173
|
[tool.ruff.lint.mccabe]
|
|
171
174
|
max-complexity = 25
|
|
172
175
|
|
|
173
176
|
[build-system]
|
|
174
|
-
build-backend = "poetry.core.masonry.api"
|
|
175
177
|
requires = ["poetry-core>=1.0.0"]
|
|
178
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -11,7 +11,6 @@ import aiohttp
|
|
|
11
11
|
from aiohttp.hdrs import METH_POST
|
|
12
12
|
from aiohttp.helpers import BasicAuth
|
|
13
13
|
from packaging import version as pkg_version
|
|
14
|
-
from typing_extensions import Self
|
|
15
14
|
from yarl import URL
|
|
16
15
|
|
|
17
16
|
from .constants import (
|
|
@@ -38,6 +37,7 @@ from .models import Device, HotWaterState, Info, Sensor, State, StaticState
|
|
|
38
37
|
|
|
39
38
|
if TYPE_CHECKING:
|
|
40
39
|
from aiohttp.client import ClientSession
|
|
40
|
+
from typing_extensions import Self
|
|
41
41
|
|
|
42
42
|
logging.basicConfig(level=logging.DEBUG)
|
|
43
43
|
logger = logging.getLogger(__name__)
|
|
@@ -71,7 +71,12 @@ class BSBLAN:
|
|
|
71
71
|
_initialized: bool = False
|
|
72
72
|
|
|
73
73
|
async def __aenter__(self) -> Self:
|
|
74
|
-
"""Enter the context manager.
|
|
74
|
+
"""Enter the context manager.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Self: The initialized BSBLAN instance.
|
|
78
|
+
|
|
79
|
+
"""
|
|
75
80
|
if self.session is None:
|
|
76
81
|
self.session = aiohttp.ClientSession()
|
|
77
82
|
self._close_session = True
|
|
@@ -79,7 +84,12 @@ class BSBLAN:
|
|
|
79
84
|
return self
|
|
80
85
|
|
|
81
86
|
async def __aexit__(self, *args: object) -> None:
|
|
82
|
-
"""Exit the context manager.
|
|
87
|
+
"""Exit the context manager.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
*args: Variable length argument list.
|
|
91
|
+
|
|
92
|
+
"""
|
|
83
93
|
if self._close_session and self.session:
|
|
84
94
|
await self.session.close()
|
|
85
95
|
|
|
@@ -100,7 +110,13 @@ class BSBLAN:
|
|
|
100
110
|
self._set_api_version()
|
|
101
111
|
|
|
102
112
|
def _set_api_version(self) -> None:
|
|
103
|
-
"""Set the API version based on the firmware version.
|
|
113
|
+
"""Set the API version based on the firmware version.
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
BSBLANError: If the firmware version is not set.
|
|
117
|
+
BSBLANVersionError: If the firmware version is not supported.
|
|
118
|
+
|
|
119
|
+
"""
|
|
104
120
|
if not self._firmware_version:
|
|
105
121
|
raise BSBLANError(FIRMWARE_VERSION_ERROR_MSG)
|
|
106
122
|
|
|
@@ -126,7 +142,15 @@ class BSBLAN:
|
|
|
126
142
|
)
|
|
127
143
|
|
|
128
144
|
async def _initialize_api_data(self) -> APIConfig:
|
|
129
|
-
"""Initialize and cache the API data.
|
|
145
|
+
"""Initialize and cache the API data.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
APIConfig: The API configuration data.
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
BSBLANError: If the API version or data is not initialized.
|
|
152
|
+
|
|
153
|
+
"""
|
|
130
154
|
if self._api_data is None:
|
|
131
155
|
if self._api_version is None:
|
|
132
156
|
raise BSBLANError(API_VERSION_ERROR_MSG)
|
|
@@ -143,7 +167,23 @@ class BSBLAN:
|
|
|
143
167
|
data: dict[str, object] | None = None,
|
|
144
168
|
params: Mapping[str, str | int] | str | None = None,
|
|
145
169
|
) -> dict[str, Any]:
|
|
146
|
-
"""Handle a request to a BSBLAN device.
|
|
170
|
+
"""Handle a request to a BSBLAN device.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
method (str): The HTTP method to use for the request.
|
|
174
|
+
base_path (str): The base path for the URL.
|
|
175
|
+
data (dict[str, object] | None): The data to send in the request body.
|
|
176
|
+
params (Mapping[str, str | int] | str | None): The query parameters
|
|
177
|
+
to include in the request.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
dict[str, Any]: The JSON response from the BSBLAN device.
|
|
181
|
+
|
|
182
|
+
Raises:
|
|
183
|
+
BSBLANConnectionError: If there is a connection error.
|
|
184
|
+
BSBLANError: If there is an error with the request.
|
|
185
|
+
|
|
186
|
+
"""
|
|
147
187
|
if self.session is None:
|
|
148
188
|
raise BSBLANError(SESSION_NOT_INITIALIZED_ERROR_MSG)
|
|
149
189
|
url = self._build_url(base_path)
|
|
@@ -170,7 +210,15 @@ class BSBLAN:
|
|
|
170
210
|
raise BSBLANError(str(e)) from e
|
|
171
211
|
|
|
172
212
|
def _build_url(self, base_path: str) -> URL:
|
|
173
|
-
"""Build the URL for the request.
|
|
213
|
+
"""Build the URL for the request.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
base_path (str): The base path for the URL.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
URL: The constructed URL.
|
|
220
|
+
|
|
221
|
+
"""
|
|
174
222
|
if self.config.passkey:
|
|
175
223
|
base_path = f"/{self.config.passkey}{base_path}"
|
|
176
224
|
return URL.build(
|
|
@@ -181,30 +229,63 @@ class BSBLAN:
|
|
|
181
229
|
)
|
|
182
230
|
|
|
183
231
|
def _get_auth(self) -> BasicAuth | None:
|
|
184
|
-
"""Get the authentication for the request.
|
|
232
|
+
"""Get the authentication for the request.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
BasicAuth | None: The authentication object or None if no authentication
|
|
236
|
+
is required.
|
|
237
|
+
|
|
238
|
+
"""
|
|
185
239
|
if self.config.username and self.config.password:
|
|
186
240
|
return BasicAuth(self.config.username, self.config.password)
|
|
187
241
|
return None
|
|
188
242
|
|
|
189
243
|
def _get_headers(self) -> dict[str, str]:
|
|
190
|
-
"""Get the headers for the request.
|
|
244
|
+
"""Get the headers for the request.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
dict[str, str]: The headers for the request.
|
|
248
|
+
|
|
249
|
+
"""
|
|
191
250
|
return {
|
|
192
251
|
"User-Agent": f"PythonBSBLAN/{self._firmware_version}",
|
|
193
252
|
"Accept": "application/json, */*",
|
|
194
253
|
}
|
|
195
254
|
|
|
196
255
|
def _validate_single_parameter(self, *params: Any, error_msg: str) -> None:
|
|
197
|
-
"""Validate that exactly one parameter is provided.
|
|
256
|
+
"""Validate that exactly one parameter is provided.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
*params: Variable length argument list of parameters to validate.
|
|
260
|
+
error_msg (str): The error message to raise if validation fails.
|
|
261
|
+
|
|
262
|
+
Raises:
|
|
263
|
+
BSBLANError: If the validation fails.
|
|
264
|
+
|
|
265
|
+
"""
|
|
198
266
|
if sum(param is not None for param in params) != 1:
|
|
199
267
|
raise BSBLANError(error_msg)
|
|
200
268
|
|
|
201
269
|
async def _get_parameters(self, params: dict[Any, Any]) -> dict[Any, Any]:
|
|
202
|
-
"""Get the parameters info from BSBLAN device.
|
|
270
|
+
"""Get the parameters info from BSBLAN device.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
params (dict[Any, Any]): The parameters to get info for.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
dict[Any, Any]: The parameters info from the BSBLAN device.
|
|
277
|
+
|
|
278
|
+
"""
|
|
203
279
|
string_params = ",".join(map(str, params))
|
|
204
280
|
return {"string_par": string_params, "list": list(params.values())}
|
|
205
281
|
|
|
206
282
|
async def state(self) -> State:
|
|
207
|
-
"""Get the current state from BSBLAN device.
|
|
283
|
+
"""Get the current state from BSBLAN device.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
State: The current state of the BSBLAN device.
|
|
287
|
+
|
|
288
|
+
"""
|
|
208
289
|
api_data = await self._initialize_api_data()
|
|
209
290
|
params = await self._get_parameters(api_data["heating"])
|
|
210
291
|
data = await self._request(params={"Parameter": params["string_par"]})
|
|
@@ -213,7 +294,12 @@ class BSBLAN:
|
|
|
213
294
|
return State.from_dict(data)
|
|
214
295
|
|
|
215
296
|
async def sensor(self) -> Sensor:
|
|
216
|
-
"""Get the sensor information from BSBLAN device.
|
|
297
|
+
"""Get the sensor information from BSBLAN device.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Sensor: The sensor information from the BSBLAN device.
|
|
301
|
+
|
|
302
|
+
"""
|
|
217
303
|
api_data = await self._initialize_api_data()
|
|
218
304
|
params = await self._get_parameters(api_data["sensor"])
|
|
219
305
|
data = await self._request(params={"Parameter": params["string_par"]})
|
|
@@ -221,7 +307,12 @@ class BSBLAN:
|
|
|
221
307
|
return Sensor.from_dict(data)
|
|
222
308
|
|
|
223
309
|
async def static_values(self) -> StaticState:
|
|
224
|
-
"""Get the static information from BSBLAN device.
|
|
310
|
+
"""Get the static information from BSBLAN device.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
StaticState: The static information from the BSBLAN device.
|
|
314
|
+
|
|
315
|
+
"""
|
|
225
316
|
api_data = await self._initialize_api_data()
|
|
226
317
|
params = await self._get_parameters(api_data["staticValues"])
|
|
227
318
|
data = await self._request(params={"Parameter": params["string_par"]})
|
|
@@ -229,12 +320,22 @@ class BSBLAN:
|
|
|
229
320
|
return StaticState.from_dict(data)
|
|
230
321
|
|
|
231
322
|
async def device(self) -> Device:
|
|
232
|
-
"""Get BSBLAN device info.
|
|
323
|
+
"""Get BSBLAN device info.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Device: The BSBLAN device information.
|
|
327
|
+
|
|
328
|
+
"""
|
|
233
329
|
device_info = await self._request(base_path="/JI")
|
|
234
330
|
return Device.from_dict(device_info)
|
|
235
331
|
|
|
236
332
|
async def info(self) -> Info:
|
|
237
|
-
"""Get information about the current heating system config.
|
|
333
|
+
"""Get information about the current heating system config.
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Info: The information about the current heating system config.
|
|
337
|
+
|
|
338
|
+
"""
|
|
238
339
|
api_data = await self._initialize_api_data()
|
|
239
340
|
params = await self._get_parameters(api_data["device"])
|
|
240
341
|
data = await self._request(params={"Parameter": params["string_par"]})
|
|
@@ -246,7 +347,13 @@ class BSBLAN:
|
|
|
246
347
|
target_temperature: str | None = None,
|
|
247
348
|
hvac_mode: str | None = None,
|
|
248
349
|
) -> None:
|
|
249
|
-
"""Change the state of the thermostat through BSB-Lan.
|
|
350
|
+
"""Change the state of the thermostat through BSB-Lan.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
target_temperature (str | None): The target temperature to set.
|
|
354
|
+
hvac_mode (str | None): The HVAC mode to set.
|
|
355
|
+
|
|
356
|
+
"""
|
|
250
357
|
await self._initialize_temperature_range()
|
|
251
358
|
|
|
252
359
|
self._validate_single_parameter(
|
|
@@ -263,11 +370,22 @@ class BSBLAN:
|
|
|
263
370
|
target_temperature: str | None,
|
|
264
371
|
hvac_mode: str | None,
|
|
265
372
|
) -> dict[str, Any]:
|
|
266
|
-
"""Prepare the thermostat state for setting.
|
|
373
|
+
"""Prepare the thermostat state for setting.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
target_temperature (str | None): The target temperature to set.
|
|
377
|
+
hvac_mode (str | None): The HVAC mode to set.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
dict[str, Any]: The prepared state for the thermostat.
|
|
381
|
+
|
|
382
|
+
"""
|
|
267
383
|
state: dict[str, Any] = {}
|
|
268
384
|
if target_temperature is not None:
|
|
269
385
|
self._validate_target_temperature(target_temperature)
|
|
270
|
-
state.update(
|
|
386
|
+
state.update(
|
|
387
|
+
{"Parameter": "710", "Value": target_temperature, "Type": "1"},
|
|
388
|
+
)
|
|
271
389
|
if hvac_mode is not None:
|
|
272
390
|
self._validate_hvac_mode(hvac_mode)
|
|
273
391
|
state.update(
|
|
@@ -280,7 +398,16 @@ class BSBLAN:
|
|
|
280
398
|
return state
|
|
281
399
|
|
|
282
400
|
def _validate_target_temperature(self, target_temperature: str) -> None:
|
|
283
|
-
"""Validate the target temperature.
|
|
401
|
+
"""Validate the target temperature.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
target_temperature (str): The target temperature to validate.
|
|
405
|
+
|
|
406
|
+
Raises:
|
|
407
|
+
BSBLANError: If the temperature range is not initialized.
|
|
408
|
+
BSBLANInvalidParameterError: If the target temperature is invalid.
|
|
409
|
+
|
|
410
|
+
"""
|
|
284
411
|
if self._min_temp is None or self._max_temp is None:
|
|
285
412
|
raise BSBLANError(TEMPERATURE_RANGE_ERROR_MSG)
|
|
286
413
|
|
|
@@ -292,17 +419,35 @@ class BSBLAN:
|
|
|
292
419
|
raise BSBLANInvalidParameterError(target_temperature) from err
|
|
293
420
|
|
|
294
421
|
def _validate_hvac_mode(self, hvac_mode: str) -> None:
|
|
295
|
-
"""Validate the HVAC mode.
|
|
422
|
+
"""Validate the HVAC mode.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
hvac_mode (str): The HVAC mode to validate.
|
|
426
|
+
|
|
427
|
+
Raises:
|
|
428
|
+
BSBLANInvalidParameterError: If the HVAC mode is invalid.
|
|
429
|
+
|
|
430
|
+
"""
|
|
296
431
|
if hvac_mode not in HVAC_MODE_DICT_REVERSE:
|
|
297
432
|
raise BSBLANInvalidParameterError(hvac_mode)
|
|
298
433
|
|
|
299
434
|
async def _set_thermostat_state(self, state: dict[str, Any]) -> None:
|
|
300
|
-
"""Set the thermostat state.
|
|
435
|
+
"""Set the thermostat state.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
state (dict[str, Any]): The state to set for the thermostat.
|
|
439
|
+
|
|
440
|
+
"""
|
|
301
441
|
response = await self._request(base_path="/JS", data=state)
|
|
302
442
|
logger.debug("Response for setting: %s", response)
|
|
303
443
|
|
|
304
444
|
async def hot_water_state(self) -> HotWaterState:
|
|
305
|
-
"""Get the current hot water state from BSBLAN device.
|
|
445
|
+
"""Get the current hot water state from BSBLAN device.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
HotWaterState: The current hot water state.
|
|
449
|
+
|
|
450
|
+
"""
|
|
306
451
|
api_data = await self._initialize_api_data()
|
|
307
452
|
params = await self._get_parameters(api_data["hot_water"])
|
|
308
453
|
data = await self._request(params={"Parameter": params["string_par"]})
|
|
@@ -311,20 +456,23 @@ class BSBLAN:
|
|
|
311
456
|
|
|
312
457
|
async def set_hot_water(
|
|
313
458
|
self,
|
|
314
|
-
operating_mode: str | None = None,
|
|
315
459
|
nominal_setpoint: float | None = None,
|
|
316
460
|
reduced_setpoint: float | None = None,
|
|
317
461
|
) -> None:
|
|
318
|
-
"""Change the state of the hot water system through BSB-Lan.
|
|
462
|
+
"""Change the state of the hot water system through BSB-Lan.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
nominal_setpoint (float | None): The nominal setpoint temperature to set.
|
|
466
|
+
reduced_setpoint (float | None): The reduced setpoint temperature to set.
|
|
467
|
+
|
|
468
|
+
"""
|
|
319
469
|
self._validate_single_parameter(
|
|
320
|
-
operating_mode,
|
|
321
470
|
nominal_setpoint,
|
|
322
471
|
reduced_setpoint,
|
|
323
472
|
error_msg=MULTI_PARAMETER_ERROR_MSG,
|
|
324
473
|
)
|
|
325
474
|
|
|
326
475
|
state = self._prepare_hot_water_state(
|
|
327
|
-
operating_mode,
|
|
328
476
|
nominal_setpoint,
|
|
329
477
|
reduced_setpoint,
|
|
330
478
|
)
|
|
@@ -332,16 +480,23 @@ class BSBLAN:
|
|
|
332
480
|
|
|
333
481
|
def _prepare_hot_water_state(
|
|
334
482
|
self,
|
|
335
|
-
operating_mode: str | None,
|
|
336
483
|
nominal_setpoint: float | None,
|
|
337
484
|
reduced_setpoint: float | None,
|
|
338
485
|
) -> dict[str, Any]:
|
|
339
|
-
"""Prepare the hot water state for setting.
|
|
486
|
+
"""Prepare the hot water state for setting.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
nominal_setpoint (float | None): The nominal setpoint temperature to set.
|
|
490
|
+
reduced_setpoint (float | None): The reduced setpoint temperature to set.
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
dict[str, Any]: The prepared state for the hot water.
|
|
494
|
+
|
|
495
|
+
Raises:
|
|
496
|
+
BSBLANError: If no state is provided.
|
|
497
|
+
|
|
498
|
+
"""
|
|
340
499
|
state: dict[str, Any] = {}
|
|
341
|
-
if operating_mode is not None:
|
|
342
|
-
state.update(
|
|
343
|
-
{"Parameter": "1600", "EnumValue": operating_mode, "Type": "1"},
|
|
344
|
-
)
|
|
345
500
|
if nominal_setpoint is not None:
|
|
346
501
|
state.update(
|
|
347
502
|
{"Parameter": "1610", "Value": str(nominal_setpoint), "Type": "1"},
|
|
@@ -355,6 +510,11 @@ class BSBLAN:
|
|
|
355
510
|
return state
|
|
356
511
|
|
|
357
512
|
async def _set_hot_water_state(self, state: dict[str, Any]) -> None:
|
|
358
|
-
"""Set the hot water state.
|
|
513
|
+
"""Set the hot water state.
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
state (dict[str, Any]): The state to set for the hot water.
|
|
517
|
+
|
|
518
|
+
"""
|
|
359
519
|
response = await self._request(base_path="/JS", data=state)
|
|
360
520
|
logger.debug("Response for setting: %s", response)
|
|
@@ -75,11 +75,14 @@ API_V3: Final[APIConfig] = {
|
|
|
75
75
|
"hot_water": {
|
|
76
76
|
"1600": "operating_mode",
|
|
77
77
|
"1610": "nominal_setpoint",
|
|
78
|
+
"1614": "nominal_setpoint_max",
|
|
78
79
|
"1612": "reduced_setpoint",
|
|
79
80
|
"1620": "release",
|
|
80
81
|
"1640": "legionella_function",
|
|
81
82
|
"1645": "legionella_setpoint",
|
|
82
83
|
"1641": "legionella_periodically",
|
|
84
|
+
"8830": "dhw_actual_value_top_temperature",
|
|
85
|
+
"8820": "state_dhw_pump",
|
|
83
86
|
},
|
|
84
87
|
}
|
|
85
88
|
|
|
@@ -9,7 +9,12 @@ class BSBLANError(Exception):
|
|
|
9
9
|
message: str = "Unexpected response from the BSBLAN device."
|
|
10
10
|
|
|
11
11
|
def __init__(self, message: str | None = None) -> None:
|
|
12
|
-
"""Initialize a new instance of the BSBLANError class.
|
|
12
|
+
"""Initialize a new instance of the BSBLANError class.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
message: Optional custom error message.
|
|
16
|
+
|
|
17
|
+
"""
|
|
13
18
|
if message is not None:
|
|
14
19
|
self.message = message
|
|
15
20
|
super().__init__(self.message)
|
|
@@ -22,7 +27,12 @@ class BSBLANConnectionError(BSBLANError):
|
|
|
22
27
|
message_error: str = "Error occurred while connecting to BSBLAN device."
|
|
23
28
|
|
|
24
29
|
def __init__(self, response: str | None = None) -> None:
|
|
25
|
-
"""Initialize a new instance of the BSBLANConnectionError class.
|
|
30
|
+
"""Initialize a new instance of the BSBLANConnectionError class.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
response: Optional response message.
|
|
34
|
+
|
|
35
|
+
"""
|
|
26
36
|
self.response = response
|
|
27
37
|
super().__init__(self.message)
|
|
28
38
|
|
|
@@ -37,6 +47,11 @@ class BSBLANInvalidParameterError(BSBLANError):
|
|
|
37
47
|
"""Raised when an invalid parameter is provided."""
|
|
38
48
|
|
|
39
49
|
def __init__(self, parameter: str) -> None:
|
|
40
|
-
"""Initialize a new instance of the BSBLANInvalidParameterError class.
|
|
50
|
+
"""Initialize a new instance of the BSBLANInvalidParameterError class.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
parameter: The invalid parameter that caused the error.
|
|
54
|
+
|
|
55
|
+
"""
|
|
41
56
|
self.message = f"Invalid values provided: {parameter}"
|
|
42
57
|
super().__init__(self.message)
|
|
@@ -93,11 +93,14 @@ class HotWaterState(DataClassJSONMixin):
|
|
|
93
93
|
|
|
94
94
|
operating_mode: EntityInfo
|
|
95
95
|
nominal_setpoint: EntityInfo
|
|
96
|
+
nominal_setpoint_max: EntityInfo # 1614
|
|
96
97
|
reduced_setpoint: EntityInfo
|
|
97
98
|
release: EntityInfo
|
|
98
99
|
legionella_function: EntityInfo
|
|
99
100
|
legionella_setpoint: EntityInfo
|
|
100
101
|
legionella_periodically: EntityInfo
|
|
102
|
+
dhw_actual_value_top_temperature: EntityInfo # 8830
|
|
103
|
+
state_dhw_pump: EntityInfo # 8820
|
|
101
104
|
|
|
102
105
|
|
|
103
106
|
@dataclass
|
|
File without changes
|
|
File without changes
|