11"""Use of this source code is governed by the MIT license found in the LICENSE file.
22Plugwise backend module for Home Assistant Core.
33"""
4- import logging
5-
64import aiohttp
5+ from defusedxml import ElementTree as etree
76
87# Dict as class
98from munch import Munch
1817 DEFAULT_USERNAME ,
1918 DOMAIN_OBJECTS ,
2019 LOCATIONS ,
20+ LOGGER ,
2121 MODULES ,
2222 NOTIFICATIONS ,
2323 PW_NOTIFICATION ,
3131from .exceptions import ConnectionFailedError , InvalidXMLError , UnsupportedDeviceError
3232from .helper import SmileComm , SmileHelper , pw_notification_updater , update_helper
3333
34- _LOGGER = logging .getLogger (__name__ )
35-
3634
3735class SmileData (SmileHelper ):
3836 """The Plugwise Smile main class."""
@@ -42,7 +40,7 @@ def _append_special(self, data, d_id, bs_dict, s_dict):
4240 When conditions are met, the plugwise_notification binary_sensor is appended.
4341 """
4442 if d_id == self .gateway_id :
45- if self ._sm_thermostat is not None :
43+ if self ._is_thermostat :
4644 bs_dict .update (PW_NOTIFICATION )
4745
4846 def _all_device_data (self ):
@@ -69,11 +67,13 @@ def _all_device_data(self):
6967
7068 self .gw_devices [dev_id ] = dev_and_data
7169
72- self .gw_data ["active_device" ] = self ._ot_device or self ._on_off_device
70+ self .gw_data ["active_device" ] = self ._opentherm_device or self ._on_off_device
7371 self .gw_data ["cooling_present" ] = self ._cooling_present
7472 self .gw_data ["gateway_id" ] = self .gateway_id
7573 self .gw_data ["heater_id" ] = self ._heater_id
76- self .gw_data ["single_master_thermostat" ] = self ._sm_thermostat
74+ self .gw_data ["single_master_thermostat" ] = (
75+ self ._is_thermostat and not self ._multi_thermostats
76+ )
7777 self .gw_data ["smile_name" ] = self .smile_name
7878
7979 def get_all_devices (self ):
@@ -221,20 +221,19 @@ def _get_device_data(self, dev_id):
221221 return device_data
222222
223223 def single_master_thermostat (self ):
224- """Determine if there is a single master thermostat in the setup.
225- Possible output: None, True, False.
226- """
224+ """Determine if there is a single master thermostat in the setup."""
227225 if self .smile_type == "thermostat" :
226+ self ._is_thermostat = True
228227 count = 0
229228 for dummy , data in self ._thermo_locs .items ():
230229 if "master_prio" in data :
231230 if data .get ("master_prio" ) > 0 :
232231 count += 1
233232
234233 if count == 1 :
235- self ._sm_thermostat = True
234+ self ._multi_thermostats = False
236235 if count > 1 :
237- self ._sm_thermostat = False
236+ self ._multi_thermostats = True
238237
239238
240239class Smile (SmileComm , SmileData ):
@@ -244,13 +243,13 @@ class Smile(SmileComm, SmileData):
244243
245244 def __init__ (
246245 self ,
247- host ,
248- password ,
249- username = DEFAULT_USERNAME ,
250- port = DEFAULT_PORT ,
251- timeout = DEFAULT_TIMEOUT ,
246+ host : str ,
247+ password : str ,
248+ username : str = DEFAULT_USERNAME ,
249+ port : str = DEFAULT_PORT ,
250+ timeout : str = DEFAULT_TIMEOUT ,
252251 websession : aiohttp .ClientSession = None ,
253- ):
252+ ) -> None :
254253 """Set the constructor for this class."""
255254 super ().__init__ (
256255 host ,
@@ -262,17 +261,17 @@ def __init__(
262261 )
263262 SmileData .__init__ (self )
264263
265- self ._notifications = {}
266- self .smile_hostname = None
264+ self ._notifications : dict [ str , str ] = {}
265+ self .smile_hostname : str = "Unknown"
267266
268- async def connect (self ):
267+ async def connect (self ) -> bool :
269268 """Connect to Plugwise device and determine its name, type and version."""
270- names = []
269+ names : list [ str ] = []
271270
272- result = await self ._request (DOMAIN_OBJECTS )
273- dsmrmain = result .find (".//module/protocols/dsmrmain" )
271+ result : etree = await self ._request (DOMAIN_OBJECTS )
272+ dsmrmain : etree | None = result .find (".//module/protocols/dsmrmain" )
274273
275- vendor_names = result .findall (".//module/vendor_name" )
274+ vendor_names : list [ etree ] = result .findall (".//module/vendor_name" )
276275 if not vendor_names :
277276 # Work-around for Stretch fv 2.7.18
278277 result = await self ._request (MODULES )
@@ -283,7 +282,7 @@ async def connect(self):
283282
284283 if "Plugwise" not in names :
285284 if dsmrmain is None : # pragma: no cover
286- _LOGGER .error (
285+ LOGGER .error (
287286 "Connected but expected text not returned, \
288287 we got %s" ,
289288 result ,
@@ -298,23 +297,23 @@ async def connect(self):
298297
299298 return True
300299
301- async def _smile_detect_legacy (self , result , dsmrmain ):
300+ async def _smile_detect_legacy (self , result , dsmrmain ) -> None :
302301 """Helper-function for _smile_detect()."""
303- network = result .find (".//module/protocols/master_controller" )
302+ network : etree = result .find (".//module/protocols/master_controller" )
304303
305304 # Assume legacy
306305 self ._smile_legacy = True
307306 # Try if it is an Anna, assuming appliance thermostat
308- anna = result .find ('.//appliance[type="thermostat"]' )
307+ anna : etree = result .find ('.//appliance[type="thermostat"]' )
309308 # Fake insert version assuming Anna
310309 # couldn't find another way to identify as legacy Anna
311- version = "1.8.0"
312- model = "smile_thermo"
310+ version : str = "1.8.0"
311+ model : str = "smile_thermo"
313312 if anna is None :
314313 # P1 legacy:
315314 if dsmrmain is not None :
316315 try :
317- status = await self ._request (STATUS )
316+ status : etree = await self ._request (STATUS )
318317 version = status .find (".//system/version" ).text
319318 model = status .find (".//system/product" ).text
320319 self .smile_hostname = status .find (".//network/hostname" ).text
@@ -334,7 +333,7 @@ async def _smile_detect_legacy(self, result, dsmrmain):
334333 raise ConnectionFailedError
335334 else : # pragma: no cover
336335 # No cornercase, just end of the line
337- _LOGGER .error ("Connected but no gateway device information found" )
336+ LOGGER .error ("Connected but no gateway device information found" )
338337 raise ConnectionFailedError
339338 return model , version
340339
@@ -353,14 +352,14 @@ async def _smile_detect(self, result, dsmrmain):
353352
354353 if model is None or version is None : # pragma: no cover
355354 # Corner case check
356- _LOGGER .error ("Unable to find model or version information" )
355+ LOGGER .error ("Unable to find model or version information" )
357356 raise UnsupportedDeviceError
358357
359358 ver = semver .VersionInfo .parse (version )
360359 target_smile = f"{ model } _v{ ver .major } "
361- _LOGGER .debug ("Plugwise identified as %s" , target_smile )
360+ LOGGER .debug ("Plugwise identified as %s" , target_smile )
362361 if target_smile not in SMILES :
363- _LOGGER .error (
362+ LOGGER .error (
364363 'Your version Smile identified as "%s" seems\
365364 unsupported by our plugin, please create an issue\
366365 on http://github.com/plugwise/python-plugwise!' ,
@@ -407,9 +406,9 @@ async def _update_domain_objects(self):
407406 msg_type = notification .find ("type" ).text
408407 msg = notification .find ("message" ).text
409408 self ._notifications .update ({msg_id : {msg_type : msg }})
410- _LOGGER .debug ("Plugwise notifications: %s" , self ._notifications )
409+ LOGGER .debug ("Plugwise notifications: %s" , self ._notifications )
411410 except AttributeError : # pragma: no cover
412- _LOGGER .info (
411+ LOGGER .info (
413412 "Plugwise notification present but unable to process, manually investigate: %s" ,
414413 f"{ self ._endpoint } { DOMAIN_OBJECTS } " ,
415414 )
0 commit comments