mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
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
@ -16,6 +16,7 @@ from homeassistant.helpers.typing import ConfigType
|
||||
from . import api
|
||||
from .const import (
|
||||
CONF_CAT,
|
||||
CONF_DEV_PATH,
|
||||
CONF_DIM_STEPS,
|
||||
CONF_HOUSECODE,
|
||||
CONF_OVERRIDE,
|
||||
@ -84,6 +85,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up an Insteon entry."""
|
||||
|
||||
if dev_path := entry.options.get(CONF_DEV_PATH):
|
||||
hass.data[DOMAIN] = {}
|
||||
hass.data[DOMAIN][CONF_DEV_PATH] = dev_path
|
||||
|
||||
api.async_load_api(hass)
|
||||
await api.async_register_insteon_frontend(hass)
|
||||
|
||||
if not devices.modem:
|
||||
try:
|
||||
await async_connect(**entry.data)
|
||||
@ -149,9 +157,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
create_insteon_device(hass, devices.modem, entry.entry_id)
|
||||
|
||||
api.async_load_api(hass)
|
||||
await api.async_register_insteon_frontend(hass)
|
||||
|
||||
entry.async_create_background_task(
|
||||
hass, async_get_device_config(hass, entry), "insteon-get-device-config"
|
||||
)
|
||||
|
@ -16,10 +16,19 @@ from .aldb import (
|
||||
websocket_reset_aldb,
|
||||
websocket_write_aldb,
|
||||
)
|
||||
from .config import (
|
||||
websocket_add_device_override,
|
||||
websocket_get_config,
|
||||
websocket_get_modem_schema,
|
||||
websocket_remove_device_override,
|
||||
websocket_update_modem_config,
|
||||
)
|
||||
from .device import (
|
||||
websocket_add_device,
|
||||
websocket_add_x10_device,
|
||||
websocket_cancel_add_device,
|
||||
websocket_get_device,
|
||||
websocket_remove_device,
|
||||
)
|
||||
from .properties import (
|
||||
websocket_change_properties_record,
|
||||
@ -58,6 +67,8 @@ def async_load_api(hass):
|
||||
websocket_api.async_register_command(hass, websocket_reset_aldb)
|
||||
websocket_api.async_register_command(hass, websocket_add_default_links)
|
||||
websocket_api.async_register_command(hass, websocket_notify_on_aldb_status)
|
||||
websocket_api.async_register_command(hass, websocket_add_x10_device)
|
||||
websocket_api.async_register_command(hass, websocket_remove_device)
|
||||
|
||||
websocket_api.async_register_command(hass, websocket_get_properties)
|
||||
websocket_api.async_register_command(hass, websocket_change_properties_record)
|
||||
@ -65,6 +76,12 @@ def async_load_api(hass):
|
||||
websocket_api.async_register_command(hass, websocket_load_properties)
|
||||
websocket_api.async_register_command(hass, websocket_reset_properties)
|
||||
|
||||
websocket_api.async_register_command(hass, websocket_get_config)
|
||||
websocket_api.async_register_command(hass, websocket_get_modem_schema)
|
||||
websocket_api.async_register_command(hass, websocket_update_modem_config)
|
||||
websocket_api.async_register_command(hass, websocket_add_device_override)
|
||||
websocket_api.async_register_command(hass, websocket_remove_device_override)
|
||||
|
||||
|
||||
async def async_register_insteon_frontend(hass: HomeAssistant):
|
||||
"""Register the Insteon frontend configuration panel."""
|
||||
@ -80,8 +97,7 @@ async def async_register_insteon_frontend(hass: HomeAssistant):
|
||||
hass=hass,
|
||||
frontend_url_path=DOMAIN,
|
||||
webcomponent_name="insteon-frontend",
|
||||
sidebar_title=DOMAIN.capitalize(),
|
||||
sidebar_icon="mdi:power",
|
||||
config_panel_domain=DOMAIN,
|
||||
module_url=f"{URL_BASE}/entrypoint-{build_id}.js",
|
||||
embed_iframe=True,
|
||||
require_admin=True,
|
||||
|
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])
|
@ -3,12 +3,14 @@
|
||||
from typing import Any
|
||||
|
||||
from pyinsteon import devices
|
||||
from pyinsteon.address import Address
|
||||
from pyinsteon.constants import DeviceAction
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
|
||||
from ..const import (
|
||||
DEVICE_ADDRESS,
|
||||
@ -18,8 +20,17 @@ from ..const import (
|
||||
ID,
|
||||
INSTEON_DEVICE_NOT_FOUND,
|
||||
MULTIPLE,
|
||||
SIGNAL_REMOVE_HA_DEVICE,
|
||||
SIGNAL_REMOVE_INSTEON_DEVICE,
|
||||
SIGNAL_REMOVE_X10_DEVICE,
|
||||
TYPE,
|
||||
)
|
||||
from ..schemas import build_x10_schema
|
||||
from .config import add_x10_device, remove_device_override, remove_x10_device
|
||||
|
||||
X10_DEVICE = "x10_device"
|
||||
X10_DEVICE_SCHEMA = build_x10_schema()
|
||||
REMOVE_ALL_REFS = "remove_all_refs"
|
||||
|
||||
|
||||
def compute_device_name(ha_device):
|
||||
@ -139,3 +150,61 @@ async def websocket_cancel_add_device(
|
||||
"""Cancel the Insteon all-linking process."""
|
||||
await devices.async_cancel_all_linking()
|
||||
connection.send_result(msg[ID])
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required(TYPE): "insteon/device/remove",
|
||||
vol.Required(DEVICE_ADDRESS): str,
|
||||
vol.Required(REMOVE_ALL_REFS): bool,
|
||||
}
|
||||
)
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def websocket_remove_device(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.connection.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Remove an Insteon device."""
|
||||
|
||||
address = msg[DEVICE_ADDRESS]
|
||||
remove_all_refs = msg[REMOVE_ALL_REFS]
|
||||
if address.startswith("X10"):
|
||||
_, housecode, unitcode = address.split(".")
|
||||
unitcode = int(unitcode)
|
||||
async_dispatcher_send(hass, SIGNAL_REMOVE_X10_DEVICE, housecode, unitcode)
|
||||
remove_x10_device(hass, housecode, unitcode)
|
||||
else:
|
||||
address = Address(address)
|
||||
remove_device_override(hass, address)
|
||||
async_dispatcher_send(hass, SIGNAL_REMOVE_HA_DEVICE, address)
|
||||
async_dispatcher_send(
|
||||
hass, SIGNAL_REMOVE_INSTEON_DEVICE, address, remove_all_refs
|
||||
)
|
||||
|
||||
connection.send_result(msg[ID])
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required(TYPE): "insteon/device/add_x10",
|
||||
vol.Required(X10_DEVICE): X10_DEVICE_SCHEMA,
|
||||
}
|
||||
)
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def websocket_add_x10_device(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.connection.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Get the schema for the X10 devices configuration."""
|
||||
x10_device = msg[X10_DEVICE]
|
||||
try:
|
||||
add_x10_device(hass, x10_device)
|
||||
except ValueError:
|
||||
connection.send_error(msg[ID], code="duplicate", message="Duplicate X10 device")
|
||||
return
|
||||
|
||||
connection.send_result(msg[ID])
|
||||
|
@ -4,52 +4,19 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from pyinsteon import async_close, async_connect, devices
|
||||
from pyinsteon import async_connect
|
||||
|
||||
from homeassistant.components import dhcp, usb
|
||||
from homeassistant.config_entries import (
|
||||
DEFAULT_DISCOVERY_UNIQUE_ID,
|
||||
ConfigEntry,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_ADDRESS,
|
||||
CONF_DEVICE,
|
||||
CONF_HOST,
|
||||
CONF_NAME,
|
||||
CONF_PASSWORD,
|
||||
CONF_PORT,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.const import CONF_DEVICE, CONF_HOST, CONF_NAME
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
|
||||
from .const import (
|
||||
CONF_HOUSECODE,
|
||||
CONF_HUB_VERSION,
|
||||
CONF_OVERRIDE,
|
||||
CONF_UNITCODE,
|
||||
CONF_X10,
|
||||
DOMAIN,
|
||||
SIGNAL_ADD_DEVICE_OVERRIDE,
|
||||
SIGNAL_ADD_X10_DEVICE,
|
||||
SIGNAL_REMOVE_DEVICE_OVERRIDE,
|
||||
SIGNAL_REMOVE_X10_DEVICE,
|
||||
)
|
||||
from .schemas import (
|
||||
add_device_override,
|
||||
add_x10_device,
|
||||
build_device_override_schema,
|
||||
build_hub_schema,
|
||||
build_plm_manual_schema,
|
||||
build_plm_schema,
|
||||
build_remove_override_schema,
|
||||
build_remove_x10_schema,
|
||||
build_x10_schema,
|
||||
)
|
||||
from .const import CONF_HUB_VERSION, DOMAIN
|
||||
from .schemas import build_hub_schema, build_plm_manual_schema, build_plm_schema
|
||||
from .utils import async_get_usb_ports
|
||||
|
||||
STEP_PLM = "plm"
|
||||
@ -80,41 +47,6 @@ async def _async_connect(**kwargs):
|
||||
return True
|
||||
|
||||
|
||||
def _remove_override(address, options):
|
||||
"""Remove a device override from config."""
|
||||
new_options = {}
|
||||
if options.get(CONF_X10):
|
||||
new_options[CONF_X10] = options.get(CONF_X10)
|
||||
new_overrides = [
|
||||
override
|
||||
for override in options[CONF_OVERRIDE]
|
||||
if override[CONF_ADDRESS] != address
|
||||
]
|
||||
if new_overrides:
|
||||
new_options[CONF_OVERRIDE] = new_overrides
|
||||
return new_options
|
||||
|
||||
|
||||
def _remove_x10(device, options):
|
||||
"""Remove an X10 device from the config."""
|
||||
housecode = device[11].lower()
|
||||
unitcode = int(device[24:])
|
||||
new_options = {}
|
||||
if options.get(CONF_OVERRIDE):
|
||||
new_options[CONF_OVERRIDE] = options.get(CONF_OVERRIDE)
|
||||
new_x10 = [
|
||||
existing_device
|
||||
for existing_device in options[CONF_X10]
|
||||
if (
|
||||
existing_device[CONF_HOUSECODE].lower() != housecode
|
||||
or existing_device[CONF_UNITCODE] != unitcode
|
||||
)
|
||||
]
|
||||
if new_x10:
|
||||
new_options[CONF_X10] = new_x10
|
||||
return new_options, housecode, unitcode
|
||||
|
||||
|
||||
class InsteonFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"""Insteon config flow handler."""
|
||||
|
||||
@ -122,14 +54,6 @@ class InsteonFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
_device_name: str | None = None
|
||||
discovered_conf: dict[str, str] = {}
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(
|
||||
config_entry: ConfigEntry,
|
||||
) -> InsteonOptionsFlowHandler:
|
||||
"""Define the config flow to handle options."""
|
||||
return InsteonOptionsFlowHandler(config_entry)
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Init the config flow."""
|
||||
if self._async_current_entries():
|
||||
@ -237,140 +161,3 @@ class InsteonFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
}
|
||||
await self.async_set_unique_id(format_mac(discovery_info.macaddress))
|
||||
return await self.async_step_user()
|
||||
|
||||
|
||||
class InsteonOptionsFlowHandler(OptionsFlow):
|
||||
"""Handle an Insteon options flow."""
|
||||
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
"""Init the InsteonOptionsFlowHandler class."""
|
||||
self.config_entry = config_entry
|
||||
|
||||
async def async_step_init(self, user_input=None) -> ConfigFlowResult:
|
||||
"""Init the options config flow."""
|
||||
menu_options = [STEP_ADD_OVERRIDE, STEP_ADD_X10]
|
||||
|
||||
if self.config_entry.data.get(CONF_HOST):
|
||||
menu_options.append(STEP_CHANGE_HUB_CONFIG)
|
||||
else:
|
||||
menu_options.append(STEP_CHANGE_PLM_CONFIG)
|
||||
|
||||
options = {**self.config_entry.options}
|
||||
if options.get(CONF_OVERRIDE):
|
||||
menu_options.append(STEP_REMOVE_OVERRIDE)
|
||||
if options.get(CONF_X10):
|
||||
menu_options.append(STEP_REMOVE_X10)
|
||||
|
||||
return self.async_show_menu(step_id="init", menu_options=menu_options)
|
||||
|
||||
async def async_step_change_hub_config(self, user_input=None) -> ConfigFlowResult:
|
||||
"""Change the Hub configuration."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
data = {
|
||||
**self.config_entry.data,
|
||||
CONF_HOST: user_input[CONF_HOST],
|
||||
CONF_PORT: user_input[CONF_PORT],
|
||||
}
|
||||
if self.config_entry.data[CONF_HUB_VERSION] == 2:
|
||||
data[CONF_USERNAME] = user_input[CONF_USERNAME]
|
||||
data[CONF_PASSWORD] = user_input[CONF_PASSWORD]
|
||||
if devices.modem:
|
||||
await async_close()
|
||||
|
||||
if await _async_connect(**data):
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self.config_entry, data=data
|
||||
)
|
||||
return self.async_create_entry(data={**self.config_entry.options})
|
||||
errors["base"] = "cannot_connect"
|
||||
data_schema = build_hub_schema(**self.config_entry.data)
|
||||
return self.async_show_form(
|
||||
step_id=STEP_CHANGE_HUB_CONFIG, data_schema=data_schema, errors=errors
|
||||
)
|
||||
|
||||
async def async_step_change_plm_config(self, user_input=None) -> ConfigFlowResult:
|
||||
"""Change the PLM configuration."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
data = {
|
||||
**self.config_entry.data,
|
||||
CONF_DEVICE: user_input[CONF_DEVICE],
|
||||
}
|
||||
if devices.modem:
|
||||
await async_close()
|
||||
if await _async_connect(**data):
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self.config_entry, data=data
|
||||
)
|
||||
return self.async_create_entry(data={**self.config_entry.options})
|
||||
errors["base"] = "cannot_connect"
|
||||
|
||||
ports = await async_get_usb_ports(self.hass)
|
||||
data_schema = build_plm_schema(ports, **self.config_entry.data)
|
||||
return self.async_show_form(
|
||||
step_id=STEP_CHANGE_PLM_CONFIG, data_schema=data_schema, errors=errors
|
||||
)
|
||||
|
||||
async def async_step_add_override(self, user_input=None) -> ConfigFlowResult:
|
||||
"""Add a device override."""
|
||||
errors = {}
|
||||
if user_input is not None:
|
||||
try:
|
||||
data = add_device_override({**self.config_entry.options}, user_input)
|
||||
async_dispatcher_send(self.hass, SIGNAL_ADD_DEVICE_OVERRIDE, user_input)
|
||||
return self.async_create_entry(data=data)
|
||||
except ValueError:
|
||||
errors["base"] = "input_error"
|
||||
schema_defaults = user_input if user_input is not None else {}
|
||||
data_schema = build_device_override_schema(**schema_defaults)
|
||||
return self.async_show_form(
|
||||
step_id=STEP_ADD_OVERRIDE, data_schema=data_schema, errors=errors
|
||||
)
|
||||
|
||||
async def async_step_add_x10(self, user_input=None) -> ConfigFlowResult:
|
||||
"""Add an X10 device."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
options = add_x10_device({**self.config_entry.options}, user_input)
|
||||
async_dispatcher_send(self.hass, SIGNAL_ADD_X10_DEVICE, user_input)
|
||||
return self.async_create_entry(data=options)
|
||||
schema_defaults: dict[str, str] = user_input if user_input is not None else {}
|
||||
data_schema = build_x10_schema(**schema_defaults)
|
||||
return self.async_show_form(
|
||||
step_id=STEP_ADD_X10, data_schema=data_schema, errors=errors
|
||||
)
|
||||
|
||||
async def async_step_remove_override(self, user_input=None) -> ConfigFlowResult:
|
||||
"""Remove a device override."""
|
||||
errors: dict[str, str] = {}
|
||||
options = self.config_entry.options
|
||||
if user_input is not None:
|
||||
options = _remove_override(user_input[CONF_ADDRESS], options)
|
||||
async_dispatcher_send(
|
||||
self.hass,
|
||||
SIGNAL_REMOVE_DEVICE_OVERRIDE,
|
||||
user_input[CONF_ADDRESS],
|
||||
)
|
||||
return self.async_create_entry(data=options)
|
||||
|
||||
data_schema = build_remove_override_schema(options[CONF_OVERRIDE])
|
||||
return self.async_show_form(
|
||||
step_id=STEP_REMOVE_OVERRIDE, data_schema=data_schema, errors=errors
|
||||
)
|
||||
|
||||
async def async_step_remove_x10(self, user_input=None) -> ConfigFlowResult:
|
||||
"""Remove an X10 device."""
|
||||
errors: dict[str, str] = {}
|
||||
options = self.config_entry.options
|
||||
if user_input is not None:
|
||||
options, housecode, unitcode = _remove_x10(user_input[CONF_DEVICE], options)
|
||||
async_dispatcher_send(
|
||||
self.hass, SIGNAL_REMOVE_X10_DEVICE, housecode, unitcode
|
||||
)
|
||||
return self.async_create_entry(data=options)
|
||||
|
||||
data_schema = build_remove_x10_schema(options[CONF_X10])
|
||||
return self.async_show_form(
|
||||
step_id=STEP_REMOVE_X10, data_schema=data_schema, errors=errors
|
||||
)
|
||||
|
@ -101,6 +101,8 @@ SIGNAL_SAVE_DEVICES = "save_devices"
|
||||
SIGNAL_ADD_ENTITIES = "insteon_add_entities"
|
||||
SIGNAL_ADD_DEFAULT_LINKS = "add_default_links"
|
||||
SIGNAL_ADD_DEVICE_OVERRIDE = "add_device_override"
|
||||
SIGNAL_REMOVE_HA_DEVICE = "insteon_remove_ha_device"
|
||||
SIGNAL_REMOVE_INSTEON_DEVICE = "insteon_remove_insteon_device"
|
||||
SIGNAL_REMOVE_DEVICE_OVERRIDE = "insteon_remove_device_override"
|
||||
SIGNAL_REMOVE_ENTITY = "insteon_remove_entity"
|
||||
SIGNAL_ADD_X10_DEVICE = "insteon_add_x10_device"
|
||||
|
@ -95,6 +95,7 @@ class InsteonEntity(Entity):
|
||||
f" {self._insteon_device.engine_version}"
|
||||
),
|
||||
via_device=(DOMAIN, str(devices.modem.address)),
|
||||
configuration_url=f"homeassistant://insteon/device/config/{self._insteon_device.id}",
|
||||
)
|
||||
|
||||
@callback
|
||||
|
@ -18,7 +18,7 @@
|
||||
"loggers": ["pyinsteon", "pypubsub"],
|
||||
"requirements": [
|
||||
"pyinsteon==1.5.3",
|
||||
"insteon-frontend-home-assistant==0.4.0"
|
||||
"insteon-frontend-home-assistant==0.5.0"
|
||||
],
|
||||
"usb": [
|
||||
{
|
||||
|
@ -2,9 +2,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from binascii import Error as HexError, unhexlify
|
||||
|
||||
from pyinsteon.address import Address
|
||||
from pyinsteon.constants import HC_LOOKUP
|
||||
import voluptuous as vol
|
||||
|
||||
@ -25,10 +22,8 @@ from .const import (
|
||||
CONF_CAT,
|
||||
CONF_DIM_STEPS,
|
||||
CONF_HOUSECODE,
|
||||
CONF_OVERRIDE,
|
||||
CONF_SUBCAT,
|
||||
CONF_UNITCODE,
|
||||
CONF_X10,
|
||||
HOUSECODES,
|
||||
PORT_HUB_V1,
|
||||
PORT_HUB_V2,
|
||||
@ -76,76 +71,6 @@ TRIGGER_SCENE_SCHEMA = vol.Schema(
|
||||
ADD_DEFAULT_LINKS_SCHEMA = vol.Schema({vol.Required(CONF_ENTITY_ID): cv.entity_id})
|
||||
|
||||
|
||||
def normalize_byte_entry_to_int(entry: int | bytes | str):
|
||||
"""Format a hex entry value."""
|
||||
if isinstance(entry, int):
|
||||
if entry in range(256):
|
||||
return entry
|
||||
raise ValueError("Must be single byte")
|
||||
if isinstance(entry, str):
|
||||
if entry[0:2].lower() == "0x":
|
||||
entry = entry[2:]
|
||||
if len(entry) != 2:
|
||||
raise ValueError("Not a valid hex code")
|
||||
try:
|
||||
entry = unhexlify(entry)
|
||||
except HexError as err:
|
||||
raise ValueError("Not a valid hex code") from err
|
||||
return int.from_bytes(entry, byteorder="big")
|
||||
|
||||
|
||||
def add_device_override(config_data, new_override):
|
||||
"""Add a new device override."""
|
||||
try:
|
||||
address = str(Address(new_override[CONF_ADDRESS]))
|
||||
cat = normalize_byte_entry_to_int(new_override[CONF_CAT])
|
||||
subcat = normalize_byte_entry_to_int(new_override[CONF_SUBCAT])
|
||||
except ValueError as err:
|
||||
raise ValueError("Incorrect values") from err
|
||||
|
||||
overrides = [
|
||||
override
|
||||
for override in config_data.get(CONF_OVERRIDE, [])
|
||||
if override[CONF_ADDRESS] != address
|
||||
]
|
||||
overrides.append(
|
||||
{
|
||||
CONF_ADDRESS: address,
|
||||
CONF_CAT: cat,
|
||||
CONF_SUBCAT: subcat,
|
||||
}
|
||||
)
|
||||
|
||||
new_config = {}
|
||||
if config_data.get(CONF_X10):
|
||||
new_config[CONF_X10] = config_data[CONF_X10]
|
||||
new_config[CONF_OVERRIDE] = overrides
|
||||
return new_config
|
||||
|
||||
|
||||
def add_x10_device(config_data, new_x10):
|
||||
"""Add a new X10 device to X10 device list."""
|
||||
x10_devices = [
|
||||
x10_device
|
||||
for x10_device in config_data.get(CONF_X10, [])
|
||||
if x10_device[CONF_HOUSECODE] != new_x10[CONF_HOUSECODE]
|
||||
or x10_device[CONF_UNITCODE] != new_x10[CONF_UNITCODE]
|
||||
]
|
||||
x10_devices.append(
|
||||
{
|
||||
CONF_HOUSECODE: new_x10[CONF_HOUSECODE],
|
||||
CONF_UNITCODE: new_x10[CONF_UNITCODE],
|
||||
CONF_PLATFORM: new_x10[CONF_PLATFORM],
|
||||
CONF_DIM_STEPS: new_x10[CONF_DIM_STEPS],
|
||||
}
|
||||
)
|
||||
new_config = {}
|
||||
if config_data.get(CONF_OVERRIDE):
|
||||
new_config[CONF_OVERRIDE] = config_data[CONF_OVERRIDE]
|
||||
new_config[CONF_X10] = x10_devices
|
||||
return new_config
|
||||
|
||||
|
||||
def build_device_override_schema(
|
||||
address=vol.UNDEFINED,
|
||||
cat=vol.UNDEFINED,
|
||||
@ -169,12 +94,16 @@ def build_x10_schema(
|
||||
dim_steps=22,
|
||||
):
|
||||
"""Build the X10 schema for config flow."""
|
||||
if platform == "light":
|
||||
dim_steps_schema = vol.Required(CONF_DIM_STEPS, default=dim_steps)
|
||||
else:
|
||||
dim_steps_schema = vol.Optional(CONF_DIM_STEPS, default=dim_steps)
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_HOUSECODE, default=housecode): vol.In(HC_LOOKUP.keys()),
|
||||
vol.Required(CONF_UNITCODE, default=unitcode): vol.In(range(1, 17)),
|
||||
vol.Required(CONF_PLATFORM, default=platform): vol.In(X10_PLATFORMS),
|
||||
vol.Optional(CONF_DIM_STEPS, default=dim_steps): vol.In(range(1, 255)),
|
||||
dim_steps_schema: vol.Range(min=0, max=255),
|
||||
}
|
||||
)
|
||||
|
||||
@ -219,18 +148,3 @@ def build_hub_schema(
|
||||
schema[vol.Required(CONF_USERNAME, default=username)] = str
|
||||
schema[vol.Required(CONF_PASSWORD, default=password)] = str
|
||||
return vol.Schema(schema)
|
||||
|
||||
|
||||
def build_remove_override_schema(data):
|
||||
"""Build the schema to remove device overrides in config flow options."""
|
||||
selection = [override[CONF_ADDRESS] for override in data]
|
||||
return vol.Schema({vol.Required(CONF_ADDRESS): vol.In(selection)})
|
||||
|
||||
|
||||
def build_remove_x10_schema(data):
|
||||
"""Build the schema to remove an X10 device in config flow options."""
|
||||
selection = [
|
||||
f"Housecode: {device[CONF_HOUSECODE].upper()}, Unitcode: {device[CONF_UNITCODE]}"
|
||||
for device in data
|
||||
]
|
||||
return vol.Schema({vol.Required(CONF_DEVICE): vol.In(selection)})
|
||||
|
@ -65,6 +65,8 @@ from .const import (
|
||||
SIGNAL_PRINT_ALDB,
|
||||
SIGNAL_REMOVE_DEVICE_OVERRIDE,
|
||||
SIGNAL_REMOVE_ENTITY,
|
||||
SIGNAL_REMOVE_HA_DEVICE,
|
||||
SIGNAL_REMOVE_INSTEON_DEVICE,
|
||||
SIGNAL_REMOVE_X10_DEVICE,
|
||||
SIGNAL_SAVE_DEVICES,
|
||||
SRV_ADD_ALL_LINK,
|
||||
@ -179,7 +181,7 @@ def register_new_device_callback(hass):
|
||||
|
||||
|
||||
@callback
|
||||
def async_register_services(hass):
|
||||
def async_register_services(hass): # noqa: C901
|
||||
"""Register services used by insteon component."""
|
||||
|
||||
save_lock = asyncio.Lock()
|
||||
@ -270,14 +272,14 @@ def async_register_services(hass):
|
||||
async def async_add_device_override(override):
|
||||
"""Remove an Insten device and associated entities."""
|
||||
address = Address(override[CONF_ADDRESS])
|
||||
await async_remove_device(address)
|
||||
await async_remove_ha_device(address)
|
||||
devices.set_id(address, override[CONF_CAT], override[CONF_SUBCAT], 0)
|
||||
await async_srv_save_devices()
|
||||
|
||||
async def async_remove_device_override(address):
|
||||
"""Remove an Insten device and associated entities."""
|
||||
address = Address(address)
|
||||
await async_remove_device(address)
|
||||
await async_remove_ha_device(address)
|
||||
devices.set_id(address, None, None, None)
|
||||
await devices.async_identify_device(address)
|
||||
await async_srv_save_devices()
|
||||
@ -304,9 +306,9 @@ def async_register_services(hass):
|
||||
"""Remove an X10 device and associated entities."""
|
||||
address = create_x10_address(housecode, unitcode)
|
||||
devices.pop(address)
|
||||
await async_remove_device(address)
|
||||
await async_remove_ha_device(address)
|
||||
|
||||
async def async_remove_device(address):
|
||||
async def async_remove_ha_device(address: Address, remove_all_refs: bool = False):
|
||||
"""Remove the device and all entities from hass."""
|
||||
signal = f"{address.id}_{SIGNAL_REMOVE_ENTITY}"
|
||||
async_dispatcher_send(hass, signal)
|
||||
@ -315,6 +317,15 @@ def async_register_services(hass):
|
||||
if device:
|
||||
dev_registry.async_remove_device(device.id)
|
||||
|
||||
async def async_remove_insteon_device(
|
||||
address: Address, remove_all_refs: bool = False
|
||||
):
|
||||
"""Remove the underlying Insteon device from the network."""
|
||||
await devices.async_remove_device(
|
||||
address=address, force=False, remove_all_refs=remove_all_refs
|
||||
)
|
||||
await async_srv_save_devices()
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SRV_ADD_ALL_LINK, async_srv_add_all_link, schema=ADD_ALL_LINK_SCHEMA
|
||||
)
|
||||
@ -368,6 +379,10 @@ def async_register_services(hass):
|
||||
)
|
||||
async_dispatcher_connect(hass, SIGNAL_ADD_X10_DEVICE, async_add_x10_device)
|
||||
async_dispatcher_connect(hass, SIGNAL_REMOVE_X10_DEVICE, async_remove_x10_device)
|
||||
async_dispatcher_connect(hass, SIGNAL_REMOVE_HA_DEVICE, async_remove_ha_device)
|
||||
async_dispatcher_connect(
|
||||
hass, SIGNAL_REMOVE_INSTEON_DEVICE, async_remove_insteon_device
|
||||
)
|
||||
_LOGGER.debug("Insteon Services registered")
|
||||
|
||||
|
||||
|
@ -1142,7 +1142,7 @@ influxdb==5.3.1
|
||||
inkbird-ble==0.5.6
|
||||
|
||||
# homeassistant.components.insteon
|
||||
insteon-frontend-home-assistant==0.4.0
|
||||
insteon-frontend-home-assistant==0.5.0
|
||||
|
||||
# homeassistant.components.intellifire
|
||||
intellifire4py==2.2.2
|
||||
|
@ -926,7 +926,7 @@ influxdb==5.3.1
|
||||
inkbird-ble==0.5.6
|
||||
|
||||
# homeassistant.components.insteon
|
||||
insteon-frontend-home-assistant==0.4.0
|
||||
insteon-frontend-home-assistant==0.5.0
|
||||
|
||||
# homeassistant.components.intellifire
|
||||
intellifire4py==2.2.2
|
||||
|
44
tests/components/insteon/mock_setup.py
Normal file
44
tests/components/insteon/mock_setup.py
Normal file
@ -0,0 +1,44 @@
|
||||
"""Utility to setup the Insteon integration."""
|
||||
|
||||
from homeassistant.components.insteon.api import async_load_api
|
||||
from homeassistant.components.insteon.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .const import MOCK_USER_INPUT_PLM
|
||||
from .mock_devices import MockDevices
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
||||
|
||||
async def async_mock_setup(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
config_data: dict | None = None,
|
||||
config_options: dict | None = None,
|
||||
):
|
||||
"""Set up for tests."""
|
||||
config_data = MOCK_USER_INPUT_PLM if config_data is None else config_data
|
||||
config_options = {} if config_options is None else config_options
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
entry_id="abcde12345",
|
||||
data=config_data,
|
||||
options=config_options,
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
async_load_api(hass)
|
||||
|
||||
ws_client = await hass_ws_client(hass)
|
||||
devices = MockDevices()
|
||||
await devices.async_load()
|
||||
|
||||
dev_reg = dr.async_get(hass)
|
||||
# Create device registry entry for mock node
|
||||
ha_device = dev_reg.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
identifiers={(DOMAIN, "11.11.11")},
|
||||
name="Device 11.11.11",
|
||||
)
|
||||
return ws_client, devices, ha_device, dev_reg
|
391
tests/components/insteon/test_api_config.py
Normal file
391
tests/components/insteon/test_api_config.py
Normal file
@ -0,0 +1,391 @@
|
||||
"""Test the Insteon APIs for configuring the integration."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.components.insteon.api.device import ID, TYPE
|
||||
from homeassistant.components.insteon.const import (
|
||||
CONF_HUB_VERSION,
|
||||
CONF_OVERRIDE,
|
||||
CONF_X10,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import (
|
||||
MOCK_DEVICE,
|
||||
MOCK_HOSTNAME,
|
||||
MOCK_USER_INPUT_HUB_V1,
|
||||
MOCK_USER_INPUT_HUB_V2,
|
||||
MOCK_USER_INPUT_PLM,
|
||||
)
|
||||
from .mock_connection import mock_failed_connection, mock_successful_connection
|
||||
from .mock_setup import async_mock_setup
|
||||
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
||||
|
||||
class MockProtocol:
|
||||
"""A mock Insteon protocol object."""
|
||||
|
||||
connected = True
|
||||
|
||||
|
||||
async def test_get_config(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test getting the Insteon configuration."""
|
||||
|
||||
ws_client, _, _, _ = await async_mock_setup(hass, hass_ws_client)
|
||||
await ws_client.send_json({ID: 2, TYPE: "insteon/config/get"})
|
||||
msg = await ws_client.receive_json()
|
||||
result = msg["result"]
|
||||
|
||||
assert result["modem_config"] == {"device": MOCK_DEVICE}
|
||||
|
||||
|
||||
async def test_get_modem_schema_plm(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test getting the Insteon PLM modem configuration schema."""
|
||||
|
||||
ws_client, _, _, _ = await async_mock_setup(hass, hass_ws_client)
|
||||
await ws_client.send_json({ID: 2, TYPE: "insteon/config/get_modem_schema"})
|
||||
msg = await ws_client.receive_json()
|
||||
result = msg["result"][0]
|
||||
|
||||
assert result["default"] == MOCK_DEVICE
|
||||
assert result["name"] == "device"
|
||||
assert result["required"]
|
||||
|
||||
|
||||
async def test_get_modem_schema_hub(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test getting the Insteon PLM modem configuration schema."""
|
||||
|
||||
ws_client, devices, _, _ = await async_mock_setup(
|
||||
hass,
|
||||
hass_ws_client,
|
||||
config_data={**MOCK_USER_INPUT_HUB_V2, CONF_HUB_VERSION: 2},
|
||||
)
|
||||
await ws_client.send_json({ID: 2, TYPE: "insteon/config/get_modem_schema"})
|
||||
msg = await ws_client.receive_json()
|
||||
result = msg["result"][0]
|
||||
|
||||
assert result["default"] == MOCK_HOSTNAME
|
||||
assert result["name"] == "host"
|
||||
assert result["required"]
|
||||
|
||||
|
||||
async def test_update_modem_config_plm(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test getting the Insteon PLM modem configuration schema."""
|
||||
|
||||
ws_client, mock_devices, _, _ = await async_mock_setup(
|
||||
hass,
|
||||
hass_ws_client,
|
||||
)
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.insteon.api.config.async_connect",
|
||||
new=mock_successful_connection,
|
||||
),
|
||||
patch("homeassistant.components.insteon.api.config.devices", mock_devices),
|
||||
patch("homeassistant.components.insteon.api.config.async_close"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 2,
|
||||
TYPE: "insteon/config/update_modem_config",
|
||||
"config": MOCK_USER_INPUT_PLM,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
result = msg["result"]
|
||||
|
||||
assert result["status"] == "success"
|
||||
|
||||
|
||||
async def test_update_modem_config_hub_v2(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test getting the Insteon HubV2 modem configuration schema."""
|
||||
|
||||
ws_client, mock_devices, _, _ = await async_mock_setup(
|
||||
hass,
|
||||
hass_ws_client,
|
||||
config_data={**MOCK_USER_INPUT_HUB_V2, CONF_HUB_VERSION: 2},
|
||||
config_options={"dev_path": "/some/path"},
|
||||
)
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.insteon.api.config.async_connect",
|
||||
new=mock_successful_connection,
|
||||
),
|
||||
patch("homeassistant.components.insteon.api.config.devices", mock_devices),
|
||||
patch("homeassistant.components.insteon.api.config.async_close"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 2,
|
||||
TYPE: "insteon/config/update_modem_config",
|
||||
"config": MOCK_USER_INPUT_HUB_V2,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
result = msg["result"]
|
||||
|
||||
assert result["status"] == "success"
|
||||
|
||||
|
||||
async def test_update_modem_config_hub_v1(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test getting the Insteon HubV1 modem configuration schema."""
|
||||
|
||||
ws_client, mock_devices, _, _ = await async_mock_setup(
|
||||
hass,
|
||||
hass_ws_client,
|
||||
config_data={**MOCK_USER_INPUT_HUB_V1, CONF_HUB_VERSION: 1},
|
||||
)
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.insteon.api.config.async_connect",
|
||||
new=mock_successful_connection,
|
||||
),
|
||||
patch("homeassistant.components.insteon.api.config.devices", mock_devices),
|
||||
patch("homeassistant.components.insteon.api.config.async_close"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 2,
|
||||
TYPE: "insteon/config/update_modem_config",
|
||||
"config": MOCK_USER_INPUT_HUB_V1,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
result = msg["result"]
|
||||
|
||||
assert result["status"] == "success"
|
||||
|
||||
|
||||
async def test_update_modem_config_bad(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test updating the Insteon modem configuration with bad connection information."""
|
||||
|
||||
ws_client, mock_devices, _, _ = await async_mock_setup(
|
||||
hass,
|
||||
hass_ws_client,
|
||||
)
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.insteon.api.config.async_connect",
|
||||
new=mock_failed_connection,
|
||||
),
|
||||
patch("homeassistant.components.insteon.api.config.devices", mock_devices),
|
||||
patch("homeassistant.components.insteon.api.config.async_close"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 2,
|
||||
TYPE: "insteon/config/update_modem_config",
|
||||
"config": MOCK_USER_INPUT_PLM,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
result = msg["error"]
|
||||
assert result["code"] == "connection_failed"
|
||||
|
||||
|
||||
async def test_update_modem_config_bad_reconnect(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test updating the Insteon modem configuration with bad connection information so reconnect to old."""
|
||||
|
||||
ws_client, mock_devices, _, _ = await async_mock_setup(
|
||||
hass,
|
||||
hass_ws_client,
|
||||
)
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.insteon.api.config.async_connect",
|
||||
new=mock_failed_connection,
|
||||
),
|
||||
patch("homeassistant.components.insteon.api.config.devices", mock_devices),
|
||||
patch("homeassistant.components.insteon.api.config.async_close"),
|
||||
):
|
||||
mock_devices.modem.protocol = MockProtocol()
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 2,
|
||||
TYPE: "insteon/config/update_modem_config",
|
||||
"config": MOCK_USER_INPUT_PLM,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
result = msg["error"]
|
||||
assert result["code"] == "connection_failed"
|
||||
|
||||
|
||||
async def test_add_device_override(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test adding a device configuration override."""
|
||||
|
||||
ws_client, _, _, _ = await async_mock_setup(hass, hass_ws_client)
|
||||
override = {
|
||||
"address": "99.99.99",
|
||||
"cat": "0x01",
|
||||
"subcat": "0x03",
|
||||
}
|
||||
await ws_client.send_json(
|
||||
{ID: 2, TYPE: "insteon/config/device_override/add", "override": override}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["success"]
|
||||
|
||||
config_entry = hass.config_entries.async_get_entry("abcde12345")
|
||||
assert len(config_entry.options[CONF_OVERRIDE]) == 1
|
||||
assert config_entry.options[CONF_OVERRIDE][0]["address"] == "99.99.99"
|
||||
|
||||
|
||||
async def test_add_device_override_duplicate(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test adding a duplicate device configuration override."""
|
||||
|
||||
override = {
|
||||
"address": "99.99.99",
|
||||
"cat": "0x01",
|
||||
"subcat": "0x03",
|
||||
}
|
||||
|
||||
ws_client, _, _, _ = await async_mock_setup(
|
||||
hass, hass_ws_client, config_options={CONF_OVERRIDE: [override]}
|
||||
)
|
||||
await ws_client.send_json(
|
||||
{ID: 2, TYPE: "insteon/config/device_override/add", "override": override}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["error"]
|
||||
|
||||
|
||||
async def test_remove_device_override(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test removing a device configuration override."""
|
||||
|
||||
override = {
|
||||
"address": "99.99.99",
|
||||
"cat": "0x01",
|
||||
"subcat": "0x03",
|
||||
}
|
||||
overrides = [
|
||||
override,
|
||||
{
|
||||
"address": "88.88.88",
|
||||
"cat": "0x02",
|
||||
"subcat": "0x05",
|
||||
},
|
||||
]
|
||||
|
||||
ws_client, _, _, _ = await async_mock_setup(
|
||||
hass, hass_ws_client, config_options={CONF_OVERRIDE: overrides}
|
||||
)
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 2,
|
||||
TYPE: "insteon/config/device_override/remove",
|
||||
"device_address": "99.99.99",
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["success"]
|
||||
|
||||
config_entry = hass.config_entries.async_get_entry("abcde12345")
|
||||
assert len(config_entry.options[CONF_OVERRIDE]) == 1
|
||||
assert config_entry.options[CONF_OVERRIDE][0]["address"] == "88.88.88"
|
||||
|
||||
|
||||
async def test_add_device_override_with_x10(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test adding a device configuration override when X10 configuration exists."""
|
||||
|
||||
x10_device = {"housecode": "a", "unitcode": 1, "platform": "switch"}
|
||||
ws_client, _, _, _ = await async_mock_setup(
|
||||
hass, hass_ws_client, config_options={CONF_X10: [x10_device]}
|
||||
)
|
||||
override = {
|
||||
"address": "99.99.99",
|
||||
"cat": "0x01",
|
||||
"subcat": "0x03",
|
||||
}
|
||||
await ws_client.send_json(
|
||||
{ID: 2, TYPE: "insteon/config/device_override/add", "override": override}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["success"]
|
||||
|
||||
config_entry = hass.config_entries.async_get_entry("abcde12345")
|
||||
assert len(config_entry.options[CONF_X10]) == 1
|
||||
|
||||
|
||||
async def test_remove_device_override_with_x10(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test removing a device configuration override when X10 configuration exists."""
|
||||
|
||||
override = {
|
||||
"address": "99.99.99",
|
||||
"cat": "0x01",
|
||||
"subcat": "0x03",
|
||||
}
|
||||
overrides = [
|
||||
override,
|
||||
{
|
||||
"address": "88.88.88",
|
||||
"cat": "0x02",
|
||||
"subcat": "0x05",
|
||||
},
|
||||
]
|
||||
x10_device = {"housecode": "a", "unitcode": 1, "platform": "switch"}
|
||||
|
||||
ws_client, _, _, _ = await async_mock_setup(
|
||||
hass,
|
||||
hass_ws_client,
|
||||
config_options={CONF_OVERRIDE: overrides, CONF_X10: [x10_device]},
|
||||
)
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 2,
|
||||
TYPE: "insteon/config/device_override/remove",
|
||||
"device_address": "99.99.99",
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["success"]
|
||||
|
||||
config_entry = hass.config_entries.async_get_entry("abcde12345")
|
||||
assert len(config_entry.options[CONF_X10]) == 1
|
||||
|
||||
|
||||
async def test_remove_device_override_no_overrides(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test removing a device override when no overrides are configured."""
|
||||
|
||||
ws_client, _, _, _ = await async_mock_setup(hass, hass_ws_client)
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 2,
|
||||
TYPE: "insteon/config/device_override/remove",
|
||||
"device_address": "99.99.99",
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["success"]
|
||||
|
||||
config_entry = hass.config_entries.async_get_entry("abcde12345")
|
||||
assert not config_entry.options.get(CONF_OVERRIDE)
|
@ -18,48 +18,29 @@ from homeassistant.components.insteon.api.device import (
|
||||
TYPE,
|
||||
async_device_name,
|
||||
)
|
||||
from homeassistant.components.insteon.const import DOMAIN, MULTIPLE
|
||||
from homeassistant.components.insteon.const import (
|
||||
CONF_OVERRIDE,
|
||||
CONF_X10,
|
||||
DOMAIN,
|
||||
MULTIPLE,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .const import MOCK_USER_INPUT_PLM
|
||||
from .mock_devices import MockDevices
|
||||
from .mock_setup import async_mock_setup
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
||||
|
||||
async def _async_setup(hass, hass_ws_client):
|
||||
"""Set up for tests."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
entry_id="abcde12345",
|
||||
data=MOCK_USER_INPUT_PLM,
|
||||
options={},
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
async_load_api(hass)
|
||||
|
||||
ws_client = await hass_ws_client(hass)
|
||||
devices = MockDevices()
|
||||
await devices.async_load()
|
||||
|
||||
dev_reg = dr.async_get(hass)
|
||||
# Create device registry entry for mock node
|
||||
ha_device = dev_reg.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
identifiers={(DOMAIN, "11.11.11")},
|
||||
name="Device 11.11.11",
|
||||
)
|
||||
return ws_client, devices, ha_device, dev_reg
|
||||
|
||||
|
||||
async def test_get_device_api(
|
||||
async def test_get_config(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test getting an Insteon device."""
|
||||
|
||||
ws_client, devices, ha_device, _ = await _async_setup(hass, hass_ws_client)
|
||||
ws_client, devices, ha_device, _ = await async_mock_setup(hass, hass_ws_client)
|
||||
with patch.object(insteon.api.device, "devices", devices):
|
||||
await ws_client.send_json(
|
||||
{ID: 2, TYPE: "insteon/device/get", DEVICE_ID: ha_device.id}
|
||||
@ -76,7 +57,7 @@ async def test_no_ha_device(
|
||||
) -> None:
|
||||
"""Test response when no HA device exists."""
|
||||
|
||||
ws_client, devices, _, _ = await _async_setup(hass, hass_ws_client)
|
||||
ws_client, devices, _, _ = await async_mock_setup(hass, hass_ws_client)
|
||||
with patch.object(insteon.api.device, "devices", devices):
|
||||
await ws_client.send_json(
|
||||
{ID: 2, TYPE: "insteon/device/get", DEVICE_ID: "not_a_device"}
|
||||
@ -141,7 +122,7 @@ async def test_get_ha_device_name(
|
||||
) -> None:
|
||||
"""Test getting the HA device name from an Insteon address."""
|
||||
|
||||
_, devices, _, device_reg = await _async_setup(hass, hass_ws_client)
|
||||
_, devices, _, device_reg = await async_mock_setup(hass, hass_ws_client)
|
||||
|
||||
with patch.object(insteon.api.device, "devices", devices):
|
||||
# Test a real HA and Insteon device
|
||||
@ -164,7 +145,7 @@ async def test_add_device_api(
|
||||
) -> None:
|
||||
"""Test adding an Insteon device."""
|
||||
|
||||
ws_client, devices, _, _ = await _async_setup(hass, hass_ws_client)
|
||||
ws_client, devices, _, _ = await async_mock_setup(hass, hass_ws_client)
|
||||
with patch.object(insteon.api.device, "devices", devices):
|
||||
await ws_client.send_json({ID: 2, TYPE: "insteon/device/add", MULTIPLE: True})
|
||||
|
||||
@ -194,7 +175,7 @@ async def test_cancel_add_device(
|
||||
) -> None:
|
||||
"""Test cancelling adding of a new device."""
|
||||
|
||||
ws_client, devices, _, _ = await _async_setup(hass, hass_ws_client)
|
||||
ws_client, devices, _, _ = await async_mock_setup(hass, hass_ws_client)
|
||||
|
||||
with patch.object(insteon.api.aldb, "devices", devices):
|
||||
await ws_client.send_json(
|
||||
@ -205,3 +186,127 @@ async def test_cancel_add_device(
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["success"]
|
||||
|
||||
|
||||
async def test_add_x10_device(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test adding an X10 device."""
|
||||
|
||||
ws_client, _, _, _ = await async_mock_setup(hass, hass_ws_client)
|
||||
x10_device = {"housecode": "a", "unitcode": 1, "platform": "switch"}
|
||||
await ws_client.send_json(
|
||||
{ID: 2, TYPE: "insteon/device/add_x10", "x10_device": x10_device}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["success"]
|
||||
|
||||
config_entry = hass.config_entries.async_get_entry("abcde12345")
|
||||
assert len(config_entry.options[CONF_X10]) == 1
|
||||
assert config_entry.options[CONF_X10][0]["housecode"] == "a"
|
||||
assert config_entry.options[CONF_X10][0]["unitcode"] == 1
|
||||
assert config_entry.options[CONF_X10][0]["platform"] == "switch"
|
||||
|
||||
|
||||
async def test_add_x10_device_duplicate(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test adding a duplicate X10 device."""
|
||||
|
||||
x10_device = {"housecode": "a", "unitcode": 1, "platform": "switch"}
|
||||
|
||||
ws_client, _, _, _ = await async_mock_setup(
|
||||
hass, hass_ws_client, config_options={CONF_X10: [x10_device]}
|
||||
)
|
||||
await ws_client.send_json(
|
||||
{ID: 2, TYPE: "insteon/device/add_x10", "x10_device": x10_device}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["error"]
|
||||
assert msg["error"]["code"] == "duplicate"
|
||||
|
||||
|
||||
async def test_remove_device(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test removing an Insteon device."""
|
||||
ws_client, _, _, _ = await async_mock_setup(hass, hass_ws_client)
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 2,
|
||||
TYPE: "insteon/device/remove",
|
||||
"device_address": "11.22.33",
|
||||
"remove_all_refs": True,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["success"]
|
||||
|
||||
|
||||
async def test_remove_x10_device(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test removing an X10 device."""
|
||||
ws_client, _, _, _ = await async_mock_setup(hass, hass_ws_client)
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 2,
|
||||
TYPE: "insteon/device/remove",
|
||||
"device_address": "X10.A.01",
|
||||
"remove_all_refs": True,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["success"]
|
||||
|
||||
|
||||
async def test_remove_one_x10_device(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test one X10 device without removing others."""
|
||||
x10_device = {"housecode": "a", "unitcode": 1, "platform": "light", "dim_steps": 22}
|
||||
x10_devices = [
|
||||
x10_device,
|
||||
{"housecode": "a", "unitcode": 2, "platform": "switch"},
|
||||
]
|
||||
ws_client, _, _, _ = await async_mock_setup(
|
||||
hass, hass_ws_client, config_options={CONF_X10: x10_devices}
|
||||
)
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 2,
|
||||
TYPE: "insteon/device/remove",
|
||||
"device_address": "X10.A.01",
|
||||
"remove_all_refs": True,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["success"]
|
||||
config_entry = hass.config_entries.async_get_entry("abcde12345")
|
||||
assert len(config_entry.options[CONF_X10]) == 1
|
||||
assert config_entry.options[CONF_X10][0]["housecode"] == "a"
|
||||
assert config_entry.options[CONF_X10][0]["unitcode"] == 2
|
||||
|
||||
|
||||
async def test_remove_device_with_overload(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test removing an Insteon device that has a device overload."""
|
||||
overload = {"address": "99.99.99", "cat": 1, "subcat": 3}
|
||||
overloads = {CONF_OVERRIDE: [overload]}
|
||||
ws_client, _, _, _ = await async_mock_setup(
|
||||
hass, hass_ws_client, config_options=overloads
|
||||
)
|
||||
await ws_client.send_json(
|
||||
{
|
||||
ID: 2,
|
||||
TYPE: "insteon/device/remove",
|
||||
"device_address": "99.99.99",
|
||||
"remove_all_refs": True,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["success"]
|
||||
|
||||
config_entry = hass.config_entries.async_get_entry("abcde12345")
|
||||
assert not config_entry.options.get(CONF_OVERRIDE)
|
||||
|
@ -8,38 +8,14 @@ from voluptuous_serialize import convert
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import dhcp, usb
|
||||
from homeassistant.components.insteon.config_flow import (
|
||||
STEP_ADD_OVERRIDE,
|
||||
STEP_ADD_X10,
|
||||
STEP_CHANGE_HUB_CONFIG,
|
||||
STEP_CHANGE_PLM_CONFIG,
|
||||
STEP_HUB_V1,
|
||||
STEP_HUB_V2,
|
||||
STEP_PLM,
|
||||
STEP_PLM_MANUALLY,
|
||||
STEP_REMOVE_OVERRIDE,
|
||||
STEP_REMOVE_X10,
|
||||
)
|
||||
from homeassistant.components.insteon.const import (
|
||||
CONF_CAT,
|
||||
CONF_DIM_STEPS,
|
||||
CONF_HOUSECODE,
|
||||
CONF_HUB_VERSION,
|
||||
CONF_OVERRIDE,
|
||||
CONF_SUBCAT,
|
||||
CONF_UNITCODE,
|
||||
CONF_X10,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.components.insteon.const import CONF_HUB_VERSION, DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import (
|
||||
CONF_ADDRESS,
|
||||
CONF_DEVICE,
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
CONF_PLATFORM,
|
||||
CONF_PORT,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.const import CONF_DEVICE, CONF_HOST
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
@ -52,11 +28,8 @@ from .const import (
|
||||
PATCH_ASYNC_SETUP,
|
||||
PATCH_ASYNC_SETUP_ENTRY,
|
||||
PATCH_CONNECTION,
|
||||
PATCH_CONNECTION_CLOSE,
|
||||
PATCH_DEVICES,
|
||||
PATCH_USB_LIST,
|
||||
)
|
||||
from .mock_devices import MockDevices
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@ -294,379 +267,6 @@ async def test_failed_connection_hub(hass: HomeAssistant) -> None:
|
||||
assert result2["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
|
||||
async def _options_init_form(hass, entry_id, step):
|
||||
"""Run the init options form."""
|
||||
with patch(PATCH_ASYNC_SETUP_ENTRY, return_value=True):
|
||||
result = await hass.config_entries.options.async_init(entry_id)
|
||||
|
||||
assert result["type"] is FlowResultType.MENU
|
||||
assert result["step_id"] == "init"
|
||||
|
||||
return await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
{"next_step_id": step},
|
||||
)
|
||||
|
||||
|
||||
async def _options_form(
|
||||
hass, flow_id, user_input, connection=mock_successful_connection
|
||||
):
|
||||
"""Test an options form."""
|
||||
mock_devices = MockDevices(connected=True)
|
||||
await mock_devices.async_load()
|
||||
mock_devices.modem = mock_devices["AA.AA.AA"]
|
||||
with (
|
||||
patch(PATCH_CONNECTION, new=connection),
|
||||
patch(PATCH_ASYNC_SETUP_ENTRY, return_value=True) as mock_setup_entry,
|
||||
patch(PATCH_DEVICES, mock_devices),
|
||||
patch(PATCH_CONNECTION_CLOSE),
|
||||
):
|
||||
result = await hass.config_entries.options.async_configure(flow_id, user_input)
|
||||
return result, mock_setup_entry
|
||||
|
||||
|
||||
async def test_options_change_hub_config(hass: HomeAssistant) -> None:
|
||||
"""Test changing Hub v2 config."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
entry_id="abcde12345",
|
||||
data={**MOCK_USER_INPUT_HUB_V2, CONF_HUB_VERSION: 2},
|
||||
options={},
|
||||
)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
result = await _options_init_form(
|
||||
hass, config_entry.entry_id, STEP_CHANGE_HUB_CONFIG
|
||||
)
|
||||
|
||||
user_input = {
|
||||
CONF_HOST: "2.3.4.5",
|
||||
CONF_PORT: 9999,
|
||||
CONF_USERNAME: "new username",
|
||||
CONF_PASSWORD: "new password",
|
||||
}
|
||||
result, _ = await _options_form(hass, result["flow_id"], user_input)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert config_entry.options == {}
|
||||
assert config_entry.data == {**user_input, CONF_HUB_VERSION: 2}
|
||||
|
||||
|
||||
async def test_options_change_hub_bad_config(hass: HomeAssistant) -> None:
|
||||
"""Test changing Hub v2 with bad config."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
entry_id="abcde12345",
|
||||
data={**MOCK_USER_INPUT_HUB_V2, CONF_HUB_VERSION: 2},
|
||||
options={},
|
||||
)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
result = await _options_init_form(
|
||||
hass, config_entry.entry_id, STEP_CHANGE_HUB_CONFIG
|
||||
)
|
||||
|
||||
user_input = {
|
||||
CONF_HOST: "2.3.4.5",
|
||||
CONF_PORT: 9999,
|
||||
CONF_USERNAME: "new username",
|
||||
CONF_PASSWORD: "new password",
|
||||
}
|
||||
result, _ = await _options_form(
|
||||
hass, result["flow_id"], user_input, mock_failed_connection
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"]["base"] == "cannot_connect"
|
||||
|
||||
|
||||
async def test_options_change_plm_config(hass: HomeAssistant) -> None:
|
||||
"""Test changing PLM config."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
entry_id="abcde12345",
|
||||
data=MOCK_USER_INPUT_PLM,
|
||||
options={},
|
||||
)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
result = await _options_init_form(
|
||||
hass, config_entry.entry_id, STEP_CHANGE_PLM_CONFIG
|
||||
)
|
||||
|
||||
user_input = {CONF_DEVICE: "/dev/ttyUSB0"}
|
||||
result, _ = await _options_form(hass, result["flow_id"], user_input)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert config_entry.options == {}
|
||||
assert config_entry.data == user_input
|
||||
|
||||
|
||||
async def test_options_change_plm_bad_config(hass: HomeAssistant) -> None:
|
||||
"""Test changing PLM config."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
entry_id="abcde12345",
|
||||
data=MOCK_USER_INPUT_PLM,
|
||||
options={},
|
||||
)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
result = await _options_init_form(
|
||||
hass, config_entry.entry_id, STEP_CHANGE_PLM_CONFIG
|
||||
)
|
||||
|
||||
user_input = {CONF_DEVICE: "/dev/ttyUSB0"}
|
||||
result, _ = await _options_form(
|
||||
hass, result["flow_id"], user_input, mock_failed_connection
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"]["base"] == "cannot_connect"
|
||||
|
||||
|
||||
async def test_options_add_device_override(hass: HomeAssistant) -> None:
|
||||
"""Test adding a device override."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
entry_id="abcde12345",
|
||||
data={**MOCK_USER_INPUT_HUB_V2, CONF_HUB_VERSION: 2},
|
||||
options={},
|
||||
)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
result = await _options_init_form(hass, config_entry.entry_id, STEP_ADD_OVERRIDE)
|
||||
|
||||
user_input = {
|
||||
CONF_ADDRESS: "1a2b3c",
|
||||
CONF_CAT: "0x04",
|
||||
CONF_SUBCAT: "0xaa",
|
||||
}
|
||||
result, _ = await _options_form(hass, result["flow_id"], user_input)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert len(config_entry.options[CONF_OVERRIDE]) == 1
|
||||
assert config_entry.options[CONF_OVERRIDE][0][CONF_ADDRESS] == "1A.2B.3C"
|
||||
assert config_entry.options[CONF_OVERRIDE][0][CONF_CAT] == 4
|
||||
assert config_entry.options[CONF_OVERRIDE][0][CONF_SUBCAT] == 170
|
||||
|
||||
result2 = await _options_init_form(hass, config_entry.entry_id, STEP_ADD_OVERRIDE)
|
||||
|
||||
user_input = {
|
||||
CONF_ADDRESS: "4d5e6f",
|
||||
CONF_CAT: "05",
|
||||
CONF_SUBCAT: "bb",
|
||||
}
|
||||
result3, _ = await _options_form(hass, result2["flow_id"], user_input)
|
||||
|
||||
assert len(config_entry.options[CONF_OVERRIDE]) == 2
|
||||
assert config_entry.options[CONF_OVERRIDE][1][CONF_ADDRESS] == "4D.5E.6F"
|
||||
assert config_entry.options[CONF_OVERRIDE][1][CONF_CAT] == 5
|
||||
assert config_entry.options[CONF_OVERRIDE][1][CONF_SUBCAT] == 187
|
||||
|
||||
# If result1 eq result2 the changes will not save
|
||||
assert result["data"] != result3["data"]
|
||||
|
||||
|
||||
async def test_options_remove_device_override(hass: HomeAssistant) -> None:
|
||||
"""Test removing a device override."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
entry_id="abcde12345",
|
||||
data={**MOCK_USER_INPUT_HUB_V2, CONF_HUB_VERSION: 2},
|
||||
options={
|
||||
CONF_OVERRIDE: [
|
||||
{CONF_ADDRESS: "1A.2B.3C", CONF_CAT: 6, CONF_SUBCAT: 100},
|
||||
{CONF_ADDRESS: "4D.5E.6F", CONF_CAT: 7, CONF_SUBCAT: 200},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
result = await _options_init_form(hass, config_entry.entry_id, STEP_REMOVE_OVERRIDE)
|
||||
|
||||
user_input = {CONF_ADDRESS: "1A.2B.3C"}
|
||||
result, _ = await _options_form(hass, result["flow_id"], user_input)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert len(config_entry.options[CONF_OVERRIDE]) == 1
|
||||
|
||||
|
||||
async def test_options_remove_device_override_with_x10(hass: HomeAssistant) -> None:
|
||||
"""Test removing a device override when an X10 device is configured."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
entry_id="abcde12345",
|
||||
data={**MOCK_USER_INPUT_HUB_V2, CONF_HUB_VERSION: 2},
|
||||
options={
|
||||
CONF_OVERRIDE: [
|
||||
{CONF_ADDRESS: "1A.2B.3C", CONF_CAT: 6, CONF_SUBCAT: 100},
|
||||
{CONF_ADDRESS: "4D.5E.6F", CONF_CAT: 7, CONF_SUBCAT: 200},
|
||||
],
|
||||
CONF_X10: [
|
||||
{
|
||||
CONF_HOUSECODE: "d",
|
||||
CONF_UNITCODE: 5,
|
||||
CONF_PLATFORM: "light",
|
||||
CONF_DIM_STEPS: 22,
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
result = await _options_init_form(hass, config_entry.entry_id, STEP_REMOVE_OVERRIDE)
|
||||
|
||||
user_input = {CONF_ADDRESS: "1A.2B.3C"}
|
||||
result, _ = await _options_form(hass, result["flow_id"], user_input)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert len(config_entry.options[CONF_OVERRIDE]) == 1
|
||||
assert len(config_entry.options[CONF_X10]) == 1
|
||||
|
||||
|
||||
async def test_options_add_x10_device(hass: HomeAssistant) -> None:
|
||||
"""Test adding an X10 device."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
entry_id="abcde12345",
|
||||
data={**MOCK_USER_INPUT_HUB_V2, CONF_HUB_VERSION: 2},
|
||||
options={},
|
||||
)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
result = await _options_init_form(hass, config_entry.entry_id, STEP_ADD_X10)
|
||||
|
||||
user_input = {
|
||||
CONF_HOUSECODE: "c",
|
||||
CONF_UNITCODE: 12,
|
||||
CONF_PLATFORM: "light",
|
||||
CONF_DIM_STEPS: 18,
|
||||
}
|
||||
result2, _ = await _options_form(hass, result["flow_id"], user_input)
|
||||
|
||||
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert len(config_entry.options[CONF_X10]) == 1
|
||||
assert config_entry.options[CONF_X10][0][CONF_HOUSECODE] == "c"
|
||||
assert config_entry.options[CONF_X10][0][CONF_UNITCODE] == 12
|
||||
assert config_entry.options[CONF_X10][0][CONF_PLATFORM] == "light"
|
||||
assert config_entry.options[CONF_X10][0][CONF_DIM_STEPS] == 18
|
||||
|
||||
result = await _options_init_form(hass, config_entry.entry_id, STEP_ADD_X10)
|
||||
user_input = {
|
||||
CONF_HOUSECODE: "d",
|
||||
CONF_UNITCODE: 10,
|
||||
CONF_PLATFORM: "binary_sensor",
|
||||
CONF_DIM_STEPS: 15,
|
||||
}
|
||||
result3, _ = await _options_form(hass, result["flow_id"], user_input)
|
||||
|
||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert len(config_entry.options[CONF_X10]) == 2
|
||||
assert config_entry.options[CONF_X10][1][CONF_HOUSECODE] == "d"
|
||||
assert config_entry.options[CONF_X10][1][CONF_UNITCODE] == 10
|
||||
assert config_entry.options[CONF_X10][1][CONF_PLATFORM] == "binary_sensor"
|
||||
assert config_entry.options[CONF_X10][1][CONF_DIM_STEPS] == 15
|
||||
|
||||
# If result2 eq result3 the changes will not save
|
||||
assert result2["data"] != result3["data"]
|
||||
|
||||
|
||||
async def test_options_remove_x10_device(hass: HomeAssistant) -> None:
|
||||
"""Test removing an X10 device."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
entry_id="abcde12345",
|
||||
data={**MOCK_USER_INPUT_HUB_V2, CONF_HUB_VERSION: 2},
|
||||
options={
|
||||
CONF_X10: [
|
||||
{
|
||||
CONF_HOUSECODE: "C",
|
||||
CONF_UNITCODE: 4,
|
||||
CONF_PLATFORM: "light",
|
||||
CONF_DIM_STEPS: 18,
|
||||
},
|
||||
{
|
||||
CONF_HOUSECODE: "D",
|
||||
CONF_UNITCODE: 10,
|
||||
CONF_PLATFORM: "binary_sensor",
|
||||
CONF_DIM_STEPS: 15,
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
result = await _options_init_form(hass, config_entry.entry_id, STEP_REMOVE_X10)
|
||||
|
||||
user_input = {CONF_DEVICE: "Housecode: C, Unitcode: 4"}
|
||||
result, _ = await _options_form(hass, result["flow_id"], user_input)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert len(config_entry.options[CONF_X10]) == 1
|
||||
|
||||
|
||||
async def test_options_remove_x10_device_with_override(hass: HomeAssistant) -> None:
|
||||
"""Test removing an X10 device when a device override is configured."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
entry_id="abcde12345",
|
||||
data={**MOCK_USER_INPUT_HUB_V2, CONF_HUB_VERSION: 2},
|
||||
options={
|
||||
CONF_X10: [
|
||||
{
|
||||
CONF_HOUSECODE: "C",
|
||||
CONF_UNITCODE: 4,
|
||||
CONF_PLATFORM: "light",
|
||||
CONF_DIM_STEPS: 18,
|
||||
},
|
||||
{
|
||||
CONF_HOUSECODE: "D",
|
||||
CONF_UNITCODE: 10,
|
||||
CONF_PLATFORM: "binary_sensor",
|
||||
CONF_DIM_STEPS: 15,
|
||||
},
|
||||
],
|
||||
CONF_OVERRIDE: [{CONF_ADDRESS: "1A.2B.3C", CONF_CAT: 1, CONF_SUBCAT: 18}],
|
||||
},
|
||||
)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
result = await _options_init_form(hass, config_entry.entry_id, STEP_REMOVE_X10)
|
||||
|
||||
user_input = {CONF_DEVICE: "Housecode: C, Unitcode: 4"}
|
||||
result, _ = await _options_form(hass, result["flow_id"], user_input)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert len(config_entry.options[CONF_X10]) == 1
|
||||
assert len(config_entry.options[CONF_OVERRIDE]) == 1
|
||||
|
||||
|
||||
async def test_options_override_bad_data(hass: HomeAssistant) -> None:
|
||||
"""Test for bad data in a device override."""
|
||||
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
entry_id="abcde12345",
|
||||
data={**MOCK_USER_INPUT_HUB_V2, CONF_HUB_VERSION: 2},
|
||||
options={},
|
||||
)
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
result = await _options_init_form(hass, config_entry.entry_id, STEP_ADD_OVERRIDE)
|
||||
|
||||
user_input = {
|
||||
CONF_ADDRESS: "zzzzzz",
|
||||
CONF_CAT: "bad",
|
||||
CONF_SUBCAT: "data",
|
||||
}
|
||||
result, _ = await _options_form(hass, result["flow_id"], user_input)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {"base": "input_error"}
|
||||
|
||||
|
||||
async def test_discovery_via_usb(hass: HomeAssistant) -> None:
|
||||
"""Test usb flow."""
|
||||
discovery_info = usb.UsbServiceInfo(
|
||||
|
@ -1,6 +1,5 @@
|
||||
"""Test the init file for the Insteon component."""
|
||||
|
||||
import asyncio
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
@ -11,7 +10,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .const import MOCK_USER_INPUT_PLM, PATCH_CONNECTION
|
||||
from .const import MOCK_USER_INPUT_PLM
|
||||
from .mock_devices import MockDevices
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
@ -70,22 +69,24 @@ async def test_setup_entry_failed_connection(
|
||||
|
||||
async def test_import_frontend_dev_url(hass: HomeAssistant) -> None:
|
||||
"""Test importing a dev_url config entry."""
|
||||
config = {}
|
||||
config[DOMAIN] = {CONF_DEV_PATH: "/some/path"}
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN, data=MOCK_USER_INPUT_PLM, options={CONF_DEV_PATH: "/some/path"}
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
with (
|
||||
patch.object(insteon, "async_connect", new=mock_successful_connection),
|
||||
patch.object(insteon, "close_insteon_connection"),
|
||||
patch.object(insteon, "async_close") as mock_close,
|
||||
patch.object(insteon, "devices", new=MockDevices()),
|
||||
patch(
|
||||
PATCH_CONNECTION,
|
||||
new=mock_successful_connection,
|
||||
),
|
||||
):
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
insteon.DOMAIN,
|
||||
config,
|
||||
{},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
await asyncio.sleep(0.01)
|
||||
assert hass.data[DOMAIN][CONF_DEV_PATH] == "/some/path"
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
||||
await hass.async_block_till_done()
|
||||
assert insteon.devices.async_save.call_count == 1
|
||||
assert mock_close.called
|
||||
|
Loading…
x
Reference in New Issue
Block a user