Change Insteon backend module to pyinsteon from insteonplm (#35198)

* Migrate to pyinsteon from insteonplm

* Rename devices entities

* Print ALDB even if not loaded

* Add relay to name map

* Change insteonplm to pyinsteon

* Update requirements_all correctly

* Code review updates

* async_set_speed receive std speed value

* default speed to std medium value

* Call async methods for fan on/off

* Comment await required in loop

* Remove emtpy and add codeowner

* Make services async and remove async_add_job call

* Remove extra logging

* New device as async task and aldb load in loop

* Place lock in context bloxk

* Limiting lock to min

* Remove .env file
This commit is contained in:
Tom Harris 2020-05-17 09:27:38 -04:00 committed by GitHub
parent 47801e7350
commit dbd821a564
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 617 additions and 514 deletions

View File

@ -194,6 +194,7 @@ homeassistant/components/input_datetime/* @home-assistant/core
homeassistant/components/input_number/* @home-assistant/core
homeassistant/components/input_select/* @home-assistant/core
homeassistant/components/input_text/* @home-assistant/core
homeassistant/components/insteon/* @teharris1
homeassistant/components/integration/* @dgomes
homeassistant/components/intent/* @home-assistant/core
homeassistant/components/intesishome/* @jnimmo

View File

@ -1,7 +1,8 @@
"""Support for INSTEON Modems (PLM and Hub)."""
import asyncio
import logging
import insteonplm
from pyinsteon import async_close, async_connect, devices
from homeassistant.const import (
CONF_HOST,
@ -24,21 +25,75 @@ from .const import (
CONF_SUBCAT,
CONF_UNITCODE,
CONF_X10,
CONF_X10_ALL_LIGHTS_OFF,
CONF_X10_ALL_LIGHTS_ON,
CONF_X10_ALL_UNITS_OFF,
DOMAIN,
INSTEON_ENTITIES,
INSTEON_COMPONENTS,
ON_OFF_EVENTS,
)
from .schemas import CONFIG_SCHEMA # noqa F440
from .utils import async_register_services, register_new_device_callback
from .utils import (
add_on_off_event_device,
async_register_services,
get_device_platforms,
register_new_device_callback,
)
_LOGGER = logging.getLogger(__name__)
async def async_id_unknown_devices(config_dir):
"""Send device ID commands to all unidentified devices."""
await devices.async_load(id_devices=1)
for addr in devices:
device = devices[addr]
flags = True
for name in device.operating_flags:
if not device.operating_flags[name].is_loaded:
flags = False
break
if flags:
for name in device.properties:
if not device.properties[name].is_loaded:
flags = False
break
# Cannot be done concurrently due to issues with the underlying protocol.
if not device.aldb.is_loaded or not flags:
await device.async_read_config()
await devices.async_save(workdir=config_dir)
async def async_setup_platforms(hass, config):
"""Initiate the connection and services."""
tasks = [
hass.helpers.discovery.async_load_platform(component, DOMAIN, {}, config)
for component in INSTEON_COMPONENTS
]
await asyncio.gather(*tasks)
for address in devices:
device = devices[address]
platforms = get_device_platforms(device)
if ON_OFF_EVENTS in platforms:
add_on_off_event_device(hass, device)
_LOGGER.debug("Insteon device count: %s", len(devices))
register_new_device_callback(hass, config)
async_register_services(hass)
# Cannot be done concurrently due to issues with the underlying protocol.
for address in devices:
await devices[address].async_status()
await async_id_unknown_devices(hass.config.config_dir)
async def close_insteon_connection(*args):
"""Close the Insteon connection."""
await async_close()
async def async_setup(hass, config):
"""Set up the connection to the modem."""
insteon_modem = None
conf = config[DOMAIN]
port = conf.get(CONF_PORT)
@ -47,68 +102,50 @@ async def async_setup(hass, config):
username = conf.get(CONF_HUB_USERNAME)
password = conf.get(CONF_HUB_PASSWORD)
hub_version = conf.get(CONF_HUB_VERSION)
overrides = conf.get(CONF_OVERRIDE, [])
x10_devices = conf.get(CONF_X10, [])
x10_all_units_off_housecode = conf.get(CONF_X10_ALL_UNITS_OFF)
x10_all_lights_on_housecode = conf.get(CONF_X10_ALL_LIGHTS_ON)
x10_all_lights_off_housecode = conf.get(CONF_X10_ALL_LIGHTS_OFF)
if host:
_LOGGER.info("Connecting to Insteon Hub on %s", host)
conn = await insteonplm.Connection.create(
_LOGGER.info("Connecting to Insteon Hub on %s:%d", host, ip_port)
else:
_LOGGER.info("Connecting to Insteon PLM on %s", port)
try:
await async_connect(
device=port,
host=host,
port=ip_port,
username=username,
password=password,
hub_version=hub_version,
loop=hass.loop,
workdir=hass.config.config_dir,
)
else:
_LOGGER.info("Looking for Insteon PLM on %s", port)
conn = await insteonplm.Connection.create(
device=port, loop=hass.loop, workdir=hass.config.config_dir
)
except ConnectionError:
_LOGGER.error("Could not connect to Insteon modem")
return False
_LOGGER.info("Connection to Insteon modem successful")
insteon_modem = conn.protocol
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, close_insteon_connection)
conf = config[DOMAIN]
overrides = conf.get(CONF_OVERRIDE, [])
x10_devices = conf.get(CONF_X10, [])
hass.data[DOMAIN] = {}
hass.data[DOMAIN]["modem"] = insteon_modem
hass.data[DOMAIN][INSTEON_ENTITIES] = set()
register_new_device_callback(hass, config, insteon_modem)
async_register_services(hass, config, insteon_modem)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, conn.close)
await devices.async_load(
workdir=hass.config.config_dir, id_devices=0, load_modem_aldb=0
)
for device_override in overrides:
#
# Override the device default capabilities for a specific address
#
address = device_override.get("address")
for prop in device_override:
if prop in [CONF_CAT, CONF_SUBCAT]:
insteon_modem.devices.add_override(address, prop, device_override[prop])
elif prop in [CONF_FIRMWARE, CONF_PRODUCT_KEY]:
insteon_modem.devices.add_override(
address, CONF_PRODUCT_KEY, device_override[prop]
)
if not devices.get(address):
cat = device_override[CONF_CAT]
subcat = device_override[CONF_SUBCAT]
firmware = device_override.get(CONF_FIRMWARE)
if firmware is None:
firmware = device_override.get(CONF_PRODUCT_KEY, 0)
devices.set_id(address, cat, subcat, firmware)
if x10_all_units_off_housecode:
device = insteon_modem.add_x10_device(
x10_all_units_off_housecode, 20, "allunitsoff"
)
if x10_all_lights_on_housecode:
device = insteon_modem.add_x10_device(
x10_all_lights_on_housecode, 21, "alllightson"
)
if x10_all_lights_off_housecode:
device = insteon_modem.add_x10_device(
x10_all_lights_off_housecode, 22, "alllightsoff"
)
for device in x10_devices:
housecode = device.get(CONF_HOUSECODE)
unitcode = device.get(CONF_UNITCODE)
x10_type = "onoff"
x10_type = "on_off"
steps = device.get(CONF_DIM_STEPS, 22)
if device.get(CONF_PLATFORM) == "light":
x10_type = "dimmable"
@ -117,8 +154,7 @@ async def async_setup(hass, config):
_LOGGER.debug(
"Adding X10 device to Insteon: %s %d %s", housecode, unitcode, x10_type
)
device = insteon_modem.add_x10_device(housecode, unitcode, x10_type)
if device and hasattr(device.states[0x01], "steps"):
device.states[0x01].steps = steps
device = devices.add_x10_device(housecode, unitcode, x10_type, steps)
asyncio.create_task(async_setup_platforms(hass, config))
return True

View File

@ -1,50 +1,69 @@
"""Support for INSTEON dimmers via PowerLinc Modem."""
import logging
from homeassistant.components.binary_sensor import BinarySensorEntity
from pyinsteon.groups import (
CO_SENSOR,
DOOR_SENSOR,
HEARTBEAT,
LEAK_SENSOR_WET,
LIGHT_SENSOR,
LOW_BATTERY,
MOTION_SENSOR,
OPEN_CLOSE_SENSOR,
SENSOR_MALFUNCTION,
SMOKE_SENSOR,
TEST_SENSOR,
)
from homeassistant.components.binary_sensor import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_DOOR,
DEVICE_CLASS_GAS,
DEVICE_CLASS_LIGHT,
DEVICE_CLASS_MOISTURE,
DEVICE_CLASS_MOTION,
DEVICE_CLASS_OPENING,
DEVICE_CLASS_PROBLEM,
DEVICE_CLASS_SAFETY,
DEVICE_CLASS_SMOKE,
DOMAIN,
BinarySensorEntity,
)
from .insteon_entity import InsteonEntity
from .utils import async_add_insteon_entities
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
"openClosedSensor": "opening",
"ioLincSensor": "opening",
"motionSensor": "motion",
"doorSensor": "door",
"wetLeakSensor": "moisture",
"lightSensor": "light",
"batterySensor": "battery",
OPEN_CLOSE_SENSOR: DEVICE_CLASS_OPENING,
MOTION_SENSOR: DEVICE_CLASS_MOTION,
DOOR_SENSOR: DEVICE_CLASS_DOOR,
LEAK_SENSOR_WET: DEVICE_CLASS_MOISTURE,
LIGHT_SENSOR: DEVICE_CLASS_LIGHT,
LOW_BATTERY: DEVICE_CLASS_BATTERY,
CO_SENSOR: DEVICE_CLASS_GAS,
SMOKE_SENSOR: DEVICE_CLASS_SMOKE,
TEST_SENSOR: DEVICE_CLASS_SAFETY,
SENSOR_MALFUNCTION: DEVICE_CLASS_PROBLEM,
HEARTBEAT: DEVICE_CLASS_PROBLEM,
}
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the INSTEON device class for the hass platform."""
insteon_modem = hass.data["insteon"].get("modem")
address = discovery_info["address"]
device = insteon_modem.devices[address]
state_key = discovery_info["state_key"]
name = device.states[state_key].name
if name != "dryLeakSensor":
_LOGGER.debug(
"Adding device %s entity %s to Binary Sensor platform",
device.address.hex,
name,
)
new_entity = InsteonBinarySensor(device, state_key)
async_add_entities([new_entity])
"""Set up the INSTEON entity class for the hass platform."""
async_add_insteon_entities(
hass, DOMAIN, InsteonBinarySensorEntity, async_add_entities, discovery_info
)
class InsteonBinarySensor(InsteonEntity, BinarySensorEntity):
"""A Class for an Insteon device entity."""
class InsteonBinarySensorEntity(InsteonEntity, BinarySensorEntity):
"""A Class for an Insteon binary sensor entity."""
def __init__(self, device, state_key):
def __init__(self, device, group):
"""Initialize the INSTEON binary sensor."""
super().__init__(device, state_key)
self._sensor_type = SENSOR_TYPES.get(self._insteon_device_state.name)
super().__init__(device, group)
self._sensor_type = SENSOR_TYPES.get(self._insteon_device_group.name)
@property
def device_class(self):
@ -54,9 +73,4 @@ class InsteonBinarySensor(InsteonEntity, BinarySensorEntity):
@property
def is_on(self):
"""Return the boolean response if the node is on."""
on_val = bool(self._insteon_device_state.value)
if self._insteon_device_state.name in ["lightSensor", "ioLincSensor"]:
return not on_val
return on_val
return bool(self._insteon_device_group.value)

View File

@ -1,7 +1,46 @@
"""Constants used by insteon component."""
from pyinsteon.groups import (
CO_SENSOR,
COVER,
DIMMABLE_FAN,
DIMMABLE_LIGHT,
DIMMABLE_LIGHT_MAIN,
DIMMABLE_OUTLET,
DOOR_SENSOR,
HEARTBEAT,
LEAK_SENSOR_WET,
LIGHT_SENSOR,
LOW_BATTERY,
MOTION_SENSOR,
NEW_SENSOR,
ON_OFF_OUTLET_BOTTOM,
ON_OFF_OUTLET_TOP,
ON_OFF_SWITCH,
ON_OFF_SWITCH_A,
ON_OFF_SWITCH_B,
ON_OFF_SWITCH_C,
ON_OFF_SWITCH_D,
ON_OFF_SWITCH_E,
ON_OFF_SWITCH_F,
ON_OFF_SWITCH_G,
ON_OFF_SWITCH_H,
ON_OFF_SWITCH_MAIN,
OPEN_CLOSE_SENSOR,
RELAY,
SENSOR_MALFUNCTION,
SMOKE_SENSOR,
TEST_SENSOR,
)
DOMAIN = "insteon"
INSTEON_ENTITIES = "entities"
INSTEON_COMPONENTS = [
"binary_sensor",
"cover",
"fan",
"light",
"switch",
]
CONF_IP_PORT = "ip_port"
CONF_HUB_USERNAME = "username"
@ -40,6 +79,7 @@ SRV_SCENE_OFF = "scene_off"
SIGNAL_LOAD_ALDB = "load_aldb"
SIGNAL_PRINT_ALDB = "print_aldb"
SIGNAL_SAVE_DEVICES = "save_devices"
HOUSECODES = [
"a",
@ -60,47 +100,42 @@ HOUSECODES = [
"p",
]
BUTTON_PRESSED_STATE_NAME = "onLevelButton"
EVENT_BUTTON_ON = "insteon.button_on"
EVENT_BUTTON_OFF = "insteon.button_off"
EVENT_GROUP_ON = "insteon.button_on"
EVENT_GROUP_OFF = "insteon.button_off"
EVENT_GROUP_ON_FAST = "insteon.button_on_fast"
EVENT_GROUP_OFF_FAST = "insteon.button_off_fast"
EVENT_CONF_BUTTON = "button"
ON_OFF_EVENTS = "on_off_events"
STATE_NAME_LABEL_MAP = {
"keypadButtonA": "Button A",
"keypadButtonB": "Button B",
"keypadButtonC": "Button C",
"keypadButtonD": "Button D",
"keypadButtonE": "Button E",
"keypadButtonF": "Button F",
"keypadButtonG": "Button G",
"keypadButtonH": "Button H",
"keypadButtonMain": "Main",
"onOffButtonA": "Button A",
"onOffButtonB": "Button B",
"onOffButtonC": "Button C",
"onOffButtonD": "Button D",
"onOffButtonE": "Button E",
"onOffButtonF": "Button F",
"onOffButtonG": "Button G",
"onOffButtonH": "Button H",
"onOffButtonMain": "Main",
"fanOnLevel": "Fan",
"lightOnLevel": "Light",
"coolSetPoint": "Cool Set",
"heatSetPoint": "HeatSet",
"statusReport": "Status",
"generalSensor": "Sensor",
"motionSensor": "Motion",
"lightSensor": "Light",
"batterySensor": "Battery",
"dryLeakSensor": "Dry",
"wetLeakSensor": "Wet",
"heartbeatLeakSensor": "Heartbeat",
"openClosedRelay": "Relay",
"openClosedSensor": "Sensor",
"lightOnOff": "Light",
"outletTopOnOff": "Top",
"outletBottomOnOff": "Bottom",
"coverOpenLevel": "Cover",
DIMMABLE_LIGHT_MAIN: "Main",
ON_OFF_SWITCH_A: "Button A",
ON_OFF_SWITCH_B: "Button B",
ON_OFF_SWITCH_C: "Button C",
ON_OFF_SWITCH_D: "Button D",
ON_OFF_SWITCH_E: "Button E",
ON_OFF_SWITCH_F: "Button F",
ON_OFF_SWITCH_G: "Button G",
ON_OFF_SWITCH_H: "Button H",
ON_OFF_SWITCH_MAIN: "Main",
DIMMABLE_FAN: "Fan",
DIMMABLE_LIGHT: "Light",
DIMMABLE_OUTLET: "Outlet",
MOTION_SENSOR: "Motion",
LIGHT_SENSOR: "Light",
LOW_BATTERY: "Battery",
LEAK_SENSOR_WET: "Wet",
DOOR_SENSOR: "Door",
SMOKE_SENSOR: "Smoke",
CO_SENSOR: "Carbon Monoxide",
TEST_SENSOR: "Test",
NEW_SENSOR: "New",
SENSOR_MALFUNCTION: "Malfunction",
HEARTBEAT: "Heartbeat",
OPEN_CLOSE_SENSOR: "Sensor",
ON_OFF_SWITCH: "Light",
ON_OFF_OUTLET_TOP: "Top",
ON_OFF_OUTLET_BOTTOM: "Bottom",
COVER: "Cover",
RELAY: "Relay",
}

View File

@ -4,6 +4,7 @@ import math
from homeassistant.components.cover import (
ATTR_POSITION,
DOMAIN,
SUPPORT_CLOSE,
SUPPORT_OPEN,
SUPPORT_SET_POSITION,
@ -11,6 +12,7 @@ from homeassistant.components.cover import (
)
from .insteon_entity import InsteonEntity
from .utils import async_add_insteon_entities
_LOGGER = logging.getLogger(__name__)
@ -19,33 +21,18 @@ SUPPORTED_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the Insteon platform."""
if not discovery_info:
return
insteon_modem = hass.data["insteon"].get("modem")
address = discovery_info["address"]
device = insteon_modem.devices[address]
state_key = discovery_info["state_key"]
_LOGGER.debug(
"Adding device %s entity %s to Cover platform",
device.address.hex,
device.states[state_key].name,
async_add_insteon_entities(
hass, DOMAIN, InsteonCoverEntity, async_add_entities, discovery_info
)
new_entity = InsteonCoverEntity(device, state_key)
async_add_entities([new_entity])
class InsteonCoverEntity(InsteonEntity, CoverEntity):
"""A Class for an Insteon device."""
"""A Class for an Insteon cover entity."""
@property
def current_cover_position(self):
"""Return the current cover position."""
return int(math.ceil(self._insteon_device_state.value * 100 / 255))
return int(math.ceil(self._insteon_device_group.value * 100 / 255))
@property
def supported_features(self):
@ -58,17 +45,19 @@ class InsteonCoverEntity(InsteonEntity, CoverEntity):
return bool(self.current_cover_position)
async def async_open_cover(self, **kwargs):
"""Open device."""
self._insteon_device_state.open()
"""Open cover."""
await self._insteon_device.async_open()
async def async_close_cover(self, **kwargs):
"""Close device."""
self._insteon_device_state.close()
"""Close cover."""
await self._insteon_device.async_close()
async def async_set_cover_position(self, **kwargs):
"""Set the cover position."""
position = int(kwargs[ATTR_POSITION] * 255 / 100)
if position == 0:
self._insteon_device_state.close()
await self._insteon_device.async_close()
else:
self._insteon_device_state.set_position(position)
await self._insteon_device.async_open(
position=position, group=self._insteon_device_group.group
)

View File

@ -1,7 +1,10 @@
"""Support for INSTEON fans via PowerLinc Modem."""
import logging
from pyinsteon.constants import FanSpeed
from homeassistant.components.fan import (
DOMAIN,
SPEED_HIGH,
SPEED_LOW,
SPEED_MEDIUM,
@ -9,43 +12,40 @@ from homeassistant.components.fan import (
SUPPORT_SET_SPEED,
FanEntity,
)
from homeassistant.const import STATE_OFF
from .insteon_entity import InsteonEntity
from .utils import async_add_insteon_entities
_LOGGER = logging.getLogger(__name__)
SPEED_TO_HEX = {SPEED_OFF: 0x00, SPEED_LOW: 0x3F, SPEED_MEDIUM: 0xBE, SPEED_HIGH: 0xFF}
FAN_SPEEDS = [STATE_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
FAN_SPEEDS = [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
SPEED_TO_VALUE = {
SPEED_OFF: FanSpeed.OFF,
SPEED_LOW: FanSpeed.LOW,
SPEED_MEDIUM: FanSpeed.MEDIUM,
SPEED_HIGH: FanSpeed.HIGH,
}
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the INSTEON device class for the hass platform."""
insteon_modem = hass.data["insteon"].get("modem")
address = discovery_info["address"]
device = insteon_modem.devices[address]
state_key = discovery_info["state_key"]
_LOGGER.debug(
"Adding device %s entity %s to Fan platform",
device.address.hex,
device.states[state_key].name,
"""Set up the INSTEON entity class for the hass platform."""
async_add_insteon_entities(
hass, DOMAIN, InsteonFanEntity, async_add_entities, discovery_info
)
new_entity = InsteonFan(device, state_key)
async_add_entities([new_entity])
class InsteonFan(InsteonEntity, FanEntity):
"""An INSTEON fan component."""
class InsteonFanEntity(InsteonEntity, FanEntity):
"""An INSTEON fan entity."""
@property
def speed(self) -> str:
"""Return the current speed."""
return self._hex_to_speed(self._insteon_device_state.value)
if self._insteon_device_group.value == FanSpeed.HIGH:
return SPEED_HIGH
if self._insteon_device_group.value == FanSpeed.MEDIUM:
return SPEED_MEDIUM
if self._insteon_device_group.value == FanSpeed.LOW:
return SPEED_LOW
return SPEED_OFF
@property
def speed_list(self) -> list:
@ -58,30 +58,19 @@ class InsteonFan(InsteonEntity, FanEntity):
return SUPPORT_SET_SPEED
async def async_turn_on(self, speed: str = None, **kwargs) -> None:
"""Turn on the entity."""
"""Turn on the fan."""
if speed is None:
speed = SPEED_MEDIUM
await self.async_set_speed(speed)
async def async_turn_off(self, **kwargs) -> None:
"""Turn off the entity."""
await self.async_set_speed(SPEED_OFF)
"""Turn off the fan."""
await self._insteon_device.async_fan_off()
async def async_set_speed(self, speed: str) -> None:
"""Set the speed of the fan."""
fan_speed = SPEED_TO_HEX[speed]
if fan_speed == 0x00:
self._insteon_device_state.off()
fan_speed = SPEED_TO_VALUE[speed]
if fan_speed == FanSpeed.OFF:
await self._insteon_device.async_fan_off()
else:
self._insteon_device_state.set_level(fan_speed)
@staticmethod
def _hex_to_speed(speed: int):
hex_speed = SPEED_OFF
if speed > 0xFE:
hex_speed = SPEED_HIGH
elif speed > 0x7F:
hex_speed = SPEED_MEDIUM
elif speed > 0:
hex_speed = SPEED_LOW
return hex_speed
await self._insteon_device.async_fan_on(on_level=fan_speed)

View File

@ -2,14 +2,16 @@
import logging
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
)
from homeassistant.helpers.entity import Entity
from .const import (
DOMAIN,
INSTEON_ENTITIES,
SIGNAL_LOAD_ALDB,
SIGNAL_PRINT_ALDB,
SIGNAL_SAVE_DEVICES,
STATE_NAME_LABEL_MAP,
)
from .utils import print_aldb_to_log
@ -20,11 +22,14 @@ _LOGGER = logging.getLogger(__name__)
class InsteonEntity(Entity):
"""INSTEON abstract base entity."""
def __init__(self, device, state_key):
def __init__(self, device, group):
"""Initialize the INSTEON binary sensor."""
self._insteon_device_state = device.states[state_key]
self._insteon_device_group = device.groups[group]
self._insteon_device = device
self._insteon_device.aldb.add_loaded_callback(self._aldb_loaded)
def __hash__(self):
"""Return the hash of the Insteon Entity."""
return hash(self._insteon_device)
@property
def should_poll(self):
@ -34,20 +39,20 @@ class InsteonEntity(Entity):
@property
def address(self):
"""Return the address of the node."""
return self._insteon_device.address.human
return str(self._insteon_device.address)
@property
def group(self):
"""Return the INSTEON group that the entity responds to."""
return self._insteon_device_state.group
return self._insteon_device_group.group
@property
def unique_id(self) -> str:
"""Return a unique ID."""
if self._insteon_device_state.group == 0x01:
if self._insteon_device_group.group == 0x01:
uid = self._insteon_device.id
else:
uid = f"{self._insteon_device.id}_{self._insteon_device_state.group}"
uid = f"{self._insteon_device.id}_{self._insteon_device_group.group}"
return uid
@property
@ -61,7 +66,7 @@ class InsteonEntity(Entity):
extension = self._get_label()
if extension:
extension = f" {extension}"
return f"{description} {self._insteon_device.address.human}{extension}"
return f"{description} {self._insteon_device.address}{extension}"
@property
def device_state_attributes(self):
@ -69,56 +74,45 @@ class InsteonEntity(Entity):
return {"insteon_address": self.address, "insteon_group": self.group}
@callback
def async_entity_update(self, deviceid, group, val):
def async_entity_update(self, name, address, value, group):
"""Receive notification from transport that new data exists."""
_LOGGER.debug(
"Received update for device %s group %d value %s",
deviceid.human,
group,
val,
"Received update for device %s group %d value %s", address, group, value,
)
self.async_write_ha_state()
async def async_added_to_hass(self):
"""Register INSTEON update events."""
_LOGGER.debug(
"Tracking updates for device %s group %d statename %s",
"Tracking updates for device %s group %d name %s",
self.address,
self.group,
self._insteon_device_state.name,
self._insteon_device_group.name,
)
self._insteon_device_state.register_updates(self.async_entity_update)
self.hass.data[DOMAIN][INSTEON_ENTITIES].add(self.entity_id)
self._insteon_device_group.subscribe(self.async_entity_update)
load_signal = f"{self.entity_id}_{SIGNAL_LOAD_ALDB}"
self.async_on_remove(
async_dispatcher_connect(self.hass, load_signal, self._load_aldb)
async_dispatcher_connect(self.hass, load_signal, self._async_read_aldb)
)
print_signal = f"{self.entity_id}_{SIGNAL_PRINT_ALDB}"
self.async_on_remove(
async_dispatcher_connect(self.hass, print_signal, self._print_aldb)
)
async_dispatcher_connect(self.hass, print_signal, self._print_aldb)
def _load_aldb(self, reload=False):
"""Load the device All-Link Database."""
if reload:
self._insteon_device.aldb.clear()
self._insteon_device.read_aldb()
async def _async_read_aldb(self, reload):
"""Call device load process and print to log."""
await self._insteon_device.aldb.async_load(refresh=reload)
self._print_aldb()
async_dispatcher_send(self.hass, SIGNAL_SAVE_DEVICES)
def _print_aldb(self):
"""Print the device ALDB to the log file."""
print_aldb_to_log(self._insteon_device.aldb)
@callback
def _aldb_loaded(self):
"""All-Link Database loaded for the device."""
self._print_aldb()
def _get_label(self):
"""Get the device label for grouped devices."""
label = ""
if len(self._insteon_device.states) > 1:
if self._insteon_device_state.name in STATE_NAME_LABEL_MAP:
label = STATE_NAME_LABEL_MAP[self._insteon_device_state.name]
if len(self._insteon_device.groups) > 1:
if self._insteon_device_group.name in STATE_NAME_LABEL_MAP:
label = STATE_NAME_LABEL_MAP[self._insteon_device_group.name]
else:
label = f"Group {self.group:d}"
return label

View File

@ -1,81 +1,112 @@
"""Insteon product database."""
import collections
"""Utility methods for the Insteon platform."""
import logging
from insteonplm.states.cover import Cover
from insteonplm.states.dimmable import (
DimmableKeypadA,
DimmableRemote,
DimmableSwitch,
DimmableSwitch_Fan,
)
from insteonplm.states.onOff import (
OnOffKeypad,
OnOffKeypadA,
OnOffSwitch,
OnOffSwitch_OutletBottom,
OnOffSwitch_OutletTop,
OpenClosedRelay,
)
from insteonplm.states.sensor import (
IoLincSensor,
LeakSensorDryWet,
OnOffSensor,
SmokeCO2Sensor,
VariableSensor,
)
from insteonplm.states.x10 import (
X10AllLightsOffSensor,
X10AllLightsOnSensor,
X10AllUnitsOffSensor,
X10DimmableSwitch,
from pyinsteon.device_types import (
DimmableLightingControl,
DimmableLightingControl_DinRail,
DimmableLightingControl_FanLinc,
DimmableLightingControl_InLineLinc,
DimmableLightingControl_KeypadLinc_6,
DimmableLightingControl_KeypadLinc_8,
DimmableLightingControl_LampLinc,
DimmableLightingControl_OutletLinc,
DimmableLightingControl_SwitchLinc,
DimmableLightingControl_ToggleLinc,
GeneralController_ControlLinc,
GeneralController_MiniRemote_4,
GeneralController_MiniRemote_8,
GeneralController_MiniRemote_Switch,
GeneralController_RemoteLinc,
SecurityHealthSafety_DoorSensor,
SecurityHealthSafety_LeakSensor,
SecurityHealthSafety_MotionSensor,
SecurityHealthSafety_OpenCloseSensor,
SecurityHealthSafety_Smokebridge,
SensorsActuators_IOLink,
SwitchedLightingControl,
SwitchedLightingControl_ApplianceLinc,
SwitchedLightingControl_DinRail,
SwitchedLightingControl_InLineLinc,
SwitchedLightingControl_KeypadLinc_6,
SwitchedLightingControl_KeypadLinc_8,
SwitchedLightingControl_OnOffOutlet,
SwitchedLightingControl_OutletLinc,
SwitchedLightingControl_SwitchLinc,
SwitchedLightingControl_ToggleLinc,
WindowCovering,
X10Dimmable,
X10OnOff,
X10OnOffSensor,
X10OnOffSwitch,
)
State = collections.namedtuple("Product", "stateType platform")
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR
from homeassistant.components.cover import DOMAIN as COVER
from homeassistant.components.fan import DOMAIN as FAN
from homeassistant.components.light import DOMAIN as LIGHT
from homeassistant.components.switch import DOMAIN as SWITCH
from .const import ON_OFF_EVENTS
_LOGGER = logging.getLogger(__name__)
DEVICE_PLATFORM = {
DimmableLightingControl: {LIGHT: [1], ON_OFF_EVENTS: [1]},
DimmableLightingControl_DinRail: {LIGHT: [1], ON_OFF_EVENTS: [1]},
DimmableLightingControl_FanLinc: {LIGHT: [1], FAN: [2], ON_OFF_EVENTS: [1, 2]},
DimmableLightingControl_InLineLinc: {LIGHT: [1], ON_OFF_EVENTS: [1]},
DimmableLightingControl_KeypadLinc_6: {
LIGHT: [1],
SWITCH: [3, 4, 5, 6],
ON_OFF_EVENTS: [1, 3, 4, 5, 6],
},
DimmableLightingControl_KeypadLinc_8: {
LIGHT: [1],
SWITCH: range(2, 9),
ON_OFF_EVENTS: range(1, 9),
},
DimmableLightingControl_LampLinc: {LIGHT: [1], ON_OFF_EVENTS: [1]},
DimmableLightingControl_OutletLinc: {LIGHT: [1], ON_OFF_EVENTS: [1]},
DimmableLightingControl_SwitchLinc: {LIGHT: [1], ON_OFF_EVENTS: [1]},
DimmableLightingControl_ToggleLinc: {LIGHT: [1], ON_OFF_EVENTS: [1]},
GeneralController_ControlLinc: {ON_OFF_EVENTS: [1]},
GeneralController_MiniRemote_4: {ON_OFF_EVENTS: range(1, 5)},
GeneralController_MiniRemote_8: {ON_OFF_EVENTS: range(1, 9)},
GeneralController_MiniRemote_Switch: {ON_OFF_EVENTS: [1, 2]},
GeneralController_RemoteLinc: {ON_OFF_EVENTS: [1]},
SecurityHealthSafety_DoorSensor: {BINARY_SENSOR: [1, 3, 4], ON_OFF_EVENTS: [1]},
SecurityHealthSafety_LeakSensor: {BINARY_SENSOR: [2, 4]},
SecurityHealthSafety_MotionSensor: {BINARY_SENSOR: [1, 2, 3], ON_OFF_EVENTS: [1]},
SecurityHealthSafety_OpenCloseSensor: {BINARY_SENSOR: [1]},
SecurityHealthSafety_Smokebridge: {BINARY_SENSOR: [1]},
SensorsActuators_IOLink: {SWITCH: [1], BINARY_SENSOR: [2], ON_OFF_EVENTS: [1, 2]},
SwitchedLightingControl: {SWITCH: [1], ON_OFF_EVENTS: [1]},
SwitchedLightingControl_ApplianceLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]},
SwitchedLightingControl_DinRail: {SWITCH: [1], ON_OFF_EVENTS: [1]},
SwitchedLightingControl_InLineLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]},
SwitchedLightingControl_KeypadLinc_6: {
SWITCH: [1, 3, 4, 5, 6],
ON_OFF_EVENTS: [1, 3, 4, 5, 6],
},
SwitchedLightingControl_KeypadLinc_8: {
SWITCH: range(1, 9),
ON_OFF_EVENTS: range(1, 9),
},
SwitchedLightingControl_OnOffOutlet: {SWITCH: [1, 2], ON_OFF_EVENTS: [1, 2]},
SwitchedLightingControl_OutletLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]},
SwitchedLightingControl_SwitchLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]},
SwitchedLightingControl_ToggleLinc: {SWITCH: [1], ON_OFF_EVENTS: [1]},
WindowCovering: {COVER: [1]},
X10Dimmable: {LIGHT: [1]},
X10OnOff: {SWITCH: [1]},
X10OnOffSensor: {BINARY_SENSOR: [1]},
}
class IPDB:
"""Embodies the INSTEON Product Database static data and access methods."""
def get_device_platforms(device):
"""Return the HA platforms for a device type."""
return DEVICE_PLATFORM.get(type(device), {}).keys()
def __init__(self):
"""Create the INSTEON Product Database (IPDB)."""
self.states = [
State(Cover, "cover"),
State(OnOffSwitch_OutletTop, "switch"),
State(OnOffSwitch_OutletBottom, "switch"),
State(OpenClosedRelay, "switch"),
State(OnOffSwitch, "switch"),
State(OnOffKeypadA, "switch"),
State(OnOffKeypad, "switch"),
State(LeakSensorDryWet, "binary_sensor"),
State(IoLincSensor, "binary_sensor"),
State(SmokeCO2Sensor, "sensor"),
State(OnOffSensor, "binary_sensor"),
State(VariableSensor, "sensor"),
State(DimmableSwitch_Fan, "fan"),
State(DimmableSwitch, "light"),
State(DimmableRemote, "on_off_events"),
State(DimmableKeypadA, "light"),
State(X10DimmableSwitch, "light"),
State(X10OnOffSwitch, "switch"),
State(X10OnOffSensor, "binary_sensor"),
State(X10AllUnitsOffSensor, "binary_sensor"),
State(X10AllLightsOnSensor, "binary_sensor"),
State(X10AllLightsOffSensor, "binary_sensor"),
]
def __len__(self):
"""Return the number of INSTEON state types mapped to HA platforms."""
return len(self.states)
def __iter__(self):
"""Itterate through the INSTEON state types to HA platforms."""
yield from self.states
def __getitem__(self, key):
"""Return a Home Assistant platform from an INSTEON state type."""
for state in self.states:
if isinstance(key, state.stateType):
return state
return None
def get_platform_groups(device, domain) -> dict:
"""Return the platforms that a device belongs in."""
return DEVICE_PLATFORM.get(type(device), {}).get(domain, {})

View File

@ -3,11 +3,13 @@ import logging
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
DOMAIN,
SUPPORT_BRIGHTNESS,
LightEntity,
)
from .insteon_entity import InsteonEntity
from .utils import async_add_insteon_entities
_LOGGER = logging.getLogger(__name__)
@ -16,31 +18,18 @@ MAX_BRIGHTNESS = 255
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the Insteon component."""
insteon_modem = hass.data["insteon"].get("modem")
address = discovery_info["address"]
device = insteon_modem.devices[address]
state_key = discovery_info["state_key"]
_LOGGER.debug(
"Adding device %s entity %s to Light platform",
device.address.hex,
device.states[state_key].name,
async_add_insteon_entities(
hass, DOMAIN, InsteonDimmerEntity, async_add_entities, discovery_info
)
new_entity = InsteonDimmerDevice(device, state_key)
async_add_entities([new_entity])
class InsteonDimmerDevice(InsteonEntity, LightEntity):
"""A Class for an Insteon device."""
class InsteonDimmerEntity(InsteonEntity, LightEntity):
"""A Class for an Insteon light entity."""
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
onlevel = self._insteon_device_state.value
return int(onlevel)
return self._insteon_device_group.value
@property
def is_on(self):
@ -53,13 +42,15 @@ class InsteonDimmerDevice(InsteonEntity, LightEntity):
return SUPPORT_BRIGHTNESS
async def async_turn_on(self, **kwargs):
"""Turn device on."""
"""Turn light on."""
if ATTR_BRIGHTNESS in kwargs:
brightness = int(kwargs[ATTR_BRIGHTNESS])
self._insteon_device_state.set_level(brightness)
await self._insteon_device.async_on(
on_level=brightness, group=self._insteon_device_group.group
)
else:
self._insteon_device_state.on()
await self._insteon_device.async_on(group=self._insteon_device_group.group)
async def async_turn_off(self, **kwargs):
"""Turn device off."""
self._insteon_device_state.off()
"""Turn light off."""
await self._insteon_device.async_off(self._insteon_device_group.group)

View File

@ -2,6 +2,6 @@
"domain": "insteon",
"name": "Insteon",
"documentation": "https://www.home-assistant.io/integrations/insteon",
"requirements": ["insteonplm==0.16.8"],
"codeowners": []
}
"requirements": ["pyinsteon==1.0.0"],
"codeowners": ["@teharris1"]
}

View File

@ -11,7 +11,6 @@ from homeassistant.const import (
CONF_PLATFORM,
CONF_PORT,
ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
)
import homeassistant.helpers.config_validation as cv
@ -57,7 +56,6 @@ def set_default_port(schema: Dict) -> Dict:
CONF_DEVICE_OVERRIDE_SCHEMA = vol.All(
cv.deprecated(CONF_PLATFORM),
vol.Schema(
{
vol.Required(CONF_ADDRESS): cv.string,
@ -86,6 +84,9 @@ CONF_X10_SCHEMA = vol.All(
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.All(
cv.deprecated(CONF_X10_ALL_UNITS_OFF),
cv.deprecated(CONF_X10_ALL_LIGHTS_ON),
cv.deprecated(CONF_X10_ALL_LIGHTS_OFF),
vol.Schema(
{
vol.Exclusive(
@ -101,9 +102,6 @@ CONFIG_SCHEMA = vol.Schema(
vol.Optional(CONF_OVERRIDE): vol.All(
cv.ensure_list_csv, [CONF_DEVICE_OVERRIDE_SCHEMA]
),
vol.Optional(CONF_X10_ALL_UNITS_OFF): vol.In(HOUSECODES),
vol.Optional(CONF_X10_ALL_LIGHTS_ON): vol.In(HOUSECODES),
vol.Optional(CONF_X10_ALL_LIGHTS_OFF): vol.In(HOUSECODES),
vol.Optional(CONF_X10): vol.All(
cv.ensure_list_csv, [CONF_X10_SCHEMA]
),
@ -134,9 +132,7 @@ DEL_ALL_LINK_SCHEMA = vol.Schema(
LOAD_ALDB_SCHEMA = vol.Schema(
{
vol.Required(CONF_ENTITY_ID): vol.Any(
cv.entity_id, ENTITY_MATCH_ALL, ENTITY_MATCH_NONE
),
vol.Required(CONF_ENTITY_ID): vol.Any(cv.entity_id, ENTITY_MATCH_ALL),
vol.Optional(SRV_LOAD_DB_RELOAD, default=False): cv.boolean,
}
)

View File

@ -1,31 +0,0 @@
"""Support for INSTEON dimmers via PowerLinc Modem."""
import logging
from homeassistant.helpers.entity import Entity
from .insteon_entity import InsteonEntity
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the INSTEON device class for the hass platform."""
insteon_modem = hass.data["insteon"].get("modem")
address = discovery_info["address"]
device = insteon_modem.devices[address]
state_key = discovery_info["state_key"]
_LOGGER.debug(
"Adding device %s entity %s to Sensor platform",
device.address.hex,
device.states[state_key].name,
)
new_entity = InsteonSensorDevice(device, state_key)
async_add_entities([new_entity])
class InsteonSensorDevice(InsteonEntity, Entity):
"""A Class for an Insteon device."""

View File

@ -1,66 +1,33 @@
"""Support for INSTEON dimmers via PowerLinc Modem."""
import logging
from homeassistant.components.switch import SwitchEntity
from homeassistant.components.switch import DOMAIN, SwitchEntity
from .insteon_entity import InsteonEntity
from .utils import async_add_insteon_entities
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the INSTEON device class for the hass platform."""
insteon_modem = hass.data["insteon"].get("modem")
address = discovery_info["address"]
device = insteon_modem.devices[address]
state_key = discovery_info["state_key"]
state_name = device.states[state_key].name
_LOGGER.debug(
"Adding device %s entity %s to Switch platform", device.address.hex, state_name,
"""Set up the INSTEON entity class for the hass platform."""
async_add_insteon_entities(
hass, DOMAIN, InsteonSwitchEntity, async_add_entities, discovery_info
)
new_entity = None
if state_name == "openClosedRelay":
new_entity = InsteonOpenClosedDevice(device, state_key)
else:
new_entity = InsteonSwitchDevice(device, state_key)
if new_entity is not None:
async_add_entities([new_entity])
class InsteonSwitchDevice(InsteonEntity, SwitchEntity):
"""A Class for an Insteon device."""
class InsteonSwitchEntity(InsteonEntity, SwitchEntity):
"""A Class for an Insteon switch entity."""
@property
def is_on(self):
"""Return the boolean response if the node is on."""
return bool(self._insteon_device_state.value)
return bool(self._insteon_device_group.value)
async def async_turn_on(self, **kwargs):
"""Turn device on."""
self._insteon_device_state.on()
"""Turn switch on."""
await self._insteon_device.async_on(group=self._insteon_device_group.group)
async def async_turn_off(self, **kwargs):
"""Turn device off."""
self._insteon_device_state.off()
class InsteonOpenClosedDevice(InsteonEntity, SwitchEntity):
"""A Class for an Insteon device."""
@property
def is_on(self):
"""Return the boolean response if the node is on."""
return bool(self._insteon_device_state.value)
async def async_turn_on(self, **kwargs):
"""Turn device on."""
self._insteon_device_state.open()
async def async_turn_off(self, **kwargs):
"""Turn device off."""
self._insteon_device_state.close()
"""Turn switch off."""
await self._insteon_device.async_off(group=self._insteon_device_group.group)

View File

@ -1,23 +1,44 @@
"""Utilities used by insteon component."""
import asyncio
import logging
from insteonplm.devices import ALDBStatus
from pyinsteon import devices
from pyinsteon.constants import ALDBStatus
from pyinsteon.events import OFF_EVENT, OFF_FAST_EVENT, ON_EVENT, ON_FAST_EVENT
from pyinsteon.managers.link_manager import (
async_enter_linking_mode,
async_enter_unlinking_mode,
)
from pyinsteon.managers.scene_manager import (
async_trigger_scene_off,
async_trigger_scene_on,
)
from pyinsteon.managers.x10_manager import (
async_x10_all_lights_off,
async_x10_all_lights_on,
async_x10_all_units_off,
)
from homeassistant.const import CONF_ADDRESS, CONF_ENTITY_ID, ENTITY_MATCH_ALL
from homeassistant.core import callback
from homeassistant.helpers import discovery
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
dispatcher_send,
)
from .const import (
BUTTON_PRESSED_STATE_NAME,
DOMAIN,
EVENT_BUTTON_OFF,
EVENT_BUTTON_ON,
EVENT_CONF_BUTTON,
INSTEON_ENTITIES,
EVENT_GROUP_OFF,
EVENT_GROUP_OFF_FAST,
EVENT_GROUP_ON,
EVENT_GROUP_ON_FAST,
ON_OFF_EVENTS,
SIGNAL_LOAD_ALDB,
SIGNAL_PRINT_ALDB,
SIGNAL_SAVE_DEVICES,
SRV_ADD_ALL_LINK,
SRV_ALL_LINK_GROUP,
SRV_ALL_LINK_MODE,
@ -34,7 +55,7 @@ from .const import (
SRV_X10_ALL_LIGHTS_ON,
SRV_X10_ALL_UNITS_OFF,
)
from .ipdb import IPDB
from .ipdb import get_device_platforms, get_platform_groups
from .schemas import (
ADD_ALL_LINK_SCHEMA,
DEL_ALL_LINK_SCHEMA,
@ -47,91 +68,129 @@ from .schemas import (
_LOGGER = logging.getLogger(__name__)
def register_new_device_callback(hass, config, insteon_modem):
"""Register callback for new Insteon device."""
def _fire_button_on_off_event(address, group, val):
# Firing an event when a button is pressed.
device = insteon_modem.devices[address.hex]
state_name = device.states[group].name
button = (
"" if state_name == BUTTON_PRESSED_STATE_NAME else state_name[-1].lower()
)
schema = {CONF_ADDRESS: address.hex}
if button:
schema[EVENT_CONF_BUTTON] = button
event = EVENT_BUTTON_ON if val else EVENT_BUTTON_OFF
_LOGGER.debug(
"Firing event %s with address %s and button %s", event, address.hex, button
)
hass.bus.fire(event, schema)
def add_on_off_event_device(hass, device):
"""Register an Insteon device as an on/off event device."""
@callback
def async_new_insteon_device(device):
def async_fire_group_on_off_event(name, address, group, button):
# Firing an event when a button is pressed.
if button and button[-2] == "_":
button_id = button[-1].lower()
else:
button_id = None
schema = {CONF_ADDRESS: address}
if button_id:
schema[EVENT_CONF_BUTTON] = button_id
if name == ON_EVENT:
event = EVENT_GROUP_ON
if name == OFF_EVENT:
event = EVENT_GROUP_OFF
if name == ON_FAST_EVENT:
event = EVENT_GROUP_ON_FAST
if name == OFF_FAST_EVENT:
event = EVENT_GROUP_OFF_FAST
_LOGGER.debug("Firing event %s with %s", event, schema)
hass.bus.async_fire(event, schema)
for group in device.events:
if isinstance(group, int):
for event in device.events[group]:
if event in [
OFF_EVENT,
ON_EVENT,
OFF_FAST_EVENT,
ON_FAST_EVENT,
]:
_LOGGER.debug(
"Registering on/off event for %s %d %s",
str(device.address),
group,
event,
)
device.events[group][event].subscribe(
async_fire_group_on_off_event, force_strong_ref=True
)
def register_new_device_callback(hass, config):
"""Register callback for new Insteon device."""
new_device_lock = asyncio.Lock()
@callback
def async_new_insteon_device(address=None):
"""Detect device from transport to be delegated to platform."""
ipdb = IPDB()
for state_key in device.states:
platform_info = ipdb[device.states[state_key]]
if platform_info and platform_info.platform:
platform = platform_info.platform
hass.async_create_task(async_create_new_entities(address))
if platform == "on_off_events":
device.states[state_key].register_updates(_fire_button_on_off_event)
async def async_create_new_entities(address):
_LOGGER.debug(
"Adding new INSTEON device to Home Assistant with address %s", address
)
async with new_device_lock:
await devices.async_save(workdir=hass.config.config_dir)
device = devices[address]
await device.async_status()
platforms = get_device_platforms(device)
tasks = []
for platform in platforms:
if platform == ON_OFF_EVENTS:
add_on_off_event_device(hass, device)
else:
_LOGGER.info(
"New INSTEON device: %s (%s) %s",
device.address,
device.states[state_key].name,
else:
tasks.append(
discovery.async_load_platform(
hass,
platform,
DOMAIN,
discovered={"address": device.address.id},
hass_config=config,
)
)
await asyncio.gather(*tasks)
hass.async_create_task(
discovery.async_load_platform(
hass,
platform,
DOMAIN,
discovered={
"address": device.address.id,
"state_key": state_key,
},
hass_config=config,
)
)
insteon_modem.devices.add_device_callback(async_new_insteon_device)
devices.subscribe(async_new_insteon_device, force_strong_ref=True)
@callback
def async_register_services(hass, config, insteon_modem):
def async_register_services(hass):
"""Register services used by insteon component."""
def add_all_link(service):
async def async_srv_add_all_link(service):
"""Add an INSTEON All-Link between two devices."""
group = service.data.get(SRV_ALL_LINK_GROUP)
mode = service.data.get(SRV_ALL_LINK_MODE)
link_mode = 1 if mode.lower() == SRV_CONTROLLER else 0
insteon_modem.start_all_linking(link_mode, group)
link_mode = mode.lower() == SRV_CONTROLLER
await async_enter_linking_mode(link_mode, group)
def del_all_link(service):
async def async_srv_del_all_link(service):
"""Delete an INSTEON All-Link between two devices."""
group = service.data.get(SRV_ALL_LINK_GROUP)
insteon_modem.start_all_linking(255, group)
await async_enter_unlinking_mode(group)
def load_aldb(service):
async def async_srv_load_aldb(service):
"""Load the device All-Link database."""
entity_id = service.data[CONF_ENTITY_ID]
reload = service.data[SRV_LOAD_DB_RELOAD]
if entity_id.lower() == ENTITY_MATCH_ALL:
for entity_id in hass.data[DOMAIN][INSTEON_ENTITIES]:
_send_load_aldb_signal(entity_id, reload)
await async_srv_load_aldb_all(reload)
else:
_send_load_aldb_signal(entity_id, reload)
signal = f"{entity_id}_{SIGNAL_LOAD_ALDB}"
async_dispatcher_send(hass, signal, reload)
def _send_load_aldb_signal(entity_id, reload):
"""Send the load All-Link database signal to INSTEON entity."""
signal = f"{entity_id}_{SIGNAL_LOAD_ALDB}"
dispatcher_send(hass, signal, reload)
async def async_srv_load_aldb_all(reload):
"""Load the All-Link database for all devices."""
# Cannot be done concurrently due to issues with the underlying protocol.
for address in devices:
device = devices[address]
if device != devices.modem and device.cat != 0x03:
await device.aldb.async_load(
refresh=reload, callback=async_srv_save_devices
)
async def async_srv_save_devices():
"""Write the Insteon device configuration to file."""
_LOGGER.debug("Saving Insteon devices")
await devices.async_save(hass.config.config_dir)
def print_aldb(service):
"""Print the All-Link Database for a device."""
@ -145,71 +204,85 @@ def async_register_services(hass, config, insteon_modem):
"""Print the All-Link Database for a device."""
# For now this sends logs to the log file.
# Future direction is to create an INSTEON control panel.
print_aldb_to_log(insteon_modem.aldb)
print_aldb_to_log(devices.modem.aldb)
def x10_all_units_off(service):
async def async_srv_x10_all_units_off(service):
"""Send the X10 All Units Off command."""
housecode = service.data.get(SRV_HOUSECODE)
insteon_modem.x10_all_units_off(housecode)
await async_x10_all_units_off(housecode)
def x10_all_lights_off(service):
async def async_srv_x10_all_lights_off(service):
"""Send the X10 All Lights Off command."""
housecode = service.data.get(SRV_HOUSECODE)
insteon_modem.x10_all_lights_off(housecode)
await async_x10_all_lights_off(housecode)
def x10_all_lights_on(service):
async def async_srv_x10_all_lights_on(service):
"""Send the X10 All Lights On command."""
housecode = service.data.get(SRV_HOUSECODE)
insteon_modem.x10_all_lights_on(housecode)
await async_x10_all_lights_on(housecode)
def scene_on(service):
async def async_srv_scene_on(service):
"""Trigger an INSTEON scene ON."""
group = service.data.get(SRV_ALL_LINK_GROUP)
insteon_modem.trigger_group_on(group)
await async_trigger_scene_on(group)
def scene_off(service):
async def async_srv_scene_off(service):
"""Trigger an INSTEON scene ON."""
group = service.data.get(SRV_ALL_LINK_GROUP)
insteon_modem.trigger_group_off(group)
await async_trigger_scene_off(group)
hass.services.async_register(
DOMAIN, SRV_ADD_ALL_LINK, add_all_link, schema=ADD_ALL_LINK_SCHEMA
DOMAIN, SRV_ADD_ALL_LINK, async_srv_add_all_link, schema=ADD_ALL_LINK_SCHEMA
)
hass.services.async_register(
DOMAIN, SRV_DEL_ALL_LINK, del_all_link, schema=DEL_ALL_LINK_SCHEMA
DOMAIN, SRV_DEL_ALL_LINK, async_srv_del_all_link, schema=DEL_ALL_LINK_SCHEMA
)
hass.services.async_register(
DOMAIN, SRV_LOAD_ALDB, load_aldb, schema=LOAD_ALDB_SCHEMA
DOMAIN, SRV_LOAD_ALDB, async_srv_load_aldb, schema=LOAD_ALDB_SCHEMA
)
hass.services.async_register(
DOMAIN, SRV_PRINT_ALDB, print_aldb, schema=PRINT_ALDB_SCHEMA
)
hass.services.async_register(DOMAIN, SRV_PRINT_IM_ALDB, print_im_aldb, schema=None)
hass.services.async_register(
DOMAIN, SRV_X10_ALL_UNITS_OFF, x10_all_units_off, schema=X10_HOUSECODE_SCHEMA
DOMAIN,
SRV_X10_ALL_UNITS_OFF,
async_srv_x10_all_units_off,
schema=X10_HOUSECODE_SCHEMA,
)
hass.services.async_register(
DOMAIN, SRV_X10_ALL_LIGHTS_OFF, x10_all_lights_off, schema=X10_HOUSECODE_SCHEMA
DOMAIN,
SRV_X10_ALL_LIGHTS_OFF,
async_srv_x10_all_lights_off,
schema=X10_HOUSECODE_SCHEMA,
)
hass.services.async_register(
DOMAIN, SRV_X10_ALL_LIGHTS_ON, x10_all_lights_on, schema=X10_HOUSECODE_SCHEMA
DOMAIN,
SRV_X10_ALL_LIGHTS_ON,
async_srv_x10_all_lights_on,
schema=X10_HOUSECODE_SCHEMA,
)
hass.services.async_register(
DOMAIN, SRV_SCENE_ON, scene_on, schema=TRIGGER_SCENE_SCHEMA
DOMAIN, SRV_SCENE_ON, async_srv_scene_on, schema=TRIGGER_SCENE_SCHEMA
)
hass.services.async_register(
DOMAIN, SRV_SCENE_OFF, scene_off, schema=TRIGGER_SCENE_SCHEMA
DOMAIN, SRV_SCENE_OFF, async_srv_scene_off, schema=TRIGGER_SCENE_SCHEMA
)
async_dispatcher_connect(hass, SIGNAL_SAVE_DEVICES, async_srv_save_devices)
_LOGGER.debug("Insteon Services registered")
def print_aldb_to_log(aldb):
"""Print the All-Link Database to the log file."""
_LOGGER.info("ALDB load status is %s", aldb.status.name)
# This service is useless if the log level is not INFO for the
# insteon component. Setting the log level to INFO and resetting it
# back when we are done
orig_log_level = _LOGGER.level
if orig_log_level > logging.INFO:
_LOGGER.setLevel(logging.INFO)
_LOGGER.info("%s ALDB load status is %s", aldb.address, aldb.status.name)
if aldb.status not in [ALDBStatus.LOADED, ALDBStatus.PARTIAL]:
_LOGGER.warning("Device All-Link database not loaded")
_LOGGER.warning("Use service insteon.load_aldb first")
return
_LOGGER.warning("All-Link database not loaded")
_LOGGER.info("RecID In Use Mode HWM Group Address Data 1 Data 2 Data 3")
_LOGGER.info("----- ------ ---- --- ----- -------- ------ ------ ------")
@ -217,12 +290,30 @@ def print_aldb_to_log(aldb):
rec = aldb[mem_addr]
# For now we write this to the log
# Roadmap is to create a configuration panel
in_use = "Y" if rec.control_flags.is_in_use else "N"
mode = "C" if rec.control_flags.is_controller else "R"
hwm = "Y" if rec.control_flags.is_high_water_mark else "N"
in_use = "Y" if rec.is_in_use else "N"
mode = "C" if rec.is_controller else "R"
hwm = "Y" if rec.is_high_water_mark else "N"
log_msg = (
f" {rec.mem_addr:04x} {in_use:s} {mode:s} {hwm:s} "
f"{rec.group:3d} {rec.address.human:s} {rec.data1:3d} "
f"{rec.group:3d} {str(rec.target):s} {rec.data1:3d} "
f"{rec.data2:3d} {rec.data3:3d}"
)
_LOGGER.info(log_msg)
_LOGGER.setLevel(orig_log_level)
@callback
def async_add_insteon_entities(
hass, platform, entity_type, async_add_entities, discovery_info
):
"""Add Insteon devices to a platform."""
new_entities = []
device_list = [discovery_info.get("address")] if discovery_info else devices
for address in device_list:
device = devices[address]
groups = get_platform_groups(device, platform)
for group in groups:
new_entities.append(entity_type(device, group))
if new_entities:
async_add_entities(new_entities)

View File

@ -787,9 +787,6 @@ incomfort-client==0.4.0
# homeassistant.components.influxdb
influxdb==5.2.3
# homeassistant.components.insteon
insteonplm==0.16.8
# homeassistant.components.iperf3
iperf3==0.1.11
@ -1377,6 +1374,9 @@ pyialarm==0.3
# homeassistant.components.icloud
pyicloud==0.9.7
# homeassistant.components.insteon
pyinsteon==1.0.0
# homeassistant.components.intesishome
pyintesishome==1.7.4