Skip to content

Commit 2ae8883

Browse files
committed
Refactor connect/disconnect (#247)
* Refactor init code * Add init tests
1 parent 845dc5e commit 2ae8883

File tree

2 files changed

+101
-104
lines changed

2 files changed

+101
-104
lines changed

custom_components/nefiteasy/__init__.py

+41-90
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
4343
client = NefitEasy(hass, credentials)
4444

4545
await client.connect()
46-
_LOGGER.debug("Is connected state? %s", client.connected_state)
47-
4846
if client.connected_state == STATE_CONNECTION_VERIFIED:
4947
hass.data[DOMAIN][entry.entry_id]["client"] = client
50-
_LOGGER.info(
51-
"Successfully connected %s to Nefit device!",
52-
credentials.get(CONF_SERIAL),
53-
)
5448
else:
5549
raise ConfigEntryNotReady
5650

@@ -66,23 +60,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
6660

6761
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
6862
"""Unload nefit easy component."""
69-
if not all(
70-
await asyncio.gather(
71-
*(
72-
hass.config_entries.async_forward_entry_unload(entry, component)
73-
for component in DOMAINS
74-
)
75-
)
76-
):
77-
return False
63+
unload_ok = await hass.config_entries.async_unload_platforms(entry, DOMAINS)
7864

79-
client = hass.data[DOMAIN][entry.entry_id]["client"]
65+
if unload_ok:
66+
client = hass.data[DOMAIN][entry.entry_id]["client"]
8067

81-
await client.shutdown("Unload entry")
68+
await client.shutdown("Unload entry")
8269

83-
hass.data[DOMAIN].pop(entry.entry_id)
70+
hass.data[DOMAIN].pop(entry.entry_id)
8471

85-
return True
72+
return unload_ok
8673

8774

8875
class NefitEasy(DataUpdateCoordinator):
@@ -139,85 +126,49 @@ async def add_key(self, entity_description: NefitEntityDescription) -> None:
139126

140127
async def connect(self) -> None:
141128
"""Connect to nefit easy."""
142-
_LOGGER.debug("Starting connecting..")
129+
_LOGGER.debug("Start connecting.")
143130
if not self.is_connecting:
144131
self.is_connecting = True
145-
retries_connection = 0
146132

147-
while self.connected_state != STATE_CONNECTED and retries_connection < 3:
148-
await self.nefit.connect()
149-
_LOGGER.debug("Waiting for connected event")
133+
await self.nefit.connect()
134+
_LOGGER.debug("Waiting for connected event.")
135+
try:
136+
await asyncio.wait_for(
137+
self.nefit.xmppclient.connected_event.wait(), timeout=29.0
138+
)
139+
except asyncio.TimeoutError:
140+
_LOGGER.debug("TimeoutError on waiting for connected event.")
141+
except: # noqa: E722 pylint: disable=bare-except
142+
_LOGGER.debug("Unknown error.")
143+
else:
144+
_LOGGER.debug("Connected successfully.")
145+
self.connected_state = STATE_CONNECTED
146+
147+
if self.connected_state == STATE_CONNECTED:
148+
self.nefit.get("/gateway/brandID")
150149
try:
151150
await asyncio.wait_for(
152-
self.nefit.xmppclient.connected_event.wait(), timeout=29.0
153-
)
154-
self.connected_state = STATE_CONNECTED
155-
_LOGGER.debug("adding stop listener")
156-
self.hass.bus.async_listen_once(
157-
EVENT_HOMEASSISTANT_STOP, self.shutdown
151+
self.nefit.xmppclient.message_event.wait(), timeout=29.0
158152
)
159153
except asyncio.TimeoutError:
160154
_LOGGER.debug(
161-
"TimeoutError on waiting for connected event (connection retries=%d)",
162-
retries_connection,
155+
"Did not get a response in time for testing connection."
163156
)
164-
retries_connection = retries_connection + 1
165157
except: # noqa: E722 pylint: disable=bare-except
166-
_LOGGER.debug("Unknown error")
167-
168-
# test password for decrypting messages if connected
169-
if self.connected_state == STATE_CONNECTED:
170-
_LOGGER.info(
171-
"Testing connection (connect retries=%d)", retries_connection
172-
)
173-
retries_validation = 0
174-
while (
175-
self.connected_state != STATE_CONNECTION_VERIFIED
176-
and retries_validation < 3
177-
):
178-
try:
179-
self.nefit.get("/gateway/brandID")
180-
await asyncio.wait_for(
181-
self.nefit.xmppclient.message_event.wait(), timeout=29.0
182-
)
183-
self.nefit.xmppclient.message_event.clear()
184-
185-
if self.connected_state == STATE_ERROR_AUTH:
186-
self.is_connecting = False
187-
return
158+
_LOGGER.debug("No connection while testing connection.")
159+
else:
160+
self.nefit.xmppclient.message_event.clear()
188161

162+
# No exception and no auth error
163+
if self.connected_state == STATE_CONNECTED:
189164
self.connected_state = STATE_CONNECTION_VERIFIED
190-
_LOGGER.info(
191-
"Connected %s with %d retries and %d test retries.",
192-
self.serial,
193-
retries_connection,
194-
retries_validation,
195-
)
196-
except asyncio.TimeoutError:
197-
_LOGGER.error(
198-
"Did not get a response in time for testing connection (validation retries=%d).",
199-
retries_validation,
200-
)
201-
retries_validation = retries_validation + 1
202-
except: # noqa: E722 pylint: disable=bare-except
203-
_LOGGER.error("No connection while testing connection.")
204-
break
205165

206166
if self.connected_state != STATE_CONNECTION_VERIFIED:
207-
self.hass.components.persistent_notification.create(
208-
f"Did not succeed in connecting {self.serial} to Bosch cloud after retrying 3 times. Retry in 30 seconds.",
209-
title="Nefit connect error",
210-
notification_id="nefit_connect_error",
211-
)
212-
self.is_connecting = False
213-
214-
# wait 30 seconds to retry
215-
await asyncio.sleep(30)
216-
await self.connect()
167+
_LOGGER.debug("Successfully verified connection.")
217168

218169
self.is_connecting = False
219170
else:
220-
_LOGGER.debug("Connection procedure was already running..")
171+
_LOGGER.debug("Connection procedure is already running.")
221172

222173
async def shutdown(self, event: str) -> None:
223174
"""Shutdown."""
@@ -250,23 +201,20 @@ async def failed_auth_handler(self, event: str) -> None:
250201
notification_id="nefit_logon_error",
251202
)
252203

204+
if self.config_entry:
205+
self.config_entry.async_start_reauth(self.hass)
206+
253207
async def session_end_callback(self) -> None:
254208
"""If connection is closed unexpectedly, try to reconnect."""
255209
if not self.expected_end:
256-
self.hass.components.persistent_notification.create(
257-
f"Unexpected disconnect of {self.serial} with Bosch server. Try to reconnect..",
258-
title="Nefit disconnect",
259-
notification_id="nefit_disconnect",
210+
_LOGGER.debug(
211+
f"Unexpected disconnect of {self.serial} with Bosch server. Try to reconnect.."
260212
)
261213

262-
_LOGGER.info("Starting reconnect procedure.")
263214
# Reset values
264215
self.connected_state = STATE_INIT
265216
self.expected_end = False
266217

267-
# Retry connection
268-
await self.connect()
269-
270218
async def parse_message(self, data: dict[str, Any]) -> None:
271219
"""Message received callback function for the XMPP client."""
272220
if (
@@ -310,7 +258,10 @@ async def parse_message(self, data: dict[str, Any]) -> None:
310258
async def _async_update_data(self) -> dict[str, Any]:
311259
"""Update data via library."""
312260
if self.connected_state != STATE_CONNECTION_VERIFIED:
313-
raise UpdateFailed("Nefit easy not connected!")
261+
_LOGGER.debug("Starting reconnect procedure.")
262+
await self.connect()
263+
if self.connected_state != STATE_CONNECTION_VERIFIED:
264+
raise UpdateFailed("Nefit easy not connected!")
314265

315266
async with self._lock:
316267
_url = "/ecus/rrc/uiStatus"

tests/test_init.py

+60-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Tests of the initialization of the nefiteasy integration."""
2+
import asyncio
23
from unittest.mock import patch
34

45
from homeassistant import config_entries
@@ -7,30 +8,75 @@
78

89
from .conftest import ClientMock
910

11+
entry_data = {
12+
"serial": "123456789",
13+
"accesskey": "myAccessKey",
14+
"password": "myPass",
15+
"min_temp": 10,
16+
"max_temp": 28,
17+
"temp_step": 0.5,
18+
"name": "Nefit",
19+
}
20+
1021

1122
@patch("custom_components.nefiteasy.NefitCore")
12-
async def test_setup_entry(mock_class, hass: HomeAssistant):
23+
async def test_load_unload_entry(mock_class, hass: HomeAssistant):
1324
"""Validate that setup entry also configure the client."""
1425
client = ClientMock(mock_class)
1526
mock_class.return_value = client
1627

17-
config_entry = MockConfigEntry(
18-
domain="nefiteasy",
19-
data={
20-
"serial": "123456789",
21-
"accesskey": "myAccessKey",
22-
"password": "myPass",
23-
"min_temp": 10,
24-
"max_temp": 28,
25-
"temp_step": 0.5,
26-
"name": "Nefit",
27-
},
28-
)
28+
config_entry = MockConfigEntry(domain="nefiteasy", data=entry_data)
2929

3030
config_entry.add_to_hass(hass)
3131

3232
assert await hass.config_entries.async_setup(config_entry.entry_id)
33-
3433
await hass.async_block_till_done()
3534

3635
assert config_entry.state == config_entries.ConfigEntryState.LOADED
36+
37+
assert await hass.config_entries.async_unload(config_entry.entry_id)
38+
await hass.async_block_till_done()
39+
40+
assert config_entry.state == config_entries.ConfigEntryState.NOT_LOADED
41+
42+
43+
@patch("custom_components.nefiteasy.NefitCore")
44+
async def test_setup_connection_fail_timeout(mock_class, hass: HomeAssistant):
45+
"""Test setup connection with timeout failure."""
46+
client = ClientMock(mock_class)
47+
mock_class.return_value = client
48+
49+
config_entry = MockConfigEntry(domain="nefiteasy", data=entry_data)
50+
51+
config_entry.add_to_hass(hass)
52+
53+
async def wait():
54+
raise asyncio.TimeoutError
55+
56+
client.xmppclient.connected_event.wait = wait
57+
58+
await hass.config_entries.async_setup(config_entry.entry_id)
59+
await hass.async_block_till_done()
60+
61+
assert config_entry.state == config_entries.ConfigEntryState.SETUP_RETRY
62+
63+
64+
@patch("custom_components.nefiteasy.NefitCore")
65+
async def test_setup_validation_fail_timeout(mock_class, hass: HomeAssistant):
66+
"""Test setup with validation timeout."""
67+
client = ClientMock(mock_class)
68+
mock_class.return_value = client
69+
70+
config_entry = MockConfigEntry(domain="nefiteasy", data=entry_data)
71+
72+
config_entry.add_to_hass(hass)
73+
74+
async def wait():
75+
raise asyncio.TimeoutError
76+
77+
client.xmppclient.message_event.wait = wait
78+
79+
await hass.config_entries.async_setup(config_entry.entry_id)
80+
await hass.async_block_till_done()
81+
82+
assert config_entry.state == config_entries.ConfigEntryState.SETUP_RETRY

0 commit comments

Comments
 (0)