2015-04-04 08:33:03 +00:00
|
|
|
"""
|
2016-03-07 17:49:31 +00:00
|
|
|
Support the ISY-994 controllers.
|
2015-08-08 17:16:15 +00:00
|
|
|
|
|
|
|
For configuration details please visit the documentation for this component at
|
2015-11-09 12:12:18 +00:00
|
|
|
https://home-assistant.io/components/isy994/
|
2015-04-04 08:33:03 +00:00
|
|
|
"""
|
ISY994 sensor improvements (#10805)
* Fire events for ISY994 control events
This allows hass to react directly to Insteon button presses (on switches and remotes), including presses, double-presses, and long holds
* Move change event subscription to after entity is added to hass
The event handler method requires `self.hass` to exist, which doesn't have a value until the async_added_to_hass method is called. Should eliminate a race condition.
* Overhaul binary sensors in ISY994 to be functional "out of the box"
We now smash all of the subnodes from the ISY994 in to one Hass binary_sensor, and automatically support both paradigms of state reporting that Insteon sensors can do. Sometimes a single node's state represents the sensor's state, other times two nodes are used and only "ON" events are sent from each. The logic between the two forunately do not conflict so we can support both without knowing which mode the device is in.
This also allows us to handle the heartbeat functionality that certain sensors have - we simply store the timestamp of the heartbeat as an attribute on the sensor device. It defaults to Unknown on bootup if and only if the device supports heartbeats, due to the presence of subnode 4.
* Parse the binary sensor device class from the ISY's device "type"
Now we automatically know which sensors are moisture, motion, and openings! (We also reverse the moisture sensor state, because Insteon reports ON for dry on the primary node.)
* Code review tweaks
The one material change here is that the event subscribers were moved to the `async_added_to_hass` method, as the handlers depend on things that only exist after the entity has been added.
* Handle cases where a sensor's state is unknown
When the ISY first boots up, if a battery-powered sensor has not reported in yet (due to heartbeat or a change in state), the state is unknown until it does.
* Clean up from code review
Fix coroutine await, remove unnecessary exception check, and return None when state is unknown
* Unknown value from PyISY is now -inf rather than -1
* Move heartbeat handling to a separate sensor
Now all heartbeat-compatible sensors will have a separate `binary_sensor` device that represents the battery state (on = dead)
* Add support for Unknown state, which is being added in next PyISY
PyISY will report unknown states as the number "-inf". This is implemented in the base ISY994 component, but subcomponents that override the `state` method needed some extra logic to handle it as well.
* Change a couple try blocks to explicit None checks
* Bump PyISY to 1.1.0, now that it has been published!
* Remove -inf checking from base component
The implementation of the -inf checking was improved in another branch which has been merged in to this branch already.
* Restrict negative-node and heartbeat support to known compatible types
Not all Insteon sensors use the same subnode IDs for the same things, so we need to use different logic depending on device type. Negative node and heartbeat support is now only used for leak sensors and open/close sensors.
* Use new style string formatting
* Add binary sensor detection for pre-5.x firmware
Meant to do this originally; writing documentation revealed that this requirement was missed!
2017-12-14 04:14:56 +00:00
|
|
|
import asyncio
|
2017-01-05 22:33:52 +00:00
|
|
|
from collections import namedtuple
|
2015-04-04 08:33:03 +00:00
|
|
|
import logging
|
|
|
|
from urllib.parse import urlparse
|
2017-04-30 05:04:49 +00:00
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
import voluptuous as vol
|
2015-04-04 08:33:03 +00:00
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
from homeassistant.core import HomeAssistant # noqa
|
2016-02-19 05:27:50 +00:00
|
|
|
from homeassistant.const import (
|
2017-04-30 05:04:49 +00:00
|
|
|
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP)
|
2016-09-11 18:18:53 +00:00
|
|
|
from homeassistant.helpers import discovery, config_validation as cv
|
|
|
|
from homeassistant.helpers.entity import Entity
|
|
|
|
from homeassistant.helpers.typing import ConfigType, Dict # noqa
|
|
|
|
|
ISY994 sensor improvements (#10805)
* Fire events for ISY994 control events
This allows hass to react directly to Insteon button presses (on switches and remotes), including presses, double-presses, and long holds
* Move change event subscription to after entity is added to hass
The event handler method requires `self.hass` to exist, which doesn't have a value until the async_added_to_hass method is called. Should eliminate a race condition.
* Overhaul binary sensors in ISY994 to be functional "out of the box"
We now smash all of the subnodes from the ISY994 in to one Hass binary_sensor, and automatically support both paradigms of state reporting that Insteon sensors can do. Sometimes a single node's state represents the sensor's state, other times two nodes are used and only "ON" events are sent from each. The logic between the two forunately do not conflict so we can support both without knowing which mode the device is in.
This also allows us to handle the heartbeat functionality that certain sensors have - we simply store the timestamp of the heartbeat as an attribute on the sensor device. It defaults to Unknown on bootup if and only if the device supports heartbeats, due to the presence of subnode 4.
* Parse the binary sensor device class from the ISY's device "type"
Now we automatically know which sensors are moisture, motion, and openings! (We also reverse the moisture sensor state, because Insteon reports ON for dry on the primary node.)
* Code review tweaks
The one material change here is that the event subscribers were moved to the `async_added_to_hass` method, as the handlers depend on things that only exist after the entity has been added.
* Handle cases where a sensor's state is unknown
When the ISY first boots up, if a battery-powered sensor has not reported in yet (due to heartbeat or a change in state), the state is unknown until it does.
* Clean up from code review
Fix coroutine await, remove unnecessary exception check, and return None when state is unknown
* Unknown value from PyISY is now -inf rather than -1
* Move heartbeat handling to a separate sensor
Now all heartbeat-compatible sensors will have a separate `binary_sensor` device that represents the battery state (on = dead)
* Add support for Unknown state, which is being added in next PyISY
PyISY will report unknown states as the number "-inf". This is implemented in the base ISY994 component, but subcomponents that override the `state` method needed some extra logic to handle it as well.
* Change a couple try blocks to explicit None checks
* Bump PyISY to 1.1.0, now that it has been published!
* Remove -inf checking from base component
The implementation of the -inf checking was improved in another branch which has been merged in to this branch already.
* Restrict negative-node and heartbeat support to known compatible types
Not all Insteon sensors use the same subnode IDs for the same things, so we need to use different logic depending on device type. Negative node and heartbeat support is now only used for leak sensors and open/close sensors.
* Use new style string formatting
* Add binary sensor detection for pre-5.x firmware
Meant to do this originally; writing documentation revealed that this requirement was missed!
2017-12-14 04:14:56 +00:00
|
|
|
REQUIREMENTS = ['PyISY==1.1.0']
|
2016-06-12 00:43:13 +00:00
|
|
|
|
2017-04-30 05:04:49 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
DOMAIN = 'isy994'
|
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
CONF_HIDDEN_STRING = 'hidden_string'
|
|
|
|
CONF_SENSOR_STRING = 'sensor_string'
|
2017-04-30 05:04:49 +00:00
|
|
|
CONF_TLS_VER = 'tls'
|
|
|
|
|
|
|
|
DEFAULT_HIDDEN_STRING = '{HIDE ME}'
|
|
|
|
DEFAULT_SENSOR_STRING = 'sensor'
|
|
|
|
|
|
|
|
ISY = None
|
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
KEY_ACTIONS = 'actions'
|
2017-04-30 05:04:49 +00:00
|
|
|
KEY_FOLDER = 'folder'
|
|
|
|
KEY_MY_PROGRAMS = 'My Programs'
|
2016-09-11 18:18:53 +00:00
|
|
|
KEY_STATUS = 'status'
|
2015-04-04 08:33:03 +00:00
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
CONFIG_SCHEMA = vol.Schema({
|
|
|
|
DOMAIN: vol.Schema({
|
|
|
|
vol.Required(CONF_HOST): cv.url,
|
|
|
|
vol.Required(CONF_USERNAME): cv.string,
|
|
|
|
vol.Required(CONF_PASSWORD): cv.string,
|
|
|
|
vol.Optional(CONF_TLS_VER): vol.Coerce(float),
|
|
|
|
vol.Optional(CONF_HIDDEN_STRING,
|
|
|
|
default=DEFAULT_HIDDEN_STRING): cv.string,
|
|
|
|
vol.Optional(CONF_SENSOR_STRING,
|
|
|
|
default=DEFAULT_SENSOR_STRING): cv.string
|
|
|
|
})
|
|
|
|
}, extra=vol.ALLOW_EXTRA)
|
|
|
|
|
|
|
|
SENSOR_NODES = []
|
2017-01-05 22:33:52 +00:00
|
|
|
WEATHER_NODES = []
|
2016-09-11 18:18:53 +00:00
|
|
|
NODES = []
|
|
|
|
GROUPS = []
|
|
|
|
PROGRAMS = {}
|
|
|
|
|
|
|
|
PYISY = None
|
|
|
|
|
|
|
|
HIDDEN_STRING = DEFAULT_HIDDEN_STRING
|
|
|
|
|
|
|
|
SUPPORTED_DOMAINS = ['binary_sensor', 'cover', 'fan', 'light', 'lock',
|
|
|
|
'sensor', 'switch']
|
|
|
|
|
|
|
|
|
2017-01-05 22:33:52 +00:00
|
|
|
WeatherNode = namedtuple('WeatherNode', ('status', 'name', 'uom'))
|
|
|
|
|
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
def filter_nodes(nodes: list, units: list=None, states: list=None) -> list:
|
|
|
|
"""Filter a list of ISY nodes based on the units and states provided."""
|
|
|
|
filtered_nodes = []
|
|
|
|
units = units if units else []
|
|
|
|
states = states if states else []
|
|
|
|
for node in nodes:
|
|
|
|
match_unit = False
|
|
|
|
match_state = True
|
|
|
|
for uom in node.uom:
|
|
|
|
if uom in units:
|
|
|
|
match_unit = True
|
|
|
|
continue
|
|
|
|
elif uom not in states:
|
|
|
|
match_state = False
|
|
|
|
|
|
|
|
if match_unit:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if match_unit or match_state:
|
|
|
|
filtered_nodes.append(node)
|
|
|
|
|
|
|
|
return filtered_nodes
|
|
|
|
|
|
|
|
|
ISY994 sensor improvements (#10805)
* Fire events for ISY994 control events
This allows hass to react directly to Insteon button presses (on switches and remotes), including presses, double-presses, and long holds
* Move change event subscription to after entity is added to hass
The event handler method requires `self.hass` to exist, which doesn't have a value until the async_added_to_hass method is called. Should eliminate a race condition.
* Overhaul binary sensors in ISY994 to be functional "out of the box"
We now smash all of the subnodes from the ISY994 in to one Hass binary_sensor, and automatically support both paradigms of state reporting that Insteon sensors can do. Sometimes a single node's state represents the sensor's state, other times two nodes are used and only "ON" events are sent from each. The logic between the two forunately do not conflict so we can support both without knowing which mode the device is in.
This also allows us to handle the heartbeat functionality that certain sensors have - we simply store the timestamp of the heartbeat as an attribute on the sensor device. It defaults to Unknown on bootup if and only if the device supports heartbeats, due to the presence of subnode 4.
* Parse the binary sensor device class from the ISY's device "type"
Now we automatically know which sensors are moisture, motion, and openings! (We also reverse the moisture sensor state, because Insteon reports ON for dry on the primary node.)
* Code review tweaks
The one material change here is that the event subscribers were moved to the `async_added_to_hass` method, as the handlers depend on things that only exist after the entity has been added.
* Handle cases where a sensor's state is unknown
When the ISY first boots up, if a battery-powered sensor has not reported in yet (due to heartbeat or a change in state), the state is unknown until it does.
* Clean up from code review
Fix coroutine await, remove unnecessary exception check, and return None when state is unknown
* Unknown value from PyISY is now -inf rather than -1
* Move heartbeat handling to a separate sensor
Now all heartbeat-compatible sensors will have a separate `binary_sensor` device that represents the battery state (on = dead)
* Add support for Unknown state, which is being added in next PyISY
PyISY will report unknown states as the number "-inf". This is implemented in the base ISY994 component, but subcomponents that override the `state` method needed some extra logic to handle it as well.
* Change a couple try blocks to explicit None checks
* Bump PyISY to 1.1.0, now that it has been published!
* Remove -inf checking from base component
The implementation of the -inf checking was improved in another branch which has been merged in to this branch already.
* Restrict negative-node and heartbeat support to known compatible types
Not all Insteon sensors use the same subnode IDs for the same things, so we need to use different logic depending on device type. Negative node and heartbeat support is now only used for leak sensors and open/close sensors.
* Use new style string formatting
* Add binary sensor detection for pre-5.x firmware
Meant to do this originally; writing documentation revealed that this requirement was missed!
2017-12-14 04:14:56 +00:00
|
|
|
def _is_node_a_sensor(node, path: str, sensor_identifier: str) -> bool:
|
|
|
|
"""Determine if the given node is a sensor."""
|
|
|
|
if not isinstance(node, PYISY.Nodes.Node):
|
|
|
|
return False
|
|
|
|
|
|
|
|
if sensor_identifier in path or sensor_identifier in node.name:
|
|
|
|
return True
|
|
|
|
|
|
|
|
# This method is most reliable but only works on 5.x firmware
|
|
|
|
try:
|
|
|
|
if node.node_def_id == 'BinaryAlarm':
|
|
|
|
return True
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
# This method works on all firmwares, but only for Insteon devices
|
|
|
|
try:
|
|
|
|
device_type = node.type
|
|
|
|
except AttributeError:
|
|
|
|
# Node has no type; most likely not an Insteon device
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
split_type = device_type.split('.')
|
|
|
|
return split_type[0] == '16' # 16 represents Insteon binary sensors
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
def _categorize_nodes(hidden_identifier: str, sensor_identifier: str) -> None:
|
|
|
|
"""Categorize the ISY994 nodes."""
|
|
|
|
global SENSOR_NODES
|
|
|
|
global NODES
|
|
|
|
global GROUPS
|
|
|
|
|
|
|
|
SENSOR_NODES = []
|
|
|
|
NODES = []
|
|
|
|
GROUPS = []
|
|
|
|
|
2016-10-30 21:18:53 +00:00
|
|
|
# pylint: disable=no-member
|
2016-09-11 18:18:53 +00:00
|
|
|
for (path, node) in ISY.nodes:
|
|
|
|
hidden = hidden_identifier in path or hidden_identifier in node.name
|
|
|
|
if hidden:
|
|
|
|
node.name += hidden_identifier
|
ISY994 sensor improvements (#10805)
* Fire events for ISY994 control events
This allows hass to react directly to Insteon button presses (on switches and remotes), including presses, double-presses, and long holds
* Move change event subscription to after entity is added to hass
The event handler method requires `self.hass` to exist, which doesn't have a value until the async_added_to_hass method is called. Should eliminate a race condition.
* Overhaul binary sensors in ISY994 to be functional "out of the box"
We now smash all of the subnodes from the ISY994 in to one Hass binary_sensor, and automatically support both paradigms of state reporting that Insteon sensors can do. Sometimes a single node's state represents the sensor's state, other times two nodes are used and only "ON" events are sent from each. The logic between the two forunately do not conflict so we can support both without knowing which mode the device is in.
This also allows us to handle the heartbeat functionality that certain sensors have - we simply store the timestamp of the heartbeat as an attribute on the sensor device. It defaults to Unknown on bootup if and only if the device supports heartbeats, due to the presence of subnode 4.
* Parse the binary sensor device class from the ISY's device "type"
Now we automatically know which sensors are moisture, motion, and openings! (We also reverse the moisture sensor state, because Insteon reports ON for dry on the primary node.)
* Code review tweaks
The one material change here is that the event subscribers were moved to the `async_added_to_hass` method, as the handlers depend on things that only exist after the entity has been added.
* Handle cases where a sensor's state is unknown
When the ISY first boots up, if a battery-powered sensor has not reported in yet (due to heartbeat or a change in state), the state is unknown until it does.
* Clean up from code review
Fix coroutine await, remove unnecessary exception check, and return None when state is unknown
* Unknown value from PyISY is now -inf rather than -1
* Move heartbeat handling to a separate sensor
Now all heartbeat-compatible sensors will have a separate `binary_sensor` device that represents the battery state (on = dead)
* Add support for Unknown state, which is being added in next PyISY
PyISY will report unknown states as the number "-inf". This is implemented in the base ISY994 component, but subcomponents that override the `state` method needed some extra logic to handle it as well.
* Change a couple try blocks to explicit None checks
* Bump PyISY to 1.1.0, now that it has been published!
* Remove -inf checking from base component
The implementation of the -inf checking was improved in another branch which has been merged in to this branch already.
* Restrict negative-node and heartbeat support to known compatible types
Not all Insteon sensors use the same subnode IDs for the same things, so we need to use different logic depending on device type. Negative node and heartbeat support is now only used for leak sensors and open/close sensors.
* Use new style string formatting
* Add binary sensor detection for pre-5.x firmware
Meant to do this originally; writing documentation revealed that this requirement was missed!
2017-12-14 04:14:56 +00:00
|
|
|
if _is_node_a_sensor(node, path, sensor_identifier):
|
2016-09-11 18:18:53 +00:00
|
|
|
SENSOR_NODES.append(node)
|
2016-10-30 21:18:53 +00:00
|
|
|
elif isinstance(node, PYISY.Nodes.Node):
|
2016-09-11 18:18:53 +00:00
|
|
|
NODES.append(node)
|
2016-10-30 21:18:53 +00:00
|
|
|
elif isinstance(node, PYISY.Nodes.Group):
|
2016-09-11 18:18:53 +00:00
|
|
|
GROUPS.append(node)
|
|
|
|
|
|
|
|
|
|
|
|
def _categorize_programs() -> None:
|
|
|
|
"""Categorize the ISY994 programs."""
|
|
|
|
global PROGRAMS
|
|
|
|
|
|
|
|
PROGRAMS = {}
|
|
|
|
|
|
|
|
for component in SUPPORTED_DOMAINS:
|
|
|
|
try:
|
|
|
|
folder = ISY.programs[KEY_MY_PROGRAMS]['HA.' + component]
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
for dtype, _, node_id in folder.children:
|
|
|
|
if dtype is KEY_FOLDER:
|
|
|
|
program = folder[node_id]
|
|
|
|
try:
|
|
|
|
node = program[KEY_STATUS].leaf
|
|
|
|
assert node.dtype == 'program', 'Not a program'
|
|
|
|
except (KeyError, AssertionError):
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
if component not in PROGRAMS:
|
|
|
|
PROGRAMS[component] = []
|
|
|
|
PROGRAMS[component].append(program)
|
|
|
|
|
|
|
|
|
2017-01-05 22:33:52 +00:00
|
|
|
def _categorize_weather() -> None:
|
|
|
|
"""Categorize the ISY994 weather data."""
|
|
|
|
global WEATHER_NODES
|
|
|
|
|
|
|
|
climate_attrs = dir(ISY.climate)
|
|
|
|
WEATHER_NODES = [WeatherNode(getattr(ISY.climate, attr), attr,
|
|
|
|
getattr(ISY.climate, attr + '_units'))
|
|
|
|
for attr in climate_attrs
|
|
|
|
if attr + '_units' in climate_attrs]
|
|
|
|
|
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|
|
|
"""Set up the ISY 994 platform."""
|
|
|
|
isy_config = config.get(DOMAIN)
|
|
|
|
|
|
|
|
user = isy_config.get(CONF_USERNAME)
|
|
|
|
password = isy_config.get(CONF_PASSWORD)
|
|
|
|
tls_version = isy_config.get(CONF_TLS_VER)
|
|
|
|
host = urlparse(isy_config.get(CONF_HOST))
|
|
|
|
port = host.port
|
|
|
|
addr = host.geturl()
|
2017-04-30 05:04:49 +00:00
|
|
|
hidden_identifier = isy_config.get(
|
|
|
|
CONF_HIDDEN_STRING, DEFAULT_HIDDEN_STRING)
|
|
|
|
sensor_identifier = isy_config.get(
|
|
|
|
CONF_SENSOR_STRING, DEFAULT_SENSOR_STRING)
|
2015-04-04 08:33:03 +00:00
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
global HIDDEN_STRING
|
|
|
|
HIDDEN_STRING = hidden_identifier
|
2015-04-13 16:56:37 +00:00
|
|
|
|
|
|
|
if host.scheme == 'http':
|
|
|
|
addr = addr.replace('http://', '')
|
|
|
|
https = False
|
|
|
|
elif host.scheme == 'https':
|
|
|
|
addr = addr.replace('https://', '')
|
|
|
|
https = True
|
2015-04-04 08:33:03 +00:00
|
|
|
else:
|
2017-04-30 05:04:49 +00:00
|
|
|
_LOGGER.error("isy994 host value in configuration is invalid")
|
2015-04-13 16:56:37 +00:00
|
|
|
return False
|
2016-09-11 18:18:53 +00:00
|
|
|
|
2015-04-13 16:56:37 +00:00
|
|
|
addr = addr.replace(':{}'.format(port), '')
|
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
import PyISY
|
|
|
|
|
|
|
|
global PYISY
|
|
|
|
PYISY = PyISY
|
2015-04-04 08:33:03 +00:00
|
|
|
|
2016-03-07 17:49:31 +00:00
|
|
|
# Connect to ISY controller.
|
2015-04-04 08:33:03 +00:00
|
|
|
global ISY
|
2016-09-11 18:18:53 +00:00
|
|
|
ISY = PyISY.ISY(addr, port, username=user, password=password,
|
|
|
|
use_https=https, tls_ver=tls_version, log=_LOGGER)
|
2015-04-04 08:33:03 +00:00
|
|
|
if not ISY.connected:
|
|
|
|
return False
|
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
_categorize_nodes(hidden_identifier, sensor_identifier)
|
|
|
|
|
|
|
|
_categorize_programs()
|
|
|
|
|
2017-01-05 22:33:52 +00:00
|
|
|
if ISY.configuration.get('Weather Information'):
|
|
|
|
_categorize_weather()
|
|
|
|
|
2016-03-07 17:49:31 +00:00
|
|
|
# Listen for HA stop to disconnect.
|
2015-04-25 05:10:41 +00:00
|
|
|
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop)
|
|
|
|
|
2016-06-12 00:43:13 +00:00
|
|
|
# Load platforms for the devices in the ISY controller that we support.
|
2016-09-11 18:18:53 +00:00
|
|
|
for component in SUPPORTED_DOMAINS:
|
2016-06-15 05:51:46 +00:00
|
|
|
discovery.load_platform(hass, component, DOMAIN, {}, config)
|
2015-04-04 08:33:03 +00:00
|
|
|
|
|
|
|
ISY.auto_update = True
|
|
|
|
return True
|
2015-04-12 20:45:23 +00:00
|
|
|
|
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
# pylint: disable=unused-argument
|
|
|
|
def stop(event: object) -> None:
|
|
|
|
"""Stop ISY auto updates."""
|
2015-04-25 05:10:41 +00:00
|
|
|
ISY.auto_update = False
|
|
|
|
|
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
class ISYDevice(Entity):
|
|
|
|
"""Representation of an ISY994 device."""
|
2016-03-08 16:55:57 +00:00
|
|
|
|
2015-04-12 20:45:23 +00:00
|
|
|
_attrs = {}
|
2016-09-11 18:18:53 +00:00
|
|
|
_domain = None # type: str
|
|
|
|
_name = None # type: str
|
2015-04-12 20:45:23 +00:00
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
def __init__(self, node) -> None:
|
|
|
|
"""Initialize the insteon device."""
|
|
|
|
self._node = node
|
ISY994 sensor improvements (#10805)
* Fire events for ISY994 control events
This allows hass to react directly to Insteon button presses (on switches and remotes), including presses, double-presses, and long holds
* Move change event subscription to after entity is added to hass
The event handler method requires `self.hass` to exist, which doesn't have a value until the async_added_to_hass method is called. Should eliminate a race condition.
* Overhaul binary sensors in ISY994 to be functional "out of the box"
We now smash all of the subnodes from the ISY994 in to one Hass binary_sensor, and automatically support both paradigms of state reporting that Insteon sensors can do. Sometimes a single node's state represents the sensor's state, other times two nodes are used and only "ON" events are sent from each. The logic between the two forunately do not conflict so we can support both without knowing which mode the device is in.
This also allows us to handle the heartbeat functionality that certain sensors have - we simply store the timestamp of the heartbeat as an attribute on the sensor device. It defaults to Unknown on bootup if and only if the device supports heartbeats, due to the presence of subnode 4.
* Parse the binary sensor device class from the ISY's device "type"
Now we automatically know which sensors are moisture, motion, and openings! (We also reverse the moisture sensor state, because Insteon reports ON for dry on the primary node.)
* Code review tweaks
The one material change here is that the event subscribers were moved to the `async_added_to_hass` method, as the handlers depend on things that only exist after the entity has been added.
* Handle cases where a sensor's state is unknown
When the ISY first boots up, if a battery-powered sensor has not reported in yet (due to heartbeat or a change in state), the state is unknown until it does.
* Clean up from code review
Fix coroutine await, remove unnecessary exception check, and return None when state is unknown
* Unknown value from PyISY is now -inf rather than -1
* Move heartbeat handling to a separate sensor
Now all heartbeat-compatible sensors will have a separate `binary_sensor` device that represents the battery state (on = dead)
* Add support for Unknown state, which is being added in next PyISY
PyISY will report unknown states as the number "-inf". This is implemented in the base ISY994 component, but subcomponents that override the `state` method needed some extra logic to handle it as well.
* Change a couple try blocks to explicit None checks
* Bump PyISY to 1.1.0, now that it has been published!
* Remove -inf checking from base component
The implementation of the -inf checking was improved in another branch which has been merged in to this branch already.
* Restrict negative-node and heartbeat support to known compatible types
Not all Insteon sensors use the same subnode IDs for the same things, so we need to use different logic depending on device type. Negative node and heartbeat support is now only used for leak sensors and open/close sensors.
* Use new style string formatting
* Add binary sensor detection for pre-5.x firmware
Meant to do this originally; writing documentation revealed that this requirement was missed!
2017-12-14 04:14:56 +00:00
|
|
|
self._change_handler = None
|
|
|
|
self._control_handler = None
|
2015-04-12 20:45:23 +00:00
|
|
|
|
ISY994 sensor improvements (#10805)
* Fire events for ISY994 control events
This allows hass to react directly to Insteon button presses (on switches and remotes), including presses, double-presses, and long holds
* Move change event subscription to after entity is added to hass
The event handler method requires `self.hass` to exist, which doesn't have a value until the async_added_to_hass method is called. Should eliminate a race condition.
* Overhaul binary sensors in ISY994 to be functional "out of the box"
We now smash all of the subnodes from the ISY994 in to one Hass binary_sensor, and automatically support both paradigms of state reporting that Insteon sensors can do. Sometimes a single node's state represents the sensor's state, other times two nodes are used and only "ON" events are sent from each. The logic between the two forunately do not conflict so we can support both without knowing which mode the device is in.
This also allows us to handle the heartbeat functionality that certain sensors have - we simply store the timestamp of the heartbeat as an attribute on the sensor device. It defaults to Unknown on bootup if and only if the device supports heartbeats, due to the presence of subnode 4.
* Parse the binary sensor device class from the ISY's device "type"
Now we automatically know which sensors are moisture, motion, and openings! (We also reverse the moisture sensor state, because Insteon reports ON for dry on the primary node.)
* Code review tweaks
The one material change here is that the event subscribers were moved to the `async_added_to_hass` method, as the handlers depend on things that only exist after the entity has been added.
* Handle cases where a sensor's state is unknown
When the ISY first boots up, if a battery-powered sensor has not reported in yet (due to heartbeat or a change in state), the state is unknown until it does.
* Clean up from code review
Fix coroutine await, remove unnecessary exception check, and return None when state is unknown
* Unknown value from PyISY is now -inf rather than -1
* Move heartbeat handling to a separate sensor
Now all heartbeat-compatible sensors will have a separate `binary_sensor` device that represents the battery state (on = dead)
* Add support for Unknown state, which is being added in next PyISY
PyISY will report unknown states as the number "-inf". This is implemented in the base ISY994 component, but subcomponents that override the `state` method needed some extra logic to handle it as well.
* Change a couple try blocks to explicit None checks
* Bump PyISY to 1.1.0, now that it has been published!
* Remove -inf checking from base component
The implementation of the -inf checking was improved in another branch which has been merged in to this branch already.
* Restrict negative-node and heartbeat support to known compatible types
Not all Insteon sensors use the same subnode IDs for the same things, so we need to use different logic depending on device type. Negative node and heartbeat support is now only used for leak sensors and open/close sensors.
* Use new style string formatting
* Add binary sensor detection for pre-5.x firmware
Meant to do this originally; writing documentation revealed that this requirement was missed!
2017-12-14 04:14:56 +00:00
|
|
|
@asyncio.coroutine
|
|
|
|
def async_added_to_hass(self) -> None:
|
|
|
|
"""Subscribe to the node change events."""
|
2017-04-30 05:04:49 +00:00
|
|
|
self._change_handler = self._node.status.subscribe(
|
|
|
|
'changed', self.on_update)
|
2015-04-12 20:45:23 +00:00
|
|
|
|
ISY994 sensor improvements (#10805)
* Fire events for ISY994 control events
This allows hass to react directly to Insteon button presses (on switches and remotes), including presses, double-presses, and long holds
* Move change event subscription to after entity is added to hass
The event handler method requires `self.hass` to exist, which doesn't have a value until the async_added_to_hass method is called. Should eliminate a race condition.
* Overhaul binary sensors in ISY994 to be functional "out of the box"
We now smash all of the subnodes from the ISY994 in to one Hass binary_sensor, and automatically support both paradigms of state reporting that Insteon sensors can do. Sometimes a single node's state represents the sensor's state, other times two nodes are used and only "ON" events are sent from each. The logic between the two forunately do not conflict so we can support both without knowing which mode the device is in.
This also allows us to handle the heartbeat functionality that certain sensors have - we simply store the timestamp of the heartbeat as an attribute on the sensor device. It defaults to Unknown on bootup if and only if the device supports heartbeats, due to the presence of subnode 4.
* Parse the binary sensor device class from the ISY's device "type"
Now we automatically know which sensors are moisture, motion, and openings! (We also reverse the moisture sensor state, because Insteon reports ON for dry on the primary node.)
* Code review tweaks
The one material change here is that the event subscribers were moved to the `async_added_to_hass` method, as the handlers depend on things that only exist after the entity has been added.
* Handle cases where a sensor's state is unknown
When the ISY first boots up, if a battery-powered sensor has not reported in yet (due to heartbeat or a change in state), the state is unknown until it does.
* Clean up from code review
Fix coroutine await, remove unnecessary exception check, and return None when state is unknown
* Unknown value from PyISY is now -inf rather than -1
* Move heartbeat handling to a separate sensor
Now all heartbeat-compatible sensors will have a separate `binary_sensor` device that represents the battery state (on = dead)
* Add support for Unknown state, which is being added in next PyISY
PyISY will report unknown states as the number "-inf". This is implemented in the base ISY994 component, but subcomponents that override the `state` method needed some extra logic to handle it as well.
* Change a couple try blocks to explicit None checks
* Bump PyISY to 1.1.0, now that it has been published!
* Remove -inf checking from base component
The implementation of the -inf checking was improved in another branch which has been merged in to this branch already.
* Restrict negative-node and heartbeat support to known compatible types
Not all Insteon sensors use the same subnode IDs for the same things, so we need to use different logic depending on device type. Negative node and heartbeat support is now only used for leak sensors and open/close sensors.
* Use new style string formatting
* Add binary sensor detection for pre-5.x firmware
Meant to do this originally; writing documentation revealed that this requirement was missed!
2017-12-14 04:14:56 +00:00
|
|
|
if hasattr(self._node, 'controlEvents'):
|
|
|
|
self._control_handler = self._node.controlEvents.subscribe(
|
|
|
|
self.on_control)
|
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
# pylint: disable=unused-argument
|
|
|
|
def on_update(self, event: object) -> None:
|
|
|
|
"""Handle the update event from the ISY994 Node."""
|
2017-03-04 23:10:36 +00:00
|
|
|
self.schedule_update_ha_state()
|
2015-04-12 20:45:23 +00:00
|
|
|
|
ISY994 sensor improvements (#10805)
* Fire events for ISY994 control events
This allows hass to react directly to Insteon button presses (on switches and remotes), including presses, double-presses, and long holds
* Move change event subscription to after entity is added to hass
The event handler method requires `self.hass` to exist, which doesn't have a value until the async_added_to_hass method is called. Should eliminate a race condition.
* Overhaul binary sensors in ISY994 to be functional "out of the box"
We now smash all of the subnodes from the ISY994 in to one Hass binary_sensor, and automatically support both paradigms of state reporting that Insteon sensors can do. Sometimes a single node's state represents the sensor's state, other times two nodes are used and only "ON" events are sent from each. The logic between the two forunately do not conflict so we can support both without knowing which mode the device is in.
This also allows us to handle the heartbeat functionality that certain sensors have - we simply store the timestamp of the heartbeat as an attribute on the sensor device. It defaults to Unknown on bootup if and only if the device supports heartbeats, due to the presence of subnode 4.
* Parse the binary sensor device class from the ISY's device "type"
Now we automatically know which sensors are moisture, motion, and openings! (We also reverse the moisture sensor state, because Insteon reports ON for dry on the primary node.)
* Code review tweaks
The one material change here is that the event subscribers were moved to the `async_added_to_hass` method, as the handlers depend on things that only exist after the entity has been added.
* Handle cases where a sensor's state is unknown
When the ISY first boots up, if a battery-powered sensor has not reported in yet (due to heartbeat or a change in state), the state is unknown until it does.
* Clean up from code review
Fix coroutine await, remove unnecessary exception check, and return None when state is unknown
* Unknown value from PyISY is now -inf rather than -1
* Move heartbeat handling to a separate sensor
Now all heartbeat-compatible sensors will have a separate `binary_sensor` device that represents the battery state (on = dead)
* Add support for Unknown state, which is being added in next PyISY
PyISY will report unknown states as the number "-inf". This is implemented in the base ISY994 component, but subcomponents that override the `state` method needed some extra logic to handle it as well.
* Change a couple try blocks to explicit None checks
* Bump PyISY to 1.1.0, now that it has been published!
* Remove -inf checking from base component
The implementation of the -inf checking was improved in another branch which has been merged in to this branch already.
* Restrict negative-node and heartbeat support to known compatible types
Not all Insteon sensors use the same subnode IDs for the same things, so we need to use different logic depending on device type. Negative node and heartbeat support is now only used for leak sensors and open/close sensors.
* Use new style string formatting
* Add binary sensor detection for pre-5.x firmware
Meant to do this originally; writing documentation revealed that this requirement was missed!
2017-12-14 04:14:56 +00:00
|
|
|
def on_control(self, event: object) -> None:
|
|
|
|
"""Handle a control event from the ISY994 Node."""
|
|
|
|
self.hass.bus.fire('isy994_control', {
|
|
|
|
'entity_id': self.entity_id,
|
|
|
|
'control': event
|
|
|
|
})
|
|
|
|
|
2015-04-12 20:45:23 +00:00
|
|
|
@property
|
2016-09-11 18:18:53 +00:00
|
|
|
def domain(self) -> str:
|
|
|
|
"""Get the domain of the device."""
|
|
|
|
return self._domain
|
2015-04-12 20:45:23 +00:00
|
|
|
|
|
|
|
@property
|
2016-09-11 18:18:53 +00:00
|
|
|
def unique_id(self) -> str:
|
|
|
|
"""Get the unique identifier of the device."""
|
2015-04-15 06:05:34 +00:00
|
|
|
# pylint: disable=protected-access
|
2016-09-11 18:18:53 +00:00
|
|
|
return self._node._id
|
2015-04-12 20:45:23 +00:00
|
|
|
|
|
|
|
@property
|
2016-09-11 18:18:53 +00:00
|
|
|
def raw_name(self) -> str:
|
|
|
|
"""Get the raw name of the device."""
|
2015-04-15 06:05:34 +00:00
|
|
|
return str(self._name) \
|
2016-09-11 18:18:53 +00:00
|
|
|
if self._name is not None else str(self._node.name)
|
2015-04-15 02:57:32 +00:00
|
|
|
|
|
|
|
@property
|
2016-09-11 18:18:53 +00:00
|
|
|
def name(self) -> str:
|
|
|
|
"""Get the name of the device."""
|
2015-04-17 13:30:20 +00:00
|
|
|
return self.raw_name.replace(HIDDEN_STRING, '').strip() \
|
|
|
|
.replace('_', ' ')
|
2015-04-12 20:45:23 +00:00
|
|
|
|
2015-12-02 07:32:06 +00:00
|
|
|
@property
|
2016-09-11 18:18:53 +00:00
|
|
|
def should_poll(self) -> bool:
|
|
|
|
"""No polling required since we're using the subscription."""
|
|
|
|
return False
|
2015-04-12 20:45:23 +00:00
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
@property
|
|
|
|
def value(self) -> object:
|
|
|
|
"""Get the current value of the device."""
|
|
|
|
# pylint: disable=protected-access
|
|
|
|
return self._node.status._val
|
2015-04-12 20:45:23 +00:00
|
|
|
|
ISY994 sensor improvements (#10805)
* Fire events for ISY994 control events
This allows hass to react directly to Insteon button presses (on switches and remotes), including presses, double-presses, and long holds
* Move change event subscription to after entity is added to hass
The event handler method requires `self.hass` to exist, which doesn't have a value until the async_added_to_hass method is called. Should eliminate a race condition.
* Overhaul binary sensors in ISY994 to be functional "out of the box"
We now smash all of the subnodes from the ISY994 in to one Hass binary_sensor, and automatically support both paradigms of state reporting that Insteon sensors can do. Sometimes a single node's state represents the sensor's state, other times two nodes are used and only "ON" events are sent from each. The logic between the two forunately do not conflict so we can support both without knowing which mode the device is in.
This also allows us to handle the heartbeat functionality that certain sensors have - we simply store the timestamp of the heartbeat as an attribute on the sensor device. It defaults to Unknown on bootup if and only if the device supports heartbeats, due to the presence of subnode 4.
* Parse the binary sensor device class from the ISY's device "type"
Now we automatically know which sensors are moisture, motion, and openings! (We also reverse the moisture sensor state, because Insteon reports ON for dry on the primary node.)
* Code review tweaks
The one material change here is that the event subscribers were moved to the `async_added_to_hass` method, as the handlers depend on things that only exist after the entity has been added.
* Handle cases where a sensor's state is unknown
When the ISY first boots up, if a battery-powered sensor has not reported in yet (due to heartbeat or a change in state), the state is unknown until it does.
* Clean up from code review
Fix coroutine await, remove unnecessary exception check, and return None when state is unknown
* Unknown value from PyISY is now -inf rather than -1
* Move heartbeat handling to a separate sensor
Now all heartbeat-compatible sensors will have a separate `binary_sensor` device that represents the battery state (on = dead)
* Add support for Unknown state, which is being added in next PyISY
PyISY will report unknown states as the number "-inf". This is implemented in the base ISY994 component, but subcomponents that override the `state` method needed some extra logic to handle it as well.
* Change a couple try blocks to explicit None checks
* Bump PyISY to 1.1.0, now that it has been published!
* Remove -inf checking from base component
The implementation of the -inf checking was improved in another branch which has been merged in to this branch already.
* Restrict negative-node and heartbeat support to known compatible types
Not all Insteon sensors use the same subnode IDs for the same things, so we need to use different logic depending on device type. Negative node and heartbeat support is now only used for leak sensors and open/close sensors.
* Use new style string formatting
* Add binary sensor detection for pre-5.x firmware
Meant to do this originally; writing documentation revealed that this requirement was missed!
2017-12-14 04:14:56 +00:00
|
|
|
def is_unknown(self) -> bool:
|
|
|
|
"""Get whether or not the value of this Entity's node is unknown.
|
|
|
|
|
|
|
|
PyISY reports unknown values as -inf
|
|
|
|
"""
|
|
|
|
return self.value == -1 * float('inf')
|
|
|
|
|
|
|
|
@property
|
|
|
|
def state(self):
|
|
|
|
"""Return the state of the ISY device."""
|
|
|
|
if self.is_unknown():
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
return super().state
|
|
|
|
|
2015-04-12 20:45:23 +00:00
|
|
|
@property
|
2017-01-20 06:22:33 +00:00
|
|
|
def device_state_attributes(self) -> Dict:
|
2016-09-11 18:18:53 +00:00
|
|
|
"""Get the state attributes for the device."""
|
|
|
|
attr = {}
|
|
|
|
if hasattr(self._node, 'aux_properties'):
|
|
|
|
for name, val in self._node.aux_properties.items():
|
|
|
|
attr[name] = '{} {}'.format(val.get('value'), val.get('uom'))
|
|
|
|
return attr
|
2015-04-12 20:45:23 +00:00
|
|
|
|
|
|
|
@property
|
2016-09-11 18:18:53 +00:00
|
|
|
def hidden(self) -> bool:
|
|
|
|
"""Get whether the device should be hidden from the UI."""
|
|
|
|
return HIDDEN_STRING in self.raw_name
|
2015-04-12 20:45:23 +00:00
|
|
|
|
|
|
|
@property
|
2016-09-11 18:18:53 +00:00
|
|
|
def unit_of_measurement(self) -> str:
|
|
|
|
"""Get the device unit of measure."""
|
|
|
|
return None
|
2015-04-12 20:45:23 +00:00
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
def _attr_filter(self, attr: str) -> str:
|
|
|
|
"""Filter the attribute."""
|
|
|
|
# pylint: disable=no-self-use
|
|
|
|
return attr
|
2015-04-12 20:45:23 +00:00
|
|
|
|
2016-09-11 18:18:53 +00:00
|
|
|
def update(self) -> None:
|
|
|
|
"""Perform an update for the device."""
|
|
|
|
pass
|