55
66from __future__ import annotations
77
8- import asyncio
98import datetime as dt
109from typing import cast
1110
4443 ThermoLoc ,
4544 ToggleNameType ,
4645)
47- from plugwise .exceptions import (
48- ConnectionFailedError ,
49- InvalidAuthentication ,
50- InvalidXMLError ,
51- ResponseError ,
52- )
5346from plugwise .util import (
5447 check_model ,
5548 common_match_cases ,
56- escape_illegal_xml_characters ,
5749 format_measure ,
5850 skip_obsolete_measurements ,
5951)
6052
61- # This way of importing aiohttp is because of patch/mocking in testing (aiohttp timeouts)
62- from aiohttp import BasicAuth , ClientError , ClientResponse , ClientSession , ClientTimeout
63-
6453# Time related
6554from dateutil import tz
6655from dateutil .parser import parse
@@ -78,148 +67,6 @@ def search_actuator_functionalities(appliance: etree, actuator: str) -> etree |
7867 return None
7968
8069
81- class SmileComm :
82- """The SmileComm class."""
83-
84- def __init__ (
85- self ,
86- host : str ,
87- password : str ,
88- port : int ,
89- timeout : int ,
90- username : str ,
91- websession : ClientSession | None ,
92- ) -> None :
93- """Set the constructor for this class."""
94- if not websession :
95- aio_timeout = ClientTimeout (total = timeout )
96-
97- async def _create_session () -> ClientSession :
98- return ClientSession (timeout = aio_timeout ) # pragma: no cover
99-
100- loop = asyncio .get_event_loop ()
101- if loop .is_running ():
102- self ._websession = ClientSession (timeout = aio_timeout )
103- else :
104- self ._websession = loop .run_until_complete (
105- _create_session ()
106- ) # pragma: no cover
107- else :
108- self ._websession = websession
109-
110- # Quickfix IPv6 formatting, not covering
111- if host .count (":" ) > 2 : # pragma: no cover
112- host = f"[{ host } ]"
113-
114- self ._auth = BasicAuth (username , password = password )
115- self ._endpoint = f"http://{ host } :{ str (port )} "
116-
117- async def _request (
118- self ,
119- command : str ,
120- retry : int = 3 ,
121- method : str = "get" ,
122- data : str | None = None ,
123- headers : dict [str , str ] | None = None ,
124- ) -> etree :
125- """Get/put/delete data from a give URL."""
126- resp : ClientResponse
127- url = f"{ self ._endpoint } { command } "
128- use_headers = headers
129-
130- try :
131- match method :
132- case "delete" :
133- resp = await self ._websession .delete (url , auth = self ._auth )
134- case "get" :
135- # Work-around for Stretchv2, should not hurt the other smiles
136- use_headers = {"Accept-Encoding" : "gzip" }
137- resp = await self ._websession .get (
138- url , headers = use_headers , auth = self ._auth
139- )
140- case "post" :
141- use_headers = {"Content-type" : "text/xml" }
142- resp = await self ._websession .post (
143- url ,
144- headers = use_headers ,
145- data = data ,
146- auth = self ._auth ,
147- )
148- case "put" :
149- use_headers = {"Content-type" : "text/xml" }
150- resp = await self ._websession .put (
151- url ,
152- headers = use_headers ,
153- data = data ,
154- auth = self ._auth ,
155- )
156- except (
157- ClientError
158- ) as exc : # ClientError is an ancestor class of ServerTimeoutError
159- if retry < 1 :
160- LOGGER .warning (
161- "Failed sending %s %s to Plugwise Smile, error: %s" ,
162- method ,
163- command ,
164- exc ,
165- )
166- raise ConnectionFailedError from exc
167- return await self ._request (command , retry - 1 )
168-
169- if resp .status == 504 :
170- if retry < 1 :
171- LOGGER .warning (
172- "Failed sending %s %s to Plugwise Smile, error: %s" ,
173- method ,
174- command ,
175- "504 Gateway Timeout" ,
176- )
177- raise ConnectionFailedError
178- return await self ._request (command , retry - 1 )
179-
180- return await self ._request_validate (resp , method )
181-
182- async def _request_validate (self , resp : ClientResponse , method : str ) -> etree :
183- """Helper-function for _request(): validate the returned data."""
184- match resp .status :
185- case 200 :
186- # Cornercases for server not responding with 202
187- if method in ("post" , "put" ):
188- return
189- case 202 :
190- # Command accepted gives empty body with status 202
191- return
192- case 401 :
193- msg = (
194- "Invalid Plugwise login, please retry with the correct credentials."
195- )
196- LOGGER .error ("%s" , msg )
197- raise InvalidAuthentication
198- case 405 :
199- msg = "405 Method not allowed."
200- LOGGER .error ("%s" , msg )
201- raise ConnectionFailedError
202-
203- if not (result := await resp .text ()) or (
204- "<error>" in result and "Not started" not in result
205- ):
206- LOGGER .warning ("Smile response empty or error in %s" , result )
207- raise ResponseError
208-
209- try :
210- # Encode to ensure utf8 parsing
211- xml = etree .XML (escape_illegal_xml_characters (result ).encode ())
212- except etree .ParseError as exc :
213- LOGGER .warning ("Smile returns invalid XML for %s" , self ._endpoint )
214- raise InvalidXMLError from exc
215-
216- return xml
217-
218- async def close_connection (self ) -> None :
219- """Close the Plugwise connection."""
220- await self ._websession .close ()
221-
222-
22370class SmileHelper (SmileCommon ):
22471 """The SmileHelper class."""
22572
0 commit comments