mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 03:37:07 +00:00
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
@ -41,6 +41,9 @@ omit =
|
|||||||
homeassistant/components/insteon_local.py
|
homeassistant/components/insteon_local.py
|
||||||
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
|
||||||
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
|
# homeassistant.components.insteon_local
|
||||||
insteonlocal==0.39
|
insteonlocal==0.39
|
||||||
|
|
||||||
|
# homeassistant.components.insteon_plm
|
||||||
|
insteonplm==0.7.4
|
||||||
|
|
||||||
# homeassistant.components.media_player.kodi
|
# homeassistant.components.media_player.kodi
|
||||||
jsonrpc-async==0.4
|
jsonrpc-async==0.4
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user