Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bd1f2ac
Implement using call_request() for all setter-functions
bouwew Jun 2, 2024
59c7bc8
Implement try-except ConnectionFailedError constructs for all high-le…
bouwew Jun 2, 2024
7ee9bf3
raise ConnectionFailedError instead of PlugwiseError
bouwew Jun 2, 2024
b20a1f3
Correct testcase
bouwew Jun 2, 2024
11135a2
Fix test-function
bouwew Jun 2, 2024
268990a
Update plugwise/__init__.py
bouwew Jun 2, 2024
86b5b57
Remove location-part of comment, not useful
bouwew Jun 2, 2024
043934a
Add key to set_number debug message
bouwew Jun 2, 2024
0e06a93
Add pragma-no cover for set-functions moved under set-state/set-number
bouwew Jun 2, 2024
ead0c03
Implement call_request for legacy set-functions
bouwew Jun 2, 2024
3d7e2a7
Remove pragma-no cover for set_number, needs coverage
bouwew Jun 2, 2024
6748724
Extend tinker-functions with ConnectionFailedError exception
bouwew Jun 3, 2024
9bc33d1
Use tinker_max_boiler output
bouwew Jun 3, 2024
045ce33
Add missing returns
bouwew Jun 3, 2024
7741028
Improve test-coverage for set_number()
bouwew Jun 3, 2024
25cd0cc
Improve test-methods
bouwew Jun 3, 2024
04567ba
Improve test-methods, correct test-assert
bouwew Jun 3, 2024
d6fdf4d
Improve tinker_dhw_mode use, add skip_testing argument,
bouwew Jun 3, 2024
9935f6a
Implement skip_testing further
bouwew Jun 4, 2024
2f3f703
Improve test coverage
bouwew Jun 4, 2024
e6adf34
Update CHANGELOG
bouwew Jun 4, 2024
0d15db6
Bump to v0.38.1 test-version
bouwew Jun 4, 2024
ea4411f
Update CHANGELOG
bouwew Jun 4, 2024
4730aca
Bump to v0.38.1 release-version
bouwew Jun 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# Changelog

## Ongoing
## v0.38.1

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

## v0.38.0
Expand Down
55 changes: 43 additions & 12 deletions plugwise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,10 @@ async def set_select(
state: str | None = None,
) -> None:
"""Set the selected option for the applicable Select."""
await self._smile_api.set_select(key, loc_id, option, state)
try:
await self._smile_api.set_select(key, loc_id, option, state)
except ConnectionFailedError as exc:
raise ConnectionFailedError(f"Failed to set select option '{option}': {str(exc)}") from exc

async def set_schedule_state(
self,
Expand All @@ -333,15 +336,25 @@ async def set_schedule_state(
name: str | None = None,
) -> None:
"""Activate/deactivate the Schedule, with the given name, on the relevant Thermostat."""
await self._smile_api.set_schedule_state(loc_id, state, name)
try:
await self._smile_api.set_schedule_state(loc_id, state, name)
except ConnectionFailedError as exc: # pragma no cover
raise ConnectionFailedError(f"Failed to set schedule state: {str(exc)}") from exc # pragma no cover


async def set_preset(self, loc_id: str, preset: str) -> None:
"""Set the given Preset on the relevant Thermostat."""
await self._smile_api.set_preset(loc_id, preset)
try:
await self._smile_api.set_preset(loc_id, preset)
except ConnectionFailedError as exc:
raise ConnectionFailedError(f"Failed to set preset: {str(exc)}") from exc

async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None:
"""Set the given Temperature on the relevant Thermostat."""
await self._smile_api.set_temperature(loc_id, items)
try:
await self._smile_api.set_temperature(loc_id, items)
except ConnectionFailedError as exc:
raise ConnectionFailedError(f"Failed to set temperature: {str(exc)}") from exc

async def set_number(
self,
Expand All @@ -350,40 +363,58 @@ async def set_number(
temperature: float,
) -> None:
"""Set the maximum boiler- or DHW-setpoint on the Central Heating boiler or the temperature-offset on a Thermostat."""
await self._smile_api.set_number(dev_id, key, temperature)
try:
await self._smile_api.set_number(dev_id, key, temperature)
except ConnectionFailedError as exc:
raise ConnectionFailedError(f"Failed to set number '{key}': {str(exc)}") from exc

async def set_temperature_offset(self, dev_id: str, offset: float) -> None:
"""Set the Temperature offset for thermostats that support this feature."""
await self._smile_api.set_offset(dev_id, offset) # pragma: no cover
try: # pragma no cover
await self._smile_api.set_offset(dev_id, offset) # pragma: no cover
except ConnectionFailedError as exc: # pragma no cover
raise ConnectionFailedError(f"Failed to set temperature offset: {str(exc)}") from exc # pragma no cover

async def set_switch_state(
self, appl_id: str, members: list[str] | None, model: str, state: str
) -> None:
"""Set the given State of the relevant Switch."""
await self._smile_api.set_switch_state(appl_id, members, model, state)
try:
await self._smile_api.set_switch_state(appl_id, members, model, state)
except ConnectionFailedError as exc:
raise ConnectionFailedError(f"Failed to set switch state: {str(exc)}") from exc

async def set_gateway_mode(self, mode: str) -> None:
"""Set the gateway mode."""
await self._smile_api.set_gateway_mode(mode) # pragma: no cover
try: # pragma no cover
await self._smile_api.set_gateway_mode(mode) # pragma: no cover
except ConnectionFailedError as exc: # pragma no cover
raise ConnectionFailedError(f"Failed to set gateway mode: {str(exc)}") from exc # pragma no cover

async def set_regulation_mode(self, mode: str) -> None:
"""Set the heating regulation mode."""
await self._smile_api.set_regulation_mode(mode) # pragma: no cover
try: # pragma no cover
await self._smile_api.set_regulation_mode(mode) # pragma: no cover
except ConnectionFailedError as exc: # pragma no cover
raise ConnectionFailedError(f"Failed to set regulation mode: {str(exc)}") from exc # pragma no cover

async def set_dhw_mode(self, mode: str) -> None:
"""Set the domestic hot water heating regulation mode."""
await self._smile_api.set_dhw_mode(mode) # pragma: no cover
try: # pragma no cover
await self._smile_api.set_dhw_mode(mode) # pragma: no cover
except ConnectionFailedError as exc: # pragma no cover
raise ConnectionFailedError(f"Failed to set dhw mode: {str(exc)}") from exc # pragma no cover

async def delete_notification(self) -> None:
"""Delete the active Plugwise Notification."""
try:
await self._smile_api.delete_notification()
except ConnectionFailedError as exc:
raise PlugwiseError(f"Failed to delete notification: {str(exc)}") from exc
raise ConnectionFailedError(f"Failed to delete notification: {str(exc)}") from exc

async def reboot_gateway(self) -> None:
"""Reboot the Plugwise Gateway."""
try:
await self._smile_api.reboot_gateway()
except ConnectionFailedError as exc:
raise PlugwiseError(f"Failed to reboot gateway: {str(exc)}") from exc
raise ConnectionFailedError(f"Failed to reboot gateway: {str(exc)}") from exc
22 changes: 16 additions & 6 deletions plugwise/legacy/smile.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from __future__ import annotations

import datetime as dt
from typing import Any

from plugwise.constants import (
APPLIANCES,
Expand All @@ -23,7 +24,7 @@
PlugwiseData,
ThermoLoc,
)
from plugwise.exceptions import PlugwiseError
from plugwise.exceptions import ConnectionFailedError, PlugwiseError
from plugwise.helper import SmileComm
from plugwise.legacy.data import SmileLegacyData

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

await self._request(RULES, method="put", data=data)
await self.call_request(RULES, method="put", data=data)

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

await self._request(uri, method="put", data=data)
await self.call_request(uri, method="put", data=data)

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

await self._request(uri, method="put", data=data)
await self.call_request(uri, method="put", data=data)

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

await self._request(uri, method="put", data=data)
await self.call_request(uri, method="put", data=data)

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

await self._request(uri, method="put", data=data)
await self.call_request(uri, method="put", data=data)

async def call_request(self, uri: str, **kwargs: Any) -> None:
"""ConnectionFailedError wrapper for calling _request()."""
method: str = kwargs["method"]
data: str | None = kwargs.get("data")
try:
await self._request(uri, method=method, data=data)
except ConnectionFailedError as exc:
raise ConnectionFailedError from exc
37 changes: 19 additions & 18 deletions plugwise/smile.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,14 +149,6 @@ async def async_update(self) -> PlugwiseData:
### API Set and HA Service-related Functions ###
########################################################################################################

async def call_request(self, uri: str, **kwargs: Any) -> None:
"""ConnectionFailedError wrapper for calling _request()."""
method: str = kwargs["method"]
try:
await self._request(uri, method=method)
except ConnectionFailedError as exc:
raise ConnectionFailedError from exc

async def delete_notification(self) -> None:
"""Delete the active Plugwise Notification."""
await self.call_request(NOTIFICATIONS, method="delete")
Expand Down Expand Up @@ -189,7 +181,7 @@ async def set_number(

uri = f"{APPLIANCES};id={self._heater_id}/thermostat;id={thermostat_id}"
data = f"<thermostat_functionality><setpoint>{temp}</setpoint></thermostat_functionality>"
await self._request(uri, method="put", data=data)
await self.call_request(uri, method="put", data=data)

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

await self._request(uri, method="put", data=data)
await self.call_request(uri, method="put", data=data)

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

await self._request(uri, method="put", data=data)
await self.call_request(uri, method="put", data=data)

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

await self._request(uri, method="put", data=data)
await self.call_request(uri, method="put", data=data)

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

await self._request(uri, method="put", data=data)
await self.call_request(uri, method="put", data=data)

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

await self._request(uri, method="put", data=data)
await self.call_request(uri, method="put", data=data)

async def set_schedule_state(
self,
Expand Down Expand Up @@ -335,7 +327,7 @@ async def set_schedule_state(
f"{template}{contexts}</rule></rules>"
)

await self._request(uri, method="put", data=data)
await self.call_request(uri, method="put", data=data)
self._schedule_old_states[loc_id][name] = new_state

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

await self._request(uri, method="put", data=data)
await self.call_request(uri, method="put", data=data)

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

await self._request(uri, method="put", data=data)
await self.call_request(uri, method="put", data=data)

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

await self._request(uri, method="put", data=data)
await self.call_request(uri, method="put", data=data)

async def call_request(self, uri: str, **kwargs: Any) -> None:
"""ConnectionFailedError wrapper for calling _request()."""
method: str = kwargs["method"]
data: str | None = kwargs.get("data")
try:
await self._request(uri, method=method, data=data)
except ConnectionFailedError as exc:
raise ConnectionFailedError from exc
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "plugwise"
version = "0.38.0"
version = "0.38.1"
license = {file = "LICENSE"}
description = "Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3."
readme = "README.md"
Expand Down
39 changes: 32 additions & 7 deletions tests/test_adam.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ async def test_connect_adam_zone_per_device(self):
await self.disconnect(server, client)

server, smile, client = await self.connect_wrapper(raise_timeout=True)
await self.device_test(smile, "2022-05-16 00:00:01", testdata)
await self.device_test(smile, "2022-05-16 00:00:01", testdata, skip_testing=True)
result = await self.tinker_thermostat(
smile,
"c50f167537524366a5af7aa3942feb1e",
Expand All @@ -79,15 +79,18 @@ async def test_connect_adam_zone_per_device(self):
)
assert result

tinkered = await self.tinker_max_boiler_temp(smile, unhappy=True)
assert not tinkered

try:
await smile.delete_notification()
notification_deletion = False # pragma: no cover
except pw_exceptions.PlugwiseError:
except pw_exceptions.ConnectionFailedError:
notification_deletion = True
assert notification_deletion

reboot = await self.tinker_reboot(smile, unhappy=True)
assert not reboot
assert reboot

await smile.close_connection()
await self.disconnect(server, client)
Expand Down Expand Up @@ -212,7 +215,7 @@ async def test_connect_adam_plus_anna(self):
await self.disconnect(server, client)

server, smile, client = await self.connect_wrapper(raise_timeout=True)
await self.device_test(smile, "2020-03-22 00:00:01", testdata)
await self.device_test(smile, "2020-03-22 00:00:01", testdata, skip_testing=True)
result = await self.tinker_thermostat(
smile,
"009490cc2f674ce6b576863fbb64f867",
Expand Down Expand Up @@ -322,9 +325,14 @@ async def test_connect_adam_plus_anna_new(self):
)
assert not switch_change

await self.tinker_gateway_mode(smile)
await self.tinker_regulation_mode(smile)
await self.tinker_max_boiler_temp(smile)
tinkered = await self.tinker_gateway_mode(smile)
assert not tinkered

tinkered = await self.tinker_regulation_mode(smile)
assert not tinkered

tinkered = await self.tinker_max_boiler_temp(smile)
assert not tinkered

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

self.smile_setup = "adam_plus_anna_new"
testdata = self.load_testdata(SMILE_TYPE, self.smile_setup)
server, smile, client = await self.connect_wrapper(raise_timeout=True)
await self.device_test(smile, "2023-12-17 00:00:01", testdata, skip_testing=True)

tinkered = await self.tinker_max_boiler_temp(smile, unhappy=True)
assert tinkered

tinkered = await self.tinker_gateway_mode(smile, unhappy=True)
assert tinkered

tinkered = await self.tinker_regulation_mode(smile, unhappy=True)
assert tinkered

await smile.close_connection()
await self.disconnect(server, client)

@pytest.mark.asyncio
async def test_adam_plus_jip(self):
"""Test Adam with Jip setup."""
Expand Down
Loading