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.
Files changed (32) hide show
  1. {plugwise-0.38.0 → plugwise-0.38.1}/PKG-INFO +1 -1
  2. {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/__init__.py +43 -12
  3. {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/helper.py +41 -41
  4. {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/legacy/smile.py +16 -6
  5. {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/smile.py +19 -18
  6. {plugwise-0.38.0 → plugwise-0.38.1}/plugwise.egg-info/PKG-INFO +1 -1
  7. {plugwise-0.38.0 → plugwise-0.38.1}/pyproject.toml +1 -1
  8. {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_adam.py +32 -7
  9. {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_anna.py +19 -2
  10. {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_init.py +83 -21
  11. {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_legacy_anna.py +1 -1
  12. {plugwise-0.38.0 → plugwise-0.38.1}/LICENSE +0 -0
  13. {plugwise-0.38.0 → plugwise-0.38.1}/README.md +0 -0
  14. {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/common.py +0 -0
  15. {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/constants.py +0 -0
  16. {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/data.py +0 -0
  17. {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/exceptions.py +0 -0
  18. {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/legacy/data.py +0 -0
  19. {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/legacy/helper.py +0 -0
  20. {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/py.typed +0 -0
  21. {plugwise-0.38.0 → plugwise-0.38.1}/plugwise/util.py +0 -0
  22. {plugwise-0.38.0 → plugwise-0.38.1}/plugwise.egg-info/SOURCES.txt +0 -0
  23. {plugwise-0.38.0 → plugwise-0.38.1}/plugwise.egg-info/dependency_links.txt +0 -0
  24. {plugwise-0.38.0 → plugwise-0.38.1}/plugwise.egg-info/requires.txt +0 -0
  25. {plugwise-0.38.0 → plugwise-0.38.1}/plugwise.egg-info/top_level.txt +0 -0
  26. {plugwise-0.38.0 → plugwise-0.38.1}/setup.cfg +0 -0
  27. {plugwise-0.38.0 → plugwise-0.38.1}/setup.py +0 -0
  28. {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_generic.py +0 -0
  29. {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_legacy_generic.py +0 -0
  30. {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_legacy_p1.py +0 -0
  31. {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_legacy_stretch.py +0 -0
  32. {plugwise-0.38.0 → plugwise-0.38.1}/tests/test_p1.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: plugwise
3
- Version: 0.38.0
3
+ Version: 0.38.1
4
4
  Summary: Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3.
5
5
  Home-page: https://github.com/plugwise/python-plugwise
6
6
  Author: Plugwise device owners
@@ -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
- await self._smile_api.set_select(key, loc_id, option, state)
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
- await self._smile_api.set_schedule_state(loc_id, state, name)
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
- await self._smile_api.set_preset(loc_id, preset)
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
- await self._smile_api.set_temperature(loc_id, items)
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
- await self._smile_api.set_number(dev_id, key, temperature)
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
- await self._smile_api.set_offset(dev_id, offset) # pragma: no cover
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
- await self._smile_api.set_switch_state(appl_id, members, model, state)
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
- await self._smile_api.set_gateway_mode(mode) # pragma: no cover
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
- await self._smile_api.set_regulation_mode(mode) # pragma: no cover
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
- await self._smile_api.set_dhw_mode(mode) # pragma: no cover
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 PlugwiseError(f"Failed to delete notification: {str(exc)}") from exc
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 PlugwiseError(f"Failed to reboot gateway: {str(exc)}") from exc
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
- if method == "delete":
119
- resp = await self._websession.delete(url, auth=self._auth)
120
- if method == "get":
121
- # Work-around for Stretchv2, should not hurt the other smiles
122
- use_headers = {"Accept-Encoding": "gzip"}
123
- resp = await self._websession.get(
124
- url, headers=use_headers, auth=self._auth
125
- )
126
- if method == "post":
127
- use_headers = {"Content-type": "text/xml"}
128
- resp = await self._websession.post(
129
- url,
130
- headers=use_headers,
131
- data=data,
132
- auth=self._auth,
133
- )
134
- if method == "put":
135
- use_headers = {"Content-type": "text/xml"}
136
- resp = await self._websession.put(
137
- url,
138
- headers=use_headers,
139
- data=data,
140
- auth=self._auth,
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
- # Command accepted gives empty body with status 202
171
- if resp.status == 202:
172
- return
173
-
174
- # Cornercase for server not responding with 202
175
- if method in ("post", "put") and resp.status == 200:
176
- return
177
-
178
- if resp.status == 401:
179
- msg = "Invalid Plugwise login, please retry with the correct credentials."
180
- LOGGER.error("%s", msg)
181
- raise InvalidAuthentication
182
-
183
- if resp.status == 405:
184
- msg = "405 Method not allowed."
185
- LOGGER.error("%s", msg)
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._request(RULES, method="put", data=data)
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._request(uri, method="put", data=data)
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._request(uri, method="put", data=data)
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._request(uri, method="put", data=data)
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._request(uri, method="put", data=data)
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._request(uri, method="put", data=data)
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._request(uri, method="put", data=data)
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._request(uri, method="put", data=data)
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._request(uri, method="put", data=data)
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._request(uri, method="put", data=data)
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._request(uri, method="put", data=data)
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._request(uri, method="put", data=data)
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._request(uri, method="put", data=data)
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._request(uri, method="put", data=data)
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._request(uri, method="put", data=data)
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: plugwise
3
- Version: 0.38.0
3
+ Version: 0.38.1
4
4
  Summary: Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3.
5
5
  Home-page: https://github.com/plugwise/python-plugwise
6
6
  Author: Plugwise device owners
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "plugwise"
7
- version = "0.38.0"
7
+ version = "0.38.1"
8
8
  license = {file = "LICENSE"}
9
9
  description = "Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3."
10
10
  readme = "README.md"
@@ -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.PlugwiseError:
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 not reboot
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
- await self.tinker_regulation_mode(smile)
327
- await self.tinker_max_boiler_temp(smile)
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.PlugwiseError:
651
+ except pw_exceptions.ConnectionFailedError:
648
652
  if unhappy:
649
653
  _LOGGER.info(" + failed as expected")
650
- return False
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
- tinker_switch_passed = True # test is pass!
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
- tinker_temp_passed = True
704
+ return True
701
705
  else: # pragma: no cover
702
706
  _LOGGER.info(" - tinker_thermostat_temp failed unexpectedly")
703
- tinker_temp_passed = False
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
- tinker_schedule_passed = True
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
- tinker_schedule_passed = True
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