1717
1818from .constants import (
1919 ADAM ,
20+ ANNA ,
2021 APPLIANCES ,
2122 DEFAULT_PORT ,
2223 DEFAULT_TIMEOUT ,
5152from .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
6665class 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"
0 commit comments