Skip to content

Commit df14c4b

Browse files
authored
Merge pull request #455 from plugwise/code-improve
Improve/optimize/reorder - part 1
2 parents 240ef9e + ba5cdcd commit df14c4b

File tree

3 files changed

+109
-113
lines changed

3 files changed

+109
-113
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Ongoing
44

55
- Typing-constants clean-up
6+
- Improve/optimize/reorder
67

78
## v0.35.1
89

plugwise/__init__.py

Lines changed: 92 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from .constants import (
1919
ADAM,
20+
ANNA,
2021
APPLIANCES,
2122
DEFAULT_PORT,
2223
DEFAULT_TIMEOUT,
@@ -51,7 +52,7 @@
5152
from .helper import SmileComm, SmileHelper
5253

5354

54-
def remove_empty_platform_dicts(data: DeviceData) -> DeviceData:
55+
def remove_empty_platform_dicts(data: DeviceData) -> None:
5556
"""Helper-function for removing any empty platform dicts."""
5657
if not data["binary_sensors"]:
5758
data.pop("binary_sensors")
@@ -60,16 +61,47 @@ def remove_empty_platform_dicts(data: DeviceData) -> DeviceData:
6061
if not data["switches"]:
6162
data.pop("switches")
6263

63-
return data
64-
6564

6665
class SmileData(SmileHelper):
6766
"""The Plugwise Smile main class."""
6867

69-
def update_for_cooling(self, device: DeviceData) -> DeviceData:
68+
def _update_gw_devices(self) -> None:
69+
"""Helper-function for _all_device_data() and async_update().
70+
71+
Collect data for each device and add to self.gw_devices.
72+
"""
73+
for device_id, device in self.gw_devices.items():
74+
data = self._get_device_data(device_id)
75+
self._add_or_update_notifications(data, device_id, device)
76+
device.update(data)
77+
self._update_for_cooling(device)
78+
remove_empty_platform_dicts(device)
79+
80+
def _add_or_update_notifications(
81+
self, data: DeviceData, device_id: str, device: DeviceData
82+
) -> None:
83+
"""Helper-function adding or updating the Plugwise notifications."""
84+
if (
85+
device_id == self.gateway_id
86+
and (
87+
self._is_thermostat
88+
or (self.smile_type == "power" and not self._smile_legacy)
89+
)
90+
) or (
91+
"binary_sensors" in device
92+
and "plugwise_notification" in device["binary_sensors"]
93+
):
94+
data["binary_sensors"]["plugwise_notification"] = bool(self._notifications)
95+
self._count += 1
96+
97+
def _update_for_cooling(self, device: DeviceData) -> None:
7098
"""Helper-function for adding/updating various cooling-related values."""
71-
# For heating + cooling, replace setpoint with setpoint_high/_low
72-
if self._cooling_present:
99+
# For Anna and heating + cooling, replace setpoint with setpoint_high/_low
100+
if (
101+
self.smile(ANNA)
102+
and self._cooling_present
103+
and device["dev_class"] == "thermostat"
104+
):
73105
thermostat = device["thermostat"]
74106
sensors = device["sensors"]
75107
temp_dict: ActuatorData = {
@@ -90,36 +122,27 @@ def update_for_cooling(self, device: DeviceData) -> DeviceData:
90122
sensors["setpoint_high"] = temp_dict["setpoint_high"]
91123
self._count += 2
92124

93-
return device
94-
95-
def _update_gw_devices(self) -> None:
96-
"""Helper-function for _all_device_data() and async_update().
125+
def get_all_devices(self) -> None:
126+
"""Determine the evices present from the obtained XML-data.
97127
98-
Collect data for each device and add to self.gw_devices.
128+
Run this functions once to gather the initial device configuration,
129+
then regularly run async_update() to refresh the device data.
99130
"""
100-
for device_id, device in self.gw_devices.items():
101-
data = self._get_device_data(device_id)
102-
if (
103-
"binary_sensors" in device
104-
and "plugwise_notification" in device["binary_sensors"]
105-
) or (
106-
device_id == self.gateway_id
107-
and (
108-
self._is_thermostat
109-
or (self.smile_type == "power" and not self._smile_legacy)
110-
)
111-
):
112-
data["binary_sensors"]["plugwise_notification"] = bool(
113-
self._notifications
114-
)
115-
self._count += 1
116-
device.update(data)
131+
# Gather all the devices and their initial data
132+
self._all_appliances()
133+
if self._is_thermostat:
134+
self._scan_thermostats()
135+
# Collect a list of thermostats with offset-capability
136+
self.therms_with_offset_func = (
137+
self._get_appliances_with_offset_functionality()
138+
)
117139

118-
# Update for cooling
119-
if device["dev_class"] in ZONE_THERMOSTATS and not self.smile(ADAM):
120-
self.update_for_cooling(device)
140+
# Collect and add switching- and/or pump-group devices
141+
if group_data := self._get_group_switches():
142+
self.gw_devices.update(group_data)
121143

122-
remove_empty_platform_dicts(device)
144+
# Collect the remaining data for all devices
145+
self._all_device_data()
123146

124147
def _all_device_data(self) -> None:
125148
"""Helper-function for get_all_devices().
@@ -145,28 +168,6 @@ def _all_device_data(self) -> None:
145168
{"heater_id": self._heater_id, "cooling_present": self._cooling_present}
146169
)
147170

148-
def get_all_devices(self) -> None:
149-
"""Determine the evices present from the obtained XML-data.
150-
151-
Run this functions once to gather the initial device configuration,
152-
then regularly run async_update() to refresh the device data.
153-
"""
154-
# Gather all the devices and their initial data
155-
self._all_appliances()
156-
if self.smile_type == "thermostat":
157-
self._scan_thermostats()
158-
# Collect a list of thermostats with offset-capability
159-
self.therms_with_offset_func = (
160-
self._get_appliances_with_offset_functionality()
161-
)
162-
163-
# Collect switching- or pump-group data
164-
if group_data := self._get_group_switches():
165-
self.gw_devices.update(group_data)
166-
167-
# Collect the remaining data for all device
168-
self._all_device_data()
169-
170171
def _device_data_switching_group(
171172
self, device: DeviceData, device_data: DeviceData
172173
) -> DeviceData:
@@ -190,17 +191,32 @@ def _device_data_adam(
190191
) -> DeviceData:
191192
"""Helper-function for _get_device_data().
192193
193-
Determine Adam heating-status for on-off heating via valves.
194+
Determine Adam heating-status for on-off heating via valves,
195+
available regulations_modes and thermostat control_states.
194196
"""
197+
if not self.smile(ADAM):
198+
return device_data
199+
195200
# Indicate heating_state based on valves being open in case of city-provided heating
196201
if (
197-
self.smile(ADAM)
198-
and device.get("dev_class") == "heater_central"
202+
device["dev_class"] == "heater_central"
199203
and self._on_off_device
200204
and isinstance(self._heating_valves(), int)
201205
):
202206
device_data["binary_sensors"]["heating_state"] = self._heating_valves() != 0
203207

208+
# Show the allowed regulation modes for Adam
209+
if device["dev_class"] == "gateway" and self._reg_allowed_modes:
210+
device_data["regulation_modes"] = self._reg_allowed_modes
211+
self._count += 1
212+
213+
# Control_state, only for Adam master thermostats
214+
if device["dev_class"] in ZONE_THERMOSTATS:
215+
loc_id = device["location"]
216+
if ctrl_state := self._control_state(loc_id):
217+
device_data["control_state"] = ctrl_state
218+
self._count += 1
219+
204220
return device_data
205221

206222
def _device_data_climate(
@@ -226,11 +242,6 @@ def _device_data_climate(
226242
device_data["select_schedule"] = sel_schedule
227243
self._count += 2
228244

229-
# Control_state, only for Adam master thermostats
230-
if ctrl_state := self._control_state(loc_id):
231-
device_data["control_state"] = ctrl_state
232-
self._count += 1
233-
234245
# Operation modes: auto, heat, heat_cool, cool and off
235246
device_data["mode"] = "auto"
236247
self._count += 1
@@ -247,7 +258,7 @@ def _device_data_climate(
247258
if NONE in avail_schedules:
248259
return device_data
249260

250-
device_data = self._get_schedule_states_with_off(
261+
self._get_schedule_states_with_off(
251262
loc_id, avail_schedules, sel_schedule, device_data
252263
)
253264
return device_data
@@ -261,7 +272,7 @@ def check_reg_mode(self, mode: str) -> bool:
261272

262273
def _get_schedule_states_with_off(
263274
self, location: str, schedules: list[str], selected: str, data: DeviceData
264-
) -> DeviceData:
275+
) -> None:
265276
"""Collect schedules with states for each thermostat.
266277
267278
Also, replace NONE by OFF when none of the schedules are active,
@@ -282,11 +293,7 @@ def _get_schedule_states_with_off(
282293
if all_off:
283294
data["select_schedule"] = OFF
284295

285-
return data
286-
287-
def _check_availability(
288-
self, device: DeviceData, device_data: DeviceData
289-
) -> DeviceData:
296+
def _check_availability(self, device: DeviceData, device_data: DeviceData) -> None:
290297
"""Helper-function for _get_device_data().
291298
292299
Provide availability status for the wired-commected devices.
@@ -309,45 +316,23 @@ def _check_availability(
309316
if "P1 does not seem to be connected to a smart meter" in msg:
310317
device_data["available"] = False
311318

312-
return device_data
313-
314319
def _get_device_data(self, dev_id: str) -> DeviceData:
315320
"""Helper-function for _all_device_data() and async_update().
316321
317322
Provide device-data, based on Location ID (= dev_id), from APPLIANCES.
318323
"""
319324
device = self.gw_devices[dev_id]
320325
device_data = self._get_measurement_data(dev_id)
321-
# Generic
322-
if self.smile_type == "thermostat" and device["dev_class"] == "gateway":
323-
# Adam & Anna: the Smile outdoor_temperature is present in DOMAIN_OBJECTS and LOCATIONS - under Home
324-
# The outdoor_temperature present in APPLIANCES is a local sensor connected to the active device
325-
outdoor_temperature = self._object_value(
326-
self._home_location, "outdoor_temperature"
327-
)
328-
if outdoor_temperature is not None:
329-
device_data["sensors"]["outdoor_temperature"] = outdoor_temperature
330-
self._count += 1
331-
332-
# Show the allowed regulation modes
333-
if self._reg_allowed_modes:
334-
device_data["regulation_modes"] = self._reg_allowed_modes
335-
self._count += 1
336-
337-
# Show the allowed dhw_modes
338-
if device["dev_class"] == "heater_central" and self._dhw_allowed_modes:
339-
device_data["dhw_modes"] = self._dhw_allowed_modes
340-
self._count += 1
341326

342327
# Check availability of non-legacy wired-connected devices
343328
if not self._smile_legacy:
344329
self._check_availability(device, device_data)
345330

346331
# Switching groups data
347332
device_data = self._device_data_switching_group(device, device_data)
348-
# Specific, not generic Adam data
333+
# Adam data
349334
device_data = self._device_data_adam(device, device_data)
350-
# No need to obtain thermostat data when the device is not a thermostat
335+
# Skip obtaining data for non master-thermostats
351336
if device["dev_class"] not in ZONE_THERMOSTATS:
352337
return device_data
353338

@@ -539,14 +524,18 @@ async def _smile_detect(self, result: etree, dsmrmain: etree) -> None:
539524
if result.find(locator_2) is not None:
540525
self._elga = True
541526

542-
async def _update_domain_objects(self) -> None:
543-
"""Helper-function for smile.py: full_update_device() and async_update().
544-
545-
Request domain_objects data.
546-
"""
527+
async def _full_update_device(self) -> None:
528+
"""Perform a first fetch of all XML data, needed for initialization."""
547529
self._domain_objects = await self._request(DOMAIN_OBJECTS)
530+
self._get_plugwise_notifications()
531+
self._locations = await self._request(LOCATIONS)
532+
self._modules = await self._request(MODULES)
533+
# P1 legacy has no appliances
534+
if not (self.smile_type == "power" and self._smile_legacy):
535+
self._appliances = await self._request(APPLIANCES)
548536

549-
# If Plugwise notifications present:
537+
def _get_plugwise_notifications(self) -> None:
538+
"""Collect the Plugwise notifications."""
550539
self._notifications = {}
551540
for notification in self._domain_objects.findall("./notification"):
552541
try:
@@ -561,15 +550,6 @@ async def _update_domain_objects(self) -> None:
561550
f"{self._endpoint}{DOMAIN_OBJECTS}",
562551
)
563552

564-
async def _full_update_device(self) -> None:
565-
"""Perform a first fetch of all XML data, needed for initialization."""
566-
await self._update_domain_objects()
567-
self._locations = await self._request(LOCATIONS)
568-
self._modules = await self._request(MODULES)
569-
# P1 legacy has no appliances
570-
if not (self.smile_type == "power" and self._smile_legacy):
571-
self._appliances = await self._request(APPLIANCES)
572-
573553
async def async_update(self) -> PlugwiseData:
574554
"""Perform an incremental update for updating the various device states."""
575555
# Perform a full update at day-change
@@ -587,7 +567,8 @@ async def async_update(self) -> PlugwiseData:
587567
self.get_all_devices()
588568
# Otherwise perform an incremental update
589569
else:
590-
await self._update_domain_objects()
570+
self._domain_objects = await self._request(DOMAIN_OBJECTS)
571+
self._get_plugwise_notifications()
591572
match self._target_smile:
592573
case "smile_v2":
593574
self._modules = await self._request(MODULES)
@@ -701,7 +682,7 @@ async def set_schedule_state(
701682
template = (
702683
'<template tag="zone_preset_based_on_time_and_presence_with_override" />'
703684
)
704-
if not self.smile(ADAM):
685+
if self.smile(ANNA):
705686
locator = f'.//*[@id="{schedule_rule_id}"]/template'
706687
template_id = self._domain_objects.find(locator).attrib["id"]
707688
template = f'<template id="{template_id}" />'
@@ -755,7 +736,7 @@ async def set_temperature(self, loc_id: str, items: dict[str, float]) -> None:
755736
if "setpoint" in items:
756737
setpoint = items["setpoint"]
757738

758-
if self._cooling_present and not self.smile(ADAM):
739+
if self.smile(ANNA) and self._cooling_present:
759740
if "setpoint_high" not in items:
760741
raise PlugwiseError(
761742
"Plugwise: failed setting temperature: no valid input provided"

plugwise/helper.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,10 @@ def _get_measurement_data(self, dev_id: str) -> DeviceData:
10161016
measurements = DEVICE_MEASUREMENTS
10171017
if self._is_thermostat and dev_id == self._heater_id:
10181018
measurements = HEATER_CENTRAL_MEASUREMENTS
1019+
# Show the allowed dhw_modes (Loria only)
1020+
if self._dhw_allowed_modes:
1021+
data["dhw_modes"] = self._dhw_allowed_modes
1022+
# Counting of this item is done in _appliance_measurements()
10191023

10201024
if (
10211025
appliance := self._appliances.find(f'./appliance[@id="{dev_id}"]')
@@ -1032,8 +1036,18 @@ def _get_measurement_data(self, dev_id: str) -> DeviceData:
10321036
# Collect availability-status for wireless connected devices to Adam
10331037
self._wireless_availablity(appliance, data)
10341038

1035-
if dev_id == self.gateway_id and self.smile(ADAM):
1036-
self._get_regulation_mode(appliance, data)
1039+
if dev_id == self.gateway_id and self.smile(ADAM):
1040+
self._get_regulation_mode(appliance, data)
1041+
1042+
# Adam & Anna: the Smile outdoor_temperature is present in DOMAIN_OBJECTS and LOCATIONS - under Home
1043+
# The outdoor_temperature present in APPLIANCES is a local sensor connected to the active device
1044+
if self._is_thermostat and dev_id == self.gateway_id:
1045+
outdoor_temperature = self._object_value(
1046+
self._home_location, "outdoor_temperature"
1047+
)
1048+
if outdoor_temperature is not None:
1049+
data.update({"sensors": {"outdoor_temperature": outdoor_temperature}})
1050+
self._count += 1
10371051

10381052
if "c_heating_state" in data:
10391053
self._process_c_heating_state(data)

0 commit comments

Comments
 (0)