plugwise 0.38.0__tar.gz → 0.38.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.
- {plugwise-0.38.0 → plugwise-0.38.1}/PKG-INFO +1 -1
- {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/__init__.py +43 -12
- {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/helper.py +41 -41
- {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/legacy/smile.py +16 -6
- {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/smile.py +19 -18
- {plugwise-0.38.0 → plugwise-0.38.1}/plugwise.egg-info/PKG-INFO +1 -1
- {plugwise-0.38.0 → plugwise-0.38.1}/pyproject.toml +1 -1
- {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_adam.py +32 -7
- {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_anna.py +19 -2
- {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_init.py +83 -21
- {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_legacy_anna.py +1 -1
- {plugwise-0.38.0 → plugwise-0.38.1}/LICENSE +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/README.md +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/common.py +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/constants.py +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/data.py +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/exceptions.py +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/legacy/data.py +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/legacy/helper.py +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/py.typed +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/util.py +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/plugwise.egg-info/SOURCES.txt +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/plugwise.egg-info/dependency_links.txt +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/plugwise.egg-info/requires.txt +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/plugwise.egg-info/top_level.txt +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/setup.cfg +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/setup.py +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_generic.py +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_legacy_generic.py +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_legacy_p1.py +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_legacy_stretch.py +0 -0
- {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_p1.py +0 -0
@@ -324,7 +324,10 @@ class Smile(SmileComm):
|
|
324
324
|
state: str | None = None,
|
325
325
|
) -> None:
|
326
326
|
"""Set the selected option for the applicable Select."""
|
327
|
-
|
327
|
+
try:
|
328
|
+
await self._smile_api.set_select(key, loc_id, option, state)
|
329
|
+
except ConnectionFailedError as exc:
|
330
|
+
raise ConnectionFailedError(f"Failed to set select option '{option}': {str(exc)}") from exc
|
328
331
|
|
329
332
|
async def set_schedule_state(
|
330
333
|
self,
|
@@ -333,15 +336,25 @@ class Smile(SmileComm):
|
|
333
336
|
name: str | None = None,
|
334
337
|
) -> None:
|
335
338
|
"""Activate/deactivate the Schedule, with the given name, on the relevant Thermostat."""
|
336
|
-
|
339
|
+
try:
|
340
|
+
await self._smile_api.set_schedule_state(loc_id, state, name)
|
341
|
+
except ConnectionFailedError as exc: # pragma no cover
|
342
|
+
raise ConnectionFailedError(f"Failed to set schedule state: {str(exc)}") from exc # pragma no cover
|
343
|
+
|
337
344
|
|
338
345
|
async def set_preset(self, loc_id: str, preset: str) -> None:
|
339
346
|
"""Set the given Preset on the relevant Thermostat."""
|
340
|
-
|
347
|
+
try:
|
348
|
+
await self._smile_api.set_preset(loc_id, preset)
|
349
|
+
except ConnectionFailedError as exc:
|
350
|
+
raise ConnectionFailedError(f"Failed to set preset: {str(exc)}") from exc
|
341
351
|
|
342
352
|
async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None:
|
343
353
|
"""Set the given Temperature on the relevant Thermostat."""
|
344
|
-
|
354
|
+
try:
|
355
|
+
await self._smile_api.set_temperature(loc_id, items)
|
356
|
+
except ConnectionFailedError as exc:
|
357
|
+
raise ConnectionFailedError(f"Failed to set temperature: {str(exc)}") from exc
|
345
358
|
|
346
359
|
async def set_number(
|
347
360
|
self,
|
@@ -350,40 +363,58 @@ class Smile(SmileComm):
|
|
350
363
|
temperature: float,
|
351
364
|
) -> None:
|
352
365
|
"""Set the maximum boiler- or DHW-setpoint on the Central Heating boiler or the temperature-offset on a Thermostat."""
|
353
|
-
|
366
|
+
try:
|
367
|
+
await self._smile_api.set_number(dev_id, key, temperature)
|
368
|
+
except ConnectionFailedError as exc:
|
369
|
+
raise ConnectionFailedError(f"Failed to set number '{key}': {str(exc)}") from exc
|
354
370
|
|
355
371
|
async def set_temperature_offset(self, dev_id: str, offset: float) -> None:
|
356
372
|
"""Set the Temperature offset for thermostats that support this feature."""
|
357
|
-
|
373
|
+
try: # pragma no cover
|
374
|
+
await self._smile_api.set_offset(dev_id, offset) # pragma: no cover
|
375
|
+
except ConnectionFailedError as exc: # pragma no cover
|
376
|
+
raise ConnectionFailedError(f"Failed to set temperature offset: {str(exc)}") from exc # pragma no cover
|
358
377
|
|
359
378
|
async def set_switch_state(
|
360
379
|
self, appl_id: str, members: list[str] | None, model: str, state: str
|
361
380
|
) -> None:
|
362
381
|
"""Set the given State of the relevant Switch."""
|
363
|
-
|
382
|
+
try:
|
383
|
+
await self._smile_api.set_switch_state(appl_id, members, model, state)
|
384
|
+
except ConnectionFailedError as exc:
|
385
|
+
raise ConnectionFailedError(f"Failed to set switch state: {str(exc)}") from exc
|
364
386
|
|
365
387
|
async def set_gateway_mode(self, mode: str) -> None:
|
366
388
|
"""Set the gateway mode."""
|
367
|
-
|
389
|
+
try: # pragma no cover
|
390
|
+
await self._smile_api.set_gateway_mode(mode) # pragma: no cover
|
391
|
+
except ConnectionFailedError as exc: # pragma no cover
|
392
|
+
raise ConnectionFailedError(f"Failed to set gateway mode: {str(exc)}") from exc # pragma no cover
|
368
393
|
|
369
394
|
async def set_regulation_mode(self, mode: str) -> None:
|
370
395
|
"""Set the heating regulation mode."""
|
371
|
-
|
396
|
+
try: # pragma no cover
|
397
|
+
await self._smile_api.set_regulation_mode(mode) # pragma: no cover
|
398
|
+
except ConnectionFailedError as exc: # pragma no cover
|
399
|
+
raise ConnectionFailedError(f"Failed to set regulation mode: {str(exc)}") from exc # pragma no cover
|
372
400
|
|
373
401
|
async def set_dhw_mode(self, mode: str) -> None:
|
374
402
|
"""Set the domestic hot water heating regulation mode."""
|
375
|
-
|
403
|
+
try: # pragma no cover
|
404
|
+
await self._smile_api.set_dhw_mode(mode) # pragma: no cover
|
405
|
+
except ConnectionFailedError as exc: # pragma no cover
|
406
|
+
raise ConnectionFailedError(f"Failed to set dhw mode: {str(exc)}") from exc # pragma no cover
|
376
407
|
|
377
408
|
async def delete_notification(self) -> None:
|
378
409
|
"""Delete the active Plugwise Notification."""
|
379
410
|
try:
|
380
411
|
await self._smile_api.delete_notification()
|
381
412
|
except ConnectionFailedError as exc:
|
382
|
-
raise
|
413
|
+
raise ConnectionFailedError(f"Failed to delete notification: {str(exc)}") from exc
|
383
414
|
|
384
415
|
async def reboot_gateway(self) -> None:
|
385
416
|
"""Reboot the Plugwise Gateway."""
|
386
417
|
try:
|
387
418
|
await self._smile_api.reboot_gateway()
|
388
419
|
except ConnectionFailedError as exc:
|
389
|
-
raise
|
420
|
+
raise ConnectionFailedError(f"Failed to reboot gateway: {str(exc)}") from exc
|
@@ -115,30 +115,31 @@ class SmileComm:
|
|
115
115
|
use_headers = headers
|
116
116
|
|
117
117
|
try:
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
118
|
+
match method:
|
119
|
+
case "delete":
|
120
|
+
resp = await self._websession.delete(url, auth=self._auth)
|
121
|
+
case "get":
|
122
|
+
# Work-around for Stretchv2, should not hurt the other smiles
|
123
|
+
use_headers = {"Accept-Encoding": "gzip"}
|
124
|
+
resp = await self._websession.get(
|
125
|
+
url, headers=use_headers, auth=self._auth
|
126
|
+
)
|
127
|
+
case "post":
|
128
|
+
use_headers = {"Content-type": "text/xml"}
|
129
|
+
resp = await self._websession.post(
|
130
|
+
url,
|
131
|
+
headers=use_headers,
|
132
|
+
data=data,
|
133
|
+
auth=self._auth,
|
134
|
+
)
|
135
|
+
case "put":
|
136
|
+
use_headers = {"Content-type": "text/xml"}
|
137
|
+
resp = await self._websession.put(
|
138
|
+
url,
|
139
|
+
headers=use_headers,
|
140
|
+
data=data,
|
141
|
+
auth=self._auth,
|
142
|
+
)
|
142
143
|
except (
|
143
144
|
ClientError
|
144
145
|
) as exc: # ClientError is an ancestor class of ServerTimeoutError
|
@@ -167,23 +168,22 @@ class SmileComm:
|
|
167
168
|
|
168
169
|
async def _request_validate(self, resp: ClientResponse, method: str) -> etree:
|
169
170
|
"""Helper-function for _request(): validate the returned data."""
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
raise ConnectionFailedError
|
171
|
+
match resp.status:
|
172
|
+
case 200:
|
173
|
+
# Cornercases for server not responding with 202
|
174
|
+
if method in ("post", "put"):
|
175
|
+
return
|
176
|
+
case 202:
|
177
|
+
# Command accepted gives empty body with status 202
|
178
|
+
return
|
179
|
+
case 401:
|
180
|
+
msg = "Invalid Plugwise login, please retry with the correct credentials."
|
181
|
+
LOGGER.error("%s", msg)
|
182
|
+
raise InvalidAuthentication
|
183
|
+
case 405:
|
184
|
+
msg = "405 Method not allowed."
|
185
|
+
LOGGER.error("%s", msg)
|
186
|
+
raise ConnectionFailedError
|
187
187
|
|
188
188
|
if not (result := await resp.text()) or (
|
189
189
|
"<error>" in result and "Not started" not in result
|
@@ -5,6 +5,7 @@ Plugwise backend module for Home Assistant Core - covering the legacy P1, Anna,
|
|
5
5
|
from __future__ import annotations
|
6
6
|
|
7
7
|
import datetime as dt
|
8
|
+
from typing import Any
|
8
9
|
|
9
10
|
from plugwise.constants import (
|
10
11
|
APPLIANCES,
|
@@ -23,7 +24,7 @@ from plugwise.constants import (
|
|
23
24
|
PlugwiseData,
|
24
25
|
ThermoLoc,
|
25
26
|
)
|
26
|
-
from plugwise.exceptions import PlugwiseError
|
27
|
+
from plugwise.exceptions import ConnectionFailedError, PlugwiseError
|
27
28
|
from plugwise.helper import SmileComm
|
28
29
|
from plugwise.legacy.data import SmileLegacyData
|
29
30
|
|
@@ -180,7 +181,7 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
|
|
180
181
|
rule = self._domain_objects.find(locator)
|
181
182
|
data = f'<rules><rule id="{rule.attrib["id"]}"><active>true</active></rule></rules>'
|
182
183
|
|
183
|
-
await self.
|
184
|
+
await self.call_request(RULES, method="put", data=data)
|
184
185
|
|
185
186
|
async def set_regulation_mode(self, mode: str) -> None:
|
186
187
|
"""Set-function placeholder for legacy devices."""
|
@@ -226,7 +227,7 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
|
|
226
227
|
f' id="{template_id}" /><active>{new_state}</active></rule></rules>'
|
227
228
|
)
|
228
229
|
|
229
|
-
await self.
|
230
|
+
await self.call_request(uri, method="put", data=data)
|
230
231
|
|
231
232
|
async def set_switch_state(
|
232
233
|
self, appl_id: str, members: list[str] | None, model: str, state: str
|
@@ -254,7 +255,7 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
|
|
254
255
|
if self._appliances.find(locator).text == "true":
|
255
256
|
raise PlugwiseError("Plugwise: the locked Relay was not switched.")
|
256
257
|
|
257
|
-
await self.
|
258
|
+
await self.call_request(uri, method="put", data=data)
|
258
259
|
|
259
260
|
async def _set_groupswitch_member_state(
|
260
261
|
self, members: list[str], state: str, switch: Munch
|
@@ -267,7 +268,7 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
|
|
267
268
|
uri = f"{APPLIANCES};id={member}/{switch.func_type}"
|
268
269
|
data = f"<{switch.func_type}><{switch.func}>{state}</{switch.func}></{switch.func_type}>"
|
269
270
|
|
270
|
-
await self.
|
271
|
+
await self.call_request(uri, method="put", data=data)
|
271
272
|
|
272
273
|
async def set_temperature(self, _: str, items: dict[str, float]) -> None:
|
273
274
|
"""Set the given Temperature on the relevant Thermostat."""
|
@@ -287,4 +288,13 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
|
|
287
288
|
f"{temperature}</setpoint></thermostat_functionality>"
|
288
289
|
)
|
289
290
|
|
290
|
-
await self.
|
291
|
+
await self.call_request(uri, method="put", data=data)
|
292
|
+
|
293
|
+
async def call_request(self, uri: str, **kwargs: Any) -> None:
|
294
|
+
"""ConnectionFailedError wrapper for calling _request()."""
|
295
|
+
method: str = kwargs["method"]
|
296
|
+
data: str | None = kwargs.get("data")
|
297
|
+
try:
|
298
|
+
await self._request(uri, method=method, data=data)
|
299
|
+
except ConnectionFailedError as exc:
|
300
|
+
raise ConnectionFailedError from exc
|
@@ -149,14 +149,6 @@ class SmileAPI(SmileComm, SmileData):
|
|
149
149
|
### API Set and HA Service-related Functions ###
|
150
150
|
########################################################################################################
|
151
151
|
|
152
|
-
async def call_request(self, uri: str, **kwargs: Any) -> None:
|
153
|
-
"""ConnectionFailedError wrapper for calling _request()."""
|
154
|
-
method: str = kwargs["method"]
|
155
|
-
try:
|
156
|
-
await self._request(uri, method=method)
|
157
|
-
except ConnectionFailedError as exc:
|
158
|
-
raise ConnectionFailedError from exc
|
159
|
-
|
160
152
|
async def delete_notification(self) -> None:
|
161
153
|
"""Delete the active Plugwise Notification."""
|
162
154
|
await self.call_request(NOTIFICATIONS, method="delete")
|
@@ -189,7 +181,7 @@ class SmileAPI(SmileComm, SmileData):
|
|
189
181
|
|
190
182
|
uri = f"{APPLIANCES};id={self._heater_id}/thermostat;id={thermostat_id}"
|
191
183
|
data = f"<thermostat_functionality><setpoint>{temp}</setpoint></thermostat_functionality>"
|
192
|
-
await self.
|
184
|
+
await self.call_request(uri, method="put", data=data)
|
193
185
|
|
194
186
|
async def set_offset(self, dev_id: str, offset: float) -> None:
|
195
187
|
"""Set the Temperature offset for thermostats that support this feature."""
|
@@ -202,7 +194,7 @@ class SmileAPI(SmileComm, SmileData):
|
|
202
194
|
uri = f"{APPLIANCES};id={dev_id}/offset;type=temperature_offset"
|
203
195
|
data = f"<offset_functionality><offset>{value}</offset></offset_functionality>"
|
204
196
|
|
205
|
-
await self.
|
197
|
+
await self.call_request(uri, method="put", data=data)
|
206
198
|
|
207
199
|
async def set_preset(self, loc_id: str, preset: str) -> None:
|
208
200
|
"""Set the given Preset on the relevant Thermostat - from LOCATIONS."""
|
@@ -222,7 +214,7 @@ class SmileAPI(SmileComm, SmileData):
|
|
222
214
|
f"</type><preset>{preset}</preset></location></locations>"
|
223
215
|
)
|
224
216
|
|
225
|
-
await self.
|
217
|
+
await self.call_request(uri, method="put", data=data)
|
226
218
|
|
227
219
|
async def set_select(self, key: str, loc_id: str, option: str, state: str | None) -> None:
|
228
220
|
"""Set a dhw/gateway/regulation mode or the thermostat schedule option."""
|
@@ -245,7 +237,7 @@ class SmileAPI(SmileComm, SmileData):
|
|
245
237
|
uri = f"{APPLIANCES};type=heater_central/domestic_hot_water_mode_control"
|
246
238
|
data = f"<domestic_hot_water_mode_control_functionality><mode>{mode}</mode></domestic_hot_water_mode_control_functionality>"
|
247
239
|
|
248
|
-
await self.
|
240
|
+
await self.call_request(uri, method="put", data=data)
|
249
241
|
|
250
242
|
async def set_gateway_mode(self, mode: str) -> None:
|
251
243
|
"""Set the gateway mode."""
|
@@ -268,7 +260,7 @@ class SmileAPI(SmileComm, SmileData):
|
|
268
260
|
uri = f"{APPLIANCES};id={self.gateway_id}/gateway_mode_control"
|
269
261
|
data = f"<gateway_mode_control_functionality><mode>{mode}</mode>{valid}</gateway_mode_control_functionality>"
|
270
262
|
|
271
|
-
await self.
|
263
|
+
await self.call_request(uri, method="put", data=data)
|
272
264
|
|
273
265
|
async def set_regulation_mode(self, mode: str) -> None:
|
274
266
|
"""Set the heating regulation mode."""
|
@@ -281,7 +273,7 @@ class SmileAPI(SmileComm, SmileData):
|
|
281
273
|
duration = "<duration>300</duration>"
|
282
274
|
data = f"<regulation_mode_control_functionality>{duration}<mode>{mode}</mode></regulation_mode_control_functionality>"
|
283
275
|
|
284
|
-
await self.
|
276
|
+
await self.call_request(uri, method="put", data=data)
|
285
277
|
|
286
278
|
async def set_schedule_state(
|
287
279
|
self,
|
@@ -335,7 +327,7 @@ class SmileAPI(SmileComm, SmileData):
|
|
335
327
|
f"{template}{contexts}</rule></rules>"
|
336
328
|
)
|
337
329
|
|
338
|
-
await self.
|
330
|
+
await self.call_request(uri, method="put", data=data)
|
339
331
|
self._schedule_old_states[loc_id][name] = new_state
|
340
332
|
|
341
333
|
def determine_contexts(
|
@@ -404,7 +396,7 @@ class SmileAPI(SmileComm, SmileData):
|
|
404
396
|
if self._domain_objects.find(locator).text == "true":
|
405
397
|
raise PlugwiseError("Plugwise: the locked Relay was not switched.")
|
406
398
|
|
407
|
-
await self.
|
399
|
+
await self.call_request(uri, method="put", data=data)
|
408
400
|
|
409
401
|
async def _set_groupswitch_member_state(
|
410
402
|
self, members: list[str], state: str, switch: Munch
|
@@ -419,7 +411,7 @@ class SmileAPI(SmileComm, SmileData):
|
|
419
411
|
uri = f"{APPLIANCES};id={member}/{switch.device};id={switch_id}"
|
420
412
|
data = f"<{switch.func_type}><{switch.func}>{state}</{switch.func}></{switch.func_type}>"
|
421
413
|
|
422
|
-
await self.
|
414
|
+
await self.call_request(uri, method="put", data=data)
|
423
415
|
|
424
416
|
async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None:
|
425
417
|
"""Set the given Temperature on the relevant Thermostat."""
|
@@ -460,4 +452,13 @@ class SmileAPI(SmileComm, SmileData):
|
|
460
452
|
f"{temperature}</setpoint></thermostat_functionality>"
|
461
453
|
)
|
462
454
|
|
463
|
-
await self.
|
455
|
+
await self.call_request(uri, method="put", data=data)
|
456
|
+
|
457
|
+
async def call_request(self, uri: str, **kwargs: Any) -> None:
|
458
|
+
"""ConnectionFailedError wrapper for calling _request()."""
|
459
|
+
method: str = kwargs["method"]
|
460
|
+
data: str | None = kwargs.get("data")
|
461
|
+
try:
|
462
|
+
await self._request(uri, method=method, data=data)
|
463
|
+
except ConnectionFailedError as exc:
|
464
|
+
raise ConnectionFailedError from exc
|
@@ -63,7 +63,7 @@ class TestPlugwiseAdam(TestPlugwise): # pylint: disable=attribute-defined-outsi
|
|
63
63
|
await self.disconnect(server, client)
|
64
64
|
|
65
65
|
server, smile, client = await self.connect_wrapper(raise_timeout=True)
|
66
|
-
await self.device_test(smile, "2022-05-16 00:00:01", testdata)
|
66
|
+
await self.device_test(smile, "2022-05-16 00:00:01", testdata, skip_testing=True)
|
67
67
|
result = await self.tinker_thermostat(
|
68
68
|
smile,
|
69
69
|
"c50f167537524366a5af7aa3942feb1e",
|
@@ -79,15 +79,18 @@ class TestPlugwiseAdam(TestPlugwise): # pylint: disable=attribute-defined-outsi
|
|
79
79
|
)
|
80
80
|
assert result
|
81
81
|
|
82
|
+
tinkered = await self.tinker_max_boiler_temp(smile, unhappy=True)
|
83
|
+
assert not tinkered
|
84
|
+
|
82
85
|
try:
|
83
86
|
await smile.delete_notification()
|
84
87
|
notification_deletion = False # pragma: no cover
|
85
|
-
except pw_exceptions.
|
88
|
+
except pw_exceptions.ConnectionFailedError:
|
86
89
|
notification_deletion = True
|
87
90
|
assert notification_deletion
|
88
91
|
|
89
92
|
reboot = await self.tinker_reboot(smile, unhappy=True)
|
90
|
-
assert
|
93
|
+
assert reboot
|
91
94
|
|
92
95
|
await smile.close_connection()
|
93
96
|
await self.disconnect(server, client)
|
@@ -212,7 +215,7 @@ class TestPlugwiseAdam(TestPlugwise): # pylint: disable=attribute-defined-outsi
|
|
212
215
|
await self.disconnect(server, client)
|
213
216
|
|
214
217
|
server, smile, client = await self.connect_wrapper(raise_timeout=True)
|
215
|
-
await self.device_test(smile, "2020-03-22 00:00:01", testdata)
|
218
|
+
await self.device_test(smile, "2020-03-22 00:00:01", testdata, skip_testing=True)
|
216
219
|
result = await self.tinker_thermostat(
|
217
220
|
smile,
|
218
221
|
"009490cc2f674ce6b576863fbb64f867",
|
@@ -322,9 +325,14 @@ class TestPlugwiseAdam(TestPlugwise): # pylint: disable=attribute-defined-outsi
|
|
322
325
|
)
|
323
326
|
assert not switch_change
|
324
327
|
|
325
|
-
await self.tinker_gateway_mode(smile)
|
326
|
-
|
327
|
-
|
328
|
+
tinkered = await self.tinker_gateway_mode(smile)
|
329
|
+
assert not tinkered
|
330
|
+
|
331
|
+
tinkered = await self.tinker_regulation_mode(smile)
|
332
|
+
assert not tinkered
|
333
|
+
|
334
|
+
tinkered = await self.tinker_max_boiler_temp(smile)
|
335
|
+
assert not tinkered
|
328
336
|
|
329
337
|
# Now change some data and change directory reading xml from
|
330
338
|
# emulating reading newer dataset after an update_interval
|
@@ -353,6 +361,23 @@ class TestPlugwiseAdam(TestPlugwise): # pylint: disable=attribute-defined-outsi
|
|
353
361
|
await smile.close_connection()
|
354
362
|
await self.disconnect(server, client)
|
355
363
|
|
364
|
+
self.smile_setup = "adam_plus_anna_new"
|
365
|
+
testdata = self.load_testdata(SMILE_TYPE, self.smile_setup)
|
366
|
+
server, smile, client = await self.connect_wrapper(raise_timeout=True)
|
367
|
+
await self.device_test(smile, "2023-12-17 00:00:01", testdata, skip_testing=True)
|
368
|
+
|
369
|
+
tinkered = await self.tinker_max_boiler_temp(smile, unhappy=True)
|
370
|
+
assert tinkered
|
371
|
+
|
372
|
+
tinkered = await self.tinker_gateway_mode(smile, unhappy=True)
|
373
|
+
assert tinkered
|
374
|
+
|
375
|
+
tinkered = await self.tinker_regulation_mode(smile, unhappy=True)
|
376
|
+
assert tinkered
|
377
|
+
|
378
|
+
await smile.close_connection()
|
379
|
+
await self.disconnect(server, client)
|
380
|
+
|
356
381
|
@pytest.mark.asyncio
|
357
382
|
async def test_adam_plus_jip(self):
|
358
383
|
"""Test Adam with Jip setup."""
|
@@ -70,7 +70,7 @@ class TestPlugwiseAnna(TestPlugwise): # pylint: disable=attribute-defined-outsi
|
|
70
70
|
server, smile, client = await self.connect_wrapper(raise_timeout=True)
|
71
71
|
# Reset self.smile_setup
|
72
72
|
self.smile_setup = "anna_v4"
|
73
|
-
await self.device_test(smile, "2020-04-05 00:00:01", testdata)
|
73
|
+
await self.device_test(smile, "2020-04-05 00:00:01", testdata, skip_testing=True)
|
74
74
|
result = await self.tinker_thermostat(
|
75
75
|
smile,
|
76
76
|
"eb5309212bf5407bb143e5bfa3b18aee",
|
@@ -79,6 +79,12 @@ class TestPlugwiseAnna(TestPlugwise): # pylint: disable=attribute-defined-outsi
|
|
79
79
|
unhappy=True,
|
80
80
|
)
|
81
81
|
assert result
|
82
|
+
|
83
|
+
result = await self.tinker_temp_offset(
|
84
|
+
smile, "01b85360fdd243d0aaad4d6ac2a5ba7e", unhappy=True,
|
85
|
+
)
|
86
|
+
assert result
|
87
|
+
|
82
88
|
await smile.close_connection()
|
83
89
|
await self.disconnect(server, client)
|
84
90
|
|
@@ -458,11 +464,22 @@ class TestPlugwiseAnna(TestPlugwise): # pylint: disable=attribute-defined-outsi
|
|
458
464
|
"ERROR raised setting block cooling: %s", exc.value
|
459
465
|
) # pragma: no cover
|
460
466
|
|
461
|
-
await self.tinker_dhw_mode(smile)
|
467
|
+
tinkered = await self.tinker_dhw_mode(smile)
|
468
|
+
assert not tinkered
|
462
469
|
|
463
470
|
await smile.close_connection()
|
464
471
|
await self.disconnect(server, client)
|
465
472
|
|
473
|
+
server, smile, client = await self.connect_wrapper(raise_timeout=True)
|
474
|
+
await self.device_test(smile, "2022-05-16 00:00:01", testdata, skip_testing=True)
|
475
|
+
|
476
|
+
tinkered = await self.tinker_dhw_mode(smile, unhappy=True)
|
477
|
+
assert tinkered
|
478
|
+
|
479
|
+
await smile.close_connection()
|
480
|
+
await self.disconnect(server, client)
|
481
|
+
|
482
|
+
|
466
483
|
@pytest.mark.asyncio
|
467
484
|
async def test_connect_anna_loria_cooling_active(self):
|
468
485
|
"""Test an Anna with a Loria in heating mode - state idle."""
|
@@ -536,6 +536,7 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
|
|
536
536
|
test_time=None,
|
537
537
|
testdata=None,
|
538
538
|
initialize=True,
|
539
|
+
skip_testing=False,
|
539
540
|
):
|
540
541
|
"""Perform basic device tests."""
|
541
542
|
bsw_list = ["binary_sensors", "central", "climate", "sensors", "switches"]
|
@@ -588,6 +589,9 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
|
|
588
589
|
_LOGGER.info("Device list = %s", data.devices)
|
589
590
|
self.show_setup(location_list, data.devices)
|
590
591
|
|
592
|
+
if skip_testing:
|
593
|
+
return
|
594
|
+
|
591
595
|
# Perform tests and asserts
|
592
596
|
tests = 0
|
593
597
|
asserts = 0
|
@@ -644,10 +648,10 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
|
|
644
648
|
await smile.reboot_gateway()
|
645
649
|
_LOGGER.info(" + worked as intended")
|
646
650
|
return True
|
647
|
-
except pw_exceptions.
|
651
|
+
except pw_exceptions.ConnectionFailedError:
|
648
652
|
if unhappy:
|
649
653
|
_LOGGER.info(" + failed as expected")
|
650
|
-
return
|
654
|
+
return True
|
651
655
|
else: # pragma: no cover
|
652
656
|
_LOGGER.info(" - failed unexpectedly")
|
653
657
|
return False
|
@@ -659,8 +663,8 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
|
|
659
663
|
"""Turn a Switch on and off to test functionality."""
|
660
664
|
_LOGGER.info("Asserting modifying settings for switch devices:")
|
661
665
|
_LOGGER.info("- Devices (%s):", dev_id)
|
666
|
+
tinker_switch_passed = False
|
662
667
|
for new_state in ["false", "true", "false"]:
|
663
|
-
tinker_switch_passed = False
|
664
668
|
_LOGGER.info("- Switching %s", new_state)
|
665
669
|
try:
|
666
670
|
await smile.set_switch_state(dev_id, members, model, new_state)
|
@@ -669,9 +673,9 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
|
|
669
673
|
except pw_exceptions.PlugwiseError:
|
670
674
|
_LOGGER.info(" + locked, not switched as expected")
|
671
675
|
return False
|
672
|
-
except pw_exceptions.ConnectionFailedError:
|
676
|
+
except pw_exceptions.ConnectionFailedError: # leave for-loop at connect-error
|
673
677
|
if unhappy:
|
674
|
-
|
678
|
+
return True # test is pass!
|
675
679
|
_LOGGER.info(" + failed as expected")
|
676
680
|
else: # pragma: no cover
|
677
681
|
_LOGGER.info(" - failed unexpectedly")
|
@@ -697,18 +701,18 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
|
|
697
701
|
except pw_exceptions.ConnectionFailedError:
|
698
702
|
if unhappy:
|
699
703
|
_LOGGER.info(" + tinker_thermostat_temp failed as expected")
|
700
|
-
|
704
|
+
return True
|
701
705
|
else: # pragma: no cover
|
702
706
|
_LOGGER.info(" - tinker_thermostat_temp failed unexpectedly")
|
703
|
-
|
707
|
+
return False
|
704
708
|
|
705
709
|
return tinker_temp_passed
|
706
710
|
|
707
711
|
@pytest.mark.asyncio
|
708
712
|
async def tinker_thermostat_preset(self, smile, loc_id, unhappy=False):
|
709
713
|
"""Toggle preset to test functionality."""
|
714
|
+
tinker_preset_passed = False
|
710
715
|
for new_preset in ["asleep", "home", BOGUS]:
|
711
|
-
tinker_preset_passed = False
|
712
716
|
warning = ""
|
713
717
|
if new_preset[0] == "!":
|
714
718
|
warning = " TTP Negative test"
|
@@ -721,10 +725,10 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
|
|
721
725
|
except pw_exceptions.PlugwiseError:
|
722
726
|
_LOGGER.info(" + found invalid preset, as expected")
|
723
727
|
tinker_preset_passed = True
|
724
|
-
except pw_exceptions.ConnectionFailedError:
|
728
|
+
except pw_exceptions.ConnectionFailedError: # leave for-loop at connect-error
|
725
729
|
if unhappy:
|
726
|
-
tinker_preset_passed = True
|
727
730
|
_LOGGER.info(" + tinker_thermostat_preset failed as expected")
|
731
|
+
return True
|
728
732
|
else: # pragma: no cover
|
729
733
|
_LOGGER.info(" - tinker_thermostat_preset failed unexpectedly")
|
730
734
|
return False
|
@@ -740,8 +744,9 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
|
|
740
744
|
if good_schedules != []:
|
741
745
|
if not single and ("!VeryBogusSchedule" not in good_schedules):
|
742
746
|
good_schedules.append("!VeryBogusSchedule")
|
747
|
+
|
748
|
+
tinker_schedule_passed = False
|
743
749
|
for new_schedule in good_schedules:
|
744
|
-
tinker_schedule_passed = False
|
745
750
|
warning = ""
|
746
751
|
if new_schedule is not None and new_schedule[0] == "!":
|
747
752
|
warning = " TTS Negative test"
|
@@ -754,11 +759,11 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
|
|
754
759
|
except pw_exceptions.PlugwiseError:
|
755
760
|
_LOGGER.info(" + failed as expected")
|
756
761
|
tinker_schedule_passed = True
|
757
|
-
except pw_exceptions.ConnectionFailedError:
|
762
|
+
except pw_exceptions.ConnectionFailedError: # leave for-loop at connect-error
|
758
763
|
tinker_schedule_passed = False
|
759
764
|
if unhappy:
|
760
765
|
_LOGGER.info(" + failed as expected before intended failure")
|
761
|
-
|
766
|
+
return True
|
762
767
|
else: # pragma: no cover
|
763
768
|
_LOGGER.info(" - succeeded unexpectedly for some reason")
|
764
769
|
return False
|
@@ -772,6 +777,7 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
|
|
772
777
|
async def tinker_legacy_thermostat_schedule(self, smile, unhappy=False):
|
773
778
|
"""Toggle schedules to test functionality."""
|
774
779
|
states = ["on", "off", "!Bogus"]
|
780
|
+
tinker_schedule_passed = False
|
775
781
|
for state in states:
|
776
782
|
_LOGGER.info("- Adjusting schedule to state %s", state)
|
777
783
|
try:
|
@@ -781,11 +787,11 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
|
|
781
787
|
except pw_exceptions.PlugwiseError:
|
782
788
|
_LOGGER.info(" + failed as expected")
|
783
789
|
tinker_schedule_passed = True
|
784
|
-
except pw_exceptions.ConnectionFailedError:
|
790
|
+
except pw_exceptions.ConnectionFailedError: # leave for-loop at connect-error
|
785
791
|
tinker_schedule_passed = False
|
786
792
|
if unhappy:
|
787
793
|
_LOGGER.info(" + failed as expected before intended failure")
|
788
|
-
|
794
|
+
return True
|
789
795
|
else: # pragma: no cover
|
790
796
|
_LOGGER.info(" - succeeded unexpectedly for some reason")
|
791
797
|
return False
|
@@ -833,7 +839,7 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
|
|
833
839
|
smile,
|
834
840
|
schedule_on=True,
|
835
841
|
block_cooling=False,
|
836
|
-
unhappy=False
|
842
|
+
unhappy=False,
|
837
843
|
):
|
838
844
|
"""Toggle various climate settings to test functionality."""
|
839
845
|
result_1 = await self.tinker_thermostat_temp(
|
@@ -847,8 +853,9 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
|
|
847
853
|
return result_1 and result_2 and result_3
|
848
854
|
|
849
855
|
@staticmethod
|
850
|
-
async def tinker_dhw_mode(smile):
|
856
|
+
async def tinker_dhw_mode(smile, unhappy=False):
|
851
857
|
"""Toggle dhw to test functionality."""
|
858
|
+
tinker_dhw_mode_passed = False
|
852
859
|
for mode in ["auto", "boost", BOGUS]:
|
853
860
|
warning = ""
|
854
861
|
if mode[0] == "!":
|
@@ -858,12 +865,24 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
|
|
858
865
|
try:
|
859
866
|
await smile.set_select("select_dhw_mode", "dummy", mode)
|
860
867
|
_LOGGER.info(" + tinker_dhw_mode worked as intended")
|
868
|
+
tinker_dhw_mode_passed = True
|
861
869
|
except pw_exceptions.PlugwiseError:
|
862
870
|
_LOGGER.info(" + tinker_dhw_mode found invalid mode, as expected")
|
871
|
+
tinker_dhw_mode_passed = False
|
872
|
+
except pw_exceptions.ConnectionFailedError: # leave for-loop at connect-error
|
873
|
+
if unhappy:
|
874
|
+
_LOGGER.info(" + failed as expected before intended failure")
|
875
|
+
return True
|
876
|
+
else: # pragma: no cover
|
877
|
+
_LOGGER.info(" - succeeded unexpectedly for some reason")
|
878
|
+
return False
|
879
|
+
|
880
|
+
return tinker_dhw_mode_passed
|
863
881
|
|
864
882
|
@staticmethod
|
865
|
-
async def tinker_regulation_mode(smile):
|
883
|
+
async def tinker_regulation_mode(smile, unhappy=False):
|
866
884
|
"""Toggle regulation_mode to test functionality."""
|
885
|
+
tinker_reg_mode_passed = False
|
867
886
|
for mode in ["off", "heating", "bleeding_cold", BOGUS]:
|
868
887
|
warning = ""
|
869
888
|
if mode[0] == "!":
|
@@ -873,25 +892,49 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
|
|
873
892
|
try:
|
874
893
|
await smile.set_select("select_regulation_mode", "dummy", mode)
|
875
894
|
_LOGGER.info(" + tinker_regulation_mode worked as intended")
|
895
|
+
tinker_reg_mode_passed = True
|
876
896
|
except pw_exceptions.PlugwiseError:
|
877
897
|
_LOGGER.info(
|
878
898
|
" + tinker_regulation_mode found invalid mode, as expected"
|
879
899
|
)
|
900
|
+
tinker_reg_mode_passed = False
|
901
|
+
except pw_exceptions.ConnectionFailedError: # leave for-loop at connect-error
|
902
|
+
if unhappy:
|
903
|
+
_LOGGER.info(" + failed as expected before intended failure")
|
904
|
+
return True
|
905
|
+
else: # pragma: no cover
|
906
|
+
_LOGGER.info(" - succeeded unexpectedly for some reason")
|
907
|
+
return False
|
908
|
+
|
909
|
+
return tinker_reg_mode_passed
|
880
910
|
|
881
911
|
@staticmethod
|
882
|
-
async def tinker_max_boiler_temp(smile):
|
912
|
+
async def tinker_max_boiler_temp(smile, unhappy=False):
|
883
913
|
"""Change max boiler temp setpoint to test functionality."""
|
914
|
+
tinker_max_boiler_temp_passed = False
|
884
915
|
new_temp = 60.0
|
885
916
|
_LOGGER.info("- Adjusting temperature to %s", new_temp)
|
886
917
|
for test in ["maximum_boiler_temperature", "bogus_temperature"]:
|
918
|
+
_LOGGER.info(" + for %s", test)
|
887
919
|
try:
|
888
920
|
await smile.set_number("dummy", test, new_temp)
|
889
921
|
_LOGGER.info(" + tinker_max_boiler_temp worked as intended")
|
922
|
+
tinker_max_boiler_temp_passed = True
|
890
923
|
except pw_exceptions.PlugwiseError:
|
891
924
|
_LOGGER.info(" + tinker_max_boiler_temp failed as intended")
|
925
|
+
tinker_max_boiler_temp_passed = False
|
926
|
+
except pw_exceptions.ConnectionFailedError: # leave for-loop at connect-error
|
927
|
+
if unhappy:
|
928
|
+
_LOGGER.info(" + failed as expected before intended failure")
|
929
|
+
return True
|
930
|
+
else: # pragma: no cover
|
931
|
+
_LOGGER.info(" - succeeded unexpectedly for some reason")
|
932
|
+
return False
|
933
|
+
|
934
|
+
return tinker_max_boiler_temp_passed
|
892
935
|
|
893
936
|
@staticmethod
|
894
|
-
async def tinker_temp_offset(smile, dev_id):
|
937
|
+
async def tinker_temp_offset(smile, dev_id, unhappy=False):
|
895
938
|
"""Change temperature_offset to test functionality."""
|
896
939
|
new_offset = 1.0
|
897
940
|
_LOGGER.info("- Adjusting temperature offset to %s", new_offset)
|
@@ -902,10 +945,18 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
|
|
902
945
|
except pw_exceptions.PlugwiseError:
|
903
946
|
_LOGGER.info(" + tinker_temp_offset failed as intended")
|
904
947
|
return False
|
948
|
+
except pw_exceptions.ConnectionFailedError:
|
949
|
+
if unhappy:
|
950
|
+
_LOGGER.info(" + failed as expected before intended failure")
|
951
|
+
return True
|
952
|
+
else: # pragma: no cover
|
953
|
+
_LOGGER.info(" - succeeded unexpectedly for some reason")
|
954
|
+
return False
|
905
955
|
|
906
956
|
@staticmethod
|
907
|
-
async def tinker_gateway_mode(smile):
|
957
|
+
async def tinker_gateway_mode(smile, unhappy=False):
|
908
958
|
"""Toggle gateway_mode to test functionality."""
|
959
|
+
tinker_gateway_mode_passed = False
|
909
960
|
for mode in ["away", "full", "vacation", "!bogus"]:
|
910
961
|
warning = ""
|
911
962
|
if mode[0] == "!":
|
@@ -915,8 +966,19 @@ class TestPlugwise: # pylint: disable=attribute-defined-outside-init
|
|
915
966
|
try:
|
916
967
|
await smile.set_select("select_gateway_mode", "dummy", mode)
|
917
968
|
_LOGGER.info(" + worked as intended")
|
969
|
+
tinker_gateway_mode_passed = True
|
918
970
|
except pw_exceptions.PlugwiseError:
|
919
971
|
_LOGGER.info(" + found invalid mode, as expected")
|
972
|
+
tinker_gateway_mode_passed = False
|
973
|
+
except pw_exceptions.ConnectionFailedError: # leave for-loop at connect-error
|
974
|
+
if unhappy:
|
975
|
+
_LOGGER.info(" + failed as expected before intended failure")
|
976
|
+
return True
|
977
|
+
else: # pragma: no cover
|
978
|
+
_LOGGER.info(" - succeeded unexpectedly for some reason")
|
979
|
+
return False
|
980
|
+
|
981
|
+
return tinker_gateway_mode_passed
|
920
982
|
|
921
983
|
@staticmethod
|
922
984
|
def validate_test_basics(
|
@@ -39,7 +39,7 @@ class TestPlugwiseAnna(TestPlugwise): # pylint: disable=attribute-defined-outsi
|
|
39
39
|
await self.disconnect(server, client)
|
40
40
|
|
41
41
|
server, smile, client = await self.connect_legacy_wrapper(raise_timeout=True)
|
42
|
-
await self.device_test(smile, "2020-03-22 00:00:01", testdata)
|
42
|
+
await self.device_test(smile, "2020-03-22 00:00:01", testdata, skip_testing=True)
|
43
43
|
result = await self.tinker_legacy_thermostat(smile, unhappy=True)
|
44
44
|
assert result
|
45
45
|
await smile.close_connection()
|
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
|