2017-02-21 07:53:39 +00:00
|
|
|
"""
|
2018-08-22 07:09:04 +00:00
|
|
|
Support for INSTEON Modems (PLM and Hub).
|
2017-02-21 07:53:39 +00:00
|
|
|
|
|
|
|
For more details about this component, please refer to the documentation at
|
2018-08-22 07:09:04 +00:00
|
|
|
https://home-assistant.io/components/insteon/
|
2017-02-21 07:53:39 +00:00
|
|
|
"""
|
2018-02-25 19:13:39 +00:00
|
|
|
import collections
|
|
|
|
import logging
|
2018-09-10 14:54:17 +00:00
|
|
|
from typing import Dict
|
|
|
|
|
2017-02-21 07:53:39 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
|
|
|
from homeassistant.core import callback
|
2018-02-25 19:13:39 +00:00
|
|
|
from homeassistant.const import (CONF_PORT, EVENT_HOMEASSISTANT_STOP,
|
2018-05-05 15:15:20 +00:00
|
|
|
CONF_PLATFORM,
|
2018-08-22 07:09:04 +00:00
|
|
|
CONF_ENTITY_ID,
|
|
|
|
CONF_HOST)
|
2017-02-21 07:53:39 +00:00
|
|
|
import homeassistant.helpers.config_validation as cv
|
|
|
|
from homeassistant.helpers import discovery
|
2018-02-25 19:13:39 +00:00
|
|
|
from homeassistant.helpers.entity import Entity
|
2017-02-21 07:53:39 +00:00
|
|
|
|
2018-12-13 15:52:12 +00:00
|
|
|
REQUIREMENTS = ['insteonplm==0.15.2']
|
2017-02-21 07:53:39 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2018-08-22 07:09:04 +00:00
|
|
|
DOMAIN = 'insteon'
|
2017-02-21 07:53:39 +00:00
|
|
|
|
2018-08-22 07:09:04 +00:00
|
|
|
CONF_IP_PORT = 'ip_port'
|
|
|
|
CONF_HUB_USERNAME = 'username'
|
|
|
|
CONF_HUB_PASSWORD = 'password'
|
2018-09-10 14:54:17 +00:00
|
|
|
CONF_HUB_VERSION = 'hub_version'
|
2017-02-21 07:53:39 +00:00
|
|
|
CONF_OVERRIDE = 'device_override'
|
2018-09-10 14:54:17 +00:00
|
|
|
CONF_PLM_HUB_MSG = 'Must configure either a PLM port or a Hub host'
|
2018-02-25 19:13:39 +00:00
|
|
|
CONF_ADDRESS = 'address'
|
|
|
|
CONF_CAT = 'cat'
|
|
|
|
CONF_SUBCAT = 'subcat'
|
|
|
|
CONF_FIRMWARE = 'firmware'
|
|
|
|
CONF_PRODUCT_KEY = 'product_key'
|
2018-06-21 01:44:05 +00:00
|
|
|
CONF_X10 = 'x10_devices'
|
|
|
|
CONF_HOUSECODE = 'housecode'
|
|
|
|
CONF_UNITCODE = 'unitcode'
|
|
|
|
CONF_DIM_STEPS = 'dim_steps'
|
|
|
|
CONF_X10_ALL_UNITS_OFF = 'x10_all_units_off'
|
|
|
|
CONF_X10_ALL_LIGHTS_ON = 'x10_all_lights_on'
|
|
|
|
CONF_X10_ALL_LIGHTS_OFF = 'x10_all_lights_off'
|
2018-02-25 19:13:39 +00:00
|
|
|
|
2018-05-05 15:15:20 +00:00
|
|
|
SRV_ADD_ALL_LINK = 'add_all_link'
|
|
|
|
SRV_DEL_ALL_LINK = 'delete_all_link'
|
|
|
|
SRV_LOAD_ALDB = 'load_all_link_database'
|
|
|
|
SRV_PRINT_ALDB = 'print_all_link_database'
|
|
|
|
SRV_PRINT_IM_ALDB = 'print_im_all_link_database'
|
2018-06-21 01:44:05 +00:00
|
|
|
SRV_X10_ALL_UNITS_OFF = 'x10_all_units_off'
|
|
|
|
SRV_X10_ALL_LIGHTS_OFF = 'x10_all_lights_off'
|
|
|
|
SRV_X10_ALL_LIGHTS_ON = 'x10_all_lights_on'
|
2018-05-05 15:15:20 +00:00
|
|
|
SRV_ALL_LINK_GROUP = 'group'
|
|
|
|
SRV_ALL_LINK_MODE = 'mode'
|
|
|
|
SRV_LOAD_DB_RELOAD = 'reload'
|
|
|
|
SRV_CONTROLLER = 'controller'
|
|
|
|
SRV_RESPONDER = 'responder'
|
2018-06-21 01:44:05 +00:00
|
|
|
SRV_HOUSECODE = 'housecode'
|
|
|
|
|
|
|
|
HOUSECODES = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
|
|
|
|
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p']
|
2018-05-05 15:15:20 +00:00
|
|
|
|
2018-07-18 14:11:54 +00:00
|
|
|
BUTTON_PRESSED_STATE_NAME = 'onLevelButton'
|
2018-08-22 07:09:04 +00:00
|
|
|
EVENT_BUTTON_ON = 'insteon.button_on'
|
|
|
|
EVENT_BUTTON_OFF = 'insteon.button_off'
|
2018-07-18 14:11:54 +00:00
|
|
|
EVENT_CONF_BUTTON = 'button'
|
|
|
|
|
2018-09-10 14:54:17 +00:00
|
|
|
|
|
|
|
def set_default_port(schema: Dict) -> Dict:
|
|
|
|
"""Set the default port based on the Hub version."""
|
|
|
|
# If the ip_port is found do nothing
|
|
|
|
# If it is not found the set the default
|
|
|
|
ip_port = schema.get(CONF_IP_PORT)
|
|
|
|
if not ip_port:
|
|
|
|
hub_version = schema.get(CONF_HUB_VERSION)
|
|
|
|
# Found hub_version but not ip_port
|
|
|
|
if hub_version == 1:
|
|
|
|
schema[CONF_IP_PORT] = 9761
|
|
|
|
else:
|
|
|
|
schema[CONF_IP_PORT] = 25105
|
|
|
|
return schema
|
|
|
|
|
|
|
|
|
2018-02-25 19:13:39 +00:00
|
|
|
CONF_DEVICE_OVERRIDE_SCHEMA = vol.All(
|
|
|
|
cv.deprecated(CONF_PLATFORM), vol.Schema({
|
|
|
|
vol.Required(CONF_ADDRESS): cv.string,
|
|
|
|
vol.Optional(CONF_CAT): cv.byte,
|
|
|
|
vol.Optional(CONF_SUBCAT): cv.byte,
|
|
|
|
vol.Optional(CONF_FIRMWARE): cv.byte,
|
|
|
|
vol.Optional(CONF_PRODUCT_KEY): cv.byte,
|
|
|
|
vol.Optional(CONF_PLATFORM): cv.string,
|
|
|
|
}))
|
2017-02-21 07:53:39 +00:00
|
|
|
|
2018-06-21 01:44:05 +00:00
|
|
|
CONF_X10_SCHEMA = vol.All(
|
|
|
|
vol.Schema({
|
|
|
|
vol.Required(CONF_HOUSECODE): cv.string,
|
|
|
|
vol.Required(CONF_UNITCODE): vol.Range(min=1, max=16),
|
|
|
|
vol.Required(CONF_PLATFORM): cv.string,
|
|
|
|
vol.Optional(CONF_DIM_STEPS): vol.Range(min=2, max=255)
|
|
|
|
}))
|
|
|
|
|
2017-02-21 07:53:39 +00:00
|
|
|
CONFIG_SCHEMA = vol.Schema({
|
2018-08-22 07:09:04 +00:00
|
|
|
DOMAIN: vol.All(
|
|
|
|
vol.Schema(
|
|
|
|
{vol.Exclusive(CONF_PORT, 'plm_or_hub',
|
2018-09-10 14:54:17 +00:00
|
|
|
msg=CONF_PLM_HUB_MSG): cv.string,
|
2018-08-22 07:09:04 +00:00
|
|
|
vol.Exclusive(CONF_HOST, 'plm_or_hub',
|
|
|
|
msg=CONF_PLM_HUB_MSG): cv.string,
|
2018-09-10 14:54:17 +00:00
|
|
|
vol.Optional(CONF_IP_PORT): cv.port,
|
2018-08-22 07:09:04 +00:00
|
|
|
vol.Optional(CONF_HUB_USERNAME): cv.string,
|
|
|
|
vol.Optional(CONF_HUB_PASSWORD): cv.string,
|
2018-09-10 14:54:17 +00:00
|
|
|
vol.Optional(CONF_HUB_VERSION, default=2): vol.In([1, 2]),
|
2018-08-22 07:09:04 +00:00
|
|
|
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])
|
|
|
|
}, extra=vol.ALLOW_EXTRA, required=True),
|
|
|
|
cv.has_at_least_one_key(CONF_PORT, CONF_HOST),
|
2018-09-10 14:54:17 +00:00
|
|
|
set_default_port)
|
2018-08-22 07:09:04 +00:00
|
|
|
}, extra=vol.ALLOW_EXTRA)
|
|
|
|
|
2017-02-21 07:53:39 +00:00
|
|
|
|
2018-05-05 15:15:20 +00:00
|
|
|
ADD_ALL_LINK_SCHEMA = vol.Schema({
|
|
|
|
vol.Required(SRV_ALL_LINK_GROUP): vol.Range(min=0, max=255),
|
|
|
|
vol.Required(SRV_ALL_LINK_MODE): vol.In([SRV_CONTROLLER, SRV_RESPONDER]),
|
|
|
|
})
|
|
|
|
|
|
|
|
DEL_ALL_LINK_SCHEMA = vol.Schema({
|
|
|
|
vol.Required(SRV_ALL_LINK_GROUP): vol.Range(min=0, max=255),
|
|
|
|
})
|
|
|
|
|
|
|
|
LOAD_ALDB_SCHEMA = vol.Schema({
|
|
|
|
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
|
|
|
vol.Optional(SRV_LOAD_DB_RELOAD, default='false'): cv.boolean,
|
|
|
|
})
|
|
|
|
|
|
|
|
PRINT_ALDB_SCHEMA = vol.Schema({
|
|
|
|
vol.Required(CONF_ENTITY_ID): cv.entity_id,
|
|
|
|
})
|
|
|
|
|
2018-06-21 01:44:05 +00:00
|
|
|
X10_HOUSECODE_SCHEMA = vol.Schema({
|
|
|
|
vol.Required(SRV_HOUSECODE): vol.In(HOUSECODES),
|
|
|
|
})
|
|
|
|
|
2017-02-21 07:53:39 +00:00
|
|
|
|
2018-10-01 06:52:42 +00:00
|
|
|
async def async_setup(hass, config):
|
2018-08-22 07:09:04 +00:00
|
|
|
"""Set up the connection to the modem."""
|
2017-02-21 07:53:39 +00:00
|
|
|
import insteonplm
|
|
|
|
|
2018-02-25 19:13:39 +00:00
|
|
|
ipdb = IPDB()
|
2018-08-22 07:09:04 +00:00
|
|
|
insteon_modem = None
|
2018-02-25 19:13:39 +00:00
|
|
|
|
2017-02-21 07:53:39 +00:00
|
|
|
conf = config[DOMAIN]
|
|
|
|
port = conf.get(CONF_PORT)
|
2018-08-22 07:09:04 +00:00
|
|
|
host = conf.get(CONF_HOST)
|
|
|
|
ip_port = conf.get(CONF_IP_PORT)
|
|
|
|
username = conf.get(CONF_HUB_USERNAME)
|
|
|
|
password = conf.get(CONF_HUB_PASSWORD)
|
2018-09-10 14:54:17 +00:00
|
|
|
hub_version = conf.get(CONF_HUB_VERSION)
|
2018-02-25 19:13:39 +00:00
|
|
|
overrides = conf.get(CONF_OVERRIDE, [])
|
2018-06-21 01:44:05 +00:00
|
|
|
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)
|
2017-02-21 07:53:39 +00:00
|
|
|
|
|
|
|
@callback
|
2018-08-22 07:09:04 +00:00
|
|
|
def async_new_insteon_device(device):
|
2017-04-30 05:04:49 +00:00
|
|
|
"""Detect device from transport to be delegated to platform."""
|
2018-02-25 19:13:39 +00:00
|
|
|
for state_key in device.states:
|
|
|
|
platform_info = ipdb[device.states[state_key]]
|
2018-07-18 14:11:54 +00:00
|
|
|
if platform_info and platform_info.platform:
|
2018-03-30 00:10:27 +00:00
|
|
|
platform = platform_info.platform
|
2018-07-18 14:11:54 +00:00
|
|
|
|
|
|
|
if platform == 'on_off_events':
|
|
|
|
device.states[state_key].register_updates(
|
|
|
|
_fire_button_on_off_event)
|
|
|
|
|
|
|
|
else:
|
2018-08-22 07:09:04 +00:00
|
|
|
_LOGGER.info("New INSTEON device: %s (%s) %s",
|
2018-03-30 00:10:27 +00:00
|
|
|
device.address,
|
|
|
|
device.states[state_key].name,
|
|
|
|
platform)
|
|
|
|
|
2018-07-23 12:05:38 +00:00
|
|
|
hass.async_create_task(
|
2018-03-30 00:10:27 +00:00
|
|
|
discovery.async_load_platform(
|
|
|
|
hass, platform, DOMAIN,
|
2018-06-21 01:44:05 +00:00
|
|
|
discovered={'address': device.address.id,
|
2018-03-30 00:10:27 +00:00
|
|
|
'state_key': state_key},
|
|
|
|
hass_config=config))
|
2017-02-21 07:53:39 +00:00
|
|
|
|
2018-05-05 15:15:20 +00:00
|
|
|
def 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
|
2018-08-22 07:09:04 +00:00
|
|
|
insteon_modem.start_all_linking(link_mode, group)
|
2018-05-05 15:15:20 +00:00
|
|
|
|
|
|
|
def del_all_link(service):
|
|
|
|
"""Delete an INSTEON All-Link between two devices."""
|
|
|
|
group = service.data.get(SRV_ALL_LINK_GROUP)
|
2018-08-22 07:09:04 +00:00
|
|
|
insteon_modem.start_all_linking(255, group)
|
2018-05-05 15:15:20 +00:00
|
|
|
|
|
|
|
def load_aldb(service):
|
|
|
|
"""Load the device All-Link database."""
|
|
|
|
entity_id = service.data.get(CONF_ENTITY_ID)
|
|
|
|
reload = service.data.get(SRV_LOAD_DB_RELOAD)
|
|
|
|
entities = hass.data[DOMAIN].get('entities')
|
|
|
|
entity = entities.get(entity_id)
|
|
|
|
if entity:
|
|
|
|
entity.load_aldb(reload)
|
|
|
|
else:
|
|
|
|
_LOGGER.error('Entity %s is not an INSTEON device', entity_id)
|
|
|
|
|
|
|
|
def print_aldb(service):
|
|
|
|
"""Print the All-Link Database for a device."""
|
|
|
|
# For now this sends logs to the log file.
|
|
|
|
# Furture direction is to create an INSTEON control panel.
|
|
|
|
entity_id = service.data.get(CONF_ENTITY_ID)
|
|
|
|
entities = hass.data[DOMAIN].get('entities')
|
|
|
|
entity = entities.get(entity_id)
|
|
|
|
if entity:
|
|
|
|
entity.print_aldb()
|
|
|
|
else:
|
|
|
|
_LOGGER.error('Entity %s is not an INSTEON device', entity_id)
|
|
|
|
|
|
|
|
def print_im_aldb(service):
|
|
|
|
"""Print the All-Link Database for a device."""
|
|
|
|
# For now this sends logs to the log file.
|
|
|
|
# Furture direction is to create an INSTEON control panel.
|
2018-08-22 07:09:04 +00:00
|
|
|
print_aldb_to_log(insteon_modem.aldb)
|
2018-05-05 15:15:20 +00:00
|
|
|
|
2018-06-21 01:44:05 +00:00
|
|
|
def x10_all_units_off(service):
|
|
|
|
"""Send the X10 All Units Off command."""
|
|
|
|
housecode = service.data.get(SRV_HOUSECODE)
|
2018-08-22 07:09:04 +00:00
|
|
|
insteon_modem.x10_all_units_off(housecode)
|
2018-06-21 01:44:05 +00:00
|
|
|
|
|
|
|
def x10_all_lights_off(service):
|
|
|
|
"""Send the X10 All Lights Off command."""
|
|
|
|
housecode = service.data.get(SRV_HOUSECODE)
|
2018-08-22 07:09:04 +00:00
|
|
|
insteon_modem.x10_all_lights_off(housecode)
|
2018-06-21 01:44:05 +00:00
|
|
|
|
|
|
|
def x10_all_lights_on(service):
|
|
|
|
"""Send the X10 All Lights On command."""
|
|
|
|
housecode = service.data.get(SRV_HOUSECODE)
|
2018-08-22 07:09:04 +00:00
|
|
|
insteon_modem.x10_all_lights_on(housecode)
|
2018-06-21 01:44:05 +00:00
|
|
|
|
2018-05-05 15:15:20 +00:00
|
|
|
def _register_services():
|
|
|
|
hass.services.register(DOMAIN, SRV_ADD_ALL_LINK, add_all_link,
|
|
|
|
schema=ADD_ALL_LINK_SCHEMA)
|
|
|
|
hass.services.register(DOMAIN, SRV_DEL_ALL_LINK, del_all_link,
|
|
|
|
schema=DEL_ALL_LINK_SCHEMA)
|
|
|
|
hass.services.register(DOMAIN, SRV_LOAD_ALDB, load_aldb,
|
|
|
|
schema=LOAD_ALDB_SCHEMA)
|
|
|
|
hass.services.register(DOMAIN, SRV_PRINT_ALDB, print_aldb,
|
|
|
|
schema=PRINT_ALDB_SCHEMA)
|
|
|
|
hass.services.register(DOMAIN, SRV_PRINT_IM_ALDB, print_im_aldb,
|
|
|
|
schema=None)
|
2018-06-21 01:44:05 +00:00
|
|
|
hass.services.register(DOMAIN, SRV_X10_ALL_UNITS_OFF,
|
|
|
|
x10_all_units_off,
|
|
|
|
schema=X10_HOUSECODE_SCHEMA)
|
|
|
|
hass.services.register(DOMAIN, SRV_X10_ALL_LIGHTS_OFF,
|
|
|
|
x10_all_lights_off,
|
|
|
|
schema=X10_HOUSECODE_SCHEMA)
|
|
|
|
hass.services.register(DOMAIN, SRV_X10_ALL_LIGHTS_ON,
|
|
|
|
x10_all_lights_on,
|
|
|
|
schema=X10_HOUSECODE_SCHEMA)
|
2018-08-22 07:09:04 +00:00
|
|
|
_LOGGER.debug("Insteon Services registered")
|
2018-05-05 15:15:20 +00:00
|
|
|
|
2018-07-18 14:11:54 +00:00
|
|
|
def _fire_button_on_off_event(address, group, val):
|
|
|
|
# Firing an event when a button is pressed.
|
2018-08-22 07:09:04 +00:00
|
|
|
device = insteon_modem.devices[address.hex]
|
2018-07-18 14:11:54 +00:00
|
|
|
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
|
|
|
|
if val:
|
|
|
|
event = EVENT_BUTTON_ON
|
|
|
|
else:
|
|
|
|
event = EVENT_BUTTON_OFF
|
|
|
|
_LOGGER.debug('Firing event %s with address %s and button %s',
|
|
|
|
event, address.hex, button)
|
|
|
|
hass.bus.fire(event, schema)
|
|
|
|
|
2018-08-22 07:09:04 +00:00
|
|
|
if host:
|
|
|
|
_LOGGER.info('Connecting to Insteon Hub on %s', host)
|
2018-10-01 06:52:42 +00:00
|
|
|
conn = await insteonplm.Connection.create(
|
2018-08-22 07:09:04 +00:00
|
|
|
host=host,
|
|
|
|
port=ip_port,
|
|
|
|
username=username,
|
|
|
|
password=password,
|
2018-09-10 14:54:17 +00:00
|
|
|
hub_version=hub_version,
|
2018-08-22 07:09:04 +00:00
|
|
|
loop=hass.loop,
|
|
|
|
workdir=hass.config.config_dir)
|
|
|
|
else:
|
|
|
|
_LOGGER.info("Looking for Insteon PLM on %s", port)
|
2018-10-01 06:52:42 +00:00
|
|
|
conn = await insteonplm.Connection.create(
|
2018-08-22 07:09:04 +00:00
|
|
|
device=port,
|
|
|
|
loop=hass.loop,
|
|
|
|
workdir=hass.config.config_dir)
|
|
|
|
|
|
|
|
insteon_modem = conn.protocol
|
2017-02-21 07:53:39 +00:00
|
|
|
|
2018-02-25 19:13:39 +00:00
|
|
|
for device_override in overrides:
|
2017-02-21 07:53:39 +00:00
|
|
|
#
|
|
|
|
# Override the device default capabilities for a specific address
|
|
|
|
#
|
2018-02-25 19:13:39 +00:00
|
|
|
address = device_override.get('address')
|
|
|
|
for prop in device_override:
|
|
|
|
if prop in [CONF_CAT, CONF_SUBCAT]:
|
2018-08-22 07:09:04 +00:00
|
|
|
insteon_modem.devices.add_override(address, prop,
|
|
|
|
device_override[prop])
|
2018-02-25 19:13:39 +00:00
|
|
|
elif prop in [CONF_FIRMWARE, CONF_PRODUCT_KEY]:
|
2018-08-22 07:09:04 +00:00
|
|
|
insteon_modem.devices.add_override(address, CONF_PRODUCT_KEY,
|
|
|
|
device_override[prop])
|
2017-02-21 07:53:39 +00:00
|
|
|
|
2018-05-05 15:15:20 +00:00
|
|
|
hass.data[DOMAIN] = {}
|
2018-08-22 07:09:04 +00:00
|
|
|
hass.data[DOMAIN]['modem'] = insteon_modem
|
2018-05-05 15:15:20 +00:00
|
|
|
hass.data[DOMAIN]['entities'] = {}
|
2017-02-21 07:53:39 +00:00
|
|
|
|
2018-02-25 19:13:39 +00:00
|
|
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, conn.close)
|
2017-02-21 07:53:39 +00:00
|
|
|
|
2018-08-22 07:09:04 +00:00
|
|
|
insteon_modem.devices.add_device_callback(async_new_insteon_device)
|
2018-06-21 01:44:05 +00:00
|
|
|
|
|
|
|
if x10_all_units_off_housecode:
|
2018-08-22 07:09:04 +00:00
|
|
|
device = insteon_modem.add_x10_device(x10_all_units_off_housecode,
|
|
|
|
20,
|
|
|
|
'allunitsoff')
|
2018-06-21 01:44:05 +00:00
|
|
|
if x10_all_lights_on_housecode:
|
2018-08-22 07:09:04 +00:00
|
|
|
device = insteon_modem.add_x10_device(x10_all_lights_on_housecode,
|
|
|
|
21,
|
|
|
|
'alllightson')
|
2018-06-21 01:44:05 +00:00
|
|
|
if x10_all_lights_off_housecode:
|
2018-08-22 07:09:04 +00:00
|
|
|
device = insteon_modem.add_x10_device(x10_all_lights_off_housecode,
|
|
|
|
22,
|
|
|
|
'alllightsoff')
|
2018-06-21 01:44:05 +00:00
|
|
|
for device in x10_devices:
|
|
|
|
housecode = device.get(CONF_HOUSECODE)
|
|
|
|
unitcode = device.get(CONF_UNITCODE)
|
|
|
|
x10_type = 'onoff'
|
|
|
|
steps = device.get(CONF_DIM_STEPS, 22)
|
|
|
|
if device.get(CONF_PLATFORM) == 'light':
|
|
|
|
x10_type = 'dimmable'
|
|
|
|
elif device.get(CONF_PLATFORM) == 'binary_sensor':
|
|
|
|
x10_type = 'sensor'
|
2018-08-22 07:09:04 +00:00
|
|
|
_LOGGER.debug("Adding X10 device to Insteon: %s %d %s",
|
2018-06-21 01:44:05 +00:00
|
|
|
housecode, unitcode, x10_type)
|
2018-08-22 07:09:04 +00:00
|
|
|
device = insteon_modem.add_x10_device(housecode,
|
|
|
|
unitcode,
|
|
|
|
x10_type)
|
2018-06-21 01:44:05 +00:00
|
|
|
if device and hasattr(device.states[0x01], 'steps'):
|
|
|
|
device.states[0x01].steps = steps
|
|
|
|
|
2018-05-05 15:15:20 +00:00
|
|
|
hass.async_add_job(_register_services)
|
2017-02-21 07:53:39 +00:00
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2018-02-25 19:13:39 +00:00
|
|
|
State = collections.namedtuple('Product', 'stateType platform')
|
|
|
|
|
|
|
|
|
2018-07-20 08:45:20 +00:00
|
|
|
class IPDB:
|
2018-02-25 19:13:39 +00:00
|
|
|
"""Embodies the INSTEON Product Database static data and access methods."""
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
"""Create the INSTEON Product Database (IPDB)."""
|
2018-08-31 21:56:26 +00:00
|
|
|
from insteonplm.states.cover import Cover
|
|
|
|
|
2018-02-25 19:13:39 +00:00
|
|
|
from insteonplm.states.onOff import (OnOffSwitch,
|
|
|
|
OnOffSwitch_OutletTop,
|
|
|
|
OnOffSwitch_OutletBottom,
|
2018-08-22 07:09:04 +00:00
|
|
|
OpenClosedRelay,
|
|
|
|
OnOffKeypadA,
|
|
|
|
OnOffKeypad)
|
2018-02-25 19:13:39 +00:00
|
|
|
|
|
|
|
from insteonplm.states.dimmable import (DimmableSwitch,
|
2018-06-27 10:19:56 +00:00
|
|
|
DimmableSwitch_Fan,
|
2018-08-22 07:09:04 +00:00
|
|
|
DimmableRemote,
|
|
|
|
DimmableKeypadA)
|
2018-02-25 19:13:39 +00:00
|
|
|
|
|
|
|
from insteonplm.states.sensor import (VariableSensor,
|
|
|
|
OnOffSensor,
|
|
|
|
SmokeCO2Sensor,
|
2018-03-30 00:10:27 +00:00
|
|
|
IoLincSensor,
|
|
|
|
LeakSensorDryWet)
|
2018-02-25 19:13:39 +00:00
|
|
|
|
2018-06-21 01:44:05 +00:00
|
|
|
from insteonplm.states.x10 import (X10DimmableSwitch,
|
|
|
|
X10OnOffSwitch,
|
|
|
|
X10OnOffSensor,
|
|
|
|
X10AllUnitsOffSensor,
|
|
|
|
X10AllLightsOnSensor,
|
|
|
|
X10AllLightsOffSensor)
|
|
|
|
|
2018-08-31 21:56:26 +00:00
|
|
|
self.states = [State(Cover, 'cover'),
|
|
|
|
|
|
|
|
State(OnOffSwitch_OutletTop, 'switch'),
|
2018-02-25 19:13:39 +00:00
|
|
|
State(OnOffSwitch_OutletBottom, 'switch'),
|
|
|
|
State(OpenClosedRelay, 'switch'),
|
|
|
|
State(OnOffSwitch, 'switch'),
|
2018-08-22 07:09:04 +00:00
|
|
|
State(OnOffKeypadA, 'switch'),
|
|
|
|
State(OnOffKeypad, 'switch'),
|
2018-02-25 19:13:39 +00:00
|
|
|
|
2018-03-30 00:10:27 +00:00
|
|
|
State(LeakSensorDryWet, 'binary_sensor'),
|
2018-02-25 19:13:39 +00:00
|
|
|
State(IoLincSensor, 'binary_sensor'),
|
|
|
|
State(SmokeCO2Sensor, 'sensor'),
|
|
|
|
State(OnOffSensor, 'binary_sensor'),
|
|
|
|
State(VariableSensor, 'sensor'),
|
|
|
|
|
|
|
|
State(DimmableSwitch_Fan, 'fan'),
|
2018-06-21 01:44:05 +00:00
|
|
|
State(DimmableSwitch, 'light'),
|
2018-07-18 14:11:54 +00:00
|
|
|
State(DimmableRemote, 'on_off_events'),
|
2018-08-22 07:09:04 +00:00
|
|
|
State(DimmableKeypadA, 'light'),
|
2018-06-21 01:44:05 +00:00
|
|
|
|
|
|
|
State(X10DimmableSwitch, 'light'),
|
|
|
|
State(X10OnOffSwitch, 'switch'),
|
|
|
|
State(X10OnOffSensor, 'binary_sensor'),
|
|
|
|
State(X10AllUnitsOffSensor, 'binary_sensor'),
|
|
|
|
State(X10AllLightsOnSensor, 'binary_sensor'),
|
|
|
|
State(X10AllLightsOffSensor, 'binary_sensor')]
|
2018-02-25 19:13:39 +00:00
|
|
|
|
|
|
|
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."""
|
|
|
|
for product in self.states:
|
|
|
|
yield product
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
2018-08-22 07:09:04 +00:00
|
|
|
class InsteonEntity(Entity):
|
2018-02-25 19:13:39 +00:00
|
|
|
"""INSTEON abstract base entity."""
|
|
|
|
|
|
|
|
def __init__(self, device, state_key):
|
2018-08-22 07:09:04 +00:00
|
|
|
"""Initialize the INSTEON binary sensor."""
|
2018-02-25 19:13:39 +00:00
|
|
|
self._insteon_device_state = device.states[state_key]
|
|
|
|
self._insteon_device = device
|
2018-05-05 15:15:20 +00:00
|
|
|
self._insteon_device.aldb.add_loaded_callback(self._aldb_loaded)
|
2018-02-25 19:13:39 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def should_poll(self):
|
|
|
|
"""No polling needed."""
|
|
|
|
return False
|
|
|
|
|
|
|
|
@property
|
|
|
|
def address(self):
|
|
|
|
"""Return the address of the node."""
|
|
|
|
return self._insteon_device.address.human
|
|
|
|
|
|
|
|
@property
|
|
|
|
def group(self):
|
|
|
|
"""Return the INSTEON group that the entity responds to."""
|
|
|
|
return self._insteon_device_state.group
|
|
|
|
|
2018-10-07 11:12:33 +00:00
|
|
|
@property
|
|
|
|
def unique_id(self) -> str:
|
|
|
|
"""Return a unique ID."""
|
|
|
|
if self._insteon_device_state.group == 0x01:
|
|
|
|
uid = self._insteon_device.id
|
|
|
|
else:
|
|
|
|
uid = '{:s}_{:d}'.format(self._insteon_device.id,
|
|
|
|
self._insteon_device_state.group)
|
|
|
|
return uid
|
|
|
|
|
2018-02-25 19:13:39 +00:00
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Return the name of the node (used for Entity_ID)."""
|
|
|
|
name = ''
|
|
|
|
if self._insteon_device_state.group == 0x01:
|
|
|
|
name = self._insteon_device.id
|
|
|
|
else:
|
|
|
|
name = '{:s}_{:d}'.format(self._insteon_device.id,
|
|
|
|
self._insteon_device_state.group)
|
|
|
|
return name
|
|
|
|
|
|
|
|
@property
|
|
|
|
def device_state_attributes(self):
|
|
|
|
"""Provide attributes for display on device card."""
|
|
|
|
attributes = {
|
|
|
|
'INSTEON Address': self.address,
|
|
|
|
'INSTEON Group': self.group
|
|
|
|
}
|
|
|
|
return attributes
|
|
|
|
|
|
|
|
@callback
|
2018-08-31 21:56:26 +00:00
|
|
|
def async_entity_update(self, deviceid, group, val):
|
2018-02-25 19:13:39 +00:00
|
|
|
"""Receive notification from transport that new data exists."""
|
2018-08-31 21:56:26 +00:00
|
|
|
_LOGGER.debug('Received update for device %s group %d value %s',
|
|
|
|
deviceid.human, group, val)
|
2018-02-25 19:13:39 +00:00
|
|
|
self.async_schedule_update_ha_state()
|
|
|
|
|
2018-10-01 06:52:42 +00:00
|
|
|
async def async_added_to_hass(self):
|
2018-02-25 19:13:39 +00:00
|
|
|
"""Register INSTEON update events."""
|
2018-08-22 07:09:04 +00:00
|
|
|
_LOGGER.debug('Tracking updates for device %s group %d statename %s',
|
|
|
|
self.address, self.group,
|
|
|
|
self._insteon_device_state.name)
|
2018-02-25 19:13:39 +00:00
|
|
|
self._insteon_device_state.register_updates(
|
|
|
|
self.async_entity_update)
|
2018-05-05 15:15:20 +00:00
|
|
|
self.hass.data[DOMAIN]['entities'][self.entity_id] = self
|
|
|
|
|
|
|
|
def load_aldb(self, reload=False):
|
|
|
|
"""Load the device All-Link Database."""
|
|
|
|
if reload:
|
|
|
|
self._insteon_device.aldb.clear()
|
|
|
|
self._insteon_device.read_aldb()
|
|
|
|
|
|
|
|
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 print_aldb_to_log(aldb):
|
|
|
|
"""Print the All-Link Database to the log file."""
|
|
|
|
from insteonplm.devices import ALDBStatus
|
|
|
|
_LOGGER.info('ALDB load status is %s', aldb.status.name)
|
|
|
|
if aldb.status not in [ALDBStatus.LOADED, ALDBStatus.PARTIAL]:
|
|
|
|
_LOGGER.warning('Device All-Link database not loaded')
|
2018-08-22 07:09:04 +00:00
|
|
|
_LOGGER.warning('Use service insteon.load_aldb first')
|
2018-05-05 15:15:20 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
_LOGGER.info('RecID In Use Mode HWM Group Address Data 1 Data 2 Data 3')
|
|
|
|
_LOGGER.info('----- ------ ---- --- ----- -------- ------ ------ ------')
|
|
|
|
for mem_addr in 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'
|
|
|
|
_LOGGER.info(' {:04x} {:s} {:s} {:s} {:3d} {:s}'
|
|
|
|
' {:3d} {:3d} {:3d}'.format(
|
|
|
|
rec.mem_addr, in_use, mode, hwm,
|
|
|
|
rec.group, rec.address.human,
|
|
|
|
rec.data1, rec.data2, rec.data3))
|