Skip to content

Commit 598ff4e

Browse files
committed
Improved error handling
1 parent edfcb25 commit 598ff4e

6 files changed

Lines changed: 97 additions & 60 deletions

File tree

examples/cli_example.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ async def main(loop):
6969
device_type=args.device,
7070
)
7171
else:
72-
7372
controller = IntesisHome(
7473
args.user,
7574
args.password,

examples/dhw_aquarea_domoticz.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,6 @@ def aquarea_to_domoticz(
174174

175175

176176
async def main(loop):
177-
178177
username = "xxxxxx"
179178
password = "yyyyyyyyy"
180179
idd = "zzzzzzzz"

pyintesishome/intesisbase.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def __init__(
3333
loop=None,
3434
websession=None,
3535
device_type=DEVICE_INTESISHOME,
36-
):
36+
) -> None:
3737
"""Initialize IntesisBox controller."""
3838
# Select correct API for device type
3939
self._username = username
@@ -50,7 +50,7 @@ def __init__(
5050
self._error_message = None
5151
self._web_session = websession
5252
self._own_session = False
53-
self._controller_id = username
53+
self._controller_id = None
5454
self._controller_name = username
5555
self._writer: StreamWriter = None
5656
self._reader: StreamReader = None
@@ -87,8 +87,7 @@ async def _send_command(self, command: str):
8787
)
8888
except asyncio.TimeoutError:
8989
print("oops took longer than 5s!")
90-
# need to close the connection as device not responding due to hung state
91-
self._writer.write.close()
90+
await self.stop()
9291
except OSError as exc:
9392
_LOGGER.error("%s Exception. %s / %s", type(exc), exc.args, exc)
9493

@@ -106,8 +105,8 @@ async def _data_received(self):
106105
_LOGGER.debug("Resolving set_value's await")
107106
self._received_response.set()
108107
except IncompleteReadError:
109-
_LOGGER.info(
110-
"pyIntesisHome lost connection to the %s server.", self._device_type
108+
_LOGGER.debug(
109+
"pyIntesisHome lost connection to the %s server", self._device_type
111110
)
112111
except asyncio.CancelledError:
113112
pass
@@ -117,7 +116,7 @@ async def _data_received(self):
117116
OSError,
118117
) as exc:
119118
_LOGGER.error(
120-
"pyIntesisHome lost connection to the %s server. Exception: %s",
119+
"PyIntesisHome lost connection to the %s server. Exception: %s",
121120
self._device_type,
122121
exc,
123122
)
@@ -519,7 +518,7 @@ def controller_id(self) -> str:
519518
"""Returns an account/device identifier - Serial, MAC or username."""
520519
if self._controller_id:
521520
return self._controller_id.lower()
522-
return None
521+
raise ValueError("Controller ID has not been set yet")
523522

524523
@property
525524
def name(self) -> str:

pyintesishome/intesishome.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def __init__(
3737
self._cmd_server = None
3838
self._cmd_server_port = None
3939
self._auth_token = None
40+
self._controller_id = username
4041

4142
async def _parse_response(self, decoded_data):
4243
_LOGGER.debug("%s API Received: %s", self._device_type, decoded_data)
@@ -151,7 +152,7 @@ async def poll_status(self, sendcallback=False):
151152
) as resp:
152153
status_response = await resp.json(content_type=None)
153154
_LOGGER.debug(status_response)
154-
except (aiohttp.client_exceptions.ClientConnectorError) as exc:
155+
except aiohttp.client_exceptions.ClientConnectorError as exc:
155156
raise IHConnectionError from exc
156157
except (aiohttp.client_exceptions.ClientError, socket.gaierror) as exc:
157158
self._error_message = f"Error connecting to {self._device_type} API: {exc}"

pyintesishome/intesishomelocal.py

Lines changed: 87 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import asyncio
44
import logging
5+
from json import JSONDecodeError
56

67
import aiohttp
78

@@ -25,7 +26,7 @@
2526
class IntesisHomeLocal(IntesisBase):
2627
"""pyintesishome local class."""
2728

28-
def __init__(self, host, username, password, loop=None, websession=None):
29+
def __init__(self, host, username, password, loop=None, websession=None) -> None:
2930
"""Constructor"""
3031
device_type = DEVICE_INTESISHOME_LOCAL
3132
self._session_id: str = ""
@@ -70,7 +71,7 @@ async def _run_updater(self):
7071
self._update_device_state(self._device_id, uid, value)
7172

7273
await self._send_update_callback(self._device_id)
73-
except (IHConnectionError) as exc:
74+
except IHConnectionError as exc:
7475
_LOGGER.error("Error during updater task: %s", exc)
7576
await asyncio.sleep(self._scan_interval)
7677
except asyncio.CancelledError:
@@ -83,14 +84,35 @@ async def _authenticate(self) -> bool:
8384
"command": LOCAL_CMD_LOGIN,
8485
"data": {"username": self._username, "password": self._password},
8586
}
86-
async with self._web_session.post(
87-
f"http://{self._host}/api.cgi", json=payload
88-
) as response:
89-
if response.status != 200:
90-
raise IHConnectionError("HTTP response status is unexpected (not 200)")
91-
json_response = await response.json()
92-
self._session_id = json_response["data"].get("id").get("sessionID")
93-
_LOGGER.debug("Authenticated with new session ID %s", self._session_id)
87+
try:
88+
async with self._web_session.post(
89+
f"http://{self._host}/api.cgi", json=payload
90+
) as response:
91+
if response.status != 200:
92+
raise IHConnectionError(
93+
"HTTP response status is unexpected (not 200)"
94+
)
95+
json_response = await response.json()
96+
# Check if the response has the expected format
97+
if (
98+
"data" in json_response
99+
and "id" in json_response["data"]
100+
and "sessionID" in json_response["data"]["id"]
101+
):
102+
self._session_id = json_response["data"]["id"]["sessionID"]
103+
_LOGGER.debug(
104+
"Authenticated with new session ID %s", self._session_id
105+
)
106+
else:
107+
_LOGGER.error("Unexpected response format during authentication")
108+
except (
109+
aiohttp.ClientConnectionError,
110+
aiohttp.ClientResponseError,
111+
aiohttp.ClientPayloadError,
112+
aiohttp.ContentTypeError,
113+
JSONDecodeError,
114+
) as exception:
115+
_LOGGER.error("Error during authentication: %s", str(exception))
94116

95117
async def _request(self, command: str, **kwargs) -> dict:
96118
"""Make a request."""
@@ -105,7 +127,9 @@ async def _request(self, command: str, **kwargs) -> dict:
105127
"command": command,
106128
"data": {"sessionID": self._session_id, **kwargs},
107129
}
108-
_LOGGER.debug("Sending intesishome_local command %s to %s", command, self._host)
130+
_LOGGER.debug(
131+
"Sending intesishome_local command %s to %s", command, self._host
132+
)
109133
timeout = aiohttp.ClientTimeout(total=10)
110134
json_response = {}
111135
try:
@@ -116,9 +140,8 @@ async def _request(self, command: str, **kwargs) -> dict:
116140
) as response:
117141
if response.status != 200:
118142
raise IHConnectionError(
119-
"HTTP response status is unexpected for %s (got %s, want 200)",
120-
self._host,
121-
response.status,
143+
f"HTTP response status is unexpected for {self._host}"
144+
"(got {response.status}, want 200)"
122145
)
123146
json_response = await response.json()
124147
except asyncio.exceptions.TimeoutError as exc:
@@ -127,7 +150,7 @@ async def _request(self, command: str, **kwargs) -> dict:
127150
self._host,
128151
exc,
129152
)
130-
except (aiohttp.ClientError) as exc:
153+
except aiohttp.ClientError as exc:
131154
_LOGGER.error(
132155
"IntesisHome HTTP error for %s: %s",
133156
self._host,
@@ -147,25 +170,30 @@ async def _request(self, command: str, **kwargs) -> dict:
147170
# wonky, so log an error plus the entire response.
148171
if json_response.get("success", False):
149172
return json_response.get("data")
150-
elif "error" in json_response:
173+
if "error" in json_response:
151174
error = json_response["error"]
152175
if error.get("code") in [1, 5]:
153176
self._session_id = ""
154-
_LOGGER.debug("Request failed for %s (code=%s, message=%r). Clearing session key to force re-authentication",
155-
self._host,
156-
error.get("code"),
157-
error.get("message"),
158-
)
177+
_LOGGER.debug(
178+
"Request failed for %s (code=%s, message=%r)."
179+
"Clearing session key to force re-authentication",
180+
self._host,
181+
error.get("code"),
182+
error.get("message"),
183+
)
159184
else:
160-
_LOGGER.debug("Request failed for %s (code=%s, message=%r). Error not handled.",
161-
self._host,
162-
error.get("code"),
163-
error.get("message"),
164-
)
165-
else:
166-
_LOGGER.debug("Request failed for %s - no 'success' or 'error' keys. json_response=%r",
185+
_LOGGER.debug(
186+
"Request failed for %s (code=%s, message=%r). Error not handled",
167187
self._host,
168-
json_response)
188+
error.get("code"),
189+
error.get("message"),
190+
)
191+
else:
192+
_LOGGER.debug(
193+
"Request failed for %s - no 'success' or 'error' keys. json_response=%r",
194+
self._host,
195+
json_response,
196+
)
169197

170198
async def _request_value(self, name: str) -> dict:
171199
"""Get entity value by uid."""
@@ -202,15 +230,15 @@ def _has_datapoint(self, datapoint: str):
202230
async def connect(self):
203231
"""Connect to the device and start periodic updater."""
204232
await self.poll_status()
205-
_LOGGER.debug("Successful authenticated and polled. Fetching Datapoints.")
233+
_LOGGER.debug("Successful authenticated and polled. Fetching Datapoints")
206234
await self.get_datapoints()
207235
self._connected = True
208-
_LOGGER.debug("Starting updater task.")
236+
_LOGGER.debug("Starting updater task")
209237
self._update_task = asyncio.create_task(self._run_updater())
210238

211239
async def stop(self):
212240
"""Disconnect and stop periodic updater."""
213-
_LOGGER.debug("Stopping updater task.")
241+
_LOGGER.debug("Stopping updater task")
214242
await self._cancel_task_if_exists(self._update_task)
215243
self._connected = False
216244

@@ -225,25 +253,36 @@ async def get_info(self) -> dict:
225253

226254
async def poll_status(self, sendcallback=False):
227255
"""Get device info for setup purposes."""
228-
await self._authenticate()
229-
info = await self.get_info()
230-
self._device_id = info["sn"]
231-
self._controller_id = info["sn"].lower()
232-
self._controller_name = f"{self._info['deviceModel']} ({info['ownSSID']})"
233-
# Setup devices
234-
self._devices[self._device_id] = {
235-
"name": info["ownSSID"],
236-
"widgets": [],
237-
"model": info["deviceModel"],
238-
}
256+
try:
257+
await self._authenticate()
258+
info = await self.get_info()
259+
260+
# Extract device_id up to the first space, if there is a space
261+
raw_id = info.get("sn")
262+
if raw_id:
263+
device_id, *_ = raw_id.split(" ")
264+
self._device_id = device_id
265+
self._controller_id = device_id.lower()
266+
267+
self._controller_name = (
268+
f"{self._info.get('deviceModel')} ({info.get('ownSSID')})"
269+
)
270+
# Setup devices
271+
self._devices[self._device_id] = {
272+
"name": info.get("ownSSID"),
273+
"widgets": [],
274+
"model": info.get("deviceModel"),
275+
}
239276

240-
await self.get_datapoints()
241-
_LOGGER.debug(repr(self._devices))
277+
await self.get_datapoints()
278+
_LOGGER.debug(repr(self._devices))
242279

243-
self._update_device_state(self._device_id, "acStatus", info["acStatus"])
280+
self._update_device_state(self._device_id, "acStatus", info.get("acStatus"))
244281

245-
if sendcallback:
246-
await self._send_update_callback(str(self._device_id))
282+
if sendcallback:
283+
await self._send_update_callback(str(self._device_id))
284+
except (IHConnectionError, KeyError) as exception:
285+
_LOGGER.error("Error during polling status: %s", str(exception))
247286

248287
def get_mode_list(self, device_id) -> list:
249288
"""Get possible entity modes."""

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
setup(
1111
name="pyintesishome",
12-
version="1.8.4",
12+
version="1.8.5",
1313
description="A python3 library for running asynchronus communications with IntesisHome Smart AC Controllers",
1414
long_description=long_description,
1515
long_description_content_type="text/markdown",

0 commit comments

Comments
 (0)