Skip to content

Commit 27516de

Browse files
niraclerNoRi2909joostlek
authored
Add DALI Center integration (home-assistant#151479)
Co-authored-by: Norbert Rittel <[email protected]> Co-authored-by: Joost Lekkerkerker <[email protected]>
1 parent 40c9e53 commit 27516de

File tree

19 files changed

+1425
-0
lines changed

19 files changed

+1425
-0
lines changed

CODEOWNERS

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""The DALI Center integration."""
2+
3+
from __future__ import annotations
4+
5+
import logging
6+
7+
from PySrDaliGateway import DaliGateway
8+
from PySrDaliGateway.exceptions import DaliGatewayError
9+
10+
from homeassistant.const import (
11+
CONF_HOST,
12+
CONF_NAME,
13+
CONF_PASSWORD,
14+
CONF_PORT,
15+
CONF_USERNAME,
16+
Platform,
17+
)
18+
from homeassistant.core import HomeAssistant
19+
from homeassistant.exceptions import ConfigEntryNotReady
20+
from homeassistant.helpers import device_registry as dr
21+
from homeassistant.helpers.dispatcher import async_dispatcher_send
22+
23+
from .const import CONF_SERIAL_NUMBER, DOMAIN, MANUFACTURER
24+
from .types import DaliCenterConfigEntry, DaliCenterData
25+
26+
_PLATFORMS: list[Platform] = [Platform.LIGHT]
27+
_LOGGER = logging.getLogger(__name__)
28+
29+
30+
async def async_setup_entry(hass: HomeAssistant, entry: DaliCenterConfigEntry) -> bool:
31+
"""Set up DALI Center from a config entry."""
32+
33+
gateway = DaliGateway(
34+
entry.data[CONF_SERIAL_NUMBER],
35+
entry.data[CONF_HOST],
36+
entry.data[CONF_PORT],
37+
entry.data[CONF_USERNAME],
38+
entry.data[CONF_PASSWORD],
39+
name=entry.data[CONF_NAME],
40+
)
41+
gw_sn = gateway.gw_sn
42+
43+
try:
44+
await gateway.connect()
45+
except DaliGatewayError as exc:
46+
raise ConfigEntryNotReady(
47+
"You can try to delete the gateway and add it again"
48+
) from exc
49+
50+
def on_online_status(dev_id: str, available: bool) -> None:
51+
signal = f"{DOMAIN}_update_available_{dev_id}"
52+
hass.add_job(async_dispatcher_send, hass, signal, available)
53+
54+
gateway.on_online_status = on_online_status
55+
56+
try:
57+
devices = await gateway.discover_devices()
58+
except DaliGatewayError as exc:
59+
raise ConfigEntryNotReady(
60+
"Unable to discover devices from the gateway"
61+
) from exc
62+
63+
_LOGGER.debug("Discovered %d devices on gateway %s", len(devices), gw_sn)
64+
65+
dev_reg = dr.async_get(hass)
66+
dev_reg.async_get_or_create(
67+
config_entry_id=entry.entry_id,
68+
identifiers={(DOMAIN, gw_sn)},
69+
manufacturer=MANUFACTURER,
70+
name=gateway.name,
71+
model="SR-GW-EDA",
72+
serial_number=gw_sn,
73+
)
74+
75+
entry.runtime_data = DaliCenterData(
76+
gateway=gateway,
77+
devices=devices,
78+
)
79+
await hass.config_entries.async_forward_entry_setups(entry, _PLATFORMS)
80+
81+
return True
82+
83+
84+
async def async_unload_entry(hass: HomeAssistant, entry: DaliCenterConfigEntry) -> bool:
85+
"""Unload a config entry."""
86+
if unload_ok := await hass.config_entries.async_unload_platforms(entry, _PLATFORMS):
87+
await entry.runtime_data.gateway.disconnect()
88+
return unload_ok
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
"""Config flow for the DALI Center integration."""
2+
3+
from __future__ import annotations
4+
5+
import logging
6+
from typing import Any
7+
8+
from PySrDaliGateway import DaliGateway
9+
from PySrDaliGateway.discovery import DaliGatewayDiscovery
10+
from PySrDaliGateway.exceptions import DaliGatewayError
11+
import voluptuous as vol
12+
13+
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
14+
from homeassistant.const import (
15+
CONF_HOST,
16+
CONF_NAME,
17+
CONF_PASSWORD,
18+
CONF_PORT,
19+
CONF_USERNAME,
20+
)
21+
from homeassistant.helpers.selector import (
22+
SelectOptionDict,
23+
SelectSelector,
24+
SelectSelectorConfig,
25+
)
26+
27+
from .const import CONF_SERIAL_NUMBER, DOMAIN
28+
29+
_LOGGER = logging.getLogger(__name__)
30+
31+
32+
class DaliCenterConfigFlow(ConfigFlow, domain=DOMAIN):
33+
"""Handle a config flow for DALI Center."""
34+
35+
VERSION = 1
36+
37+
def __init__(self) -> None:
38+
"""Initialize the config flow."""
39+
self._discovered_gateways: dict[str, DaliGateway] = {}
40+
41+
async def async_step_user(
42+
self, user_input: dict[str, Any] | None = None
43+
) -> ConfigFlowResult:
44+
"""Handle the initial step."""
45+
if user_input is not None:
46+
return await self.async_step_select_gateway()
47+
48+
return self.async_show_form(
49+
step_id="user",
50+
data_schema=vol.Schema({}),
51+
)
52+
53+
async def async_step_select_gateway(
54+
self, discovery_info: dict[str, Any] | None = None
55+
) -> ConfigFlowResult:
56+
"""Handle gateway discovery."""
57+
errors: dict[str, str] = {}
58+
59+
if discovery_info and "selected_gateway" in discovery_info:
60+
selected_sn = discovery_info["selected_gateway"]
61+
selected_gateway = self._discovered_gateways[selected_sn]
62+
63+
await self.async_set_unique_id(selected_gateway.gw_sn)
64+
self._abort_if_unique_id_configured()
65+
66+
try:
67+
await selected_gateway.connect()
68+
except DaliGatewayError as err:
69+
_LOGGER.debug(
70+
"Failed to connect to gateway %s during config flow",
71+
selected_gateway.gw_sn,
72+
exc_info=err,
73+
)
74+
errors["base"] = "cannot_connect"
75+
else:
76+
await selected_gateway.disconnect()
77+
return self.async_create_entry(
78+
title=selected_gateway.name,
79+
data={
80+
CONF_SERIAL_NUMBER: selected_gateway.gw_sn,
81+
CONF_HOST: selected_gateway.gw_ip,
82+
CONF_PORT: selected_gateway.port,
83+
CONF_NAME: selected_gateway.name,
84+
CONF_USERNAME: selected_gateway.username,
85+
CONF_PASSWORD: selected_gateway.passwd,
86+
},
87+
)
88+
89+
if not self._discovered_gateways:
90+
_LOGGER.debug("Starting gateway discovery")
91+
discovery = DaliGatewayDiscovery()
92+
try:
93+
discovered = await discovery.discover_gateways()
94+
except DaliGatewayError as err:
95+
_LOGGER.debug("Gateway discovery failed", exc_info=err)
96+
errors["base"] = "discovery_failed"
97+
else:
98+
configured_gateways = {
99+
entry.data[CONF_SERIAL_NUMBER]
100+
for entry in self.hass.config_entries.async_entries(DOMAIN)
101+
}
102+
103+
self._discovered_gateways = {
104+
gw.gw_sn: gw
105+
for gw in discovered
106+
if gw.gw_sn not in configured_gateways
107+
}
108+
109+
if not self._discovered_gateways:
110+
return self.async_show_form(
111+
step_id="select_gateway",
112+
errors=errors if errors else {"base": "no_devices_found"},
113+
data_schema=vol.Schema({}),
114+
)
115+
116+
gateway_options = [
117+
SelectOptionDict(
118+
value=sn,
119+
label=f"{gateway.name} [SN {sn}, IP {gateway.gw_ip}]",
120+
)
121+
for sn, gateway in self._discovered_gateways.items()
122+
]
123+
124+
return self.async_show_form(
125+
step_id="select_gateway",
126+
data_schema=vol.Schema(
127+
{
128+
vol.Optional("selected_gateway"): SelectSelector(
129+
SelectSelectorConfig(options=gateway_options, sort=True)
130+
),
131+
}
132+
),
133+
errors=errors,
134+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Constants for the DALI Center integration."""
2+
3+
DOMAIN = "sunricher_dali_center"
4+
MANUFACTURER = "Sunricher"
5+
CONF_SERIAL_NUMBER = "serial_number"

0 commit comments

Comments
 (0)