mirror of
https://github.com/home-assistant/core.git
synced 2025-04-28 19:27:51 +00:00
Insteon Device Control Panel (#70834)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
parent
9af8cd030a
commit
a9ca774e7e
@ -9,11 +9,13 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
|||||||
from homeassistant.const import CONF_PLATFORM, EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import CONF_PLATFORM, EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from . import api
|
from . import api
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_CAT,
|
CONF_CAT,
|
||||||
|
CONF_DEV_PATH,
|
||||||
CONF_DIM_STEPS,
|
CONF_DIM_STEPS,
|
||||||
CONF_HOUSECODE,
|
CONF_HOUSECODE,
|
||||||
CONF_OVERRIDE,
|
CONF_OVERRIDE,
|
||||||
@ -74,13 +76,19 @@ async def close_insteon_connection(*args):
|
|||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up the Insteon platform."""
|
"""Set up the Insteon platform."""
|
||||||
|
hass.data[DOMAIN] = {}
|
||||||
if DOMAIN not in config:
|
if DOMAIN not in config:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
conf = config[DOMAIN]
|
conf = dict(config[DOMAIN])
|
||||||
|
hass.data[DOMAIN][CONF_DEV_PATH] = conf.pop(CONF_DEV_PATH, None)
|
||||||
|
|
||||||
|
if not conf:
|
||||||
|
return True
|
||||||
|
|
||||||
data, options = convert_yaml_to_config_flow(conf)
|
data, options = convert_yaml_to_config_flow(conf)
|
||||||
|
|
||||||
if options:
|
if options:
|
||||||
hass.data[DOMAIN] = {}
|
|
||||||
hass.data[DOMAIN][OPTIONS] = options
|
hass.data[DOMAIN][OPTIONS] = options
|
||||||
# Create a config entry with the connection data
|
# Create a config entry with the connection data
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
@ -154,23 +162,30 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
platforms = get_device_platforms(device)
|
platforms = get_device_platforms(device)
|
||||||
if ON_OFF_EVENTS in platforms:
|
if ON_OFF_EVENTS in platforms:
|
||||||
add_on_off_event_device(hass, device)
|
add_on_off_event_device(hass, device)
|
||||||
|
create_insteon_device(hass, device, entry.entry_id)
|
||||||
|
|
||||||
_LOGGER.debug("Insteon device count: %s", len(devices))
|
_LOGGER.debug("Insteon device count: %s", len(devices))
|
||||||
register_new_device_callback(hass)
|
register_new_device_callback(hass)
|
||||||
async_register_services(hass)
|
async_register_services(hass)
|
||||||
|
|
||||||
device_registry = await hass.helpers.device_registry.async_get_registry()
|
create_insteon_device(hass, devices.modem, entry.entry_id)
|
||||||
device_registry.async_get_or_create(
|
|
||||||
config_entry_id=entry.entry_id,
|
|
||||||
identifiers={(DOMAIN, str(devices.modem.address))},
|
|
||||||
manufacturer="Smart Home",
|
|
||||||
name=f"{devices.modem.description} {devices.modem.address}",
|
|
||||||
model=f"{devices.modem.model} ({devices.modem.cat!r}, 0x{devices.modem.subcat:02x})",
|
|
||||||
sw_version=f"{devices.modem.firmware:02x} Engine Version: {devices.modem.engine_version}",
|
|
||||||
)
|
|
||||||
|
|
||||||
api.async_load_api(hass)
|
api.async_load_api(hass)
|
||||||
|
await api.async_register_insteon_frontend(hass)
|
||||||
|
|
||||||
asyncio.create_task(async_get_device_config(hass, entry))
|
asyncio.create_task(async_get_device_config(hass, entry))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def create_insteon_device(hass, device, config_entry_id):
|
||||||
|
"""Create an Insteon device."""
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
|
device_registry.async_get_or_create(
|
||||||
|
config_entry_id=config_entry_id, # entry.entry_id,
|
||||||
|
identifiers={(DOMAIN, str(device.address))},
|
||||||
|
manufacturer="SmartLabs, Inc",
|
||||||
|
name=f"{device.description} {device.address}",
|
||||||
|
model=f"{device.model} ({device.cat!r}, 0x{device.subcat:02x})",
|
||||||
|
sw_version=f"{device.firmware:02x} Engine Version: {device.engine_version}",
|
||||||
|
)
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
"""Insteon API interface for the frontend."""
|
"""Insteon API interface for the frontend."""
|
||||||
|
|
||||||
from homeassistant.components import websocket_api
|
from insteon_frontend import get_build_id, locate_dir
|
||||||
from homeassistant.core import callback
|
|
||||||
|
from homeassistant.components import panel_custom, websocket_api
|
||||||
|
from homeassistant.components.insteon.const import CONF_DEV_PATH, DOMAIN
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
|
||||||
from .aldb import (
|
from .aldb import (
|
||||||
websocket_add_default_links,
|
websocket_add_default_links,
|
||||||
@ -13,7 +16,11 @@ from .aldb import (
|
|||||||
websocket_reset_aldb,
|
websocket_reset_aldb,
|
||||||
websocket_write_aldb,
|
websocket_write_aldb,
|
||||||
)
|
)
|
||||||
from .device import websocket_get_device
|
from .device import (
|
||||||
|
websocket_add_device,
|
||||||
|
websocket_cancel_add_device,
|
||||||
|
websocket_get_device,
|
||||||
|
)
|
||||||
from .properties import (
|
from .properties import (
|
||||||
websocket_change_properties_record,
|
websocket_change_properties_record,
|
||||||
websocket_get_properties,
|
websocket_get_properties,
|
||||||
@ -22,11 +29,15 @@ from .properties import (
|
|||||||
websocket_write_properties,
|
websocket_write_properties,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
URL_BASE = "/insteon_static"
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_load_api(hass):
|
def async_load_api(hass):
|
||||||
"""Set up the web socket API."""
|
"""Set up the web socket API."""
|
||||||
websocket_api.async_register_command(hass, websocket_get_device)
|
websocket_api.async_register_command(hass, websocket_get_device)
|
||||||
|
websocket_api.async_register_command(hass, websocket_add_device)
|
||||||
|
websocket_api.async_register_command(hass, websocket_cancel_add_device)
|
||||||
|
|
||||||
websocket_api.async_register_command(hass, websocket_get_aldb)
|
websocket_api.async_register_command(hass, websocket_get_aldb)
|
||||||
websocket_api.async_register_command(hass, websocket_change_aldb_record)
|
websocket_api.async_register_command(hass, websocket_change_aldb_record)
|
||||||
@ -42,3 +53,31 @@ def async_load_api(hass):
|
|||||||
websocket_api.async_register_command(hass, websocket_write_properties)
|
websocket_api.async_register_command(hass, websocket_write_properties)
|
||||||
websocket_api.async_register_command(hass, websocket_load_properties)
|
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_reset_properties)
|
||||||
|
|
||||||
|
|
||||||
|
def get_entrypoint(is_dev):
|
||||||
|
"""Get the entry point for the frontend."""
|
||||||
|
if is_dev:
|
||||||
|
return "entrypoint.js"
|
||||||
|
|
||||||
|
|
||||||
|
async def async_register_insteon_frontend(hass: HomeAssistant):
|
||||||
|
"""Register the Insteon frontend configuration panel."""
|
||||||
|
# Add to sidepanel if needed
|
||||||
|
if DOMAIN not in hass.data.get("frontend_panels", {}):
|
||||||
|
dev_path = hass.data.get(DOMAIN, {}).get(CONF_DEV_PATH)
|
||||||
|
is_dev = dev_path is not None
|
||||||
|
path = dev_path if dev_path else locate_dir()
|
||||||
|
build_id = get_build_id(is_dev)
|
||||||
|
hass.http.register_static_path(URL_BASE, path, cache_headers=not is_dev)
|
||||||
|
|
||||||
|
await panel_custom.async_register_panel(
|
||||||
|
hass=hass,
|
||||||
|
frontend_url_path=DOMAIN,
|
||||||
|
webcomponent_name="insteon-frontend",
|
||||||
|
sidebar_title=DOMAIN.capitalize(),
|
||||||
|
sidebar_icon="mdi:power",
|
||||||
|
module_url=f"{URL_BASE}/entrypoint-{build_id}.js",
|
||||||
|
embed_iframe=True,
|
||||||
|
require_admin=True,
|
||||||
|
)
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
"""API interface to get an Insteon device."""
|
"""API interface to get an Insteon device."""
|
||||||
|
|
||||||
from pyinsteon import devices
|
from pyinsteon import devices
|
||||||
|
from pyinsteon.constants import DeviceAction
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components import websocket_api
|
from homeassistant.components import websocket_api
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
|
||||||
from ..const import (
|
from ..const import (
|
||||||
|
DEVICE_ADDRESS,
|
||||||
DEVICE_ID,
|
DEVICE_ID,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
HA_DEVICE_NOT_FOUND,
|
HA_DEVICE_NOT_FOUND,
|
||||||
ID,
|
ID,
|
||||||
INSTEON_DEVICE_NOT_FOUND,
|
INSTEON_DEVICE_NOT_FOUND,
|
||||||
|
MULTIPLE,
|
||||||
TYPE,
|
TYPE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,6 +24,12 @@ def compute_device_name(ha_device):
|
|||||||
return ha_device.name_by_user if ha_device.name_by_user else ha_device.name
|
return ha_device.name_by_user if ha_device.name_by_user else ha_device.name
|
||||||
|
|
||||||
|
|
||||||
|
async def async_add_devices(address, multiple):
|
||||||
|
"""Add one or more Insteon devices."""
|
||||||
|
async for _ in devices.async_add_device(address=address, multiple=multiple):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_insteon_device_from_ha_device(ha_device):
|
def get_insteon_device_from_ha_device(ha_device):
|
||||||
"""Return the Insteon device from an HA device."""
|
"""Return the Insteon device from an HA device."""
|
||||||
for identifier in ha_device.identifiers:
|
for identifier in ha_device.identifiers:
|
||||||
@ -74,3 +83,58 @@ async def websocket_get_device(
|
|||||||
"aldb_status": str(device.aldb.status),
|
"aldb_status": str(device.aldb.status),
|
||||||
}
|
}
|
||||||
connection.send_result(msg[ID], device_info)
|
connection.send_result(msg[ID], device_info)
|
||||||
|
|
||||||
|
|
||||||
|
@websocket_api.websocket_command(
|
||||||
|
{
|
||||||
|
vol.Required(TYPE): "insteon/device/add",
|
||||||
|
vol.Required(MULTIPLE): bool,
|
||||||
|
vol.Optional(DEVICE_ADDRESS): str,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@websocket_api.require_admin
|
||||||
|
@websocket_api.async_response
|
||||||
|
async def websocket_add_device(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
connection: websocket_api.connection.ActiveConnection,
|
||||||
|
msg: dict,
|
||||||
|
) -> None:
|
||||||
|
"""Add one or more Insteon devices."""
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def linking_complete(address: str, action: DeviceAction):
|
||||||
|
"""Forward device events to websocket."""
|
||||||
|
if action == DeviceAction.COMPLETED:
|
||||||
|
forward_data = {"type": "linking_stopped", "address": ""}
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
connection.send_message(websocket_api.event_message(msg["id"], forward_data))
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_cleanup() -> None:
|
||||||
|
"""Remove signal listeners."""
|
||||||
|
devices.unsubscribe(linking_complete)
|
||||||
|
|
||||||
|
connection.subscriptions[msg["id"]] = async_cleanup
|
||||||
|
devices.subscribe(linking_complete)
|
||||||
|
|
||||||
|
async for address in devices.async_add_device(
|
||||||
|
address=msg.get(DEVICE_ADDRESS), multiple=msg[MULTIPLE]
|
||||||
|
):
|
||||||
|
forward_data = {"type": "device_added", "address": str(address)}
|
||||||
|
connection.send_message(websocket_api.event_message(msg["id"], forward_data))
|
||||||
|
|
||||||
|
connection.send_result(msg[ID])
|
||||||
|
|
||||||
|
|
||||||
|
@websocket_api.websocket_command({vol.Required(TYPE): "insteon/device/add/cancel"})
|
||||||
|
@websocket_api.require_admin
|
||||||
|
@websocket_api.async_response
|
||||||
|
async def websocket_cancel_add_device(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
connection: websocket_api.connection.ActiveConnection,
|
||||||
|
msg: dict,
|
||||||
|
) -> None:
|
||||||
|
"""Cancel the Insteon all-linking process."""
|
||||||
|
await devices.async_cancel_all_linking()
|
||||||
|
connection.send_result(msg[ID])
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
"""Property update methods and schemas."""
|
"""Property update methods and schemas."""
|
||||||
from itertools import chain
|
|
||||||
|
|
||||||
from pyinsteon import devices
|
from pyinsteon import devices
|
||||||
from pyinsteon.constants import RAMP_RATES, ResponseStatus
|
from pyinsteon.config import RADIO_BUTTON_GROUPS, RAMP_RATE_IN_SEC, get_usable_value
|
||||||
from pyinsteon.device_types.device_base import Device
|
from pyinsteon.constants import (
|
||||||
from pyinsteon.extended_property import (
|
RAMP_RATES_SEC,
|
||||||
NON_TOGGLE_MASK,
|
PropertyType,
|
||||||
NON_TOGGLE_ON_OFF_MASK,
|
RelayMode,
|
||||||
OFF_MASK,
|
ResponseStatus,
|
||||||
ON_MASK,
|
ToggleMode,
|
||||||
RAMP_RATE,
|
|
||||||
)
|
)
|
||||||
from pyinsteon.utils import ramp_rate_to_seconds, seconds_to_ramp_rate
|
from pyinsteon.device_types.device_base import Device
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
import voluptuous_serialize
|
import voluptuous_serialize
|
||||||
|
|
||||||
@ -29,19 +27,12 @@ from ..const import (
|
|||||||
)
|
)
|
||||||
from .device import notify_device_not_found
|
from .device import notify_device_not_found
|
||||||
|
|
||||||
TOGGLE_ON_OFF_MODE = "toggle_on_off_mode"
|
SHOW_ADVANCED = "show_advanced"
|
||||||
NON_TOGGLE_ON_MODE = "non_toggle_on_mode"
|
RAMP_RATE_SECONDS = list(dict.fromkeys(RAMP_RATES_SEC))
|
||||||
NON_TOGGLE_OFF_MODE = "non_toggle_off_mode"
|
|
||||||
RADIO_BUTTON_GROUP_PROP = "radio_button_group_"
|
|
||||||
TOGGLE_PROP = "toggle_"
|
|
||||||
RAMP_RATE_SECONDS = list(dict.fromkeys(RAMP_RATES.values()))
|
|
||||||
RAMP_RATE_SECONDS.sort()
|
RAMP_RATE_SECONDS.sort()
|
||||||
TOGGLE_MODES = {TOGGLE_ON_OFF_MODE: 0, NON_TOGGLE_ON_MODE: 1, NON_TOGGLE_OFF_MODE: 2}
|
RAMP_RATE_LIST = [str(seconds) for seconds in RAMP_RATE_SECONDS]
|
||||||
TOGGLE_MODES_SCHEMA = {
|
TOGGLE_MODES = [str(ToggleMode(v)).lower() for v in list(ToggleMode)]
|
||||||
0: TOGGLE_ON_OFF_MODE,
|
RELAY_MODES = [str(RelayMode(v)).lower() for v in list(RelayMode)]
|
||||||
1: NON_TOGGLE_ON_MODE,
|
|
||||||
2: NON_TOGGLE_OFF_MODE,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _bool_schema(name):
|
def _bool_schema(name):
|
||||||
@ -52,239 +43,116 @@ def _byte_schema(name):
|
|||||||
return voluptuous_serialize.convert(vol.Schema({vol.Required(name): cv.byte}))[0]
|
return voluptuous_serialize.convert(vol.Schema({vol.Required(name): cv.byte}))[0]
|
||||||
|
|
||||||
|
|
||||||
def _ramp_rate_schema(name):
|
def _float_schema(name):
|
||||||
|
return voluptuous_serialize.convert(vol.Schema({vol.Required(name): float}))[0]
|
||||||
|
|
||||||
|
|
||||||
|
def _list_schema(name, values):
|
||||||
return voluptuous_serialize.convert(
|
return voluptuous_serialize.convert(
|
||||||
vol.Schema({vol.Required(name): vol.In(RAMP_RATE_SECONDS)}),
|
vol.Schema({vol.Required(name): vol.In(values)}),
|
||||||
custom_serializer=cv.custom_serializer,
|
custom_serializer=cv.custom_serializer,
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
|
|
||||||
def get_properties(device: Device):
|
def _multi_select_schema(name, values):
|
||||||
|
return voluptuous_serialize.convert(
|
||||||
|
vol.Schema({vol.Optional(name): cv.multi_select(values)}),
|
||||||
|
custom_serializer=cv.custom_serializer,
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def _read_only_schema(name, value):
|
||||||
|
"""Return a constant value schema."""
|
||||||
|
return voluptuous_serialize.convert(vol.Schema({vol.Required(name): value}))[0]
|
||||||
|
|
||||||
|
|
||||||
|
def get_schema(prop, name, groups):
|
||||||
|
"""Return the correct shema type."""
|
||||||
|
if prop.is_read_only:
|
||||||
|
return _read_only_schema(name, prop.value)
|
||||||
|
if name == RAMP_RATE_IN_SEC:
|
||||||
|
return _list_schema(name, RAMP_RATE_LIST)
|
||||||
|
if name == RADIO_BUTTON_GROUPS:
|
||||||
|
button_list = {str(group): groups[group].name for group in groups if group != 1}
|
||||||
|
return _multi_select_schema(name, button_list)
|
||||||
|
if prop.value_type == bool:
|
||||||
|
return _bool_schema(name)
|
||||||
|
if prop.value_type == int:
|
||||||
|
return _byte_schema(name)
|
||||||
|
if prop.value_type == float:
|
||||||
|
return _float_schema(name)
|
||||||
|
if prop.value_type == ToggleMode:
|
||||||
|
return _list_schema(name, TOGGLE_MODES)
|
||||||
|
if prop.value_type == RelayMode:
|
||||||
|
return _list_schema(name, RELAY_MODES)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_properties(device: Device, show_advanced=False):
|
||||||
"""Get the properties of an Insteon device and return the records and schema."""
|
"""Get the properties of an Insteon device and return the records and schema."""
|
||||||
|
|
||||||
properties = []
|
properties = []
|
||||||
schema = {}
|
schema = {}
|
||||||
|
|
||||||
# Limit the properties we manage at this time.
|
for name, prop in device.configuration.items():
|
||||||
for prop_name in device.operating_flags:
|
if prop.is_read_only and not show_advanced:
|
||||||
if not device.operating_flags[prop_name].is_read_only:
|
|
||||||
prop_dict, schema_dict = _get_property(device.operating_flags[prop_name])
|
|
||||||
properties.append(prop_dict)
|
|
||||||
schema[prop_name] = schema_dict
|
|
||||||
|
|
||||||
mask_found = False
|
|
||||||
for prop_name in device.properties:
|
|
||||||
if device.properties[prop_name].is_read_only:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if prop_name == RAMP_RATE:
|
prop_schema = get_schema(prop, name, device.groups)
|
||||||
rr_prop, rr_schema = _get_ramp_rate_property(device.properties[prop_name])
|
if name == "momentary_delay":
|
||||||
properties.append(rr_prop)
|
print(prop_schema)
|
||||||
schema[RAMP_RATE] = rr_schema
|
if prop_schema is None:
|
||||||
|
continue
|
||||||
|
schema[name] = prop_schema
|
||||||
|
properties.append(property_to_dict(prop))
|
||||||
|
|
||||||
elif not mask_found and "mask" in prop_name:
|
if show_advanced:
|
||||||
mask_found = True
|
for name, prop in device.operating_flags.items():
|
||||||
toggle_props, toggle_schema = _get_toggle_properties(device)
|
if prop.property_type != PropertyType.ADVANCED:
|
||||||
properties.extend(toggle_props)
|
continue
|
||||||
schema.update(toggle_schema)
|
prop_schema = get_schema(prop, name, device.groups)
|
||||||
|
if prop_schema is not None:
|
||||||
rb_props, rb_schema = _get_radio_button_properties(device)
|
schema[name] = prop_schema
|
||||||
properties.extend(rb_props)
|
properties.append(property_to_dict(prop))
|
||||||
schema.update(rb_schema)
|
for name, prop in device.properties.items():
|
||||||
else:
|
if prop.property_type != PropertyType.ADVANCED:
|
||||||
prop_dict, schema_dict = _get_property(device.properties[prop_name])
|
continue
|
||||||
properties.append(prop_dict)
|
prop_schema = get_schema(prop, name, device.groups)
|
||||||
schema[prop_name] = schema_dict
|
if prop_schema is not None:
|
||||||
|
schema[name] = prop_schema
|
||||||
|
properties.append(property_to_dict(prop))
|
||||||
|
|
||||||
return properties, schema
|
return properties, schema
|
||||||
|
|
||||||
|
|
||||||
def set_property(device, prop_name: str, value):
|
def property_to_dict(prop):
|
||||||
"""Update a property value."""
|
|
||||||
if isinstance(value, bool) and prop_name in device.operating_flags:
|
|
||||||
device.operating_flags[prop_name].new_value = value
|
|
||||||
|
|
||||||
elif prop_name == RAMP_RATE:
|
|
||||||
device.properties[prop_name].new_value = seconds_to_ramp_rate(value)
|
|
||||||
|
|
||||||
elif prop_name.startswith(RADIO_BUTTON_GROUP_PROP):
|
|
||||||
buttons = [int(button) for button in value]
|
|
||||||
rb_groups = _calc_radio_button_groups(device)
|
|
||||||
curr_group = int(prop_name[len(RADIO_BUTTON_GROUP_PROP) :])
|
|
||||||
if len(rb_groups) > curr_group:
|
|
||||||
removed = [btn for btn in rb_groups[curr_group] if btn not in buttons]
|
|
||||||
if removed:
|
|
||||||
device.clear_radio_buttons(removed)
|
|
||||||
if buttons:
|
|
||||||
device.set_radio_buttons(buttons)
|
|
||||||
|
|
||||||
elif prop_name.startswith(TOGGLE_PROP):
|
|
||||||
button_name = prop_name[len(TOGGLE_PROP) :]
|
|
||||||
for button in device.groups:
|
|
||||||
if device.groups[button].name == button_name:
|
|
||||||
device.set_toggle_mode(button, int(value))
|
|
||||||
|
|
||||||
else:
|
|
||||||
device.properties[prop_name].new_value = value
|
|
||||||
|
|
||||||
|
|
||||||
def _get_property(prop):
|
|
||||||
"""Return a property data row."""
|
"""Return a property data row."""
|
||||||
value, modified = _get_usable_value(prop)
|
value = get_usable_value(prop)
|
||||||
|
modified = value == prop.new_value
|
||||||
|
if prop.value_type in [ToggleMode, RelayMode] or prop.name == RAMP_RATE_IN_SEC:
|
||||||
|
value = str(value).lower()
|
||||||
prop_dict = {"name": prop.name, "value": value, "modified": modified}
|
prop_dict = {"name": prop.name, "value": value, "modified": modified}
|
||||||
if isinstance(prop.value, bool):
|
return prop_dict
|
||||||
schema = _bool_schema(prop.name)
|
|
||||||
|
|
||||||
|
def update_property(device, prop_name, value):
|
||||||
|
"""Update the value of a device property."""
|
||||||
|
prop = device.configuration[prop_name]
|
||||||
|
if prop.value_type == ToggleMode:
|
||||||
|
toggle_mode = getattr(ToggleMode, value.upper())
|
||||||
|
prop.new_value = toggle_mode
|
||||||
|
elif prop.value_type == RelayMode:
|
||||||
|
relay_mode = getattr(RelayMode, value.upper())
|
||||||
|
prop.new_value = relay_mode
|
||||||
else:
|
else:
|
||||||
schema = _byte_schema(prop.name)
|
prop.new_value = value
|
||||||
return prop_dict, {"name": prop.name, **schema}
|
|
||||||
|
|
||||||
|
|
||||||
def _get_toggle_properties(device):
|
|
||||||
"""Generate the mask properties for a KPL device."""
|
|
||||||
props = []
|
|
||||||
schema = {}
|
|
||||||
toggle_prop = device.properties[NON_TOGGLE_MASK]
|
|
||||||
toggle_on_prop = device.properties[NON_TOGGLE_ON_OFF_MASK]
|
|
||||||
for button in device.groups:
|
|
||||||
name = f"{TOGGLE_PROP}{device.groups[button].name}"
|
|
||||||
value, modified = _toggle_button_value(toggle_prop, toggle_on_prop, button)
|
|
||||||
props.append({"name": name, "value": value, "modified": modified})
|
|
||||||
toggle_schema = vol.Schema({vol.Required(name): vol.In(TOGGLE_MODES_SCHEMA)})
|
|
||||||
toggle_schema_dict = voluptuous_serialize.convert(
|
|
||||||
toggle_schema, custom_serializer=cv.custom_serializer
|
|
||||||
)
|
|
||||||
schema[name] = toggle_schema_dict[0]
|
|
||||||
return props, schema
|
|
||||||
|
|
||||||
|
|
||||||
def _toggle_button_value(non_toggle_prop, toggle_on_prop, button):
|
|
||||||
"""Determine the toggle value of a button."""
|
|
||||||
toggle_mask, toggle_modified = _get_usable_value(non_toggle_prop)
|
|
||||||
toggle_on_mask, toggle_on_modified = _get_usable_value(toggle_on_prop)
|
|
||||||
|
|
||||||
bit = button - 1
|
|
||||||
if not toggle_mask & 1 << bit:
|
|
||||||
value = 0
|
|
||||||
else:
|
|
||||||
if toggle_on_mask & 1 << bit:
|
|
||||||
value = 1
|
|
||||||
else:
|
|
||||||
value = 2
|
|
||||||
|
|
||||||
modified = False
|
|
||||||
if toggle_modified:
|
|
||||||
curr_bit = non_toggle_prop.value & 1 << bit
|
|
||||||
new_bit = non_toggle_prop.new_value & 1 << bit
|
|
||||||
modified = not curr_bit == new_bit
|
|
||||||
|
|
||||||
if not modified and value != 0 and toggle_on_modified:
|
|
||||||
curr_bit = toggle_on_prop.value & 1 << bit
|
|
||||||
new_bit = toggle_on_prop.new_value & 1 << bit
|
|
||||||
modified = not curr_bit == new_bit
|
|
||||||
|
|
||||||
return value, modified
|
|
||||||
|
|
||||||
|
|
||||||
def _get_radio_button_properties(device):
|
|
||||||
"""Return the values and schema to set KPL buttons as radio buttons."""
|
|
||||||
rb_groups = _calc_radio_button_groups(device)
|
|
||||||
props = []
|
|
||||||
schema = {}
|
|
||||||
index = 0
|
|
||||||
remaining_buttons = []
|
|
||||||
|
|
||||||
buttons_in_groups = list(chain.from_iterable(rb_groups))
|
|
||||||
|
|
||||||
# Identify buttons not belonging to any group
|
|
||||||
for button in device.groups:
|
|
||||||
if button not in buttons_in_groups:
|
|
||||||
remaining_buttons.append(button)
|
|
||||||
|
|
||||||
for rb_group in rb_groups:
|
|
||||||
name = f"{RADIO_BUTTON_GROUP_PROP}{index}"
|
|
||||||
button_1 = rb_group[0]
|
|
||||||
button_str = f"_{button_1}" if button_1 != 1 else ""
|
|
||||||
on_mask = device.properties[f"{ON_MASK}{button_str}"]
|
|
||||||
off_mask = device.properties[f"{OFF_MASK}{button_str}"]
|
|
||||||
modified = on_mask.is_dirty or off_mask.is_dirty
|
|
||||||
|
|
||||||
props.append(
|
|
||||||
{
|
|
||||||
"name": name,
|
|
||||||
"modified": modified,
|
|
||||||
"value": rb_group,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
options = {
|
|
||||||
button: device.groups[button].name
|
|
||||||
for button in chain.from_iterable([rb_group, remaining_buttons])
|
|
||||||
}
|
|
||||||
rb_schema = vol.Schema({vol.Optional(name): cv.multi_select(options)})
|
|
||||||
|
|
||||||
rb_schema_dict = voluptuous_serialize.convert(
|
|
||||||
rb_schema, custom_serializer=cv.custom_serializer
|
|
||||||
)
|
|
||||||
schema[name] = rb_schema_dict[0]
|
|
||||||
|
|
||||||
index += 1
|
|
||||||
|
|
||||||
if len(remaining_buttons) > 1:
|
|
||||||
name = f"{RADIO_BUTTON_GROUP_PROP}{index}"
|
|
||||||
|
|
||||||
props.append(
|
|
||||||
{
|
|
||||||
"name": name,
|
|
||||||
"modified": False,
|
|
||||||
"value": [],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
options = {button: device.groups[button].name for button in remaining_buttons}
|
|
||||||
rb_schema = vol.Schema({vol.Optional(name): cv.multi_select(options)})
|
|
||||||
|
|
||||||
rb_schema_dict = voluptuous_serialize.convert(
|
|
||||||
rb_schema, custom_serializer=cv.custom_serializer
|
|
||||||
)
|
|
||||||
schema[name] = rb_schema_dict[0]
|
|
||||||
|
|
||||||
return props, schema
|
|
||||||
|
|
||||||
|
|
||||||
def _calc_radio_button_groups(device):
|
|
||||||
"""Return existing radio button groups."""
|
|
||||||
rb_groups = []
|
|
||||||
for button in device.groups:
|
|
||||||
if button not in list(chain.from_iterable(rb_groups)):
|
|
||||||
button_str = "" if button == 1 else f"_{button}"
|
|
||||||
on_mask, _ = _get_usable_value(device.properties[f"{ON_MASK}{button_str}"])
|
|
||||||
if on_mask != 0:
|
|
||||||
rb_group = [button]
|
|
||||||
for bit in list(range(0, button - 1)) + list(range(button, 8)):
|
|
||||||
if on_mask & 1 << bit:
|
|
||||||
rb_group.append(bit + 1)
|
|
||||||
if len(rb_group) > 1:
|
|
||||||
rb_groups.append(rb_group)
|
|
||||||
return rb_groups
|
|
||||||
|
|
||||||
|
|
||||||
def _get_ramp_rate_property(prop):
|
|
||||||
"""Return the value and schema of a ramp rate property."""
|
|
||||||
rr_prop, _ = _get_property(prop)
|
|
||||||
rr_prop["value"] = ramp_rate_to_seconds(rr_prop["value"])
|
|
||||||
return rr_prop, _ramp_rate_schema(prop.name)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_usable_value(prop):
|
|
||||||
"""Return the current or the modified value of a property."""
|
|
||||||
value = prop.value if prop.new_value is None else prop.new_value
|
|
||||||
return value, prop.is_dirty
|
|
||||||
|
|
||||||
|
|
||||||
@websocket_api.websocket_command(
|
@websocket_api.websocket_command(
|
||||||
{
|
{
|
||||||
vol.Required(TYPE): "insteon/properties/get",
|
vol.Required(TYPE): "insteon/properties/get",
|
||||||
vol.Required(DEVICE_ADDRESS): str,
|
vol.Required(DEVICE_ADDRESS): str,
|
||||||
|
vol.Required(SHOW_ADVANCED): bool,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@websocket_api.require_admin
|
@websocket_api.require_admin
|
||||||
@ -299,7 +167,7 @@ async def websocket_get_properties(
|
|||||||
notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
|
notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
|
||||||
return
|
return
|
||||||
|
|
||||||
properties, schema = get_properties(device)
|
properties, schema = get_properties(device, msg[SHOW_ADVANCED])
|
||||||
|
|
||||||
connection.send_result(msg[ID], {"properties": properties, "schema": schema})
|
connection.send_result(msg[ID], {"properties": properties, "schema": schema})
|
||||||
|
|
||||||
@ -324,7 +192,7 @@ async def websocket_change_properties_record(
|
|||||||
notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
|
notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
|
||||||
return
|
return
|
||||||
|
|
||||||
set_property(device, msg[PROPERTY_NAME], msg[PROPERTY_VALUE])
|
update_property(device, msg[PROPERTY_NAME], msg[PROPERTY_VALUE])
|
||||||
connection.send_result(msg[ID])
|
connection.send_result(msg[ID])
|
||||||
|
|
||||||
|
|
||||||
@ -346,10 +214,9 @@ async def websocket_write_properties(
|
|||||||
notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
|
notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
|
||||||
return
|
return
|
||||||
|
|
||||||
result1 = await device.async_write_op_flags()
|
result = await device.async_write_config()
|
||||||
result2 = await device.async_write_ext_properties()
|
|
||||||
await devices.async_save(workdir=hass.config.config_dir)
|
await devices.async_save(workdir=hass.config.config_dir)
|
||||||
if result1 != ResponseStatus.SUCCESS or result2 != ResponseStatus.SUCCESS:
|
if result != ResponseStatus.SUCCESS:
|
||||||
connection.send_message(
|
connection.send_message(
|
||||||
websocket_api.error_message(
|
websocket_api.error_message(
|
||||||
msg[ID], "write_failed", "properties not written to device"
|
msg[ID], "write_failed", "properties not written to device"
|
||||||
@ -377,10 +244,9 @@ async def websocket_load_properties(
|
|||||||
notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
|
notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
|
||||||
return
|
return
|
||||||
|
|
||||||
result1 = await device.async_read_op_flags()
|
result, _ = await device.async_read_config(read_aldb=False)
|
||||||
result2 = await device.async_read_ext_properties()
|
|
||||||
await devices.async_save(workdir=hass.config.config_dir)
|
await devices.async_save(workdir=hass.config.config_dir)
|
||||||
if result1 != ResponseStatus.SUCCESS or result2 != ResponseStatus.SUCCESS:
|
if result != ResponseStatus.SUCCESS:
|
||||||
connection.send_message(
|
connection.send_message(
|
||||||
websocket_api.error_message(
|
websocket_api.error_message(
|
||||||
msg[ID], "load_failed", "properties not loaded from device"
|
msg[ID], "load_failed", "properties not loaded from device"
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"""Support for Insteon thermostat."""
|
"""Support for Insteon thermostat."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pyinsteon.config import CELSIUS
|
||||||
from pyinsteon.constants import ThermostatMode
|
from pyinsteon.constants import ThermostatMode
|
||||||
from pyinsteon.operating_flag import CELSIUS
|
|
||||||
|
|
||||||
from homeassistant.components.climate import ClimateEntity
|
from homeassistant.components.climate import ClimateEntity
|
||||||
from homeassistant.components.climate.const import (
|
from homeassistant.components.climate.const import (
|
||||||
|
@ -70,6 +70,7 @@ CONF_DIM_STEPS = "dim_steps"
|
|||||||
CONF_X10_ALL_UNITS_OFF = "x10_all_units_off"
|
CONF_X10_ALL_UNITS_OFF = "x10_all_units_off"
|
||||||
CONF_X10_ALL_LIGHTS_ON = "x10_all_lights_on"
|
CONF_X10_ALL_LIGHTS_ON = "x10_all_lights_on"
|
||||||
CONF_X10_ALL_LIGHTS_OFF = "x10_all_lights_off"
|
CONF_X10_ALL_LIGHTS_OFF = "x10_all_lights_off"
|
||||||
|
CONF_DEV_PATH = "dev_path"
|
||||||
|
|
||||||
PORT_HUB_V1 = 9761
|
PORT_HUB_V1 = 9761
|
||||||
PORT_HUB_V2 = 25105
|
PORT_HUB_V2 = 25105
|
||||||
@ -172,5 +173,6 @@ PROPERTY_NAME = "name"
|
|||||||
PROPERTY_VALUE = "value"
|
PROPERTY_VALUE = "value"
|
||||||
HA_DEVICE_NOT_FOUND = "ha_device_not_found"
|
HA_DEVICE_NOT_FOUND = "ha_device_not_found"
|
||||||
INSTEON_DEVICE_NOT_FOUND = "insteon_device_not_found"
|
INSTEON_DEVICE_NOT_FOUND = "insteon_device_not_found"
|
||||||
|
MULTIPLE = "multiple"
|
||||||
|
|
||||||
INSTEON_ADDR_REGEX = re.compile(r"([A-Fa-f0-9]{2}\.?[A-Fa-f0-9]{2}\.?[A-Fa-f0-9]{2})$")
|
INSTEON_ADDR_REGEX = re.compile(r"([A-Fa-f0-9]{2}\.?[A-Fa-f0-9]{2}\.?[A-Fa-f0-9]{2})$")
|
||||||
|
@ -85,7 +85,7 @@ class InsteonEntity(Entity):
|
|||||||
"""Return device information."""
|
"""Return device information."""
|
||||||
return DeviceInfo(
|
return DeviceInfo(
|
||||||
identifiers={(DOMAIN, str(self._insteon_device.address))},
|
identifiers={(DOMAIN, str(self._insteon_device.address))},
|
||||||
manufacturer="Smart Home",
|
manufacturer="SmartLabs, Inc",
|
||||||
model=f"{self._insteon_device.model} ({self._insteon_device.cat!r}, 0x{self._insteon_device.subcat:02x})",
|
model=f"{self._insteon_device.model} ({self._insteon_device.cat!r}, 0x{self._insteon_device.subcat:02x})",
|
||||||
name=f"{self._insteon_device.description} {self._insteon_device.address}",
|
name=f"{self._insteon_device.description} {self._insteon_device.address}",
|
||||||
sw_version=f"{self._insteon_device.firmware:02x} Engine Version: {self._insteon_device.engine_version}",
|
sw_version=f"{self._insteon_device.firmware:02x} Engine Version: {self._insteon_device.engine_version}",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
"""Support for Insteon lights via PowerLinc Modem."""
|
"""Support for Insteon lights via PowerLinc Modem."""
|
||||||
from pyinsteon.extended_property import ON_LEVEL
|
from pyinsteon.config import ON_LEVEL
|
||||||
|
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
|
@ -2,13 +2,17 @@
|
|||||||
"domain": "insteon",
|
"domain": "insteon",
|
||||||
"name": "Insteon",
|
"name": "Insteon",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/insteon",
|
"documentation": "https://www.home-assistant.io/integrations/insteon",
|
||||||
"requirements": ["pyinsteon==1.0.13"],
|
"dependencies": ["http", "websocket_api"],
|
||||||
|
"requirements": [
|
||||||
|
"pyinsteon==1.1.0b1",
|
||||||
|
"insteon-frontend-home-assistant==0.1.0"
|
||||||
|
],
|
||||||
"codeowners": ["@teharris1"],
|
"codeowners": ["@teharris1"],
|
||||||
"dhcp": [{ "macaddress": "000EF3*" }, { "registered_devices": true }],
|
"dhcp": [{ "macaddress": "000EF3*" }, { "registered_devices": true }],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["pyinsteon", "pypubsub"],
|
"loggers": ["pyinsteon", "pypubsub"],
|
||||||
"after_dependencies": ["usb"],
|
"after_dependencies": ["panel_custom", "usb"],
|
||||||
"usb": [
|
"usb": [
|
||||||
{
|
{
|
||||||
"vid": "10BF"
|
"vid": "10BF"
|
||||||
|
@ -22,6 +22,7 @@ import homeassistant.helpers.config_validation as cv
|
|||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_CAT,
|
CONF_CAT,
|
||||||
|
CONF_DEV_PATH,
|
||||||
CONF_DIM_STEPS,
|
CONF_DIM_STEPS,
|
||||||
CONF_FIRMWARE,
|
CONF_FIRMWARE,
|
||||||
CONF_HOUSECODE,
|
CONF_HOUSECODE,
|
||||||
@ -121,6 +122,7 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
vol.Optional(CONF_X10): vol.All(
|
vol.Optional(CONF_X10): vol.All(
|
||||||
cv.ensure_list_csv, [CONF_X10_SCHEMA]
|
cv.ensure_list_csv, [CONF_X10_SCHEMA]
|
||||||
),
|
),
|
||||||
|
vol.Optional(CONF_DEV_PATH): cv.string,
|
||||||
},
|
},
|
||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
required=True,
|
required=True,
|
||||||
|
@ -4,7 +4,7 @@ import logging
|
|||||||
|
|
||||||
from pyinsteon import devices
|
from pyinsteon import devices
|
||||||
from pyinsteon.address import Address
|
from pyinsteon.address import Address
|
||||||
from pyinsteon.constants import ALDBStatus
|
from pyinsteon.constants import ALDBStatus, DeviceAction
|
||||||
from pyinsteon.events import OFF_EVENT, OFF_FAST_EVENT, ON_EVENT, ON_FAST_EVENT
|
from pyinsteon.events import OFF_EVENT, OFF_FAST_EVENT, ON_EVENT, ON_FAST_EVENT
|
||||||
from pyinsteon.managers.link_manager import (
|
from pyinsteon.managers.link_manager import (
|
||||||
async_enter_linking_mode,
|
async_enter_linking_mode,
|
||||||
@ -137,8 +137,9 @@ def register_new_device_callback(hass):
|
|||||||
"""Register callback for new Insteon device."""
|
"""Register callback for new Insteon device."""
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_new_insteon_device(address=None):
|
def async_new_insteon_device(address, action: DeviceAction):
|
||||||
"""Detect device from transport to be delegated to platform."""
|
"""Detect device from transport to be delegated to platform."""
|
||||||
|
if action == DeviceAction.ADDED:
|
||||||
hass.async_create_task(async_create_new_entities(address))
|
hass.async_create_task(async_create_new_entities(address))
|
||||||
|
|
||||||
async def async_create_new_entities(address):
|
async def async_create_new_entities(address):
|
||||||
|
@ -881,6 +881,9 @@ influxdb-client==1.24.0
|
|||||||
# homeassistant.components.influxdb
|
# homeassistant.components.influxdb
|
||||||
influxdb==5.3.1
|
influxdb==5.3.1
|
||||||
|
|
||||||
|
# homeassistant.components.insteon
|
||||||
|
insteon-frontend-home-assistant==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.intellifire
|
# homeassistant.components.intellifire
|
||||||
intellifire4py==1.0.2
|
intellifire4py==1.0.2
|
||||||
|
|
||||||
@ -1544,7 +1547,7 @@ pyialarm==1.9.0
|
|||||||
pyicloud==1.0.0
|
pyicloud==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.insteon
|
# homeassistant.components.insteon
|
||||||
pyinsteon==1.0.13
|
pyinsteon==1.1.0b1
|
||||||
|
|
||||||
# homeassistant.components.intesishome
|
# homeassistant.components.intesishome
|
||||||
pyintesishome==1.7.6
|
pyintesishome==1.7.6
|
||||||
|
@ -618,6 +618,9 @@ influxdb-client==1.24.0
|
|||||||
# homeassistant.components.influxdb
|
# homeassistant.components.influxdb
|
||||||
influxdb==5.3.1
|
influxdb==5.3.1
|
||||||
|
|
||||||
|
# homeassistant.components.insteon
|
||||||
|
insteon-frontend-home-assistant==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.intellifire
|
# homeassistant.components.intellifire
|
||||||
intellifire4py==1.0.2
|
intellifire4py==1.0.2
|
||||||
|
|
||||||
@ -1026,7 +1029,7 @@ pyialarm==1.9.0
|
|||||||
pyicloud==1.0.0
|
pyicloud==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.insteon
|
# homeassistant.components.insteon
|
||||||
pyinsteon==1.0.13
|
pyinsteon==1.1.0b1
|
||||||
|
|
||||||
# homeassistant.components.ipma
|
# homeassistant.components.ipma
|
||||||
pyipma==2.0.5
|
pyipma==2.0.5
|
||||||
|
18
tests/components/insteon/fixtures/iolinc_properties.json
Normal file
18
tests/components/insteon/fixtures/iolinc_properties.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"operating_flags": {
|
||||||
|
"program_lock_on": false,
|
||||||
|
"blink_on_tx_on": true,
|
||||||
|
"relay_on_sense_on": false,
|
||||||
|
"momentary_on": true,
|
||||||
|
"momentary_on_off_trigger": false,
|
||||||
|
"x10_off": true,
|
||||||
|
"sense_sends_off": true,
|
||||||
|
"momentary_follow_sense": false
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"prescaler": 1,
|
||||||
|
"delay": 50,
|
||||||
|
"x10_house": 32,
|
||||||
|
"x10_unit": 0
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,20 @@
|
|||||||
"""Mock devices object to test Insteon."""
|
"""Mock devices object to test Insteon."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
from unittest.mock import AsyncMock, MagicMock
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
from pyinsteon.address import Address
|
from pyinsteon.address import Address
|
||||||
from pyinsteon.constants import ALDBStatus, ResponseStatus
|
from pyinsteon.constants import ALDBStatus, ResponseStatus
|
||||||
from pyinsteon.device_types import (
|
from pyinsteon.device_types import (
|
||||||
DimmableLightingControl_KeypadLinc_8,
|
DimmableLightingControl_KeypadLinc_8,
|
||||||
GeneralController,
|
GeneralController_RemoteLinc,
|
||||||
Hub,
|
Hub,
|
||||||
|
SensorsActuators_IOLink,
|
||||||
SwitchedLightingControl_SwitchLinc,
|
SwitchedLightingControl_SwitchLinc,
|
||||||
)
|
)
|
||||||
from pyinsteon.managers.saved_devices_manager import dict_to_aldb_record
|
from pyinsteon.managers.saved_devices_manager import dict_to_aldb_record
|
||||||
|
from pyinsteon.topics import DEVICE_LIST_CHANGED
|
||||||
|
from pyinsteon.utils import subscribe_topic
|
||||||
|
|
||||||
|
|
||||||
class MockSwitchLinc(SwitchedLightingControl_SwitchLinc):
|
class MockSwitchLinc(SwitchedLightingControl_SwitchLinc):
|
||||||
@ -31,7 +36,10 @@ class MockDevices:
|
|||||||
self._connected = connected
|
self._connected = connected
|
||||||
self.async_save = AsyncMock()
|
self.async_save = AsyncMock()
|
||||||
self.add_x10_device = MagicMock()
|
self.add_x10_device = MagicMock()
|
||||||
|
self.async_read_config = AsyncMock()
|
||||||
self.set_id = MagicMock()
|
self.set_id = MagicMock()
|
||||||
|
self.async_add_device_called_with = {}
|
||||||
|
self.async_cancel_all_linking = AsyncMock()
|
||||||
|
|
||||||
def __getitem__(self, address):
|
def __getitem__(self, address):
|
||||||
"""Return a a device from the device address."""
|
"""Return a a device from the device address."""
|
||||||
@ -56,18 +64,24 @@ class MockDevices:
|
|||||||
addr1 = Address("11.11.11")
|
addr1 = Address("11.11.11")
|
||||||
addr2 = Address("22.22.22")
|
addr2 = Address("22.22.22")
|
||||||
addr3 = Address("33.33.33")
|
addr3 = Address("33.33.33")
|
||||||
|
addr4 = Address("44.44.44")
|
||||||
self._devices[addr0] = Hub(addr0, 0x03, 0x00, 0x00, "Hub AA.AA.AA", "0")
|
self._devices[addr0] = Hub(addr0, 0x03, 0x00, 0x00, "Hub AA.AA.AA", "0")
|
||||||
self._devices[addr1] = MockSwitchLinc(
|
self._devices[addr1] = MockSwitchLinc(
|
||||||
addr1, 0x02, 0x00, 0x00, "Device 11.11.11", "1"
|
addr1, 0x02, 0x00, 0x00, "Device 11.11.11", "1"
|
||||||
)
|
)
|
||||||
self._devices[addr2] = GeneralController(
|
self._devices[addr2] = GeneralController_RemoteLinc(
|
||||||
addr2, 0x00, 0x00, 0x00, "Device 22.22.22", "2"
|
addr2, 0x00, 0x00, 0x00, "Device 22.22.22", "2"
|
||||||
)
|
)
|
||||||
self._devices[addr3] = DimmableLightingControl_KeypadLinc_8(
|
self._devices[addr3] = DimmableLightingControl_KeypadLinc_8(
|
||||||
addr3, 0x02, 0x00, 0x00, "Device 33.33.33", "3"
|
addr3, 0x02, 0x00, 0x00, "Device 33.33.33", "3"
|
||||||
)
|
)
|
||||||
|
self._devices[addr4] = SensorsActuators_IOLink(
|
||||||
|
addr4, 0x07, 0x00, 0x00, "Device 44.44.44", "4"
|
||||||
|
)
|
||||||
|
|
||||||
for device in [self._devices[addr] for addr in [addr1, addr2, addr3]]:
|
for device in [
|
||||||
|
self._devices[addr] for addr in [addr1, addr2, addr3, addr4]
|
||||||
|
]:
|
||||||
device.async_read_config = AsyncMock()
|
device.async_read_config = AsyncMock()
|
||||||
device.aldb.async_write = AsyncMock()
|
device.aldb.async_write = AsyncMock()
|
||||||
device.aldb.async_load = AsyncMock()
|
device.aldb.async_load = AsyncMock()
|
||||||
@ -85,7 +99,7 @@ class MockDevices:
|
|||||||
return_value=ResponseStatus.SUCCESS
|
return_value=ResponseStatus.SUCCESS
|
||||||
)
|
)
|
||||||
|
|
||||||
for device in [self._devices[addr] for addr in [addr2, addr3]]:
|
for device in [self._devices[addr] for addr in [addr2, addr3, addr4]]:
|
||||||
device.async_status = AsyncMock()
|
device.async_status = AsyncMock()
|
||||||
self._devices[addr1].async_status = AsyncMock(side_effect=AttributeError)
|
self._devices[addr1].async_status = AsyncMock(side_effect=AttributeError)
|
||||||
self._devices[addr0].aldb.async_load = AsyncMock()
|
self._devices[addr0].aldb.async_load = AsyncMock()
|
||||||
@ -104,6 +118,7 @@ class MockDevices:
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.modem = self._devices[addr0]
|
self.modem = self._devices[addr0]
|
||||||
|
self.modem.async_read_config = AsyncMock()
|
||||||
|
|
||||||
def fill_aldb(self, address, records):
|
def fill_aldb(self, address, records):
|
||||||
"""Fill the All-Link Database for a device."""
|
"""Fill the All-Link Database for a device."""
|
||||||
@ -126,3 +141,18 @@ class MockDevices:
|
|||||||
value = properties[flag]
|
value = properties[flag]
|
||||||
if device.properties.get(flag):
|
if device.properties.get(flag):
|
||||||
device.properties[flag].load(value)
|
device.properties[flag].load(value)
|
||||||
|
|
||||||
|
async def async_add_device(self, address=None, multiple=False):
|
||||||
|
"""Mock the async_add_device method."""
|
||||||
|
self.async_add_device_called_with = {"address": address, "multiple": multiple}
|
||||||
|
if multiple:
|
||||||
|
yield "aa.bb.cc"
|
||||||
|
await asyncio.sleep(0.01)
|
||||||
|
yield "bb.cc.dd"
|
||||||
|
if address:
|
||||||
|
yield address
|
||||||
|
await asyncio.sleep(0.01)
|
||||||
|
|
||||||
|
def subscribe(self, listener):
|
||||||
|
"""Mock the subscribe function."""
|
||||||
|
subscribe_topic(listener, DEVICE_LIST_CHANGED)
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
"""Test the device level APIs."""
|
"""Test the device level APIs."""
|
||||||
|
import asyncio
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from pyinsteon.constants import DeviceAction
|
||||||
|
from pyinsteon.topics import DEVICE_LIST_CHANGED
|
||||||
|
from pyinsteon.utils import publish_topic
|
||||||
|
|
||||||
from homeassistant.components import insteon
|
from homeassistant.components import insteon
|
||||||
from homeassistant.components.insteon.api import async_load_api
|
from homeassistant.components.insteon.api import async_load_api
|
||||||
from homeassistant.components.insteon.api.device import (
|
from homeassistant.components.insteon.api.device import (
|
||||||
@ -11,7 +16,7 @@ from homeassistant.components.insteon.api.device import (
|
|||||||
TYPE,
|
TYPE,
|
||||||
async_device_name,
|
async_device_name,
|
||||||
)
|
)
|
||||||
from homeassistant.components.insteon.const import DOMAIN
|
from homeassistant.components.insteon.const import DOMAIN, MULTIPLE
|
||||||
from homeassistant.helpers.device_registry import async_get_registry
|
from homeassistant.helpers.device_registry import async_get_registry
|
||||||
|
|
||||||
from .const import MOCK_USER_INPUT_PLM
|
from .const import MOCK_USER_INPUT_PLM
|
||||||
@ -137,3 +142,47 @@ async def test_get_ha_device_name(hass, hass_ws_client):
|
|||||||
# Test no HA or Insteon device
|
# Test no HA or Insteon device
|
||||||
name = await async_device_name(device_reg, "BB.BB.BB")
|
name = await async_device_name(device_reg, "BB.BB.BB")
|
||||||
assert name == ""
|
assert name == ""
|
||||||
|
|
||||||
|
|
||||||
|
async def test_add_device_api(hass, hass_ws_client):
|
||||||
|
"""Test adding an Insteon device."""
|
||||||
|
|
||||||
|
ws_client, devices, _, _ = await _async_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})
|
||||||
|
|
||||||
|
await asyncio.sleep(0.01)
|
||||||
|
assert devices.async_add_device_called_with.get("address") is None
|
||||||
|
assert devices.async_add_device_called_with["multiple"] is True
|
||||||
|
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["event"]["type"] == "device_added"
|
||||||
|
assert msg["event"]["address"] == "aa.bb.cc"
|
||||||
|
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["event"]["type"] == "device_added"
|
||||||
|
assert msg["event"]["address"] == "bb.cc.dd"
|
||||||
|
|
||||||
|
publish_topic(
|
||||||
|
DEVICE_LIST_CHANGED,
|
||||||
|
address=None,
|
||||||
|
action=DeviceAction.COMPLETED,
|
||||||
|
)
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["event"]["type"] == "linking_stopped"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_cancel_add_device(hass, hass_ws_client):
|
||||||
|
"""Test cancelling adding of a new device."""
|
||||||
|
|
||||||
|
ws_client, devices, _, _ = await _async_setup(hass, hass_ws_client)
|
||||||
|
|
||||||
|
with patch.object(insteon.api.aldb, "devices", devices):
|
||||||
|
await ws_client.send_json(
|
||||||
|
{
|
||||||
|
ID: 2,
|
||||||
|
TYPE: "insteon/device/add/cancel",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["success"]
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
"""Test the Insteon properties APIs."""
|
"""Test the Insteon properties APIs."""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from unittest.mock import patch
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
|
from pyinsteon.config import MOMENTARY_DELAY, RELAY_MODE, TOGGLE_BUTTON
|
||||||
|
from pyinsteon.config.extended_property import ExtendedProperty
|
||||||
|
from pyinsteon.constants import RelayMode, ToggleMode
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import insteon
|
from homeassistant.components import insteon
|
||||||
@ -11,19 +14,12 @@ from homeassistant.components.insteon.api.device import INSTEON_DEVICE_NOT_FOUND
|
|||||||
from homeassistant.components.insteon.api.properties import (
|
from homeassistant.components.insteon.api.properties import (
|
||||||
DEVICE_ADDRESS,
|
DEVICE_ADDRESS,
|
||||||
ID,
|
ID,
|
||||||
NON_TOGGLE_MASK,
|
|
||||||
NON_TOGGLE_OFF_MODE,
|
|
||||||
NON_TOGGLE_ON_MODE,
|
|
||||||
NON_TOGGLE_ON_OFF_MASK,
|
|
||||||
PROPERTY_NAME,
|
PROPERTY_NAME,
|
||||||
PROPERTY_VALUE,
|
PROPERTY_VALUE,
|
||||||
RADIO_BUTTON_GROUP_PROP,
|
RADIO_BUTTON_GROUPS,
|
||||||
TOGGLE_MODES,
|
RAMP_RATE_IN_SEC,
|
||||||
TOGGLE_ON_OFF_MODE,
|
SHOW_ADVANCED,
|
||||||
TOGGLE_PROP,
|
|
||||||
TYPE,
|
TYPE,
|
||||||
_get_radio_button_properties,
|
|
||||||
_get_toggle_properties,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from .mock_devices import MockDevices
|
from .mock_devices import MockDevices
|
||||||
@ -31,43 +27,172 @@ from .mock_devices import MockDevices
|
|||||||
from tests.common import load_fixture
|
from tests.common import load_fixture
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="properties_data", scope="session")
|
@pytest.fixture(name="kpl_properties_data", scope="session")
|
||||||
def aldb_data_fixture():
|
def kpl_properties_data_fixture():
|
||||||
"""Load the controller state fixture data."""
|
"""Load the controller state fixture data."""
|
||||||
return json.loads(load_fixture("insteon/kpl_properties.json"))
|
return json.loads(load_fixture("insteon/kpl_properties.json"))
|
||||||
|
|
||||||
|
|
||||||
async def _setup(hass, hass_ws_client, properties_data):
|
@pytest.fixture(name="iolinc_properties_data", scope="session")
|
||||||
|
def iolinc_properties_data_fixture():
|
||||||
|
"""Load the controller state fixture data."""
|
||||||
|
return json.loads(load_fixture("insteon/iolinc_properties.json"))
|
||||||
|
|
||||||
|
|
||||||
|
async def _setup(hass, hass_ws_client, address, properties_data):
|
||||||
"""Set up tests."""
|
"""Set up tests."""
|
||||||
ws_client = await hass_ws_client(hass)
|
ws_client = await hass_ws_client(hass)
|
||||||
devices = MockDevices()
|
devices = MockDevices()
|
||||||
await devices.async_load()
|
await devices.async_load()
|
||||||
devices.fill_properties("33.33.33", properties_data)
|
devices.fill_properties(address, properties_data)
|
||||||
async_load_api(hass)
|
async_load_api(hass)
|
||||||
return ws_client, devices
|
return ws_client, devices
|
||||||
|
|
||||||
|
|
||||||
async def test_get_properties(hass, hass_ws_client, properties_data):
|
async def test_get_properties(
|
||||||
|
hass, hass_ws_client, kpl_properties_data, iolinc_properties_data
|
||||||
|
):
|
||||||
"""Test getting an Insteon device's properties."""
|
"""Test getting an Insteon device's properties."""
|
||||||
ws_client, devices = await _setup(hass, hass_ws_client, properties_data)
|
ws_client, devices = await _setup(
|
||||||
|
hass, hass_ws_client, "33.33.33", kpl_properties_data
|
||||||
with patch.object(insteon.api.properties, "devices", devices):
|
|
||||||
await ws_client.send_json(
|
|
||||||
{ID: 2, TYPE: "insteon/properties/get", DEVICE_ADDRESS: "33.33.33"}
|
|
||||||
)
|
)
|
||||||
msg = await ws_client.receive_json()
|
devices.fill_properties("44.44.44", iolinc_properties_data)
|
||||||
assert msg["success"]
|
|
||||||
assert len(msg["result"]["properties"]) == 54
|
|
||||||
|
|
||||||
|
|
||||||
async def test_change_operating_flag(hass, hass_ws_client, properties_data):
|
|
||||||
"""Test changing an Insteon device's properties."""
|
|
||||||
ws_client, devices = await _setup(hass, hass_ws_client, properties_data)
|
|
||||||
|
|
||||||
with patch.object(insteon.api.properties, "devices", devices):
|
with patch.object(insteon.api.properties, "devices", devices):
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
{
|
{
|
||||||
ID: 2,
|
ID: 2,
|
||||||
|
TYPE: "insteon/properties/get",
|
||||||
|
DEVICE_ADDRESS: "33.33.33",
|
||||||
|
SHOW_ADVANCED: False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["success"]
|
||||||
|
assert len(msg["result"]["properties"]) == 18
|
||||||
|
|
||||||
|
await ws_client.send_json(
|
||||||
|
{
|
||||||
|
ID: 3,
|
||||||
|
TYPE: "insteon/properties/get",
|
||||||
|
DEVICE_ADDRESS: "44.44.44",
|
||||||
|
SHOW_ADVANCED: False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["success"]
|
||||||
|
assert len(msg["result"]["properties"]) == 6
|
||||||
|
|
||||||
|
await ws_client.send_json(
|
||||||
|
{
|
||||||
|
ID: 4,
|
||||||
|
TYPE: "insteon/properties/get",
|
||||||
|
DEVICE_ADDRESS: "33.33.33",
|
||||||
|
SHOW_ADVANCED: True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["success"]
|
||||||
|
assert len(msg["result"]["properties"]) == 69
|
||||||
|
|
||||||
|
await ws_client.send_json(
|
||||||
|
{
|
||||||
|
ID: 5,
|
||||||
|
TYPE: "insteon/properties/get",
|
||||||
|
DEVICE_ADDRESS: "44.44.44",
|
||||||
|
SHOW_ADVANCED: True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["success"]
|
||||||
|
assert len(msg["result"]["properties"]) == 14
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_read_only_properties(hass, hass_ws_client, iolinc_properties_data):
|
||||||
|
"""Test getting an Insteon device's properties."""
|
||||||
|
mock_read_only = ExtendedProperty(
|
||||||
|
"44.44.44", "mock_read_only", bool, is_read_only=True
|
||||||
|
)
|
||||||
|
mock_read_only.load(False)
|
||||||
|
|
||||||
|
ws_client, devices = await _setup(
|
||||||
|
hass, hass_ws_client, "44.44.44", iolinc_properties_data
|
||||||
|
)
|
||||||
|
device = devices["44.44.44"]
|
||||||
|
device.configuration["mock_read_only"] = mock_read_only
|
||||||
|
with patch.object(insteon.api.properties, "devices", devices):
|
||||||
|
await ws_client.send_json(
|
||||||
|
{
|
||||||
|
ID: 2,
|
||||||
|
TYPE: "insteon/properties/get",
|
||||||
|
DEVICE_ADDRESS: "44.44.44",
|
||||||
|
SHOW_ADVANCED: False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["success"]
|
||||||
|
assert len(msg["result"]["properties"]) == 6
|
||||||
|
await ws_client.send_json(
|
||||||
|
{
|
||||||
|
ID: 3,
|
||||||
|
TYPE: "insteon/properties/get",
|
||||||
|
DEVICE_ADDRESS: "44.44.44",
|
||||||
|
SHOW_ADVANCED: True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["success"]
|
||||||
|
assert len(msg["result"]["properties"]) == 15
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_unknown_properties(hass, hass_ws_client, iolinc_properties_data):
|
||||||
|
"""Test getting an Insteon device's properties."""
|
||||||
|
|
||||||
|
class UnknownType:
|
||||||
|
"""Mock unknown data type."""
|
||||||
|
|
||||||
|
mock_unknown = ExtendedProperty("44.44.44", "mock_unknown", UnknownType)
|
||||||
|
|
||||||
|
ws_client, devices = await _setup(
|
||||||
|
hass, hass_ws_client, "44.44.44", iolinc_properties_data
|
||||||
|
)
|
||||||
|
device = devices["44.44.44"]
|
||||||
|
device.configuration["mock_unknown"] = mock_unknown
|
||||||
|
with patch.object(insteon.api.properties, "devices", devices):
|
||||||
|
await ws_client.send_json(
|
||||||
|
{
|
||||||
|
ID: 2,
|
||||||
|
TYPE: "insteon/properties/get",
|
||||||
|
DEVICE_ADDRESS: "44.44.44",
|
||||||
|
SHOW_ADVANCED: False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["success"]
|
||||||
|
assert len(msg["result"]["properties"]) == 6
|
||||||
|
await ws_client.send_json(
|
||||||
|
{
|
||||||
|
ID: 3,
|
||||||
|
TYPE: "insteon/properties/get",
|
||||||
|
DEVICE_ADDRESS: "44.44.44",
|
||||||
|
SHOW_ADVANCED: True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
msg = await ws_client.receive_json()
|
||||||
|
assert msg["success"]
|
||||||
|
assert len(msg["result"]["properties"]) == 14
|
||||||
|
|
||||||
|
|
||||||
|
async def test_change_bool_property(hass, hass_ws_client, kpl_properties_data):
|
||||||
|
"""Test changing a bool type properties."""
|
||||||
|
ws_client, devices = await _setup(
|
||||||
|
hass, hass_ws_client, "33.33.33", kpl_properties_data
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(insteon.api.properties, "devices", devices):
|
||||||
|
await ws_client.send_json(
|
||||||
|
{
|
||||||
|
ID: 3,
|
||||||
TYPE: "insteon/properties/change",
|
TYPE: "insteon/properties/change",
|
||||||
DEVICE_ADDRESS: "33.33.33",
|
DEVICE_ADDRESS: "33.33.33",
|
||||||
PROPERTY_NAME: "led_off",
|
PROPERTY_NAME: "led_off",
|
||||||
@ -79,29 +204,33 @@ async def test_change_operating_flag(hass, hass_ws_client, properties_data):
|
|||||||
assert devices["33.33.33"].operating_flags["led_off"].is_dirty
|
assert devices["33.33.33"].operating_flags["led_off"].is_dirty
|
||||||
|
|
||||||
|
|
||||||
async def test_change_property(hass, hass_ws_client, properties_data):
|
async def test_change_int_property(hass, hass_ws_client, kpl_properties_data):
|
||||||
"""Test changing an Insteon device's properties."""
|
"""Test changing a int type properties."""
|
||||||
ws_client, devices = await _setup(hass, hass_ws_client, properties_data)
|
ws_client, devices = await _setup(
|
||||||
|
hass, hass_ws_client, "33.33.33", kpl_properties_data
|
||||||
|
)
|
||||||
|
|
||||||
with patch.object(insteon.api.properties, "devices", devices):
|
with patch.object(insteon.api.properties, "devices", devices):
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
{
|
{
|
||||||
ID: 2,
|
ID: 4,
|
||||||
TYPE: "insteon/properties/change",
|
TYPE: "insteon/properties/change",
|
||||||
DEVICE_ADDRESS: "33.33.33",
|
DEVICE_ADDRESS: "33.33.33",
|
||||||
PROPERTY_NAME: "on_mask",
|
PROPERTY_NAME: "led_dimming",
|
||||||
PROPERTY_VALUE: 100,
|
PROPERTY_VALUE: 100,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg = await ws_client.receive_json()
|
msg = await ws_client.receive_json()
|
||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
assert devices["33.33.33"].properties["on_mask"].new_value == 100
|
assert devices["33.33.33"].properties["led_dimming"].new_value == 100
|
||||||
assert devices["33.33.33"].properties["on_mask"].is_dirty
|
assert devices["33.33.33"].properties["led_dimming"].is_dirty
|
||||||
|
|
||||||
|
|
||||||
async def test_change_ramp_rate_property(hass, hass_ws_client, properties_data):
|
async def test_change_ramp_rate_property(hass, hass_ws_client, kpl_properties_data):
|
||||||
"""Test changing an Insteon device's properties."""
|
"""Test changing an Insteon device's ramp rate properties."""
|
||||||
ws_client, devices = await _setup(hass, hass_ws_client, properties_data)
|
ws_client, devices = await _setup(
|
||||||
|
hass, hass_ws_client, "33.33.33", kpl_properties_data
|
||||||
|
)
|
||||||
|
|
||||||
with patch.object(insteon.api.properties, "devices", devices):
|
with patch.object(insteon.api.properties, "devices", devices):
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
@ -109,7 +238,7 @@ async def test_change_ramp_rate_property(hass, hass_ws_client, properties_data):
|
|||||||
ID: 2,
|
ID: 2,
|
||||||
TYPE: "insteon/properties/change",
|
TYPE: "insteon/properties/change",
|
||||||
DEVICE_ADDRESS: "33.33.33",
|
DEVICE_ADDRESS: "33.33.33",
|
||||||
PROPERTY_NAME: "ramp_rate",
|
PROPERTY_NAME: RAMP_RATE_IN_SEC,
|
||||||
PROPERTY_VALUE: 4.5,
|
PROPERTY_VALUE: 4.5,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -119,208 +248,126 @@ async def test_change_ramp_rate_property(hass, hass_ws_client, properties_data):
|
|||||||
assert devices["33.33.33"].properties["ramp_rate"].is_dirty
|
assert devices["33.33.33"].properties["ramp_rate"].is_dirty
|
||||||
|
|
||||||
|
|
||||||
async def test_change_radio_button_group(hass, hass_ws_client, properties_data):
|
async def test_change_radio_button_group(hass, hass_ws_client, kpl_properties_data):
|
||||||
"""Test changing an Insteon device's properties."""
|
"""Test changing an Insteon device's properties."""
|
||||||
ws_client, devices = await _setup(hass, hass_ws_client, properties_data)
|
ws_client, devices = await _setup(
|
||||||
rb_props, schema = _get_radio_button_properties(devices["33.33.33"])
|
hass, hass_ws_client, "33.33.33", kpl_properties_data
|
||||||
|
)
|
||||||
|
rb_groups = devices["33.33.33"].configuration[RADIO_BUTTON_GROUPS]
|
||||||
|
|
||||||
# Make sure the baseline is correct
|
# Make sure the baseline is correct
|
||||||
assert rb_props[0]["name"] == f"{RADIO_BUTTON_GROUP_PROP}0"
|
assert rb_groups.value[0] == [4, 5]
|
||||||
assert rb_props[0]["value"] == [4, 5]
|
assert rb_groups.value[1] == [7, 8]
|
||||||
assert rb_props[1]["value"] == [7, 8]
|
|
||||||
assert rb_props[2]["value"] == []
|
|
||||||
assert schema[f"{RADIO_BUTTON_GROUP_PROP}0"]["options"].get(1)
|
|
||||||
assert schema[f"{RADIO_BUTTON_GROUP_PROP}1"]["options"].get(1)
|
|
||||||
assert devices["33.33.33"].properties["on_mask"].value == 0
|
|
||||||
assert devices["33.33.33"].properties["off_mask"].value == 0
|
|
||||||
assert not devices["33.33.33"].properties["on_mask"].is_dirty
|
|
||||||
assert not devices["33.33.33"].properties["off_mask"].is_dirty
|
|
||||||
|
|
||||||
# Add button 1 to the group
|
# Add button 1 to the group
|
||||||
rb_props[0]["value"].append(1)
|
new_groups_1 = [[1, 4, 5], [7, 8]]
|
||||||
with patch.object(insteon.api.properties, "devices", devices):
|
with patch.object(insteon.api.properties, "devices", devices):
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
{
|
{
|
||||||
ID: 2,
|
ID: 2,
|
||||||
TYPE: "insteon/properties/change",
|
TYPE: "insteon/properties/change",
|
||||||
DEVICE_ADDRESS: "33.33.33",
|
DEVICE_ADDRESS: "33.33.33",
|
||||||
PROPERTY_NAME: f"{RADIO_BUTTON_GROUP_PROP}0",
|
PROPERTY_NAME: RADIO_BUTTON_GROUPS,
|
||||||
PROPERTY_VALUE: rb_props[0]["value"],
|
PROPERTY_VALUE: new_groups_1,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg = await ws_client.receive_json()
|
msg = await ws_client.receive_json()
|
||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
|
assert rb_groups.new_value[0] == [1, 4, 5]
|
||||||
|
assert rb_groups.new_value[1] == [7, 8]
|
||||||
|
|
||||||
new_rb_props, _ = _get_radio_button_properties(devices["33.33.33"])
|
new_groups_2 = [[1, 4], [7, 8]]
|
||||||
assert 1 in new_rb_props[0]["value"]
|
|
||||||
assert 4 in new_rb_props[0]["value"]
|
|
||||||
assert 5 in new_rb_props[0]["value"]
|
|
||||||
assert schema[f"{RADIO_BUTTON_GROUP_PROP}0"]["options"].get(1)
|
|
||||||
assert schema[f"{RADIO_BUTTON_GROUP_PROP}1"]["options"].get(1)
|
|
||||||
|
|
||||||
assert devices["33.33.33"].properties["on_mask"].new_value == 0x18
|
|
||||||
assert devices["33.33.33"].properties["off_mask"].new_value == 0x18
|
|
||||||
assert devices["33.33.33"].properties["on_mask"].is_dirty
|
|
||||||
assert devices["33.33.33"].properties["off_mask"].is_dirty
|
|
||||||
|
|
||||||
# Remove button 5
|
|
||||||
rb_props[0]["value"].remove(5)
|
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
{
|
{
|
||||||
ID: 3,
|
ID: 3,
|
||||||
TYPE: "insteon/properties/change",
|
TYPE: "insteon/properties/change",
|
||||||
DEVICE_ADDRESS: "33.33.33",
|
DEVICE_ADDRESS: "33.33.33",
|
||||||
PROPERTY_NAME: f"{RADIO_BUTTON_GROUP_PROP}0",
|
PROPERTY_NAME: RADIO_BUTTON_GROUPS,
|
||||||
PROPERTY_VALUE: rb_props[0]["value"],
|
PROPERTY_VALUE: new_groups_2,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg = await ws_client.receive_json()
|
msg = await ws_client.receive_json()
|
||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
|
assert rb_groups.new_value[0] == [1, 4]
|
||||||
new_rb_props, _ = _get_radio_button_properties(devices["33.33.33"])
|
assert rb_groups.new_value[1] == [7, 8]
|
||||||
assert 1 in new_rb_props[0]["value"]
|
|
||||||
assert 4 in new_rb_props[0]["value"]
|
|
||||||
assert 5 not in new_rb_props[0]["value"]
|
|
||||||
assert schema[f"{RADIO_BUTTON_GROUP_PROP}0"]["options"].get(1)
|
|
||||||
assert schema[f"{RADIO_BUTTON_GROUP_PROP}1"]["options"].get(1)
|
|
||||||
|
|
||||||
assert devices["33.33.33"].properties["on_mask"].new_value == 0x08
|
|
||||||
assert devices["33.33.33"].properties["off_mask"].new_value == 0x08
|
|
||||||
assert devices["33.33.33"].properties["on_mask"].is_dirty
|
|
||||||
assert devices["33.33.33"].properties["off_mask"].is_dirty
|
|
||||||
|
|
||||||
# Remove button group 1
|
|
||||||
rb_props[1]["value"] = []
|
|
||||||
await ws_client.send_json(
|
|
||||||
{
|
|
||||||
ID: 5,
|
|
||||||
TYPE: "insteon/properties/change",
|
|
||||||
DEVICE_ADDRESS: "33.33.33",
|
|
||||||
PROPERTY_NAME: f"{RADIO_BUTTON_GROUP_PROP}1",
|
|
||||||
PROPERTY_VALUE: rb_props[1]["value"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
msg = await ws_client.receive_json()
|
|
||||||
assert msg["success"]
|
|
||||||
|
|
||||||
new_rb_props, _ = _get_radio_button_properties(devices["33.33.33"])
|
|
||||||
assert len(new_rb_props) == 2
|
|
||||||
assert new_rb_props[0]["value"] == [1, 4]
|
|
||||||
assert new_rb_props[1]["value"] == []
|
|
||||||
|
|
||||||
|
|
||||||
async def test_create_radio_button_group(hass, hass_ws_client, properties_data):
|
async def test_change_toggle_property(hass, hass_ws_client, kpl_properties_data):
|
||||||
"""Test changing an Insteon device's properties."""
|
|
||||||
ws_client, devices = await _setup(hass, hass_ws_client, properties_data)
|
|
||||||
rb_props, _ = _get_radio_button_properties(devices["33.33.33"])
|
|
||||||
|
|
||||||
# Make sure the baseline is correct
|
|
||||||
assert len(rb_props) == 3
|
|
||||||
|
|
||||||
rb_props[0]["value"].append("1")
|
|
||||||
|
|
||||||
with patch.object(insteon.api.properties, "devices", devices):
|
|
||||||
await ws_client.send_json(
|
|
||||||
{
|
|
||||||
ID: 2,
|
|
||||||
TYPE: "insteon/properties/change",
|
|
||||||
DEVICE_ADDRESS: "33.33.33",
|
|
||||||
PROPERTY_NAME: f"{RADIO_BUTTON_GROUP_PROP}2",
|
|
||||||
PROPERTY_VALUE: ["1", "3"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
msg = await ws_client.receive_json()
|
|
||||||
assert msg["success"]
|
|
||||||
|
|
||||||
new_rb_props, new_schema = _get_radio_button_properties(devices["33.33.33"])
|
|
||||||
assert len(new_rb_props) == 4
|
|
||||||
assert 1 in new_rb_props[0]["value"]
|
|
||||||
assert new_schema[f"{RADIO_BUTTON_GROUP_PROP}0"]["options"].get(1)
|
|
||||||
assert not new_schema[f"{RADIO_BUTTON_GROUP_PROP}1"]["options"].get(1)
|
|
||||||
|
|
||||||
assert devices["33.33.33"].properties["on_mask"].new_value == 4
|
|
||||||
assert devices["33.33.33"].properties["off_mask"].new_value == 4
|
|
||||||
assert devices["33.33.33"].properties["on_mask"].is_dirty
|
|
||||||
assert devices["33.33.33"].properties["off_mask"].is_dirty
|
|
||||||
|
|
||||||
|
|
||||||
async def test_change_toggle_property(hass, hass_ws_client, properties_data):
|
|
||||||
"""Update a button's toggle mode."""
|
"""Update a button's toggle mode."""
|
||||||
ws_client, devices = await _setup(hass, hass_ws_client, properties_data)
|
ws_client, devices = await _setup(
|
||||||
|
hass, hass_ws_client, "33.33.33", kpl_properties_data
|
||||||
|
)
|
||||||
device = devices["33.33.33"]
|
device = devices["33.33.33"]
|
||||||
toggle_props, _ = _get_toggle_properties(devices["33.33.33"])
|
prop_name = f"{TOGGLE_BUTTON}_c"
|
||||||
|
toggle_prop = device.configuration[prop_name]
|
||||||
# Make sure the baseline is correct
|
assert toggle_prop.value == ToggleMode.TOGGLE
|
||||||
assert toggle_props[0]["name"] == f"{TOGGLE_PROP}{device.groups[1].name}"
|
|
||||||
assert toggle_props[0]["value"] == TOGGLE_MODES[TOGGLE_ON_OFF_MODE]
|
|
||||||
assert toggle_props[1]["value"] == TOGGLE_MODES[NON_TOGGLE_ON_MODE]
|
|
||||||
assert device.properties[NON_TOGGLE_MASK].value == 2
|
|
||||||
assert device.properties[NON_TOGGLE_ON_OFF_MASK].value == 2
|
|
||||||
assert not device.properties[NON_TOGGLE_MASK].is_dirty
|
|
||||||
assert not device.properties[NON_TOGGLE_ON_OFF_MASK].is_dirty
|
|
||||||
|
|
||||||
with patch.object(insteon.api.properties, "devices", devices):
|
with patch.object(insteon.api.properties, "devices", devices):
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
{
|
{
|
||||||
ID: 2,
|
ID: 2,
|
||||||
TYPE: "insteon/properties/change",
|
TYPE: "insteon/properties/change",
|
||||||
DEVICE_ADDRESS: "33.33.33",
|
DEVICE_ADDRESS: "33.33.33",
|
||||||
PROPERTY_NAME: toggle_props[0]["name"],
|
PROPERTY_NAME: prop_name,
|
||||||
PROPERTY_VALUE: 1,
|
PROPERTY_VALUE: str(ToggleMode.ON_ONLY).lower(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg = await ws_client.receive_json()
|
msg = await ws_client.receive_json()
|
||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
|
assert toggle_prop.new_value == ToggleMode.ON_ONLY
|
||||||
|
|
||||||
new_toggle_props, _ = _get_toggle_properties(devices["33.33.33"])
|
|
||||||
assert new_toggle_props[0]["value"] == TOGGLE_MODES[NON_TOGGLE_ON_MODE]
|
|
||||||
assert device.properties[NON_TOGGLE_MASK].new_value == 3
|
|
||||||
assert device.properties[NON_TOGGLE_ON_OFF_MASK].new_value == 3
|
|
||||||
assert device.properties[NON_TOGGLE_MASK].is_dirty
|
|
||||||
assert device.properties[NON_TOGGLE_ON_OFF_MASK].is_dirty
|
|
||||||
|
|
||||||
|
async def test_change_relay_mode(hass, hass_ws_client, iolinc_properties_data):
|
||||||
|
"""Update a device's relay mode."""
|
||||||
|
ws_client, devices = await _setup(
|
||||||
|
hass, hass_ws_client, "44.44.44", iolinc_properties_data
|
||||||
|
)
|
||||||
|
device = devices["44.44.44"]
|
||||||
|
relay_prop = device.configuration[RELAY_MODE]
|
||||||
|
assert relay_prop.value == RelayMode.MOMENTARY_A
|
||||||
|
with patch.object(insteon.api.properties, "devices", devices):
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
{
|
{
|
||||||
ID: 3,
|
ID: 2,
|
||||||
TYPE: "insteon/properties/change",
|
TYPE: "insteon/properties/change",
|
||||||
DEVICE_ADDRESS: "33.33.33",
|
DEVICE_ADDRESS: "44.44.44",
|
||||||
PROPERTY_NAME: toggle_props[0]["name"],
|
PROPERTY_NAME: RELAY_MODE,
|
||||||
PROPERTY_VALUE: 2,
|
PROPERTY_VALUE: str(RelayMode.LATCHING).lower(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg = await ws_client.receive_json()
|
msg = await ws_client.receive_json()
|
||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
|
assert relay_prop.new_value == RelayMode.LATCHING
|
||||||
|
|
||||||
new_toggle_props, _ = _get_toggle_properties(devices["33.33.33"])
|
|
||||||
assert new_toggle_props[0]["value"] == TOGGLE_MODES[NON_TOGGLE_OFF_MODE]
|
|
||||||
assert device.properties[NON_TOGGLE_MASK].new_value == 3
|
|
||||||
assert device.properties[NON_TOGGLE_ON_OFF_MASK].new_value is None
|
|
||||||
assert device.properties[NON_TOGGLE_MASK].is_dirty
|
|
||||||
assert not device.properties[NON_TOGGLE_ON_OFF_MASK].is_dirty
|
|
||||||
|
|
||||||
|
async def test_change_float_property(hass, hass_ws_client, iolinc_properties_data):
|
||||||
|
"""Update a float type property."""
|
||||||
|
ws_client, devices = await _setup(
|
||||||
|
hass, hass_ws_client, "44.44.44", iolinc_properties_data
|
||||||
|
)
|
||||||
|
device = devices["44.44.44"]
|
||||||
|
delay_prop = device.configuration[MOMENTARY_DELAY]
|
||||||
|
delay_prop.load(0)
|
||||||
|
with patch.object(insteon.api.properties, "devices", devices):
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
{
|
{
|
||||||
ID: 4,
|
ID: 2,
|
||||||
TYPE: "insteon/properties/change",
|
TYPE: "insteon/properties/change",
|
||||||
DEVICE_ADDRESS: "33.33.33",
|
DEVICE_ADDRESS: "44.44.44",
|
||||||
PROPERTY_NAME: toggle_props[1]["name"],
|
PROPERTY_NAME: MOMENTARY_DELAY,
|
||||||
PROPERTY_VALUE: 0,
|
PROPERTY_VALUE: 1.8,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
msg = await ws_client.receive_json()
|
msg = await ws_client.receive_json()
|
||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
|
|
||||||
new_toggle_props, _ = _get_toggle_properties(devices["33.33.33"])
|
assert delay_prop.new_value == 1.8
|
||||||
assert new_toggle_props[1]["value"] == TOGGLE_MODES[TOGGLE_ON_OFF_MODE]
|
|
||||||
assert device.properties[NON_TOGGLE_MASK].new_value == 1
|
|
||||||
assert device.properties[NON_TOGGLE_ON_OFF_MASK].new_value == 0
|
|
||||||
assert device.properties[NON_TOGGLE_MASK].is_dirty
|
|
||||||
assert device.properties[NON_TOGGLE_ON_OFF_MASK].is_dirty
|
|
||||||
|
|
||||||
|
|
||||||
async def test_write_properties(hass, hass_ws_client, properties_data):
|
async def test_write_properties(hass, hass_ws_client, kpl_properties_data):
|
||||||
"""Test getting an Insteon device's properties."""
|
"""Test getting an Insteon device's properties."""
|
||||||
ws_client, devices = await _setup(hass, hass_ws_client, properties_data)
|
ws_client, devices = await _setup(
|
||||||
|
hass, hass_ws_client, "33.33.33", kpl_properties_data
|
||||||
|
)
|
||||||
|
|
||||||
with patch.object(insteon.api.properties, "devices", devices):
|
with patch.object(insteon.api.properties, "devices", devices):
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
@ -332,9 +379,11 @@ async def test_write_properties(hass, hass_ws_client, properties_data):
|
|||||||
assert devices["33.33.33"].async_write_ext_properties.call_count == 1
|
assert devices["33.33.33"].async_write_ext_properties.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
async def test_write_properties_failure(hass, hass_ws_client, properties_data):
|
async def test_write_properties_failure(hass, hass_ws_client, kpl_properties_data):
|
||||||
"""Test getting an Insteon device's properties."""
|
"""Test getting an Insteon device's properties."""
|
||||||
ws_client, devices = await _setup(hass, hass_ws_client, properties_data)
|
ws_client, devices = await _setup(
|
||||||
|
hass, hass_ws_client, "33.33.33", kpl_properties_data
|
||||||
|
)
|
||||||
|
|
||||||
with patch.object(insteon.api.properties, "devices", devices):
|
with patch.object(insteon.api.properties, "devices", devices):
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
@ -345,39 +394,48 @@ async def test_write_properties_failure(hass, hass_ws_client, properties_data):
|
|||||||
assert msg["error"]["code"] == "write_failed"
|
assert msg["error"]["code"] == "write_failed"
|
||||||
|
|
||||||
|
|
||||||
async def test_load_properties(hass, hass_ws_client, properties_data):
|
async def test_load_properties(hass, hass_ws_client, kpl_properties_data):
|
||||||
"""Test getting an Insteon device's properties."""
|
"""Test getting an Insteon device's properties."""
|
||||||
ws_client, devices = await _setup(hass, hass_ws_client, properties_data)
|
ws_client, devices = await _setup(
|
||||||
|
hass, hass_ws_client, "33.33.33", kpl_properties_data
|
||||||
|
)
|
||||||
|
|
||||||
|
device = devices["33.33.33"]
|
||||||
|
device.async_read_config = AsyncMock(return_value=(1, 1))
|
||||||
with patch.object(insteon.api.properties, "devices", devices):
|
with patch.object(insteon.api.properties, "devices", devices):
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
{ID: 2, TYPE: "insteon/properties/load", DEVICE_ADDRESS: "33.33.33"}
|
{ID: 2, TYPE: "insteon/properties/load", DEVICE_ADDRESS: "33.33.33"}
|
||||||
)
|
)
|
||||||
msg = await ws_client.receive_json()
|
msg = await ws_client.receive_json()
|
||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
assert devices["33.33.33"].async_read_op_flags.call_count == 1
|
assert devices["33.33.33"].async_read_config.call_count == 1
|
||||||
assert devices["33.33.33"].async_read_ext_properties.call_count == 1
|
|
||||||
|
|
||||||
|
|
||||||
async def test_load_properties_failure(hass, hass_ws_client, properties_data):
|
async def test_load_properties_failure(hass, hass_ws_client, kpl_properties_data):
|
||||||
"""Test getting an Insteon device's properties."""
|
"""Test getting an Insteon device's properties."""
|
||||||
ws_client, devices = await _setup(hass, hass_ws_client, properties_data)
|
ws_client, devices = await _setup(
|
||||||
|
hass, hass_ws_client, "33.33.33", kpl_properties_data
|
||||||
|
)
|
||||||
|
|
||||||
|
device = devices["33.33.33"]
|
||||||
|
device.async_read_config = AsyncMock(return_value=(0, 0))
|
||||||
with patch.object(insteon.api.properties, "devices", devices):
|
with patch.object(insteon.api.properties, "devices", devices):
|
||||||
await ws_client.send_json(
|
await ws_client.send_json(
|
||||||
{ID: 2, TYPE: "insteon/properties/load", DEVICE_ADDRESS: "22.22.22"}
|
{ID: 2, TYPE: "insteon/properties/load", DEVICE_ADDRESS: "33.33.33"}
|
||||||
)
|
)
|
||||||
msg = await ws_client.receive_json()
|
msg = await ws_client.receive_json()
|
||||||
assert not msg["success"]
|
assert not msg["success"]
|
||||||
assert msg["error"]["code"] == "load_failed"
|
assert msg["error"]["code"] == "load_failed"
|
||||||
|
|
||||||
|
|
||||||
async def test_reset_properties(hass, hass_ws_client, properties_data):
|
async def test_reset_properties(hass, hass_ws_client, kpl_properties_data):
|
||||||
"""Test getting an Insteon device's properties."""
|
"""Test getting an Insteon device's properties."""
|
||||||
ws_client, devices = await _setup(hass, hass_ws_client, properties_data)
|
ws_client, devices = await _setup(
|
||||||
|
hass, hass_ws_client, "33.33.33", kpl_properties_data
|
||||||
|
)
|
||||||
|
|
||||||
device = devices["33.33.33"]
|
device = devices["33.33.33"]
|
||||||
device.operating_flags["led_off"].new_value = True
|
device.configuration["led_off"].new_value = True
|
||||||
device.properties["on_mask"].new_value = 100
|
device.properties["on_mask"].new_value = 100
|
||||||
assert device.operating_flags["led_off"].is_dirty
|
assert device.operating_flags["led_off"].is_dirty
|
||||||
assert device.properties["on_mask"].is_dirty
|
assert device.properties["on_mask"].is_dirty
|
||||||
@ -391,20 +449,23 @@ async def test_reset_properties(hass, hass_ws_client, properties_data):
|
|||||||
assert not device.properties["on_mask"].is_dirty
|
assert not device.properties["on_mask"].is_dirty
|
||||||
|
|
||||||
|
|
||||||
async def test_bad_address(hass, hass_ws_client, properties_data):
|
async def test_bad_address(hass, hass_ws_client, kpl_properties_data):
|
||||||
"""Test for a bad Insteon address."""
|
"""Test for a bad Insteon address."""
|
||||||
ws_client, _ = await _setup(hass, hass_ws_client, properties_data)
|
ws_client, devices = await _setup(
|
||||||
|
hass, hass_ws_client, "33.33.33", kpl_properties_data
|
||||||
|
)
|
||||||
|
|
||||||
ws_id = 0
|
ws_id = 0
|
||||||
for call in ["get", "write", "load", "reset"]:
|
for call in ["get", "write", "load", "reset"]:
|
||||||
ws_id += 1
|
ws_id += 1
|
||||||
await ws_client.send_json(
|
params = {
|
||||||
{
|
|
||||||
ID: ws_id,
|
ID: ws_id,
|
||||||
TYPE: f"insteon/properties/{call}",
|
TYPE: f"insteon/properties/{call}",
|
||||||
DEVICE_ADDRESS: "99.99.99",
|
DEVICE_ADDRESS: "99.99.99",
|
||||||
}
|
}
|
||||||
)
|
if call == "get":
|
||||||
|
params[SHOW_ADVANCED] = False
|
||||||
|
await ws_client.send_json(params)
|
||||||
msg = await ws_client.receive_json()
|
msg = await ws_client.receive_json()
|
||||||
assert not msg["success"]
|
assert not msg["success"]
|
||||||
assert msg["error"]["message"] == INSTEON_DEVICE_NOT_FOUND
|
assert msg["error"]["message"] == INSTEON_DEVICE_NOT_FOUND
|
||||||
|
@ -2,10 +2,8 @@
|
|||||||
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import voluptuous_serialize
|
|
||||||
|
|
||||||
from homeassistant import config_entries, data_entry_flow
|
from homeassistant import config_entries, data_entry_flow
|
||||||
from homeassistant.components import dhcp, usb
|
from homeassistant.components import usb
|
||||||
from homeassistant.components.insteon.config_flow import (
|
from homeassistant.components.insteon.config_flow import (
|
||||||
HUB1,
|
HUB1,
|
||||||
HUB2,
|
HUB2,
|
||||||
@ -39,7 +37,6 @@ from homeassistant.const import (
|
|||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
import homeassistant.helpers.config_validation as cv
|
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
MOCK_HOSTNAME,
|
MOCK_HOSTNAME,
|
||||||
@ -651,48 +648,3 @@ async def test_discovery_via_usb_already_setup(hass):
|
|||||||
|
|
||||||
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
assert result["reason"] == "single_instance_allowed"
|
assert result["reason"] == "single_instance_allowed"
|
||||||
|
|
||||||
|
|
||||||
async def test_discovery_via_dhcp_hubv1(hass):
|
|
||||||
"""Test usb flow."""
|
|
||||||
await _test_dhcp(hass, HUB1)
|
|
||||||
|
|
||||||
|
|
||||||
async def test_discovery_via_dhcp_hubv2(hass):
|
|
||||||
"""Test usb flow."""
|
|
||||||
await _test_dhcp(hass, HUB2)
|
|
||||||
|
|
||||||
|
|
||||||
async def _test_dhcp(hass, modem_type):
|
|
||||||
"""Test the dhcp discovery for a moddem type."""
|
|
||||||
discovery_info = dhcp.DhcpServiceInfo(
|
|
||||||
ip="11.22.33.44", hostname="", macaddress="00:0e:f3:aa:bb:cc"
|
|
||||||
)
|
|
||||||
result = await hass.config_entries.flow.async_init(
|
|
||||||
"insteon",
|
|
||||||
context={"source": config_entries.SOURCE_DHCP},
|
|
||||||
data=discovery_info,
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
|
||||||
assert result["step_id"] == "user"
|
|
||||||
|
|
||||||
with patch("homeassistant.components.insteon.config_flow.async_connect"), patch(
|
|
||||||
"homeassistant.components.insteon.async_setup_entry", return_value=True
|
|
||||||
):
|
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
|
||||||
result["flow_id"], user_input={"modem_type": modem_type}
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
|
||||||
assert result["step_id"] == "user"
|
|
||||||
|
|
||||||
schema = voluptuous_serialize.convert(
|
|
||||||
result2["data_schema"],
|
|
||||||
custom_serializer=cv.custom_serializer,
|
|
||||||
)
|
|
||||||
for field in schema:
|
|
||||||
if field["name"] == "host":
|
|
||||||
assert field.get("default") == "11.22.33.44"
|
|
||||||
break
|
|
||||||
|
@ -7,6 +7,7 @@ from pyinsteon.address import Address
|
|||||||
from homeassistant.components import insteon
|
from homeassistant.components import insteon
|
||||||
from homeassistant.components.insteon.const import (
|
from homeassistant.components.insteon.const import (
|
||||||
CONF_CAT,
|
CONF_CAT,
|
||||||
|
CONF_DEV_PATH,
|
||||||
CONF_OVERRIDE,
|
CONF_OVERRIDE,
|
||||||
CONF_SUBCAT,
|
CONF_SUBCAT,
|
||||||
CONF_X10,
|
CONF_X10,
|
||||||
@ -222,3 +223,24 @@ async def test_setup_entry_failed_connection(hass: HomeAssistant, caplog):
|
|||||||
{},
|
{},
|
||||||
)
|
)
|
||||||
assert "Could not connect to Insteon modem" in caplog.text
|
assert "Could not connect to Insteon modem" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_import_frontend_dev_url(hass: HomeAssistant):
|
||||||
|
"""Test importing a dev_url config entry."""
|
||||||
|
config = {}
|
||||||
|
config[DOMAIN] = {CONF_DEV_PATH: "/some/path"}
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
insteon, "async_connect", new=mock_successful_connection
|
||||||
|
), patch.object(insteon, "close_insteon_connection"), 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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user