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
pull/35739/head
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_number/* @home-assistant/core
homeassistant/components/input_select/* @home-assistant/core homeassistant/components/input_select/* @home-assistant/core
homeassistant/components/input_text/* @home-assistant/core homeassistant/components/input_text/* @home-assistant/core
homeassistant/components/insteon/* @teharris1
homeassistant/components/integration/* @dgomes homeassistant/components/integration/* @dgomes
homeassistant/components/intent/* @home-assistant/core homeassistant/components/intent/* @home-assistant/core
homeassistant/components/intesishome/* @jnimmo homeassistant/components/intesishome/* @jnimmo

View File

@ -1,7 +1,8 @@
"""Support for INSTEON Modems (PLM and Hub).""" """Support for INSTEON Modems (PLM and Hub)."""
import asyncio
import logging import logging
import insteonplm from pyinsteon import async_close, async_connect, devices
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_HOST,
@ -24,21 +25,75 @@ from .const import (
CONF_SUBCAT, CONF_SUBCAT,
CONF_UNITCODE, CONF_UNITCODE,
CONF_X10, CONF_X10,
CONF_X10_ALL_LIGHTS_OFF,
CONF_X10_ALL_LIGHTS_ON,
CONF_X10_ALL_UNITS_OFF,
DOMAIN, DOMAIN,
INSTEON_ENTITIES, INSTEON_COMPONENTS,
ON_OFF_EVENTS,
) )
from .schemas import CONFIG_SCHEMA # noqa F440 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__) _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): async def async_setup(hass, config):
"""Set up the connection to the modem.""" """Set up the connection to the modem."""
insteon_modem = None
conf = config[DOMAIN] conf = config[DOMAIN]
port = conf.get(CONF_PORT) port = conf.get(CONF_PORT)
@ -47,68 +102,50 @@ async def async_setup(hass, config):
username = conf.get(CONF_HUB_USERNAME) username = conf.get(CONF_HUB_USERNAME)
password = conf.get(CONF_HUB_PASSWORD) password = conf.get(CONF_HUB_PASSWORD)
hub_version = conf.get(CONF_HUB_VERSION) 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: if host:
_LOGGER.info("Connecting to Insteon Hub on %s", host) _LOGGER.info("Connecting to Insteon Hub on %s:%d", host, ip_port)
conn = await insteonplm.Connection.create( else:
_LOGGER.info("Connecting to Insteon PLM on %s", port)
try:
await async_connect(
device=port,
host=host, host=host,
port=ip_port, port=ip_port,
username=username, username=username,
password=password, password=password,
hub_version=hub_version, hub_version=hub_version,
loop=hass.loop,
workdir=hass.config.config_dir,
) )
else: except ConnectionError:
_LOGGER.info("Looking for Insteon PLM on %s", port) _LOGGER.error("Could not connect to Insteon modem")
conn = await insteonplm.Connection.create( return False
device=port, loop=hass.loop, workdir=hass.config.config_dir _LOGGER.info("Connection to Insteon modem successful")
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, [])
await devices.async_load(
workdir=hass.config.config_dir, id_devices=0, load_modem_aldb=0
) )
insteon_modem = conn.protocol
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)
for device_override in overrides: for device_override in overrides:
#
# Override the device default capabilities for a specific address # Override the device default capabilities for a specific address
#
address = device_override.get("address") address = device_override.get("address")
for prop in device_override: if not devices.get(address):
if prop in [CONF_CAT, CONF_SUBCAT]: cat = device_override[CONF_CAT]
insteon_modem.devices.add_override(address, prop, device_override[prop]) subcat = device_override[CONF_SUBCAT]
elif prop in [CONF_FIRMWARE, CONF_PRODUCT_KEY]: firmware = device_override.get(CONF_FIRMWARE)
insteon_modem.devices.add_override( if firmware is None:
address, CONF_PRODUCT_KEY, device_override[prop] 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: for device in x10_devices:
housecode = device.get(CONF_HOUSECODE) housecode = device.get(CONF_HOUSECODE)
unitcode = device.get(CONF_UNITCODE) unitcode = device.get(CONF_UNITCODE)
x10_type = "onoff" x10_type = "on_off"
steps = device.get(CONF_DIM_STEPS, 22) steps = device.get(CONF_DIM_STEPS, 22)
if device.get(CONF_PLATFORM) == "light": if device.get(CONF_PLATFORM) == "light":
x10_type = "dimmable" x10_type = "dimmable"
@ -117,8 +154,7 @@ async def async_setup(hass, config):
_LOGGER.debug( _LOGGER.debug(
"Adding X10 device to Insteon: %s %d %s", housecode, unitcode, x10_type "Adding X10 device to Insteon: %s %d %s", housecode, unitcode, x10_type
) )
device = insteon_modem.add_x10_device(housecode, unitcode, x10_type) device = devices.add_x10_device(housecode, unitcode, x10_type, steps)
if device and hasattr(device.states[0x01], "steps"):
device.states[0x01].steps = steps
asyncio.create_task(async_setup_platforms(hass, config))
return True return True

View File

@ -1,50 +1,69 @@
"""Support for INSTEON dimmers via PowerLinc Modem.""" """Support for INSTEON dimmers via PowerLinc Modem."""
import logging 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 .insteon_entity import InsteonEntity
from .utils import async_add_insteon_entities
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = { SENSOR_TYPES = {
"openClosedSensor": "opening", OPEN_CLOSE_SENSOR: DEVICE_CLASS_OPENING,
"ioLincSensor": "opening", MOTION_SENSOR: DEVICE_CLASS_MOTION,
"motionSensor": "motion", DOOR_SENSOR: DEVICE_CLASS_DOOR,
"doorSensor": "door", LEAK_SENSOR_WET: DEVICE_CLASS_MOISTURE,
"wetLeakSensor": "moisture", LIGHT_SENSOR: DEVICE_CLASS_LIGHT,
"lightSensor": "light", LOW_BATTERY: DEVICE_CLASS_BATTERY,
"batterySensor": "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): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the INSTEON device class for the hass platform.""" """Set up the INSTEON entity class for the hass platform."""
insteon_modem = hass.data["insteon"].get("modem") async_add_insteon_entities(
hass, DOMAIN, InsteonBinarySensorEntity, async_add_entities, discovery_info
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]) class InsteonBinarySensorEntity(InsteonEntity, BinarySensorEntity):
"""A Class for an Insteon binary sensor entity."""
def __init__(self, device, group):
class InsteonBinarySensor(InsteonEntity, BinarySensorEntity):
"""A Class for an Insteon device entity."""
def __init__(self, device, state_key):
"""Initialize the INSTEON binary sensor.""" """Initialize the INSTEON binary sensor."""
super().__init__(device, state_key) super().__init__(device, group)
self._sensor_type = SENSOR_TYPES.get(self._insteon_device_state.name) self._sensor_type = SENSOR_TYPES.get(self._insteon_device_group.name)
@property @property
def device_class(self): def device_class(self):
@ -54,9 +73,4 @@ class InsteonBinarySensor(InsteonEntity, BinarySensorEntity):
@property @property
def is_on(self): def is_on(self):
"""Return the boolean response if the node is on.""" """Return the boolean response if the node is on."""
on_val = bool(self._insteon_device_state.value) return bool(self._insteon_device_group.value)
if self._insteon_device_state.name in ["lightSensor", "ioLincSensor"]:
return not on_val
return on_val

View File

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

View File

@ -4,6 +4,7 @@ import math
from homeassistant.components.cover import ( from homeassistant.components.cover import (
ATTR_POSITION, ATTR_POSITION,
DOMAIN,
SUPPORT_CLOSE, SUPPORT_CLOSE,
SUPPORT_OPEN, SUPPORT_OPEN,
SUPPORT_SET_POSITION, SUPPORT_SET_POSITION,
@ -11,6 +12,7 @@ from homeassistant.components.cover import (
) )
from .insteon_entity import InsteonEntity from .insteon_entity import InsteonEntity
from .utils import async_add_insteon_entities
_LOGGER = logging.getLogger(__name__) _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): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the Insteon platform.""" """Set up the Insteon platform."""
if not discovery_info: async_add_insteon_entities(
return hass, DOMAIN, InsteonCoverEntity, async_add_entities, discovery_info
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,
) )
new_entity = InsteonCoverEntity(device, state_key)
async_add_entities([new_entity])
class InsteonCoverEntity(InsteonEntity, CoverEntity): class InsteonCoverEntity(InsteonEntity, CoverEntity):
"""A Class for an Insteon device.""" """A Class for an Insteon cover entity."""
@property @property
def current_cover_position(self): def current_cover_position(self):
"""Return the current cover position.""" """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 @property
def supported_features(self): def supported_features(self):
@ -58,17 +45,19 @@ class InsteonCoverEntity(InsteonEntity, CoverEntity):
return bool(self.current_cover_position) return bool(self.current_cover_position)
async def async_open_cover(self, **kwargs): async def async_open_cover(self, **kwargs):
"""Open device.""" """Open cover."""
self._insteon_device_state.open() await self._insteon_device.async_open()
async def async_close_cover(self, **kwargs): async def async_close_cover(self, **kwargs):
"""Close device.""" """Close cover."""
self._insteon_device_state.close() await self._insteon_device.async_close()
async def async_set_cover_position(self, **kwargs): async def async_set_cover_position(self, **kwargs):
"""Set the cover position.""" """Set the cover position."""
position = int(kwargs[ATTR_POSITION] * 255 / 100) position = int(kwargs[ATTR_POSITION] * 255 / 100)
if position == 0: if position == 0:
self._insteon_device_state.close() await self._insteon_device.async_close()
else: 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.""" """Support for INSTEON fans via PowerLinc Modem."""
import logging import logging
from pyinsteon.constants import FanSpeed
from homeassistant.components.fan import ( from homeassistant.components.fan import (
DOMAIN,
SPEED_HIGH, SPEED_HIGH,
SPEED_LOW, SPEED_LOW,
SPEED_MEDIUM, SPEED_MEDIUM,
@ -9,43 +12,40 @@ from homeassistant.components.fan import (
SUPPORT_SET_SPEED, SUPPORT_SET_SPEED,
FanEntity, FanEntity,
) )
from homeassistant.const import STATE_OFF
from .insteon_entity import InsteonEntity from .insteon_entity import InsteonEntity
from .utils import async_add_insteon_entities
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
FAN_SPEEDS = [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
SPEED_TO_HEX = {SPEED_OFF: 0x00, SPEED_LOW: 0x3F, SPEED_MEDIUM: 0xBE, SPEED_HIGH: 0xFF} SPEED_TO_VALUE = {
SPEED_OFF: FanSpeed.OFF,
FAN_SPEEDS = [STATE_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] 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): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the INSTEON device class for the hass platform.""" """Set up the INSTEON entity class for the hass platform."""
insteon_modem = hass.data["insteon"].get("modem") async_add_insteon_entities(
hass, DOMAIN, InsteonFanEntity, async_add_entities, discovery_info
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,
) )
new_entity = InsteonFan(device, state_key)
async_add_entities([new_entity]) class InsteonFanEntity(InsteonEntity, FanEntity):
"""An INSTEON fan entity."""
class InsteonFan(InsteonEntity, FanEntity):
"""An INSTEON fan component."""
@property @property
def speed(self) -> str: def speed(self) -> str:
"""Return the current speed.""" """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 @property
def speed_list(self) -> list: def speed_list(self) -> list:
@ -58,30 +58,19 @@ class InsteonFan(InsteonEntity, FanEntity):
return SUPPORT_SET_SPEED return SUPPORT_SET_SPEED
async def async_turn_on(self, speed: str = None, **kwargs) -> None: async def async_turn_on(self, speed: str = None, **kwargs) -> None:
"""Turn on the entity.""" """Turn on the fan."""
if speed is None: if speed is None:
speed = SPEED_MEDIUM speed = SPEED_MEDIUM
await self.async_set_speed(speed) await self.async_set_speed(speed)
async def async_turn_off(self, **kwargs) -> None: async def async_turn_off(self, **kwargs) -> None:
"""Turn off the entity.""" """Turn off the fan."""
await self.async_set_speed(SPEED_OFF) await self._insteon_device.async_fan_off()
async def async_set_speed(self, speed: str) -> None: async def async_set_speed(self, speed: str) -> None:
"""Set the speed of the fan.""" """Set the speed of the fan."""
fan_speed = SPEED_TO_HEX[speed] fan_speed = SPEED_TO_VALUE[speed]
if fan_speed == 0x00: if fan_speed == FanSpeed.OFF:
self._insteon_device_state.off() await self._insteon_device.async_fan_off()
else: else:
self._insteon_device_state.set_level(fan_speed) await self._insteon_device.async_fan_on(on_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

View File

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

View File

@ -1,81 +1,112 @@
"""Insteon product database.""" """Utility methods for the Insteon platform."""
import collections import logging
from insteonplm.states.cover import Cover from pyinsteon.device_types import (
from insteonplm.states.dimmable import ( DimmableLightingControl,
DimmableKeypadA, DimmableLightingControl_DinRail,
DimmableRemote, DimmableLightingControl_FanLinc,
DimmableSwitch, DimmableLightingControl_InLineLinc,
DimmableSwitch_Fan, DimmableLightingControl_KeypadLinc_6,
) DimmableLightingControl_KeypadLinc_8,
from insteonplm.states.onOff import ( DimmableLightingControl_LampLinc,
OnOffKeypad, DimmableLightingControl_OutletLinc,
OnOffKeypadA, DimmableLightingControl_SwitchLinc,
OnOffSwitch, DimmableLightingControl_ToggleLinc,
OnOffSwitch_OutletBottom, GeneralController_ControlLinc,
OnOffSwitch_OutletTop, GeneralController_MiniRemote_4,
OpenClosedRelay, GeneralController_MiniRemote_8,
) GeneralController_MiniRemote_Switch,
from insteonplm.states.sensor import ( GeneralController_RemoteLinc,
IoLincSensor, SecurityHealthSafety_DoorSensor,
LeakSensorDryWet, SecurityHealthSafety_LeakSensor,
OnOffSensor, SecurityHealthSafety_MotionSensor,
SmokeCO2Sensor, SecurityHealthSafety_OpenCloseSensor,
VariableSensor, SecurityHealthSafety_Smokebridge,
) SensorsActuators_IOLink,
from insteonplm.states.x10 import ( SwitchedLightingControl,
X10AllLightsOffSensor, SwitchedLightingControl_ApplianceLinc,
X10AllLightsOnSensor, SwitchedLightingControl_DinRail,
X10AllUnitsOffSensor, SwitchedLightingControl_InLineLinc,
X10DimmableSwitch, SwitchedLightingControl_KeypadLinc_6,
SwitchedLightingControl_KeypadLinc_8,
SwitchedLightingControl_OnOffOutlet,
SwitchedLightingControl_OutletLinc,
SwitchedLightingControl_SwitchLinc,
SwitchedLightingControl_ToggleLinc,
WindowCovering,
X10Dimmable,
X10OnOff,
X10OnOffSensor, 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: def get_device_platforms(device):
"""Embodies the INSTEON Product Database static data and access methods.""" """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): def get_platform_groups(device, domain) -> dict:
"""Return the number of INSTEON state types mapped to HA platforms.""" """Return the platforms that a device belongs in."""
return len(self.states) return DEVICE_PLATFORM.get(type(device), {}).get(domain, {})
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

View File

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

View File

@ -2,6 +2,6 @@
"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": ["insteonplm==0.16.8"], "requirements": ["pyinsteon==1.0.0"],
"codeowners": [] "codeowners": ["@teharris1"]
} }

View File

@ -11,7 +11,6 @@ from homeassistant.const import (
CONF_PLATFORM, CONF_PLATFORM,
CONF_PORT, CONF_PORT,
ENTITY_MATCH_ALL, ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
) )
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -57,7 +56,6 @@ def set_default_port(schema: Dict) -> Dict:
CONF_DEVICE_OVERRIDE_SCHEMA = vol.All( CONF_DEVICE_OVERRIDE_SCHEMA = vol.All(
cv.deprecated(CONF_PLATFORM),
vol.Schema( vol.Schema(
{ {
vol.Required(CONF_ADDRESS): cv.string, vol.Required(CONF_ADDRESS): cv.string,
@ -86,6 +84,9 @@ CONF_X10_SCHEMA = vol.All(
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = vol.Schema(
{ {
DOMAIN: vol.All( 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.Schema(
{ {
vol.Exclusive( vol.Exclusive(
@ -101,9 +102,6 @@ CONFIG_SCHEMA = vol.Schema(
vol.Optional(CONF_OVERRIDE): vol.All( vol.Optional(CONF_OVERRIDE): vol.All(
cv.ensure_list_csv, [CONF_DEVICE_OVERRIDE_SCHEMA] 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( vol.Optional(CONF_X10): vol.All(
cv.ensure_list_csv, [CONF_X10_SCHEMA] cv.ensure_list_csv, [CONF_X10_SCHEMA]
), ),
@ -134,9 +132,7 @@ DEL_ALL_LINK_SCHEMA = vol.Schema(
LOAD_ALDB_SCHEMA = vol.Schema( LOAD_ALDB_SCHEMA = vol.Schema(
{ {
vol.Required(CONF_ENTITY_ID): vol.Any( vol.Required(CONF_ENTITY_ID): vol.Any(cv.entity_id, ENTITY_MATCH_ALL),
cv.entity_id, ENTITY_MATCH_ALL, ENTITY_MATCH_NONE
),
vol.Optional(SRV_LOAD_DB_RELOAD, default=False): cv.boolean, 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.""" """Support for INSTEON dimmers via PowerLinc Modem."""
import logging import logging
from homeassistant.components.switch import SwitchEntity from homeassistant.components.switch import DOMAIN, SwitchEntity
from .insteon_entity import InsteonEntity from .insteon_entity import InsteonEntity
from .utils import async_add_insteon_entities
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the INSTEON device class for the hass platform.""" """Set up the INSTEON entity class for the hass platform."""
insteon_modem = hass.data["insteon"].get("modem") async_add_insteon_entities(
hass, DOMAIN, InsteonSwitchEntity, async_add_entities, discovery_info
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,
) )
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: class InsteonSwitchEntity(InsteonEntity, SwitchEntity):
async_add_entities([new_entity]) """A Class for an Insteon switch entity."""
class InsteonSwitchDevice(InsteonEntity, SwitchEntity):
"""A Class for an Insteon device."""
@property @property
def is_on(self): def is_on(self):
"""Return the boolean response if the node is on.""" """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): async def async_turn_on(self, **kwargs):
"""Turn device on.""" """Turn switch on."""
self._insteon_device_state.on() await self._insteon_device.async_on(group=self._insteon_device_group.group)
async def async_turn_off(self, **kwargs): async def async_turn_off(self, **kwargs):
"""Turn device off.""" """Turn switch off."""
self._insteon_device_state.off() await self._insteon_device.async_off(group=self._insteon_device_group.group)
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()

View File

@ -1,23 +1,44 @@
"""Utilities used by insteon component.""" """Utilities used by insteon component."""
import asyncio
import logging 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.const import CONF_ADDRESS, CONF_ENTITY_ID, ENTITY_MATCH_ALL
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers import discovery 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 ( from .const import (
BUTTON_PRESSED_STATE_NAME,
DOMAIN, DOMAIN,
EVENT_BUTTON_OFF,
EVENT_BUTTON_ON,
EVENT_CONF_BUTTON, 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_LOAD_ALDB,
SIGNAL_PRINT_ALDB, SIGNAL_PRINT_ALDB,
SIGNAL_SAVE_DEVICES,
SRV_ADD_ALL_LINK, SRV_ADD_ALL_LINK,
SRV_ALL_LINK_GROUP, SRV_ALL_LINK_GROUP,
SRV_ALL_LINK_MODE, SRV_ALL_LINK_MODE,
@ -34,7 +55,7 @@ from .const import (
SRV_X10_ALL_LIGHTS_ON, SRV_X10_ALL_LIGHTS_ON,
SRV_X10_ALL_UNITS_OFF, SRV_X10_ALL_UNITS_OFF,
) )
from .ipdb import IPDB from .ipdb import get_device_platforms, get_platform_groups
from .schemas import ( from .schemas import (
ADD_ALL_LINK_SCHEMA, ADD_ALL_LINK_SCHEMA,
DEL_ALL_LINK_SCHEMA, DEL_ALL_LINK_SCHEMA,
@ -47,91 +68,129 @@ from .schemas import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def register_new_device_callback(hass, config, insteon_modem): def add_on_off_event_device(hass, device):
"""Register callback for new Insteon device.""" """Register an Insteon device as an on/off event 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)
@callback @callback
def async_new_insteon_device(device): def async_fire_group_on_off_event(name, address, group, button):
"""Detect device from transport to be delegated to platform.""" # Firing an event when a button is pressed.
ipdb = IPDB() if button and button[-2] == "_":
for state_key in device.states: button_id = button[-1].lower()
platform_info = ipdb[device.states[state_key]]
if platform_info and platform_info.platform:
platform = platform_info.platform
if platform == "on_off_events":
device.states[state_key].register_updates(_fire_button_on_off_event)
else: else:
_LOGGER.info( button_id = None
"New INSTEON device: %s (%s) %s",
device.address, schema = {CONF_ADDRESS: address}
device.states[state_key].name, if button_id:
platform, 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
) )
hass.async_create_task(
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."""
hass.async_create_task(async_create_new_entities(address))
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:
tasks.append(
discovery.async_load_platform( discovery.async_load_platform(
hass, hass,
platform, platform,
DOMAIN, DOMAIN,
discovered={ discovered={"address": device.address.id},
"address": device.address.id,
"state_key": state_key,
},
hass_config=config, hass_config=config,
) )
) )
await asyncio.gather(*tasks)
insteon_modem.devices.add_device_callback(async_new_insteon_device) devices.subscribe(async_new_insteon_device, force_strong_ref=True)
@callback @callback
def async_register_services(hass, config, insteon_modem): def async_register_services(hass):
"""Register services used by insteon component.""" """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.""" """Add an INSTEON All-Link between two devices."""
group = service.data.get(SRV_ALL_LINK_GROUP) group = service.data.get(SRV_ALL_LINK_GROUP)
mode = service.data.get(SRV_ALL_LINK_MODE) mode = service.data.get(SRV_ALL_LINK_MODE)
link_mode = 1 if mode.lower() == SRV_CONTROLLER else 0 link_mode = mode.lower() == SRV_CONTROLLER
insteon_modem.start_all_linking(link_mode, group) 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.""" """Delete an INSTEON All-Link between two devices."""
group = service.data.get(SRV_ALL_LINK_GROUP) 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.""" """Load the device All-Link database."""
entity_id = service.data[CONF_ENTITY_ID] entity_id = service.data[CONF_ENTITY_ID]
reload = service.data[SRV_LOAD_DB_RELOAD] reload = service.data[SRV_LOAD_DB_RELOAD]
if entity_id.lower() == ENTITY_MATCH_ALL: if entity_id.lower() == ENTITY_MATCH_ALL:
for entity_id in hass.data[DOMAIN][INSTEON_ENTITIES]: await async_srv_load_aldb_all(reload)
_send_load_aldb_signal(entity_id, reload)
else: else:
_send_load_aldb_signal(entity_id, 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}" signal = f"{entity_id}_{SIGNAL_LOAD_ALDB}"
dispatcher_send(hass, signal, reload) async_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): def print_aldb(service):
"""Print the All-Link Database for a device.""" """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.""" """Print the All-Link Database for a device."""
# For now this sends logs to the log file. # For now this sends logs to the log file.
# Future direction is to create an INSTEON control panel. # 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.""" """Send the X10 All Units Off command."""
housecode = service.data.get(SRV_HOUSECODE) 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.""" """Send the X10 All Lights Off command."""
housecode = service.data.get(SRV_HOUSECODE) 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.""" """Send the X10 All Lights On command."""
housecode = service.data.get(SRV_HOUSECODE) 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.""" """Trigger an INSTEON scene ON."""
group = service.data.get(SRV_ALL_LINK_GROUP) 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.""" """Trigger an INSTEON scene ON."""
group = service.data.get(SRV_ALL_LINK_GROUP) group = service.data.get(SRV_ALL_LINK_GROUP)
insteon_modem.trigger_group_off(group) await async_trigger_scene_off(group)
hass.services.async_register( 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( 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( 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( hass.services.async_register(
DOMAIN, SRV_PRINT_ALDB, print_aldb, schema=PRINT_ALDB_SCHEMA 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_PRINT_IM_ALDB, print_im_aldb, schema=None)
hass.services.async_register( 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( 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( 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( 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( 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") _LOGGER.debug("Insteon Services registered")
def print_aldb_to_log(aldb): def print_aldb_to_log(aldb):
"""Print the All-Link Database to the log file.""" """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]: if aldb.status not in [ALDBStatus.LOADED, ALDBStatus.PARTIAL]:
_LOGGER.warning("Device All-Link database not loaded") _LOGGER.warning("All-Link database not loaded")
_LOGGER.warning("Use service insteon.load_aldb first")
return
_LOGGER.info("RecID In Use Mode HWM Group Address Data 1 Data 2 Data 3") _LOGGER.info("RecID In Use Mode HWM Group Address Data 1 Data 2 Data 3")
_LOGGER.info("----- ------ ---- --- ----- -------- ------ ------ ------") _LOGGER.info("----- ------ ---- --- ----- -------- ------ ------ ------")
@ -217,12 +290,30 @@ def print_aldb_to_log(aldb):
rec = aldb[mem_addr] rec = aldb[mem_addr]
# For now we write this to the log # For now we write this to the log
# Roadmap is to create a configuration panel # Roadmap is to create a configuration panel
in_use = "Y" if rec.control_flags.is_in_use else "N" in_use = "Y" if rec.is_in_use else "N"
mode = "C" if rec.control_flags.is_controller else "R" mode = "C" if rec.is_controller else "R"
hwm = "Y" if rec.control_flags.is_high_water_mark else "N" hwm = "Y" if rec.is_high_water_mark else "N"
log_msg = ( log_msg = (
f" {rec.mem_addr:04x} {in_use:s} {mode:s} {hwm:s} " 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}" f"{rec.data2:3d} {rec.data3:3d}"
) )
_LOGGER.info(log_msg) _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 # homeassistant.components.influxdb
influxdb==5.2.3 influxdb==5.2.3
# homeassistant.components.insteon
insteonplm==0.16.8
# homeassistant.components.iperf3 # homeassistant.components.iperf3
iperf3==0.1.11 iperf3==0.1.11
@ -1377,6 +1374,9 @@ pyialarm==0.3
# homeassistant.components.icloud # homeassistant.components.icloud
pyicloud==0.9.7 pyicloud==0.9.7
# homeassistant.components.insteon
pyinsteon==1.0.0
# homeassistant.components.intesishome # homeassistant.components.intesishome
pyintesishome==1.7.4 pyintesishome==1.7.4