New component 'insteon_plm' and related platforms (#6104)
* Connect to PLM and process simple protocol callbacks * Baseline commit * Connect to PLM and process simple protocol callbacks * Baseline commit * Connection working again * Async add devices is working via callback now * Beginning to interface with PLM library for control and state * Deal with brightness in 255 levels with library * Change sub names to match API changes * Remove PLM-level update callback * Support dimmable based on underlying PLM device attributes * Expand to non-light platforms * Stubs for turn on and off * Current version of Python library * Amend to use switch device attributes * Use asyncio endpoints for control * Add logging line * Bump module version to 0.7.1 * Auto-load platforms, display device info/attributes * Unify method name for getting a device attribute * Require Current version of insteonplm module * Import the component function in each platform in the balloob-recommend manner * For consistency, handle switch state as onlevel just like lights * Use level 0xff for on state, even with binary switches Observing the behavior of a 2477S switch, it looks like even the non-dimmable devices use 0x00 and 0xff for off/on respectively. I was using 0x01 for on previously, but that yields unnecessary state change callbacks when message traffic ends up flipping the onlevel from 0xff to 0x01 or 0x01 to 0xff. * Use sensorstate attribute for sensor onoff * Move new device callback to devices attribute * Add support for platform override on a device * Bump version of insteonplm module * Default overrides is an empty list * Avoid calling private methods when doing common attributes * Remove unused CONF_DEBUG for now * flake8 and pylint code cleanup * Move get_component to local function where it is needed * Update to include insteonplm module. * New files for insteon_plm component * Legitimate class doctring instead of stub * Docstring changes. * Style changes as requested by @SEJeff * Changes requested by @pvizeli * Add @callback decorator to callback functions * Opportunistic platform loading triggered by qualifying device detection Instead of loading all the constituent platforms that comprise the insteon_plm component, instead we defer and wait until we receive a callback for a device that requires the platform.
This commit is contained in:
parent
fdc373f27e
commit
3beb87c54d
6 changed files with 426 additions and 0 deletions
|
@ -41,6 +41,9 @@ omit =
|
|||
homeassistant/components/insteon_local.py
|
||||
homeassistant/components/*/insteon_local.py
|
||||
|
||||
homeassistant/components/insteon_plm.py
|
||||
homeassistant/components/*/insteon_plm.py
|
||||
|
||||
homeassistant/components/ios.py
|
||||
homeassistant/components/*/ios.py
|
||||
|
||||
|
|
87
homeassistant/components/binary_sensor/insteon_plm.py
Normal file
87
homeassistant/components/binary_sensor/insteon_plm.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
"""
|
||||
Support for INSTEON dimmers via PowerLinc Modem.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/insteon_plm/
|
||||
"""
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.loader import get_component
|
||||
|
||||
DEPENDENCIES = ['insteon_plm']
|
||||
|
||||
_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']
|
||||
|
||||
device_list = []
|
||||
for device in discovery_info:
|
||||
name = device.get('address')
|
||||
address = device.get('address_hex')
|
||||
|
||||
_LOGGER.info('Registered %s with binary_sensor platform.', name)
|
||||
|
||||
device_list.append(
|
||||
InsteonPLMBinarySensorDevice(hass, plm, address, name)
|
||||
)
|
||||
|
||||
hass.async_add_job(async_add_devices(device_list))
|
||||
|
||||
|
||||
class InsteonPLMBinarySensorDevice(BinarySensorDevice):
|
||||
"""A Class for an Insteon device."""
|
||||
|
||||
def __init__(self, hass, plm, address, name):
|
||||
"""Initialize the binarysensor."""
|
||||
self._hass = hass
|
||||
self._plm = plm.protocol
|
||||
self._address = address
|
||||
self._name = name
|
||||
|
||||
self._plm.add_update_callback(
|
||||
self.async_binarysensor_update, {'address': self._address})
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
"""Return the the address of the node."""
|
||||
return self._address
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the the name of the node."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return the boolean response if the node is on."""
|
||||
sensorstate = self._plm.get_device_attr(self._address, 'sensorstate')
|
||||
_LOGGER.info('sensor state for %s is %s', self._address, sensorstate)
|
||||
return bool(sensorstate)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Provide attributes for display on device card."""
|
||||
insteon_plm = get_component('insteon_plm')
|
||||
return insteon_plm.common_attributes(self)
|
||||
|
||||
def get_attr(self, key):
|
||||
"""Return specified attribute for this device."""
|
||||
return self._plm.get_device_attr(self.address, key)
|
||||
|
||||
@callback
|
||||
def async_binarysensor_update(self, message):
|
||||
"""Receive notification from transport that new data exists."""
|
||||
_LOGGER.info('Received update calback from PLM for %s', self._address)
|
||||
self._hass.async_add_job(self.async_update_ha_state())
|
117
homeassistant/components/insteon_plm.py
Normal file
117
homeassistant/components/insteon_plm.py
Normal file
|
@ -0,0 +1,117 @@
|
|||
"""
|
||||
Support for INSTEON PowerLinc Modem.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/insteon_plm/
|
||||
"""
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.const import (
|
||||
CONF_PORT, EVENT_HOMEASSISTANT_STOP)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers import discovery
|
||||
|
||||
REQUIREMENTS = ['insteonplm==0.7.4']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = 'insteon_plm'
|
||||
|
||||
CONF_OVERRIDE = 'device_override'
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_PORT): cv.string,
|
||||
vol.Optional(CONF_OVERRIDE, default=[]): vol.All(
|
||||
cv.ensure_list_csv, vol.Length(min=1))
|
||||
})
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
PLM_PLATFORMS = {
|
||||
'binary_sensor': ['binary_sensor'],
|
||||
'light': ['light'],
|
||||
'switch': ['switch'],
|
||||
}
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
"""Set up our connection to the PLM."""
|
||||
import insteonplm
|
||||
|
||||
conf = config[DOMAIN]
|
||||
port = conf.get(CONF_PORT)
|
||||
overrides = conf.get(CONF_OVERRIDE)
|
||||
|
||||
@callback
|
||||
def async_plm_new_device(device):
|
||||
"""New device detected from transport to be delegated to platform."""
|
||||
name = device.get('address')
|
||||
address = device.get('address_hex')
|
||||
capabilities = device.get('capabilities', [])
|
||||
|
||||
_LOGGER.info('New INSTEON PLM device: %s (%s) %r',
|
||||
name, address, capabilities)
|
||||
|
||||
loadlist = []
|
||||
for platform in PLM_PLATFORMS:
|
||||
caplist = PLM_PLATFORMS.get(platform)
|
||||
for key in capabilities:
|
||||
if key in caplist:
|
||||
loadlist.append(platform)
|
||||
|
||||
loadlist = sorted(set(loadlist))
|
||||
|
||||
for loadplatform in loadlist:
|
||||
hass.async_add_job(
|
||||
discovery.async_load_platform(
|
||||
hass, loadplatform, DOMAIN, discovered=[device],
|
||||
hass_config=config))
|
||||
|
||||
_LOGGER.info('Looking for PLM on %s', port)
|
||||
plm = yield from insteonplm.Connection.create(device=port, loop=hass.loop)
|
||||
|
||||
for device in overrides:
|
||||
#
|
||||
# Override the device default capabilities for a specific address
|
||||
#
|
||||
plm.protocol.devices.add_override(
|
||||
device['address'], 'capabilities', [device['platform']])
|
||||
|
||||
hass.data['insteon_plm'] = plm
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, plm.close)
|
||||
|
||||
plm.protocol.devices.add_device_callback(async_plm_new_device, {})
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def common_attributes(entity):
|
||||
"""Return the device state attributes."""
|
||||
attributes = {}
|
||||
attributekeys = {
|
||||
'address': 'INSTEON Address',
|
||||
'description': 'Description',
|
||||
'model': 'Model',
|
||||
'cat': 'Cagegory',
|
||||
'subcat': 'Subcategory',
|
||||
'firmware': 'Firmware',
|
||||
'product_key': 'Product Key'
|
||||
}
|
||||
|
||||
hexkeys = ['cat', 'subcat', 'firmware']
|
||||
|
||||
for key in attributekeys:
|
||||
name = attributekeys[key]
|
||||
val = entity.get_attr(key)
|
||||
if val is not None:
|
||||
if key in hexkeys:
|
||||
attributes[name] = hex(int(val))
|
||||
else:
|
||||
attributes[name] = val
|
||||
return attributes
|
119
homeassistant/components/light/insteon_plm.py
Normal file
119
homeassistant/components/light/insteon_plm.py
Normal file
|
@ -0,0 +1,119 @@
|
|||
"""
|
||||
Support for INSTEON lights via PowerLinc Modem.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/insteon_plm/
|
||||
"""
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light)
|
||||
from homeassistant.loader import get_component
|
||||
|
||||
DEPENDENCIES = ['insteon_plm']
|
||||
|
||||
MAX_BRIGHTNESS = 255
|
||||
|
||||
_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']
|
||||
|
||||
device_list = []
|
||||
for device in discovery_info:
|
||||
name = device.get('address')
|
||||
address = device.get('address_hex')
|
||||
dimmable = bool('dimmable' in device.get('capabilities'))
|
||||
|
||||
_LOGGER.info('Registered %s with light platform.', name)
|
||||
|
||||
device_list.append(
|
||||
InsteonPLMDimmerDevice(hass, plm, address, name, dimmable)
|
||||
)
|
||||
|
||||
hass.async_add_job(async_add_devices(device_list))
|
||||
|
||||
|
||||
class InsteonPLMDimmerDevice(Light):
|
||||
"""A Class for an Insteon device."""
|
||||
|
||||
def __init__(self, hass, plm, address, name, dimmable):
|
||||
"""Initialize the light."""
|
||||
self._hass = hass
|
||||
self._plm = plm.protocol
|
||||
self._address = address
|
||||
self._name = name
|
||||
self._dimmable = dimmable
|
||||
|
||||
self._plm.add_update_callback(
|
||||
self.async_light_update, {'address': self._address})
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
"""Return the the address of the node."""
|
||||
return self._address
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the the name of the node."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
onlevel = self._plm.get_device_attr(self._address, 'onlevel')
|
||||
_LOGGER.debug('on level for %s is %s', self._address, onlevel)
|
||||
return int(onlevel)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return the boolean response if the node is on."""
|
||||
onlevel = self._plm.get_device_attr(self._address, 'onlevel')
|
||||
_LOGGER.debug('on level for %s is %s', self._address, onlevel)
|
||||
return bool(onlevel)
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
if self._dimmable:
|
||||
return SUPPORT_BRIGHTNESS
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Provide attributes for display on device card."""
|
||||
insteon_plm = get_component('insteon_plm')
|
||||
return insteon_plm.common_attributes(self)
|
||||
|
||||
def get_attr(self, key):
|
||||
"""Return specified attribute for this device."""
|
||||
return self._plm.get_device_attr(self.address, key)
|
||||
|
||||
@callback
|
||||
def async_light_update(self, message):
|
||||
"""Receive notification from transport that new data exists."""
|
||||
_LOGGER.info('Received update calback from PLM for %s', self._address)
|
||||
self._hass.async_add_job(self.async_update_ha_state())
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_turn_on(self, **kwargs):
|
||||
"""Turn device on."""
|
||||
if ATTR_BRIGHTNESS in kwargs:
|
||||
brightness = int(kwargs[ATTR_BRIGHTNESS])
|
||||
else:
|
||||
brightness = MAX_BRIGHTNESS
|
||||
self._plm.turn_on(self._address, brightness=brightness)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_turn_off(self, **kwargs):
|
||||
"""Turn device off."""
|
||||
self._plm.turn_off(self._address)
|
97
homeassistant/components/switch/insteon_plm.py
Normal file
97
homeassistant/components/switch/insteon_plm.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
"""
|
||||
Support for INSTEON dimmers via PowerLinc Modem.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/insteon_plm/
|
||||
"""
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.switch import (SwitchDevice)
|
||||
from homeassistant.loader import get_component
|
||||
|
||||
DEPENDENCIES = ['insteon_plm']
|
||||
|
||||
_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']
|
||||
|
||||
device_list = []
|
||||
for device in discovery_info:
|
||||
name = device.get('address')
|
||||
address = device.get('address_hex')
|
||||
|
||||
_LOGGER.info('Registered %s with switch platform.', name)
|
||||
|
||||
device_list.append(
|
||||
InsteonPLMSwitchDevice(hass, plm, address, name)
|
||||
)
|
||||
|
||||
hass.async_add_job(async_add_devices(device_list))
|
||||
|
||||
|
||||
class InsteonPLMSwitchDevice(SwitchDevice):
|
||||
"""A Class for an Insteon device."""
|
||||
|
||||
def __init__(self, hass, plm, address, name):
|
||||
"""Initialize the switch."""
|
||||
self._hass = hass
|
||||
self._plm = plm.protocol
|
||||
self._address = address
|
||||
self._name = name
|
||||
|
||||
self._plm.add_update_callback(
|
||||
self.async_switch_update, {'address': self._address})
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
"""Return the the address of the node."""
|
||||
return self._address
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the the name of the node."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return the boolean response if the node is on."""
|
||||
onlevel = self._plm.get_device_attr(self._address, 'onlevel')
|
||||
_LOGGER.debug('on level for %s is %s', self._address, onlevel)
|
||||
return bool(onlevel)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Provide attributes for display on device card."""
|
||||
insteon_plm = get_component('insteon_plm')
|
||||
return insteon_plm.common_attributes(self)
|
||||
|
||||
def get_attr(self, key):
|
||||
"""Return specified attribute for this device."""
|
||||
return self._plm.get_device_attr(self.address, key)
|
||||
|
||||
@callback
|
||||
def async_switch_update(self, message):
|
||||
"""Receive notification from transport that new data exists."""
|
||||
_LOGGER.info('Received update calback from PLM for %s', self._address)
|
||||
self._hass.async_add_job(self.async_update_ha_state())
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_turn_on(self, **kwargs):
|
||||
"""Turn device on."""
|
||||
self._plm.turn_on(self._address)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_turn_off(self, **kwargs):
|
||||
"""Turn device off."""
|
||||
self._plm.turn_off(self._address)
|
|
@ -297,6 +297,9 @@ insteon_hub==0.4.5
|
|||
# homeassistant.components.insteon_local
|
||||
insteonlocal==0.39
|
||||
|
||||
# homeassistant.components.insteon_plm
|
||||
insteonplm==0.7.4
|
||||
|
||||
# homeassistant.components.media_player.kodi
|
||||
jsonrpc-async==0.4
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue