Move Insteon configuration panel to config entry (#105581)
* Move Insteon panel to the config menu * Bump pyinsteon to 1.5.3 * Undo devcontainer.json changes * Bump Insteon frontend * Update config_flow.py * Code cleanup * Code review changes * Fix failing tests * Fix format * Remove unnecessary exception * codecov * Remove return from try * Fix merge mistake --------- Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
parent
1dfabf34c4
commit
c5c407b3bb
17 changed files with 988 additions and 766 deletions
272
homeassistant/components/insteon/api/config.py
Normal file
272
homeassistant/components/insteon/api/config.py
Normal file
|
@ -0,0 +1,272 @@
|
|||
"""API calls to manage Insteon configuration changes."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, TypedDict
|
||||
|
||||
from pyinsteon import async_close, async_connect, devices
|
||||
from pyinsteon.address import Address
|
||||
import voluptuous as vol
|
||||
import voluptuous_serialize
|
||||
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_ADDRESS, CONF_DEVICE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
|
||||
from ..const import (
|
||||
CONF_HOUSECODE,
|
||||
CONF_OVERRIDE,
|
||||
CONF_UNITCODE,
|
||||
CONF_X10,
|
||||
DEVICE_ADDRESS,
|
||||
DOMAIN,
|
||||
ID,
|
||||
SIGNAL_ADD_DEVICE_OVERRIDE,
|
||||
SIGNAL_ADD_X10_DEVICE,
|
||||
SIGNAL_REMOVE_DEVICE_OVERRIDE,
|
||||
TYPE,
|
||||
)
|
||||
from ..schemas import (
|
||||
build_device_override_schema,
|
||||
build_hub_schema,
|
||||
build_plm_manual_schema,
|
||||
build_plm_schema,
|
||||
)
|
||||
from ..utils import async_get_usb_ports
|
||||
|
||||
HUB_V1_SCHEMA = build_hub_schema(hub_version=1)
|
||||
HUB_V2_SCHEMA = build_hub_schema(hub_version=2)
|
||||
PLM_SCHEMA = build_plm_manual_schema()
|
||||
DEVICE_OVERRIDE_SCHEMA = build_device_override_schema()
|
||||
OVERRIDE = "override"
|
||||
|
||||
|
||||
class X10DeviceConfig(TypedDict):
|
||||
"""X10 Device Configuration Definition."""
|
||||
|
||||
housecode: str
|
||||
unitcode: int
|
||||
platform: str
|
||||
dim_steps: int
|
||||
|
||||
|
||||
class DeviceOverride(TypedDict):
|
||||
"""X10 Device Configuration Definition."""
|
||||
|
||||
address: Address | str
|
||||
cat: int
|
||||
subcat: str
|
||||
|
||||
|
||||
def get_insteon_config_entry(hass: HomeAssistant) -> ConfigEntry:
|
||||
"""Return the Insteon configuration entry."""
|
||||
return hass.config_entries.async_entries(DOMAIN)[0]
|
||||
|
||||
|
||||
def add_x10_device(hass: HomeAssistant, x10_device: X10DeviceConfig):
|
||||
"""Add an X10 device to the Insteon integration."""
|
||||
|
||||
config_entry = get_insteon_config_entry(hass)
|
||||
x10_config = config_entry.options.get(CONF_X10, [])
|
||||
if any(
|
||||
device[CONF_HOUSECODE] == x10_device["housecode"]
|
||||
and device[CONF_UNITCODE] == x10_device["unitcode"]
|
||||
for device in x10_config
|
||||
):
|
||||
raise ValueError("Duplicate X10 device")
|
||||
|
||||
hass.config_entries.async_update_entry(
|
||||
entry=config_entry,
|
||||
options=config_entry.options | {CONF_X10: [*x10_config, x10_device]},
|
||||
)
|
||||
async_dispatcher_send(hass, SIGNAL_ADD_X10_DEVICE, x10_device)
|
||||
|
||||
|
||||
def remove_x10_device(hass: HomeAssistant, housecode: str, unitcode: int):
|
||||
"""Remove an X10 device from the config."""
|
||||
|
||||
config_entry = get_insteon_config_entry(hass)
|
||||
new_options = {**config_entry.options}
|
||||
new_x10 = [
|
||||
existing_device
|
||||
for existing_device in config_entry.options.get(CONF_X10, [])
|
||||
if existing_device[CONF_HOUSECODE].lower() != housecode.lower()
|
||||
or existing_device[CONF_UNITCODE] != unitcode
|
||||
]
|
||||
|
||||
new_options[CONF_X10] = new_x10
|
||||
hass.config_entries.async_update_entry(entry=config_entry, options=new_options)
|
||||
|
||||
|
||||
def add_device_overide(hass: HomeAssistant, override: DeviceOverride):
|
||||
"""Add an Insteon device override."""
|
||||
|
||||
config_entry = get_insteon_config_entry(hass)
|
||||
override_config = config_entry.options.get(CONF_OVERRIDE, [])
|
||||
address = Address(override[CONF_ADDRESS])
|
||||
if any(
|
||||
Address(existing_override[CONF_ADDRESS]) == address
|
||||
for existing_override in override_config
|
||||
):
|
||||
raise ValueError("Duplicate override")
|
||||
|
||||
hass.config_entries.async_update_entry(
|
||||
entry=config_entry,
|
||||
options=config_entry.options | {CONF_OVERRIDE: [*override_config, override]},
|
||||
)
|
||||
async_dispatcher_send(hass, SIGNAL_ADD_DEVICE_OVERRIDE, override)
|
||||
|
||||
|
||||
def remove_device_override(hass: HomeAssistant, address: Address):
|
||||
"""Remove a device override from config."""
|
||||
|
||||
config_entry = get_insteon_config_entry(hass)
|
||||
new_options = {**config_entry.options}
|
||||
|
||||
new_overrides = [
|
||||
existing_override
|
||||
for existing_override in config_entry.options.get(CONF_OVERRIDE, [])
|
||||
if Address(existing_override[CONF_ADDRESS]) != address
|
||||
]
|
||||
new_options[CONF_OVERRIDE] = new_overrides
|
||||
hass.config_entries.async_update_entry(entry=config_entry, options=new_options)
|
||||
|
||||
|
||||
async def _async_connect(**kwargs):
|
||||
"""Connect to the Insteon modem."""
|
||||
if devices.modem:
|
||||
await async_close()
|
||||
try:
|
||||
await async_connect(**kwargs)
|
||||
except ConnectionError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@websocket_api.websocket_command({vol.Required(TYPE): "insteon/config/get"})
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def websocket_get_config(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.connection.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Get Insteon configuration."""
|
||||
config_entry = get_insteon_config_entry(hass)
|
||||
modem_config = config_entry.data
|
||||
options_config = config_entry.options
|
||||
x10_config = options_config.get(CONF_X10)
|
||||
override_config = options_config.get(CONF_OVERRIDE)
|
||||
connection.send_result(
|
||||
msg[ID],
|
||||
{
|
||||
"modem_config": {**modem_config},
|
||||
"x10_config": x10_config,
|
||||
"override_config": override_config,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required(TYPE): "insteon/config/get_modem_schema",
|
||||
}
|
||||
)
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def websocket_get_modem_schema(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.connection.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Get the schema for the modem configuration."""
|
||||
config_entry = get_insteon_config_entry(hass)
|
||||
config_data = config_entry.data
|
||||
if device := config_data.get(CONF_DEVICE):
|
||||
ports = await async_get_usb_ports(hass=hass)
|
||||
plm_schema = voluptuous_serialize.convert(
|
||||
build_plm_schema(ports=ports, device=device)
|
||||
)
|
||||
connection.send_result(msg[ID], plm_schema)
|
||||
else:
|
||||
hub_schema = voluptuous_serialize.convert(build_hub_schema(**config_data))
|
||||
connection.send_result(msg[ID], hub_schema)
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required(TYPE): "insteon/config/update_modem_config",
|
||||
vol.Required("config"): vol.Any(PLM_SCHEMA, HUB_V2_SCHEMA, HUB_V1_SCHEMA),
|
||||
}
|
||||
)
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def websocket_update_modem_config(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.connection.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Get the schema for the modem configuration."""
|
||||
config = msg["config"]
|
||||
config_entry = get_insteon_config_entry(hass)
|
||||
is_connected = devices.modem.connected
|
||||
|
||||
if not await _async_connect(**config):
|
||||
connection.send_error(
|
||||
msg_id=msg[ID], code="connection_failed", message="Connection failed"
|
||||
)
|
||||
# Try to reconnect using old info
|
||||
if is_connected:
|
||||
await _async_connect(**config_entry.data)
|
||||
return
|
||||
|
||||
hass.config_entries.async_update_entry(
|
||||
entry=config_entry,
|
||||
data=config,
|
||||
)
|
||||
connection.send_result(msg[ID], {"status": "success"})
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required(TYPE): "insteon/config/device_override/add",
|
||||
vol.Required(OVERRIDE): DEVICE_OVERRIDE_SCHEMA,
|
||||
}
|
||||
)
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def websocket_add_device_override(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.connection.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Get the schema for the modem configuration."""
|
||||
override = msg[OVERRIDE]
|
||||
try:
|
||||
add_device_overide(hass, override)
|
||||
except ValueError:
|
||||
connection.send_error(msg[ID], "duplicate", "Duplicate device address")
|
||||
|
||||
connection.send_result(msg[ID])
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required(TYPE): "insteon/config/device_override/remove",
|
||||
vol.Required(DEVICE_ADDRESS): str,
|
||||
}
|
||||
)
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def websocket_remove_device_override(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.connection.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Get the schema for the modem configuration."""
|
||||
address = Address(msg[DEVICE_ADDRESS])
|
||||
remove_device_override(hass, address)
|
||||
async_dispatcher_send(hass, SIGNAL_REMOVE_DEVICE_OVERRIDE, address)
|
||||
connection.send_result(msg[ID])
|
Loading…
Add table
Add a link
Reference in a new issue