Skip to content

Commit b3446e6

Browse files
authored
Merge pull request #574 from plugwise/call-req-impl
Add try-except constructs for all set-functions
2 parents 02990de + 4730aca commit b3446e6

File tree

9 files changed

+218
-69
lines changed

9 files changed

+218
-69
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# Changelog
22

3-
## Ongoing
3+
## v0.38.1
44

5+
- Add missing exception-handling for set-function in `__init__.py`
6+
- Add call_request() functions combining all common exception-handling for all set-functions
7+
- Update and improve test code
58
- Implementing code improvements as suggested in #567
69

710
## v0.38.0

plugwise/__init__.py

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,10 @@ async def set_select(
324324
state: str | None = None,
325325
) -> None:
326326
"""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
328331

329332
async def set_schedule_state(
330333
self,
@@ -333,15 +336,25 @@ async def set_schedule_state(
333336
name: str | None = None,
334337
) -> None:
335338
"""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+
337344

338345
async def set_preset(self, loc_id: str, preset: str) -> None:
339346
"""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
341351

342352
async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None:
343353
"""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
345358

346359
async def set_number(
347360
self,
@@ -350,40 +363,58 @@ async def set_number(
350363
temperature: float,
351364
) -> None:
352365
"""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
354370

355371
async def set_temperature_offset(self, dev_id: str, offset: float) -> None:
356372
"""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
358377

359378
async def set_switch_state(
360379
self, appl_id: str, members: list[str] | None, model: str, state: str
361380
) -> None:
362381
"""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
364386

365387
async def set_gateway_mode(self, mode: str) -> None:
366388
"""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
368393

369394
async def set_regulation_mode(self, mode: str) -> None:
370395
"""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
372400

373401
async def set_dhw_mode(self, mode: str) -> None:
374402
"""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
376407

377408
async def delete_notification(self) -> None:
378409
"""Delete the active Plugwise Notification."""
379410
try:
380411
await self._smile_api.delete_notification()
381412
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
383414

384415
async def reboot_gateway(self) -> None:
385416
"""Reboot the Plugwise Gateway."""
386417
try:
387418
await self._smile_api.reboot_gateway()
388419
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

plugwise/legacy/smile.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from __future__ import annotations
66

77
import datetime as dt
8+
from typing import Any
89

910
from plugwise.constants import (
1011
APPLIANCES,
@@ -23,7 +24,7 @@
2324
PlugwiseData,
2425
ThermoLoc,
2526
)
26-
from plugwise.exceptions import PlugwiseError
27+
from plugwise.exceptions import ConnectionFailedError, PlugwiseError
2728
from plugwise.helper import SmileComm
2829
from plugwise.legacy.data import SmileLegacyData
2930

@@ -180,7 +181,7 @@ async def set_preset(self, _: str, preset: str) -> None:
180181
rule = self._domain_objects.find(locator)
181182
data = f'<rules><rule id="{rule.attrib["id"]}"><active>true</active></rule></rules>'
182183

183-
await self._request(RULES, method="put", data=data)
184+
await self.call_request(RULES, method="put", data=data)
184185

185186
async def set_regulation_mode(self, mode: str) -> None:
186187
"""Set-function placeholder for legacy devices."""
@@ -226,7 +227,7 @@ async def set_schedule_state(self, _: str, state: str | None, name: str | None)
226227
f' id="{template_id}" /><active>{new_state}</active></rule></rules>'
227228
)
228229

229-
await self._request(uri, method="put", data=data)
230+
await self.call_request(uri, method="put", data=data)
230231

231232
async def set_switch_state(
232233
self, appl_id: str, members: list[str] | None, model: str, state: str
@@ -254,7 +255,7 @@ async def set_switch_state(
254255
if self._appliances.find(locator).text == "true":
255256
raise PlugwiseError("Plugwise: the locked Relay was not switched.")
256257

257-
await self._request(uri, method="put", data=data)
258+
await self.call_request(uri, method="put", data=data)
258259

259260
async def _set_groupswitch_member_state(
260261
self, members: list[str], state: str, switch: Munch
@@ -267,7 +268,7 @@ async def _set_groupswitch_member_state(
267268
uri = f"{APPLIANCES};id={member}/{switch.func_type}"
268269
data = f"<{switch.func_type}><{switch.func}>{state}</{switch.func}></{switch.func_type}>"
269270

270-
await self._request(uri, method="put", data=data)
271+
await self.call_request(uri, method="put", data=data)
271272

272273
async def set_temperature(self, _: str, items: dict[str, float]) -> None:
273274
"""Set the given Temperature on the relevant Thermostat."""
@@ -287,4 +288,13 @@ async def set_temperature(self, _: str, items: dict[str, float]) -> None:
287288
f"{temperature}</setpoint></thermostat_functionality>"
288289
)
289290

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

plugwise/smile.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,6 @@ async def async_update(self) -> PlugwiseData:
149149
### API Set and HA Service-related Functions ###
150150
########################################################################################################
151151

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-
160152
async def delete_notification(self) -> None:
161153
"""Delete the active Plugwise Notification."""
162154
await self.call_request(NOTIFICATIONS, method="delete")
@@ -189,7 +181,7 @@ async def set_number(
189181

190182
uri = f"{APPLIANCES};id={self._heater_id}/thermostat;id={thermostat_id}"
191183
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)
193185

194186
async def set_offset(self, dev_id: str, offset: float) -> None:
195187
"""Set the Temperature offset for thermostats that support this feature."""
@@ -202,7 +194,7 @@ async def set_offset(self, dev_id: str, offset: float) -> None:
202194
uri = f"{APPLIANCES};id={dev_id}/offset;type=temperature_offset"
203195
data = f"<offset_functionality><offset>{value}</offset></offset_functionality>"
204196

205-
await self._request(uri, method="put", data=data)
197+
await self.call_request(uri, method="put", data=data)
206198

207199
async def set_preset(self, loc_id: str, preset: str) -> None:
208200
"""Set the given Preset on the relevant Thermostat - from LOCATIONS."""
@@ -222,7 +214,7 @@ async def set_preset(self, loc_id: str, preset: str) -> None:
222214
f"</type><preset>{preset}</preset></location></locations>"
223215
)
224216

225-
await self._request(uri, method="put", data=data)
217+
await self.call_request(uri, method="put", data=data)
226218

227219
async def set_select(self, key: str, loc_id: str, option: str, state: str | None) -> None:
228220
"""Set a dhw/gateway/regulation mode or the thermostat schedule option."""
@@ -245,7 +237,7 @@ async def set_dhw_mode(self, mode: str) -> None:
245237
uri = f"{APPLIANCES};type=heater_central/domestic_hot_water_mode_control"
246238
data = f"<domestic_hot_water_mode_control_functionality><mode>{mode}</mode></domestic_hot_water_mode_control_functionality>"
247239

248-
await self._request(uri, method="put", data=data)
240+
await self.call_request(uri, method="put", data=data)
249241

250242
async def set_gateway_mode(self, mode: str) -> None:
251243
"""Set the gateway mode."""
@@ -268,7 +260,7 @@ async def set_gateway_mode(self, mode: str) -> None:
268260
uri = f"{APPLIANCES};id={self.gateway_id}/gateway_mode_control"
269261
data = f"<gateway_mode_control_functionality><mode>{mode}</mode>{valid}</gateway_mode_control_functionality>"
270262

271-
await self._request(uri, method="put", data=data)
263+
await self.call_request(uri, method="put", data=data)
272264

273265
async def set_regulation_mode(self, mode: str) -> None:
274266
"""Set the heating regulation mode."""
@@ -281,7 +273,7 @@ async def set_regulation_mode(self, mode: str) -> None:
281273
duration = "<duration>300</duration>"
282274
data = f"<regulation_mode_control_functionality>{duration}<mode>{mode}</mode></regulation_mode_control_functionality>"
283275

284-
await self._request(uri, method="put", data=data)
276+
await self.call_request(uri, method="put", data=data)
285277

286278
async def set_schedule_state(
287279
self,
@@ -335,7 +327,7 @@ async def set_schedule_state(
335327
f"{template}{contexts}</rule></rules>"
336328
)
337329

338-
await self._request(uri, method="put", data=data)
330+
await self.call_request(uri, method="put", data=data)
339331
self._schedule_old_states[loc_id][name] = new_state
340332

341333
def determine_contexts(
@@ -404,7 +396,7 @@ async def set_switch_state(
404396
if self._domain_objects.find(locator).text == "true":
405397
raise PlugwiseError("Plugwise: the locked Relay was not switched.")
406398

407-
await self._request(uri, method="put", data=data)
399+
await self.call_request(uri, method="put", data=data)
408400

409401
async def _set_groupswitch_member_state(
410402
self, members: list[str], state: str, switch: Munch
@@ -419,7 +411,7 @@ async def _set_groupswitch_member_state(
419411
uri = f"{APPLIANCES};id={member}/{switch.device};id={switch_id}"
420412
data = f"<{switch.func_type}><{switch.func}>{state}</{switch.func}></{switch.func_type}>"
421413

422-
await self._request(uri, method="put", data=data)
414+
await self.call_request(uri, method="put", data=data)
423415

424416
async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None:
425417
"""Set the given Temperature on the relevant Thermostat."""
@@ -460,4 +452,13 @@ async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None:
460452
f"{temperature}</setpoint></thermostat_functionality>"
461453
)
462454

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

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "plugwise"
7-
version = "0.38.0"
7+
version = "0.38.1"
88
license = {file = "LICENSE"}
99
description = "Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3."
1010
readme = "README.md"

tests/test_adam.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ async def test_connect_adam_zone_per_device(self):
6363
await self.disconnect(server, client)
6464

6565
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)
6767
result = await self.tinker_thermostat(
6868
smile,
6969
"c50f167537524366a5af7aa3942feb1e",
@@ -79,15 +79,18 @@ async def test_connect_adam_zone_per_device(self):
7979
)
8080
assert result
8181

82+
tinkered = await self.tinker_max_boiler_temp(smile, unhappy=True)
83+
assert not tinkered
84+
8285
try:
8386
await smile.delete_notification()
8487
notification_deletion = False # pragma: no cover
85-
except pw_exceptions.PlugwiseError:
88+
except pw_exceptions.ConnectionFailedError:
8689
notification_deletion = True
8790
assert notification_deletion
8891

8992
reboot = await self.tinker_reboot(smile, unhappy=True)
90-
assert not reboot
93+
assert reboot
9194

9295
await smile.close_connection()
9396
await self.disconnect(server, client)
@@ -212,7 +215,7 @@ async def test_connect_adam_plus_anna(self):
212215
await self.disconnect(server, client)
213216

214217
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)
216219
result = await self.tinker_thermostat(
217220
smile,
218221
"009490cc2f674ce6b576863fbb64f867",
@@ -322,9 +325,14 @@ async def test_connect_adam_plus_anna_new(self):
322325
)
323326
assert not switch_change
324327

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
328336

329337
# Now change some data and change directory reading xml from
330338
# emulating reading newer dataset after an update_interval
@@ -353,6 +361,23 @@ async def test_connect_adam_plus_anna_new(self):
353361
await smile.close_connection()
354362
await self.disconnect(server, client)
355363

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+
356381
@pytest.mark.asyncio
357382
async def test_adam_plus_jip(self):
358383
"""Test Adam with Jip setup."""

0 commit comments

Comments
 (0)