Skip to content

Commit c4d150c

Browse files
committed
Sync with latest core PR
1 parent 60814d5 commit c4d150c

File tree

12 files changed

+159
-257
lines changed

12 files changed

+159
-257
lines changed

custom_components/zimi/__init__.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,23 @@
77
from zcc import ControlPoint, ControlPointError
88

99
from homeassistant.config_entries import ConfigEntry
10-
from homeassistant.const import CONF_HOST, CONF_PORT
10+
from homeassistant.const import CONF_HOST, CONF_PORT, Platform
1111
from homeassistant.core import HomeAssistant
1212
from homeassistant.exceptions import ConfigEntryNotReady
1313
from homeassistant.helpers import device_registry as dr
1414
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
1515

16-
from .const import DOMAIN, PLATFORMS
16+
from .const import DOMAIN
1717
from .helpers import async_connect_to_controller
1818

19+
PLATFORMS = [
20+
Platform.COVER,
21+
Platform.FAN,
22+
Platform.LIGHT,
23+
Platform.SENSOR,
24+
Platform.SWITCH,
25+
]
26+
1927
_LOGGER = logging.getLogger(__name__)
2028

2129

@@ -24,7 +32,6 @@
2432

2533
async def async_setup_entry(hass: HomeAssistant, entry: ZimiConfigEntry) -> bool:
2634
"""Connect to Zimi Controller and register device."""
27-
_LOGGER.debug("Zimi setup starting")
2835

2936
try:
3037
api = await async_connect_to_controller(
@@ -35,9 +42,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ZimiConfigEntry) -> bool
3542
except ControlPointError as error:
3643
raise ConfigEntryNotReady(f"Zimi setup failed: {error}") from error
3744

38-
if not api:
39-
raise ConfigEntryNotReady("Zimi setup failed: not ready")
40-
4145
_LOGGER.debug("\n%s", api.describe())
4246

4347
entry.runtime_data = api
@@ -47,7 +51,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ZimiConfigEntry) -> bool
4751
config_entry_id=entry.entry_id,
4852
identifiers={(DOMAIN, api.mac)},
4953
manufacturer=api.brand,
50-
name=f"Controller ({api.host}:{api.port})",
54+
name=f"{api.network_name}",
5155
model="Zimi Cloud Connect",
5256
sw_version=api.firmware_version,
5357
connections={(CONNECTION_NETWORK_MAC, api.mac)},
@@ -64,7 +68,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ZimiConfigEntry) -> boo
6468
"""Unload a config entry."""
6569

6670
api = entry.runtime_data
67-
if api:
68-
api.disconnect()
71+
api.disconnect()
6972

7073
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

custom_components/zimi/config_flow.py

Lines changed: 52 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@
22

33
from __future__ import annotations
44

5-
import contextlib
65
import logging
7-
import socket
86
from typing import Any
97

10-
from enum import StrEnum
118
import voluptuous as vol
129
from zcc import (
1310
ControlPoint,
11+
ControlPointCannotConnectError,
12+
ControlPointConnectionRefusedError,
1413
ControlPointDescription,
1514
ControlPointDiscoveryService,
1615
ControlPointError,
16+
ControlPointInvalidHostError,
17+
ControlPointTimeoutError,
1718
)
1819

1920
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
@@ -26,46 +27,27 @@
2627
SelectSelectorMode,
2728
)
2829

29-
from . import async_connect_to_controller
30-
from .const import DEFAULT_PORT, DOMAIN, TITLE
30+
from .const import DOMAIN
3131

3232
_LOGGER = logging.getLogger(__name__)
33-
34-
STEP_USER_DATA_SCHEMA = vol.Schema(
33+
DEFAULT_PORT = 5003
34+
STEP_MANUAL_DATA_SCHEMA = vol.Schema(
3535
{
36-
vol.Required(CONF_HOST, default=""): str,
36+
vol.Required(CONF_HOST): str,
3737
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
3838
}
3939
)
4040

4141
SELECTED_HOST_AND_PORT = "selected_host_and_port"
4242

4343

44-
class ZimiConfigErrors(StrEnum):
45-
"""ZimiConfig errors."""
46-
47-
ALREADY_CONFIGURED = "already_configured"
48-
CANNOT_CONNECT = "cannot_connect"
49-
CONNECTION_REFUSED = "connection_refused"
50-
DISCOVERY_FAILURE = "discovery_failure"
51-
INVALID_HOST = "invalid_host"
52-
INVALID_PORT = "invalid_port"
53-
TIMEOUT = "timeout"
54-
UNKNOWN = "unknown"
55-
56-
5744
class ZimiConfigFlow(ConfigFlow, domain=DOMAIN):
5845
"""Handle a config flow for zcc."""
5946

6047
api: ControlPoint = None
6148
api_descriptions: list[ControlPointDescription]
6249
data: dict[str, Any]
6350

64-
def __del__(self):
65-
"""Disconnect from ZCC."""
66-
if self.api:
67-
self.api.disconnect()
68-
6951
async def async_step_user(
7052
self, user_input: dict[str, Any] | None = None
7153
) -> ConfigFlowResult:
@@ -75,13 +57,14 @@ async def async_step_user(
7557

7658
try:
7759
self.api_descriptions = await ControlPointDiscoveryService().discovers()
78-
except ControlPointError as e:
79-
_LOGGER.error(e)
60+
except ControlPointError:
61+
# ControlPointError is expected if no zcc are found on LAN
8062
return await self.async_step_manual()
8163

8264
if len(self.api_descriptions) == 1:
8365
self.data[CONF_HOST] = self.api_descriptions[0].host
8466
self.data[CONF_PORT] = self.api_descriptions[0].port
67+
await self.check_connection(self.data[CONF_HOST], self.data[CONF_PORT])
8568
return await self.create_entry()
8669

8770
return await self.async_step_selection()
@@ -91,14 +74,16 @@ async def async_step_selection(
9174
) -> ConfigFlowResult:
9275
"""Handle selection of zcc to configure if multiple are discovered."""
9376

94-
errors: dict[str, str] = {}
77+
errors: dict[str, str] | None = {}
9578

9679
if user_input is not None:
97-
self.data[CONF_HOST] = user_input[SELECTED_HOST_AND_PORT].split(":")[
98-
0]
99-
self.data[CONF_PORT] = int(
100-
user_input[SELECTED_HOST_AND_PORT].split(":")[1])
101-
return await self.create_entry()
80+
self.data[CONF_HOST] = user_input[SELECTED_HOST_AND_PORT].split(":")[0]
81+
self.data[CONF_PORT] = int(user_input[SELECTED_HOST_AND_PORT].split(":")[1])
82+
errors = await self.check_connection(
83+
self.data[CONF_HOST], self.data[CONF_PORT]
84+
)
85+
if not errors:
86+
return await self.create_entry()
10287

10388
available_options = [
10489
SelectOptionDict(
@@ -130,12 +115,12 @@ async def async_step_manual(
130115
) -> ConfigFlowResult:
131116
"""Handle manual configuration step if needed."""
132117

133-
errors: dict[str, str] = {}
118+
errors: dict[str, str] | None = {}
134119

135120
if user_input is not None:
136121
self.data = {**self.data, **user_input}
137122

138-
errors = await self.validate_connection(
123+
errors = await self.check_connection(
139124
self.data[CONF_HOST], self.data[CONF_PORT]
140125
)
141126

@@ -145,73 +130,43 @@ async def async_step_manual(
145130
return self.async_show_form(
146131
step_id="manual",
147132
data_schema=self.add_suggested_values_to_schema(
148-
STEP_USER_DATA_SCHEMA, self.data
133+
STEP_MANUAL_DATA_SCHEMA, self.data
149134
),
150135
errors=errors,
151136
)
152137

153-
async def create_entry(self) -> ConfigFlowResult:
154-
"""Create entry for zcc."""
138+
async def check_connection(self, host: str, port: int) -> dict[str, str] | None:
139+
"""Check connection to zcc.
155140
156-
if not self.api:
157-
with contextlib.suppress(ControlPointError):
158-
self.api = await async_connect_to_controller(
159-
self.data[CONF_HOST], self.data[CONF_PORT], fast=False
160-
)
141+
Stores mac and returns None if successful, otherwise returns error message.
142+
"""
161143

162-
if self.api and self.api.ready:
163-
self.data[CONF_MAC] = format_mac(self.api.mac)
164-
self.api.disconnect()
165-
await self.async_set_unique_id(self.data[CONF_MAC])
166-
self._abort_if_unique_id_configured()
167-
return self.async_create_entry(
168-
title=f"{TITLE} ({self.data[CONF_HOST]}:{self.data[CONF_PORT]})",
169-
data=self.data,
144+
try:
145+
result = await ControlPointDiscoveryService().validate_connection(
146+
self.data[CONF_HOST], self.data[CONF_PORT]
170147
)
148+
except ControlPointInvalidHostError:
149+
return {"base": "invalid_host"}
150+
except ControlPointConnectionRefusedError:
151+
return {"base": "connection_refused"}
152+
except ControlPointCannotConnectError:
153+
return {"base": "cannot_connect"}
154+
except ControlPointTimeoutError:
155+
return {"base": "timeout"}
156+
except Exception:
157+
_LOGGER.exception("Unexpected error")
158+
return {"base": "unknown"}
159+
160+
self.data[CONF_MAC] = format_mac(result.mac)
161+
162+
return None
171163

172-
return self.async_abort(reason="cannot_connect")
173-
174-
async def validate_connection(self, host: str, port: int) -> dict[str, str]:
175-
"""Check for errors with configuration.
176-
177-
1. Check connectivity to configured host and port; and
178-
2. Connect to ZCC to get mac address and store in self.data
179-
180-
Return error dictionary upon failure.
181-
"""
164+
async def create_entry(self) -> ConfigFlowResult:
165+
"""Create entry for zcc."""
182166

183-
try:
184-
hostbyname = socket.gethostbyname(host)
185-
except socket.gaierror as e:
186-
_LOGGER.error(e)
187-
return {"base": ZimiConfigErrors.INVALID_HOST}
188-
if hostbyname:
189-
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
190-
s.settimeout(10)
191-
try:
192-
s.connect((host, port))
193-
s.close()
194-
except ConnectionRefusedError as e:
195-
_LOGGER.error(e)
196-
return {"base": ZimiConfigErrors.CONNECTION_REFUSED}
197-
except TimeoutError as e:
198-
_LOGGER.error(e)
199-
return {"base": ZimiConfigErrors.TIMEOUT}
200-
except socket.gaierror as e:
201-
_LOGGER.error(e)
202-
return {"base": ZimiConfigErrors.CANNOT_CONNECT}
203-
else:
204-
return {"base": ZimiConfigErrors.INVALID_HOST}
205-
206-
if not self.api or not self.api.ready:
207-
try:
208-
self.api = await async_connect_to_controller(host, port, fast=True)
209-
except ControlPointError as e:
210-
_LOGGER.error(e)
211-
return {"base": ZimiConfigErrors.CANNOT_CONNECT}
212-
213-
self.data[CONF_MAC] = format_mac(self.api.mac)
214-
self.api.disconnect()
215-
self.api = None
216-
217-
return {}
167+
await self.async_set_unique_id(self.data[CONF_MAC])
168+
self._abort_if_unique_id_configured()
169+
return self.async_create_entry(
170+
title=f"ZIMI Controller ({self.data[CONF_HOST]}:{self.data[CONF_PORT]})",
171+
data=self.data,
172+
)

custom_components/zimi/const.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
11
"""Constants for the zcc integration."""
22

3-
from homeassistant.const import Platform
4-
5-
CONTROLLER = "zimi_controller"
6-
DEFAULT_PORT = 5003
73
DOMAIN = "zimi"
8-
PLATFORMS = [
9-
Platform.COVER,
10-
Platform.FAN,
11-
Platform.LIGHT,
12-
Platform.SENSOR,
13-
Platform.SWITCH,
14-
]
15-
TITLE = "ZIMI Controller"

0 commit comments

Comments
 (0)