diff --git a/homeassistant/components/binary_sensor/insteon_plm.py b/homeassistant/components/binary_sensor/insteon_plm.py index 06079d6aa3b..9cb87b31749 100644 --- a/homeassistant/components/binary_sensor/insteon_plm.py +++ b/homeassistant/components/binary_sensor/insteon_plm.py @@ -23,7 +23,7 @@ SENSOR_TYPES = {'openClosedSensor': 'opening', @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the INSTEON PLM device class for the hass platform.""" - plm = hass.data['insteon_plm'] + plm = hass.data['insteon_plm'].get('plm') address = discovery_info['address'] device = plm.devices[address] diff --git a/homeassistant/components/fan/insteon_plm.py b/homeassistant/components/fan/insteon_plm.py index f30abdbaa30..0911295d090 100644 --- a/homeassistant/components/fan/insteon_plm.py +++ b/homeassistant/components/fan/insteon_plm.py @@ -31,7 +31,7 @@ _LOGGER = logging.getLogger(__name__) @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the INSTEON PLM device class for the hass platform.""" - plm = hass.data['insteon_plm'] + plm = hass.data['insteon_plm'].get('plm') address = discovery_info['address'] device = plm.devices[address] diff --git a/homeassistant/components/insteon_plm.py b/homeassistant/components/insteon_plm/__init__.py similarity index 57% rename from homeassistant/components/insteon_plm.py rename to homeassistant/components/insteon_plm/__init__.py index d867f0c3d28..246e84ec71f 100644 --- a/homeassistant/components/insteon_plm.py +++ b/homeassistant/components/insteon_plm/__init__.py @@ -11,12 +11,13 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.const import (CONF_PORT, EVENT_HOMEASSISTANT_STOP, - CONF_PLATFORM) + CONF_PLATFORM, + CONF_ENTITY_ID) import homeassistant.helpers.config_validation as cv from homeassistant.helpers import discovery from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['insteonplm==0.8.6'] +REQUIREMENTS = ['insteonplm==0.9.1'] _LOGGER = logging.getLogger(__name__) @@ -29,6 +30,17 @@ CONF_SUBCAT = 'subcat' CONF_FIRMWARE = 'firmware' CONF_PRODUCT_KEY = 'product_key' +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' +SRV_ALL_LINK_GROUP = 'group' +SRV_ALL_LINK_MODE = 'mode' +SRV_LOAD_DB_RELOAD = 'reload' +SRV_CONTROLLER = 'controller' +SRV_RESPONDER = 'responder' + CONF_DEVICE_OVERRIDE_SCHEMA = vol.All( cv.deprecated(CONF_PLATFORM), vol.Schema({ vol.Required(CONF_ADDRESS): cv.string, @@ -47,6 +59,24 @@ CONFIG_SCHEMA = vol.Schema({ }) }, extra=vol.ALLOW_EXTRA) +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, + }) + @asyncio.coroutine def async_setup(hass, config): @@ -54,6 +84,7 @@ def async_setup(hass, config): import insteonplm ipdb = IPDB() + plm = None conf = config[DOMAIN] port = conf.get(CONF_PORT) @@ -79,6 +110,60 @@ def async_setup(hass, config): 'state_key': state_key}, hass_config=config)) + 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 + plm.start_all_linking(link_mode, group) + + def del_all_link(service): + """Delete an INSTEON All-Link between two devices.""" + group = service.data.get(SRV_ALL_LINK_GROUP) + plm.start_all_linking(255, group) + + 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. + print_aldb_to_log(plm.aldb) + + 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) + _LOGGER.debug("Insteon_plm Services registered") + _LOGGER.info("Looking for PLM on %s", port) conn = yield from insteonplm.Connection.create( device=port, @@ -100,11 +185,14 @@ def async_setup(hass, config): plm.devices.add_override(address, CONF_PRODUCT_KEY, device_override[prop]) - hass.data['insteon_plm'] = plm + hass.data[DOMAIN] = {} + hass.data[DOMAIN]['plm'] = plm + hass.data[DOMAIN]['entities'] = {} hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, conn.close) plm.devices.add_device_callback(async_plm_new_device) + hass.async_add_job(_register_services) return True @@ -169,6 +257,7 @@ class InsteonPLMEntity(Entity): """Initialize the INSTEON PLM binary sensor.""" self._insteon_device_state = device.states[state_key] self._insteon_device = device + self._insteon_device.aldb.add_loaded_callback(self._aldb_loaded) @property def should_poll(self): @@ -215,3 +304,44 @@ class InsteonPLMEntity(Entity): """Register INSTEON update events.""" self._insteon_device_state.register_updates( self.async_entity_update) + 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') + _LOGGER.warning('Use service insteon_plm.load_aldb first') + 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)) diff --git a/homeassistant/components/insteon_plm/services.yaml b/homeassistant/components/insteon_plm/services.yaml new file mode 100644 index 00000000000..a0e250fef1f --- /dev/null +++ b/homeassistant/components/insteon_plm/services.yaml @@ -0,0 +1,32 @@ +add_all_link: + description: Tells the Insteom Modem (IM) start All-Linking mode. Once the the IM is in All-Linking mode, press the link button on the device to complete All-Linking. + fields: + group: + description: All-Link group number. + example: 1 + mode: + description: Linking mode controller - IM is controller responder - IM is responder + example: 'controller' +delete_all_link: + description: Tells the Insteon Modem (IM) to remove an All-Link record from the All-Link Database of the IM and a device. Once the IM is set to delete the link, press the link button on the corresponding device to complete the process. + fields: + group: + description: All-Link group number. + example: 1 +load_all_link_database: + description: Load the All-Link Database for a device. WARNING - Loading a device All-LInk database is very time consuming and inconsistant. This may take a LONG time and may need to be repeated to obtain all records. + fields: + entity_id: + description: Name of the device to print + example: 'light.1a2b3c' + reload: + description: Reload all records. If true the current records are cleared from memory (does not effect the device) and the records are reloaded. If false the existing records are left in place and only missing records are added. Default is false. + example: 'true' +print_all_link_database: + description: Print the All-Link Database for a device. Requires that the All-Link Database is loaded into memory. + fields: + entity_id: + description: Name of the device to print + example: 'light.1a2b3c' +print_im_all_link_database: + description: Print the All-Link Database for the INSTEON Modem (IM). diff --git a/homeassistant/components/light/insteon_plm.py b/homeassistant/components/light/insteon_plm.py index 40453da38e5..8a3b463c2bd 100644 --- a/homeassistant/components/light/insteon_plm.py +++ b/homeassistant/components/light/insteon_plm.py @@ -21,7 +21,7 @@ MAX_BRIGHTNESS = 255 @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the Insteon PLM device.""" - plm = hass.data['insteon_plm'] + plm = hass.data['insteon_plm'].get('plm') address = discovery_info['address'] device = plm.devices[address] diff --git a/homeassistant/components/sensor/insteon_plm.py b/homeassistant/components/sensor/insteon_plm.py index a72b8efbc05..61f5877ed78 100644 --- a/homeassistant/components/sensor/insteon_plm.py +++ b/homeassistant/components/sensor/insteon_plm.py @@ -18,7 +18,7 @@ _LOGGER = logging.getLogger(__name__) @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the INSTEON PLM device class for the hass platform.""" - plm = hass.data['insteon_plm'] + plm = hass.data['insteon_plm'].get('plm') address = discovery_info['address'] device = plm.devices[address] diff --git a/homeassistant/components/switch/insteon_plm.py b/homeassistant/components/switch/insteon_plm.py index 5f9482ce955..be562e9d909 100644 --- a/homeassistant/components/switch/insteon_plm.py +++ b/homeassistant/components/switch/insteon_plm.py @@ -18,7 +18,7 @@ _LOGGER = logging.getLogger(__name__) @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Set up the INSTEON PLM device class for the hass platform.""" - plm = hass.data['insteon_plm'] + plm = hass.data['insteon_plm'].get('plm') address = discovery_info['address'] device = plm.devices[address] diff --git a/requirements_all.txt b/requirements_all.txt index d24c2e07043..e9033091f3e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -446,7 +446,7 @@ influxdb==5.0.0 insteonlocal==0.53 # homeassistant.components.insteon_plm -insteonplm==0.8.6 +insteonplm==0.9.1 # homeassistant.components.verisure jsonpath==0.75