Skip to content

Commit 6e4258c

Browse files
Add Satel Integra config flow (#138946)
Co-authored-by: Shay Levy <[email protected]>
1 parent d65e704 commit 6e4258c

File tree

15 files changed

+1655
-176
lines changed

15 files changed

+1655
-176
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: 139 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,67 @@
11
"""Support for Satel Integra devices."""
22

3-
import collections
43
import logging
54

65
from satel_integra.satel_integra import AsyncSatel
76
import voluptuous as vol
87

9-
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP, Platform
10-
from homeassistant.core import HomeAssistant, callback
11-
from homeassistant.helpers import config_validation as cv
12-
from homeassistant.helpers.discovery import async_load_platform
8+
from homeassistant.config_entries import SOURCE_IMPORT
9+
from homeassistant.const import (
10+
CONF_CODE,
11+
CONF_HOST,
12+
CONF_NAME,
13+
CONF_PORT,
14+
EVENT_HOMEASSISTANT_STOP,
15+
Platform,
16+
)
17+
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
18+
from homeassistant.data_entry_flow import FlowResultType
19+
from homeassistant.exceptions import ConfigEntryNotReady
20+
from homeassistant.helpers import config_validation as cv, issue_registry as ir
1321
from homeassistant.helpers.dispatcher import async_dispatcher_send
1422
from homeassistant.helpers.typing import ConfigType
1523

16-
DEFAULT_ALARM_NAME = "satel_integra"
17-
DEFAULT_PORT = 7094
18-
DEFAULT_CONF_ARM_HOME_MODE = 1
19-
DEFAULT_DEVICE_PARTITION = 1
20-
DEFAULT_ZONE_TYPE = "motion"
24+
from .const import (
25+
CONF_ARM_HOME_MODE,
26+
CONF_DEVICE_PARTITIONS,
27+
CONF_OUTPUT_NUMBER,
28+
CONF_OUTPUTS,
29+
CONF_PARTITION_NUMBER,
30+
CONF_SWITCHABLE_OUTPUT_NUMBER,
31+
CONF_SWITCHABLE_OUTPUTS,
32+
CONF_ZONE_NUMBER,
33+
CONF_ZONE_TYPE,
34+
CONF_ZONES,
35+
DEFAULT_CONF_ARM_HOME_MODE,
36+
DEFAULT_PORT,
37+
DEFAULT_ZONE_TYPE,
38+
DOMAIN,
39+
SIGNAL_OUTPUTS_UPDATED,
40+
SIGNAL_PANEL_MESSAGE,
41+
SIGNAL_ZONES_UPDATED,
42+
SUBENTRY_TYPE_OUTPUT,
43+
SUBENTRY_TYPE_PARTITION,
44+
SUBENTRY_TYPE_SWITCHABLE_OUTPUT,
45+
SUBENTRY_TYPE_ZONE,
46+
ZONES,
47+
SatelConfigEntry,
48+
)
2149

2250
_LOGGER = logging.getLogger(__name__)
2351

24-
DOMAIN = "satel_integra"
25-
26-
DATA_SATEL = "satel_integra"
27-
28-
CONF_DEVICE_CODE = "code"
29-
CONF_DEVICE_PARTITIONS = "partitions"
30-
CONF_ARM_HOME_MODE = "arm_home_mode"
31-
CONF_ZONE_NAME = "name"
32-
CONF_ZONE_TYPE = "type"
33-
CONF_ZONES = "zones"
34-
CONF_OUTPUTS = "outputs"
35-
CONF_SWITCHABLE_OUTPUTS = "switchable_outputs"
36-
37-
ZONES = "zones"
52+
PLATFORMS = [Platform.ALARM_CONTROL_PANEL, Platform.BINARY_SENSOR, Platform.SWITCH]
3853

39-
SIGNAL_PANEL_MESSAGE = "satel_integra.panel_message"
40-
SIGNAL_PANEL_ARM_AWAY = "satel_integra.panel_arm_away"
41-
SIGNAL_PANEL_ARM_HOME = "satel_integra.panel_arm_home"
42-
SIGNAL_PANEL_DISARM = "satel_integra.panel_disarm"
43-
44-
SIGNAL_ZONES_UPDATED = "satel_integra.zones_updated"
45-
SIGNAL_OUTPUTS_UPDATED = "satel_integra.outputs_updated"
4654

4755
ZONE_SCHEMA = vol.Schema(
4856
{
49-
vol.Required(CONF_ZONE_NAME): cv.string,
57+
vol.Required(CONF_NAME): cv.string,
5058
vol.Optional(CONF_ZONE_TYPE, default=DEFAULT_ZONE_TYPE): cv.string,
5159
}
5260
)
53-
EDITABLE_OUTPUT_SCHEMA = vol.Schema({vol.Required(CONF_ZONE_NAME): cv.string})
61+
EDITABLE_OUTPUT_SCHEMA = vol.Schema({vol.Required(CONF_NAME): cv.string})
5462
PARTITION_SCHEMA = vol.Schema(
5563
{
56-
vol.Required(CONF_ZONE_NAME): cv.string,
64+
vol.Required(CONF_NAME): cv.string,
5765
vol.Optional(CONF_ARM_HOME_MODE, default=DEFAULT_CONF_ARM_HOME_MODE): vol.In(
5866
[1, 2, 3]
5967
),
@@ -63,7 +71,7 @@
6371

6472
def is_alarm_code_necessary(value):
6573
"""Check if alarm code must be configured."""
66-
if value.get(CONF_SWITCHABLE_OUTPUTS) and CONF_DEVICE_CODE not in value:
74+
if value.get(CONF_SWITCHABLE_OUTPUTS) and CONF_CODE not in value:
6775
raise vol.Invalid("You need to specify alarm code to use switchable_outputs")
6876

6977
return value
@@ -75,7 +83,7 @@ def is_alarm_code_necessary(value):
7583
{
7684
vol.Required(CONF_HOST): cv.string,
7785
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
78-
vol.Optional(CONF_DEVICE_CODE): cv.string,
86+
vol.Optional(CONF_CODE): cv.string,
7987
vol.Optional(CONF_DEVICE_PARTITIONS, default={}): {
8088
vol.Coerce(int): PARTITION_SCHEMA
8189
},
@@ -92,64 +100,106 @@ def is_alarm_code_necessary(value):
92100
)
93101

94102

95-
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
96-
"""Set up the Satel Integra component."""
97-
conf = config[DOMAIN]
103+
async def async_setup(hass: HomeAssistant, hass_config: ConfigType) -> bool:
104+
"""Set up Satel Integra from YAML."""
105+
106+
if config := hass_config.get(DOMAIN):
107+
hass.async_create_task(_async_import(hass, config))
108+
109+
return True
110+
98111

99-
zones = conf.get(CONF_ZONES)
100-
outputs = conf.get(CONF_OUTPUTS)
101-
switchable_outputs = conf.get(CONF_SWITCHABLE_OUTPUTS)
102-
host = conf.get(CONF_HOST)
103-
port = conf.get(CONF_PORT)
104-
partitions = conf.get(CONF_DEVICE_PARTITIONS)
112+
async def _async_import(hass: HomeAssistant, config: ConfigType) -> None:
113+
"""Process YAML import."""
105114

106-
monitored_outputs = collections.OrderedDict(
107-
list(outputs.items()) + list(switchable_outputs.items())
115+
if not hass.config_entries.async_entries(DOMAIN):
116+
# Start import flow
117+
result = await hass.config_entries.flow.async_init(
118+
DOMAIN, context={"source": SOURCE_IMPORT}, data=config
119+
)
120+
121+
if result.get("type") == FlowResultType.ABORT:
122+
ir.async_create_issue(
123+
hass,
124+
DOMAIN,
125+
"deprecated_yaml_import_issue_cannot_connect",
126+
breaks_in_ha_version="2026.4.0",
127+
is_fixable=False,
128+
issue_domain=DOMAIN,
129+
severity=ir.IssueSeverity.WARNING,
130+
translation_key="deprecated_yaml_import_issue_cannot_connect",
131+
translation_placeholders={
132+
"domain": DOMAIN,
133+
"integration_title": "Satel Integra",
134+
},
135+
)
136+
return
137+
138+
ir.async_create_issue(
139+
hass,
140+
HOMEASSISTANT_DOMAIN,
141+
f"deprecated_yaml_{DOMAIN}",
142+
breaks_in_ha_version="2026.4.0",
143+
is_fixable=False,
144+
issue_domain=DOMAIN,
145+
severity=ir.IssueSeverity.WARNING,
146+
translation_key="deprecated_yaml",
147+
translation_placeholders={
148+
"domain": DOMAIN,
149+
"integration_title": "Satel Integra",
150+
},
108151
)
109152

110-
controller = AsyncSatel(host, port, hass.loop, zones, monitored_outputs, partitions)
111153

112-
hass.data[DATA_SATEL] = controller
154+
async def async_setup_entry(hass: HomeAssistant, entry: SatelConfigEntry) -> bool:
155+
"""Set up Satel Integra from a config entry."""
156+
157+
host = entry.data[CONF_HOST]
158+
port = entry.data[CONF_PORT]
159+
160+
# Make sure we initialize the Satel controller with the configured entries to monitor
161+
partitions = [
162+
subentry.data[CONF_PARTITION_NUMBER]
163+
for subentry in entry.subentries.values()
164+
if subentry.subentry_type == SUBENTRY_TYPE_PARTITION
165+
]
166+
167+
zones = [
168+
subentry.data[CONF_ZONE_NUMBER]
169+
for subentry in entry.subentries.values()
170+
if subentry.subentry_type == SUBENTRY_TYPE_ZONE
171+
]
172+
173+
outputs = [
174+
subentry.data[CONF_OUTPUT_NUMBER]
175+
for subentry in entry.subentries.values()
176+
if subentry.subentry_type == SUBENTRY_TYPE_OUTPUT
177+
]
178+
179+
switchable_outputs = [
180+
subentry.data[CONF_SWITCHABLE_OUTPUT_NUMBER]
181+
for subentry in entry.subentries.values()
182+
if subentry.subentry_type == SUBENTRY_TYPE_SWITCHABLE_OUTPUT
183+
]
184+
185+
monitored_outputs = outputs + switchable_outputs
186+
187+
controller = AsyncSatel(host, port, hass.loop, zones, monitored_outputs, partitions)
113188

114189
result = await controller.connect()
115190

116191
if not result:
117-
return False
192+
raise ConfigEntryNotReady("Controller failed to connect")
193+
194+
entry.runtime_data = controller
118195

119196
@callback
120197
def _close(*_):
121198
controller.close()
122199

123-
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _close)
124-
125-
_LOGGER.debug("Arm home config: %s, mode: %s ", conf, conf.get(CONF_ARM_HOME_MODE))
200+
entry.async_on_unload(hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _close))
126201

127-
hass.async_create_task(
128-
async_load_platform(hass, Platform.ALARM_CONTROL_PANEL, DOMAIN, conf, config)
129-
)
130-
131-
hass.async_create_task(
132-
async_load_platform(
133-
hass,
134-
Platform.BINARY_SENSOR,
135-
DOMAIN,
136-
{CONF_ZONES: zones, CONF_OUTPUTS: outputs},
137-
config,
138-
)
139-
)
140-
141-
hass.async_create_task(
142-
async_load_platform(
143-
hass,
144-
Platform.SWITCH,
145-
DOMAIN,
146-
{
147-
CONF_SWITCHABLE_OUTPUTS: switchable_outputs,
148-
CONF_DEVICE_CODE: conf.get(CONF_DEVICE_CODE),
149-
},
150-
config,
151-
)
152-
)
202+
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
153203

154204
@callback
155205
def alarm_status_update_callback():
@@ -179,3 +229,13 @@ def outputs_update_callback(status):
179229
)
180230

181231
return True
232+
233+
234+
async def async_unload_entry(hass: HomeAssistant, entry: SatelConfigEntry) -> bool:
235+
"""Unloading the Satel platforms."""
236+
237+
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
238+
controller = entry.runtime_data
239+
controller.close()
240+
241+
return unload_ok

homeassistant/components/satel_integra/alarm_control_panel.py

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,46 +14,49 @@
1414
AlarmControlPanelState,
1515
CodeFormat,
1616
)
17+
from homeassistant.const import CONF_NAME
1718
from homeassistant.core import HomeAssistant, callback
1819
from homeassistant.helpers.dispatcher import async_dispatcher_connect
19-
from homeassistant.helpers.entity_platform import AddEntitiesCallback
20-
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
20+
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
2121

22-
from . import (
22+
from .const import (
2323
CONF_ARM_HOME_MODE,
24-
CONF_DEVICE_PARTITIONS,
25-
CONF_ZONE_NAME,
26-
DATA_SATEL,
24+
CONF_PARTITION_NUMBER,
2725
SIGNAL_PANEL_MESSAGE,
26+
SUBENTRY_TYPE_PARTITION,
27+
SatelConfigEntry,
2828
)
2929

3030
_LOGGER = logging.getLogger(__name__)
3131

3232

33-
async def async_setup_platform(
33+
async def async_setup_entry(
3434
hass: HomeAssistant,
35-
config: ConfigType,
36-
async_add_entities: AddEntitiesCallback,
37-
discovery_info: DiscoveryInfoType | None = None,
35+
config_entry: SatelConfigEntry,
36+
async_add_entities: AddConfigEntryEntitiesCallback,
3837
) -> None:
3938
"""Set up for Satel Integra alarm panels."""
40-
if not discovery_info:
41-
return
4239

43-
configured_partitions = discovery_info[CONF_DEVICE_PARTITIONS]
44-
controller = hass.data[DATA_SATEL]
40+
controller = config_entry.runtime_data
4541

46-
devices = []
42+
partition_subentries = filter(
43+
lambda entry: entry.subentry_type == SUBENTRY_TYPE_PARTITION,
44+
config_entry.subentries.values(),
45+
)
4746

48-
for partition_num, device_config_data in configured_partitions.items():
49-
zone_name = device_config_data[CONF_ZONE_NAME]
50-
arm_home_mode = device_config_data.get(CONF_ARM_HOME_MODE)
51-
device = SatelIntegraAlarmPanel(
52-
controller, zone_name, arm_home_mode, partition_num
53-
)
54-
devices.append(device)
47+
for subentry in partition_subentries:
48+
partition_num = subentry.data[CONF_PARTITION_NUMBER]
49+
zone_name = subentry.data[CONF_NAME]
50+
arm_home_mode = subentry.data[CONF_ARM_HOME_MODE]
5551

56-
async_add_entities(devices)
52+
async_add_entities(
53+
[
54+
SatelIntegraAlarmPanel(
55+
controller, zone_name, arm_home_mode, partition_num
56+
)
57+
],
58+
config_subentry_id=subentry.subentry_id,
59+
)
5760

5861

5962
class SatelIntegraAlarmPanel(AlarmControlPanelEntity):
@@ -66,7 +69,7 @@ class SatelIntegraAlarmPanel(AlarmControlPanelEntity):
6669
| AlarmControlPanelEntityFeature.ARM_AWAY
6770
)
6871

69-
def __init__(self, controller, name, arm_home_mode, partition_id):
72+
def __init__(self, controller, name, arm_home_mode, partition_id) -> None:
7073
"""Initialize the alarm panel."""
7174
self._attr_name = name
7275
self._attr_unique_id = f"satel_alarm_panel_{partition_id}"

0 commit comments

Comments
 (0)