44from __future__ import annotations
55
66import aiohttp
7- from dateutil .parser import parse
87from defusedxml import ElementTree as etree
98
109# Dict as class
4645 ResponseError ,
4746 UnsupportedDeviceError ,
4847)
49- from .helper import SmileComm , SmileHelper , update_helper
48+ from .helper import SmileComm , SmileHelper , _find , _findall , update_helper
5049
5150
5251class SmileData (SmileHelper ):
@@ -130,11 +129,11 @@ def get_all_devices(self) -> None:
130129 # Start by determining the system capabilities:
131130 # Find the connected heating/cooling device (heater_central), e.g. heat-pump or gas-fired heater
132131 if self .smile_type == "thermostat" :
133- onoff_boiler : etree = self . _domain_objects . find (
134- "./module/protocols/onoff_boiler"
132+ onoff_boiler : etree = _find (
133+ self . _domain_objects , "./module/protocols/onoff_boiler"
135134 )
136- open_therm_boiler : etree = self . _domain_objects . find (
137- "./module/protocols/open_therm_boiler"
135+ open_therm_boiler : etree = _find (
136+ self . _domain_objects , "./module/protocols/open_therm_boiler"
138137 )
139138 self ._on_off_device = onoff_boiler is not None
140139 self ._opentherm_device = open_therm_boiler is not None
@@ -144,11 +143,12 @@ def get_all_devices(self) -> None:
144143 locator_2 = "./gateway/features/elga_support"
145144 search = self ._domain_objects
146145 self ._cooling_present = False
147- if search . find ( locator_1 ) is not None :
146+ if _find ( search , locator_1 ) is not None :
148147 self ._cooling_present = True
149148 # Alternative method for the Anna with Elga
150- elif search . find ( locator_2 ) is not None :
149+ elif _find ( search , locator_2 ) is not None :
151150 self ._cooling_present = True
151+ self ._elga = True
152152
153153 # Gather all the device and initial data
154154 self ._scan_thermostats ()
@@ -267,23 +267,6 @@ def _check_availability(
267267 if "P1 does not seem to be connected to a smart meter" in msg :
268268 device_data ["available" ] = False
269269
270- # Anna thermostat
271- if "modified" in device_data :
272- time_now : str | None = None
273- if (
274- time_now := self ._domain_objects .find ("./gateway/time" ).text
275- ) is not None :
276- interval = (
277- parse (time_now ) - parse (device_data ["modified" ])
278- ).total_seconds ()
279- if interval > 0 :
280- if details ["dev_class" ] == "thermostat" :
281- device_data ["available" ] = False
282- if interval < 90 :
283- device_data ["available" ] = True
284-
285- device_data .pop ("modified" )
286-
287270 def _get_device_data (self , dev_id : str ) -> DeviceData :
288271 """Helper-function for _all_device_data() and async_update().
289272 Provide device-data, based on Location ID (= dev_id), from APPLIANCES.
@@ -368,12 +351,12 @@ def __init__(
368351 async def connect (self ) -> bool :
369352 """Connect to Plugwise device and determine its name, type and version."""
370353 result = await self ._request (DOMAIN_OBJECTS )
371- vendor_names : list [etree ] = result . findall ( "./module/vendor_name" )
372- vendor_models : list [etree ] = result . findall ( "./module/vendor_model" )
354+ vendor_names : list [etree ] = _findall ( result , "./module/vendor_name" )
355+ vendor_models : list [etree ] = _findall ( result , "./module/vendor_model" )
373356 # Work-around for Stretch fv 2.7.18
374357 if not vendor_names :
375358 result = await self ._request (MODULES )
376- vendor_names = result . findall ( "./module/vendor_name" )
359+ vendor_names = _findall ( result , "./module/vendor_name" )
377360
378361 names : list [str ] = []
379362 models : list [str ] = []
@@ -382,7 +365,7 @@ async def connect(self) -> bool:
382365 for model in vendor_models :
383366 models .append (model .text )
384367
385- dsmrmain = result . find ( "./module/protocols/dsmrmain" )
368+ dsmrmain = _find ( result , "./module/protocols/dsmrmain" )
386369 if "Plugwise" not in names and dsmrmain is None : # pragma: no cover
387370 LOGGER .error (
388371 "Connected but expected text not returned, we got %s. Please create \
@@ -409,40 +392,42 @@ async def connect(self) -> bool:
409392 async def _smile_detect_legacy (self , result : etree , dsmrmain : etree ) -> str :
410393 """Helper-function for _smile_detect()."""
411394 # Stretch: find the MAC of the zigbee master_controller (= Stick)
412- if network := result . find ( "./module/protocols/master_controller" ):
413- self .smile_zigbee_mac_address = network . find ( "mac_address" ).text
395+ if network := _find ( result , "./module/protocols/master_controller" ):
396+ self .smile_zigbee_mac_address = _find ( network , "mac_address" ).text
414397 # Find the active MAC in case there is an orphaned Stick
415- if zb_networks := result . findall ( "./network" ):
398+ if zb_networks := _findall ( result , "./network" ):
416399 for zb_network in zb_networks :
417- if zb_network . find ( "./nodes/network_router" ):
418- network = zb_network . find ( "./master_controller" )
419- self .smile_zigbee_mac_address = network . find ( "mac_address" ).text
400+ if _find ( zb_network , "./nodes/network_router" ):
401+ network = _find ( zb_network , "./master_controller" )
402+ self .smile_zigbee_mac_address = _find ( network , "mac_address" ).text
420403
421404 # Assume legacy
422405 self ._smile_legacy = True
423406 # Try if it is a legacy Anna, assuming appliance thermostat,
424407 # fake insert version assuming Anna, couldn't find another way to identify as legacy Anna
425408 self .smile_fw_version = "1.8.0"
426409 model = "smile_thermo"
427- if result . find ( './appliance[type="thermostat"]' ) is None :
410+ if _find ( result , './appliance[type="thermostat"]' ) is None :
428411 # It's a P1 legacy:
429412 if dsmrmain is not None :
430413 self ._status = await self ._request (STATUS )
431- self .smile_fw_version = self ._status .find ("./system/version" ).text
432- model = self ._status .find ("./system/product" ).text
433- self .smile_hostname = self ._status .find ("./network/hostname" ).text
434- self .smile_mac_address = self ._status .find ("./network/mac_address" ).text
414+ self .smile_fw_version = _find (self ._status , "./system/version" ).text
415+ model = _find (self ._status , "./system/product" ).text
416+ self .smile_hostname = _find (self ._status , "./network/hostname" ).text
417+ self .smile_mac_address = _find (
418+ self ._status , "./network/mac_address"
419+ ).text
435420
436421 # Or a legacy Stretch:
437422 elif network is not None :
438423 self ._system = await self ._request (SYSTEM )
439- self .smile_fw_version = self ._system . find ( "./gateway/firmware" ).text
440- model = self ._system . find ( "./gateway/product" ).text
441- self .smile_hostname = self ._system . find ( "./gateway/hostname" ).text
424+ self .smile_fw_version = _find ( self ._system , "./gateway/firmware" ).text
425+ model = _find ( self ._system , "./gateway/product" ).text
426+ self .smile_hostname = _find ( self ._system , "./gateway/hostname" ).text
442427 # If wlan0 contains data it's active, so eth0 should be checked last
443428 for network in ("wlan0" , "eth0" ):
444429 locator = f"./{ network } /mac"
445- if (net_locator := self ._system . find ( locator )) is not None :
430+ if (net_locator := _find ( self ._system , locator )) is not None :
446431 self .smile_mac_address = net_locator .text
447432
448433 else : # pragma: no cover
@@ -460,12 +445,12 @@ async def _smile_detect(self, result: etree, dsmrmain: etree) -> None:
460445 Detect which type of Smile is connected.
461446 """
462447 model : str | None = None
463- if (gateway := result . find ( "./gateway" )) is not None :
464- model = gateway . find ( "vendor_model" ).text
465- self .smile_fw_version = gateway . find ( "firmware_version" ).text
466- self .smile_hw_version = gateway . find ( "hardware_version" ).text
467- self .smile_hostname = gateway . find ( "hostname" ).text
468- self .smile_mac_address = gateway . find ( "mac_address" ).text
448+ if (gateway := _find ( result , "./gateway" )) is not None :
449+ model = _find ( gateway , "vendor_model" ).text
450+ self .smile_fw_version = _find ( gateway , "firmware_version" ).text
451+ self .smile_hw_version = _find ( gateway , "hardware_version" ).text
452+ self .smile_hostname = _find ( gateway , "hostname" ).text
453+ self .smile_mac_address = _find ( gateway , "mac_address" ).text
469454 else :
470455 model = await self ._smile_detect_legacy (result , dsmrmain )
471456
@@ -520,11 +505,11 @@ async def _update_domain_objects(self) -> None:
520505
521506 # If Plugwise notifications present:
522507 self ._notifications = {}
523- for notification in self ._domain_objects . findall ( "./notification" ):
508+ for notification in _findall ( self ._domain_objects , "./notification" ):
524509 try :
525510 msg_id = notification .attrib ["id" ]
526- msg_type = notification . find ( "type" ).text
527- msg = notification . find ( "message" ).text
511+ msg_type = _find ( notification , "type" ).text
512+ msg = _find ( notification , "message" ).text
528513 self ._notifications .update ({msg_id : {msg_type : msg }})
529514 LOGGER .debug ("Plugwise notifications: %s" , self ._notifications )
530515 except AttributeError : # pragma: no cover
@@ -580,8 +565,8 @@ async def _set_schedule_state_legacy(
580565 ) -> None :
581566 """Helper-function for set_schedule_state()."""
582567 schedule_rule_id : str | None = None
583- for rule in self ._domain_objects . findall ( "rule" ):
584- if rule . find ( "name" ).text == name :
568+ for rule in _findall ( self ._domain_objects , "rule" ):
569+ if _find ( rule , "name" ).text == name :
585570 schedule_rule_id = rule .attrib ["id" ]
586571
587572 if schedule_rule_id is None :
@@ -595,7 +580,7 @@ async def _set_schedule_state_legacy(
595580 return
596581
597582 locator = f'.//*[@id="{ schedule_rule_id } "]/template'
598- for rule in self ._domain_objects . findall ( locator ):
583+ for rule in _findall ( self ._domain_objects , locator ):
599584 template_id = rule .attrib ["id" ]
600585
601586 uri = f"{ RULES } ;id={ schedule_rule_id } "
@@ -643,13 +628,13 @@ async def set_schedule_state(
643628 )
644629 if self .smile_name != "Adam" :
645630 locator = f'.//*[@id="{ schedule_rule_id } "]/template'
646- template_id = self ._domain_objects . find ( locator ).attrib ["id" ]
631+ template_id = _find ( self ._domain_objects , locator ).attrib ["id" ]
647632 template = f'<template id="{ template_id } " />'
648633
649634 locator = f'.//*[@id="{ schedule_rule_id } "]/contexts'
650- contexts = self ._domain_objects . find ( locator )
635+ contexts = _find ( self ._domain_objects , locator )
651636 locator = f'.//*[@id="{ loc_id } "].../...'
652- if (subject := contexts . find ( locator )) is None :
637+ if (subject := _find ( contexts , locator )) is None :
653638 subject = f'<context><zone><location id="{ loc_id } " /></zone></context>'
654639 subject = etree .fromstring (subject )
655640
@@ -672,7 +657,7 @@ async def set_schedule_state(
672657 async def _set_preset_legacy (self , preset : str ) -> None :
673658 """Set the given Preset on the relevant Thermostat - from DOMAIN_OBJECTS."""
674659 locator = f'rule/directives/when/then[@icon="{ preset } "].../.../...'
675- rule = self ._domain_objects . find ( locator )
660+ rule = _find ( self ._domain_objects , locator )
676661 data = f'<rules><rule id="{ rule .attrib ["id" ]} "><active>true</active></rule></rules>'
677662
678663 await self ._request (RULES , method = "put" , data = data )
@@ -688,9 +673,9 @@ async def set_preset(self, loc_id: str, preset: str) -> None:
688673 await self ._set_preset_legacy (preset )
689674 return
690675
691- current_location = self ._locations . find ( f'location[@id="{ loc_id } "]' )
692- location_name = current_location . find ( "name" ).text
693- location_type = current_location . find ( "type" ).text
676+ current_location = _find ( self ._locations , f'location[@id="{ loc_id } "]' )
677+ location_name = _find ( current_location , "name" ).text
678+ location_type = _find ( current_location , "type" ).text
694679
695680 uri = f"{ LOCATIONS } ;id={ loc_id } "
696681 data = (
@@ -732,9 +717,9 @@ async def set_number_setpoint(self, key: str, temperature: float) -> None:
732717 temp = str (temperature )
733718 thermostat_id : str | None = None
734719 locator = f'appliance[@id="{ self ._heater_id } "]/actuator_functionalities/thermostat_functionality'
735- if th_func_list := self ._appliances . findall ( locator ):
720+ if th_func_list := _findall ( self ._appliances , locator ):
736721 for th_func in th_func_list :
737- if th_func . find ( "type" ).text == key :
722+ if _find ( th_func , "type" ).text == key :
738723 thermostat_id = th_func .attrib ["id" ]
739724
740725 if thermostat_id is None :
@@ -752,7 +737,7 @@ async def _set_groupswitch_member_state(
752737 """
753738 for member in members :
754739 locator = f'appliance[@id="{ member } "]/{ switch .actuator } /{ switch .func_type } '
755- switch_id = self ._appliances . find ( locator ).attrib ["id" ]
740+ switch_id = _find ( self ._appliances , locator ).attrib ["id" ]
756741 uri = f"{ APPLIANCES } ;id={ member } /{ switch .device } ;id={ switch_id } "
757742 if self ._stretch_v2 :
758743 uri = f"{ APPLIANCES } ;id={ member } /{ switch .device } "
@@ -791,9 +776,9 @@ async def set_switch_state(
791776 return await self ._set_groupswitch_member_state (members , state , switch )
792777
793778 locator = f'appliance[@id="{ appl_id } "]/{ switch .actuator } /{ switch .func_type } '
794- found : list [etree ] = self ._appliances . findall ( locator )
779+ found : list [etree ] = _findall ( self ._appliances , locator )
795780 for item in found :
796- if (sw_type := item . find ( "type" )) is not None :
781+ if (sw_type := _find ( item , "type" )) is not None :
797782 if sw_type .text == switch .act_type :
798783 switch_id = item .attrib ["id" ]
799784 else :
@@ -810,7 +795,7 @@ async def set_switch_state(
810795 f'appliance[@id="{ appl_id } "]/{ switch .actuator } /{ switch .func_type } /lock'
811796 )
812797 # Don't bother switching a relay when the corresponding lock-state is true
813- if self ._appliances . find ( locator ).text == "true" :
798+ if _find ( self ._appliances , locator ).text == "true" :
814799 raise PlugwiseError ("Plugwise: the locked Relay was not switched." )
815800
816801 await self ._request (uri , method = "put" , data = data )
0 commit comments