ISY994 Structure updates in prep for PyISYv2 (Part 2) (#35225)

- Move core Entity Class definitions to separate file.
- New ISYNodeEntity and ISYProgramEntity base classes.
- Update inheriances on other classes.
- Move helper functions to separate file.
This commit is contained in:
shbatm 2020-05-04 23:03:12 -05:00 committed by GitHub
parent 710deb8316
commit e45f2cf5a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 153 additions and 379 deletions

View File

@ -1,15 +1,9 @@
"""Support the ISY-994 controllers.""" """Support the ISY-994 controllers."""
from collections import namedtuple
from urllib.parse import urlparse from urllib.parse import urlparse
import PyISY import PyISY
from PyISY.Nodes import Group
import voluptuous as vol import voluptuous as vol
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR
from homeassistant.components.fan import DOMAIN as FAN
from homeassistant.components.light import DOMAIN as LIGHT
from homeassistant.components.sensor import DOMAIN as SENSOR
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_HOST,
CONF_PASSWORD, CONF_PASSWORD,
@ -18,8 +12,7 @@ from homeassistant.const import (
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, discovery from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.typing import ConfigType, Dict
from .const import ( from .const import (
_LOGGER, _LOGGER,
@ -33,15 +26,10 @@ from .const import (
ISY994_NODES, ISY994_NODES,
ISY994_PROGRAMS, ISY994_PROGRAMS,
ISY994_WEATHER, ISY994_WEATHER,
ISY_GROUP_PLATFORM,
KEY_ACTIONS,
KEY_FOLDER,
KEY_MY_PROGRAMS,
KEY_STATUS,
NODE_FILTERS,
SUPPORTED_PLATFORMS, SUPPORTED_PLATFORMS,
SUPPORTED_PROGRAM_PLATFORMS, SUPPORTED_PROGRAM_PLATFORMS,
) )
from .helpers import _categorize_nodes, _categorize_programs, _categorize_weather
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = vol.Schema(
{ {
@ -64,229 +52,6 @@ CONFIG_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA, extra=vol.ALLOW_EXTRA,
) )
WeatherNode = namedtuple("WeatherNode", ("status", "name", "uom"))
def _check_for_node_def(hass: HomeAssistant, node, single_platform: str = None) -> bool:
"""Check if the node matches the node_def_id for any platforms.
This is only present on the 5.0 ISY firmware, and is the most reliable
way to determine a device's type.
"""
if not hasattr(node, "node_def_id") or node.node_def_id is None:
# Node doesn't have a node_def (pre 5.0 firmware most likely)
return False
node_def_id = node.node_def_id
platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform]
for platform in platforms:
if node_def_id in NODE_FILTERS[platform]["node_def_id"]:
hass.data[ISY994_NODES][platform].append(node)
return True
_LOGGER.warning("Unsupported node: %s, type: %s", node.name, node.type)
return False
def _check_for_insteon_type(
hass: HomeAssistant, node, single_platform: str = None
) -> bool:
"""Check if the node matches the Insteon type for any platforms.
This is for (presumably) every version of the ISY firmware, but only
works for Insteon device. "Node Server" (v5+) and Z-Wave and others will
not have a type.
"""
if not hasattr(node, "type") or node.type is None:
# Node doesn't have a type (non-Insteon device most likely)
return False
device_type = node.type
platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform]
for platform in platforms:
if any(
[
device_type.startswith(t)
for t in set(NODE_FILTERS[platform]["insteon_type"])
]
):
# Hacky special-case just for FanLinc, which has a light module
# as one of its nodes. Note that this special-case is not necessary
# on ISY 5.x firmware as it uses the superior NodeDefs method
if platform == FAN and int(node.nid[-1]) == 1:
hass.data[ISY994_NODES][LIGHT].append(node)
return True
hass.data[ISY994_NODES][platform].append(node)
return True
return False
def _check_for_uom_id(
hass: HomeAssistant, node, single_platform: str = None, uom_list: list = None
) -> bool:
"""Check if a node's uom matches any of the platforms uom filter.
This is used for versions of the ISY firmware that report uoms as a single
ID. We can often infer what type of device it is by that ID.
"""
if not hasattr(node, "uom") or node.uom is None:
# Node doesn't have a uom (Scenes for example)
return False
node_uom = set(map(str.lower, node.uom))
if uom_list:
if node_uom.intersection(uom_list):
hass.data[ISY994_NODES][single_platform].append(node)
return True
else:
platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform]
for platform in platforms:
if node_uom.intersection(NODE_FILTERS[platform]["uom"]):
hass.data[ISY994_NODES][platform].append(node)
return True
return False
def _check_for_states_in_uom(
hass: HomeAssistant, node, single_platform: str = None, states_list: list = None
) -> bool:
"""Check if a list of uoms matches two possible filters.
This is for versions of the ISY firmware that report uoms as a list of all
possible "human readable" states. This filter passes if all of the possible
states fit inside the given filter.
"""
if not hasattr(node, "uom") or node.uom is None:
# Node doesn't have a uom (Scenes for example)
return False
node_uom = set(map(str.lower, node.uom))
if states_list:
if node_uom == set(states_list):
hass.data[ISY994_NODES][single_platform].append(node)
return True
else:
platforms = SUPPORTED_PLATFORMS if not single_platform else [single_platform]
for platform in platforms:
if node_uom == set(NODE_FILTERS[platform]["states"]):
hass.data[ISY994_NODES][platform].append(node)
return True
return False
def _is_sensor_a_binary_sensor(hass: HomeAssistant, node) -> bool:
"""Determine if the given sensor node should be a binary_sensor."""
if _check_for_node_def(hass, node, single_platform=BINARY_SENSOR):
return True
if _check_for_insteon_type(hass, node, single_platform=BINARY_SENSOR):
return True
# For the next two checks, we're providing our own set of uoms that
# represent on/off devices. This is because we can only depend on these
# checks in the context of already knowing that this is definitely a
# sensor device.
if _check_for_uom_id(
hass, node, single_platform=BINARY_SENSOR, uom_list=["2", "78"]
):
return True
if _check_for_states_in_uom(
hass, node, single_platform=BINARY_SENSOR, states_list=["on", "off"]
):
return True
return False
def _categorize_nodes(
hass: HomeAssistant, nodes, ignore_identifier: str, sensor_identifier: str
) -> None:
"""Sort the nodes to their proper platforms."""
for (path, node) in nodes:
ignored = ignore_identifier in path or ignore_identifier in node.name
if ignored:
# Don't import this node as a device at all
continue
if isinstance(node, Group):
hass.data[ISY994_NODES][ISY_GROUP_PLATFORM].append(node)
continue
if sensor_identifier in path or sensor_identifier in node.name:
# User has specified to treat this as a sensor. First we need to
# determine if it should be a binary_sensor.
if _is_sensor_a_binary_sensor(hass, node):
continue
hass.data[ISY994_NODES][SENSOR].append(node)
continue
# We have a bunch of different methods for determining the device type,
# each of which works with different ISY firmware versions or device
# family. The order here is important, from most reliable to least.
if _check_for_node_def(hass, node):
continue
if _check_for_insteon_type(hass, node):
continue
if _check_for_uom_id(hass, node):
continue
if _check_for_states_in_uom(hass, node):
continue
def _categorize_programs(hass: HomeAssistant, programs: dict) -> None:
"""Categorize the ISY994 programs."""
for platform in SUPPORTED_PROGRAM_PLATFORMS:
try:
folder = programs[KEY_MY_PROGRAMS][f"HA.{platform}"]
except KeyError:
pass
else:
for dtype, _, node_id in folder.children:
if dtype != KEY_FOLDER:
continue
entity_folder = folder[node_id]
try:
status = entity_folder[KEY_STATUS]
assert status.dtype == "program", "Not a program"
if platform != BINARY_SENSOR:
actions = entity_folder[KEY_ACTIONS]
assert actions.dtype == "program", "Not a program"
else:
actions = None
except (AttributeError, KeyError, AssertionError):
_LOGGER.warning(
"Program entity '%s' not loaded due "
"to invalid folder structure.",
entity_folder.name,
)
continue
entity = (entity_folder.name, status, actions)
hass.data[ISY994_PROGRAMS][platform].append(entity)
def _categorize_weather(hass: HomeAssistant, climate) -> None:
"""Categorize the ISY994 weather data."""
climate_attrs = dir(climate)
weather_nodes = [
WeatherNode(
getattr(climate, attr),
attr.replace("_", " "),
getattr(climate, f"{attr}_units"),
)
for attr in climate_attrs
if f"{attr}_units" in climate_attrs
]
hass.data[ISY994_WEATHER].extend(weather_nodes)
def setup(hass: HomeAssistant, config: ConfigType) -> bool: def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the ISY 994 platform.""" """Set up the ISY 994 platform."""
@ -352,81 +117,3 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
isy.auto_update = True isy.auto_update = True
return True return True
class ISYDevice(Entity):
"""Representation of an ISY994 device."""
_attrs = {}
_name: str = None
def __init__(self, node) -> None:
"""Initialize the insteon device."""
self._node = node
self._change_handler = None
self._control_handler = None
async def async_added_to_hass(self) -> None:
"""Subscribe to the node change events."""
self._change_handler = self._node.status.subscribe("changed", self.on_update)
if hasattr(self._node, "controlEvents"):
self._control_handler = self._node.controlEvents.subscribe(self.on_control)
def on_update(self, event: object) -> None:
"""Handle the update event from the ISY994 Node."""
self.schedule_update_ha_state()
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}
)
@property
def unique_id(self) -> str:
"""Get the unique identifier of the device."""
# pylint: disable=protected-access
if hasattr(self._node, "_id"):
return self._node._id
return None
@property
def name(self) -> str:
"""Get the name of the device."""
return self._name or str(self._node.name)
@property
def should_poll(self) -> bool:
"""No polling required since we're using the subscription."""
return False
@property
def value(self) -> int:
"""Get the current value of the device."""
# pylint: disable=protected-access
return self._node.status._val
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
return super().state
@property
def device_state_attributes(self) -> Dict:
"""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] = f"{val.get('value')} {val.get('uom')}"
return attr

View File

@ -12,8 +12,9 @@ from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice from . import ISY994_NODES, ISY994_PROGRAMS
from .const import _LOGGER, ISY_BIN_SENS_DEVICE_TYPES from .const import _LOGGER, ISY_BIN_SENS_DEVICE_TYPES
from .entity import ISYNodeEntity, ISYProgramEntity
def setup_platform( def setup_platform(
@ -65,7 +66,7 @@ def setup_platform(
devices.append(device) devices.append(device)
for name, status, _ in hass.data[ISY994_PROGRAMS][BINARY_SENSOR]: for name, status, _ in hass.data[ISY994_PROGRAMS][BINARY_SENSOR]:
devices.append(ISYBinarySensorProgram(name, status)) devices.append(ISYBinarySensorProgramEntity(name, status))
add_entities(devices) add_entities(devices)
@ -90,7 +91,7 @@ def _is_val_unknown(val):
return val == -1 * float("inf") return val == -1 * float("inf")
class ISYBinarySensorEntity(ISYDevice, BinarySensorEntity): class ISYBinarySensorEntity(ISYNodeEntity, BinarySensorEntity):
"""Representation of an ISY994 binary sensor device. """Representation of an ISY994 binary sensor device.
Often times, a single device is represented by multiple nodes in the ISY, Often times, a single device is represented by multiple nodes in the ISY,
@ -248,7 +249,7 @@ class ISYBinarySensorEntity(ISYDevice, BinarySensorEntity):
return self._device_class_from_type return self._device_class_from_type
class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorEntity): class ISYBinarySensorHeartbeat(ISYNodeEntity, BinarySensorEntity):
"""Representation of the battery state of an ISY994 sensor.""" """Representation of the battery state of an ISY994 sensor."""
def __init__(self, node, parent_device) -> None: def __init__(self, node, parent_device) -> None:
@ -348,18 +349,13 @@ class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorEntity):
return attr return attr
class ISYBinarySensorProgram(ISYDevice, BinarySensorEntity): class ISYBinarySensorProgramEntity(ISYProgramEntity, BinarySensorEntity):
"""Representation of an ISY994 binary sensor program. """Representation of an ISY994 binary sensor program.
This does not need all of the subnode logic in the device version of binary This does not need all of the subnode logic in the device version of binary
sensors. sensors.
""" """
def __init__(self, name, node) -> None:
"""Initialize the ISY994 binary sensor program."""
super().__init__(node)
self._name = name
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:
"""Get whether the ISY994 binary sensor device is on.""" """Get whether the ISY994 binary sensor device is on."""

View File

@ -5,8 +5,9 @@ from homeassistant.components.cover import DOMAIN as COVER, CoverEntity
from homeassistant.const import STATE_CLOSED, STATE_OPEN from homeassistant.const import STATE_CLOSED, STATE_OPEN
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice from . import ISY994_NODES, ISY994_PROGRAMS
from .const import _LOGGER, UOM_TO_STATES from .const import _LOGGER, UOM_TO_STATES
from .entity import ISYNodeEntity, ISYProgramEntity
def setup_platform( def setup_platform(
@ -18,12 +19,12 @@ def setup_platform(
devices.append(ISYCoverEntity(node)) devices.append(ISYCoverEntity(node))
for name, status, actions in hass.data[ISY994_PROGRAMS][COVER]: for name, status, actions in hass.data[ISY994_PROGRAMS][COVER]:
devices.append(ISYCoverProgram(name, status, actions)) devices.append(ISYCoverProgramEntity(name, status, actions))
add_entities(devices) add_entities(devices)
class ISYCoverEntity(ISYDevice, CoverEntity): class ISYCoverEntity(ISYNodeEntity, CoverEntity):
"""Representation of an ISY994 cover device.""" """Representation of an ISY994 cover device."""
@property @property
@ -57,15 +58,9 @@ class ISYCoverEntity(ISYDevice, CoverEntity):
_LOGGER.error("Unable to close the cover") _LOGGER.error("Unable to close the cover")
class ISYCoverProgram(ISYCoverEntity): class ISYCoverProgramEntity(ISYProgramEntity, CoverEntity):
"""Representation of an ISY994 cover program.""" """Representation of an ISY994 cover program."""
def __init__(self, name: str, node: object, actions: object) -> None:
"""Initialize the ISY994 cover program."""
super().__init__(node)
self._name = name
self._actions = actions
@property @property
def state(self) -> str: def state(self) -> str:
"""Get the state of the ISY994 cover program.""" """Get the state of the ISY994 cover program."""

View File

@ -0,0 +1,96 @@
"""Representation of ISYEntity Types."""
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import Dict
class ISYEntity(Entity):
"""Representation of an ISY994 device."""
_attrs = {}
_name: str = None
def __init__(self, node) -> None:
"""Initialize the insteon device."""
self._node = node
self._change_handler = None
self._control_handler = None
async def async_added_to_hass(self) -> None:
"""Subscribe to the node change events."""
self._change_handler = self._node.status.subscribe("changed", self.on_update)
if hasattr(self._node, "controlEvents"):
self._control_handler = self._node.controlEvents.subscribe(self.on_control)
def on_update(self, event: object) -> None:
"""Handle the update event from the ISY994 Node."""
self.schedule_update_ha_state()
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}
)
@property
def unique_id(self) -> str:
"""Get the unique identifier of the device."""
# pylint: disable=protected-access
if hasattr(self._node, "_id"):
return self._node._id
return None
@property
def name(self) -> str:
"""Get the name of the device."""
return self._name or str(self._node.name)
@property
def should_poll(self) -> bool:
"""No polling required since we're using the subscription."""
return False
@property
def value(self) -> int:
"""Get the current value of the device."""
# pylint: disable=protected-access
return self._node.status._val
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
return super().state
class ISYNodeEntity(ISYEntity):
"""Representation of a ISY Nodebase (Node/Group) entity."""
@property
def device_state_attributes(self) -> Dict:
"""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] = f"{val.get('value')} {val.get('uom')}"
return attr
class ISYProgramEntity(ISYEntity):
"""Representation of an ISY994 program base."""
def __init__(self, name: str, status, actions=None) -> None:
"""Initialize the ISY994 program-based entity."""
super().__init__(status)
self._name = name
self._actions = actions

View File

@ -12,8 +12,9 @@ from homeassistant.components.fan import (
) )
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice from . import ISY994_NODES, ISY994_PROGRAMS
from .const import _LOGGER from .const import _LOGGER
from .entity import ISYNodeEntity, ISYProgramEntity
VALUE_TO_STATE = { VALUE_TO_STATE = {
0: SPEED_OFF, 0: SPEED_OFF,
@ -36,15 +37,15 @@ def setup_platform(
devices = [] devices = []
for node in hass.data[ISY994_NODES][FAN]: for node in hass.data[ISY994_NODES][FAN]:
devices.append(ISYFanDevice(node)) devices.append(ISYFanEntity(node))
for name, status, actions in hass.data[ISY994_PROGRAMS][FAN]: for name, status, actions in hass.data[ISY994_PROGRAMS][FAN]:
devices.append(ISYFanProgram(name, status, actions)) devices.append(ISYFanProgramEntity(name, status, actions))
add_entities(devices) add_entities(devices)
class ISYFanDevice(ISYDevice, FanEntity): class ISYFanEntity(ISYNodeEntity, FanEntity):
"""Representation of an ISY994 fan device.""" """Representation of an ISY994 fan device."""
@property @property
@ -80,14 +81,20 @@ class ISYFanDevice(ISYDevice, FanEntity):
return SUPPORT_SET_SPEED return SUPPORT_SET_SPEED
class ISYFanProgram(ISYFanDevice): class ISYFanProgramEntity(ISYProgramEntity, FanEntity):
"""Representation of an ISY994 fan program.""" """Representation of an ISY994 fan program."""
def __init__(self, name: str, node, actions) -> None: @property
"""Initialize the ISY994 fan program.""" def speed(self) -> str:
super().__init__(node) """Return the current speed."""
self._name = name # TEMPORARY: Cast value to int until PyISYv2.
self._actions = actions return VALUE_TO_STATE.get(int(self.value))
@property
def is_on(self) -> bool:
"""Get if the fan is on."""
# TEMPORARY: Cast value to int until PyISYv2.
return int(self.value) != 0
def turn_off(self, **kwargs) -> None: def turn_off(self, **kwargs) -> None:
"""Send the turn on command to ISY994 fan program.""" """Send the turn on command to ISY994 fan program."""
@ -98,8 +105,3 @@ class ISYFanProgram(ISYFanDevice):
"""Send the turn off command to ISY994 fan program.""" """Send the turn off command to ISY994 fan program."""
if not self._actions.runElse(): if not self._actions.runElse():
_LOGGER.error("Unable to turn on the fan") _LOGGER.error("Unable to turn on the fan")
@property
def supported_features(self) -> int:
"""Flag supported features."""
return 0

View File

@ -9,8 +9,9 @@ from homeassistant.components.light import (
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from . import ISY994_NODES, ISYDevice from . import ISY994_NODES
from .const import _LOGGER from .const import _LOGGER
from .entity import ISYNodeEntity
ATTR_LAST_BRIGHTNESS = "last_brightness" ATTR_LAST_BRIGHTNESS = "last_brightness"
@ -21,12 +22,12 @@ def setup_platform(
"""Set up the ISY994 light platform.""" """Set up the ISY994 light platform."""
devices = [] devices = []
for node in hass.data[ISY994_NODES][LIGHT]: for node in hass.data[ISY994_NODES][LIGHT]:
devices.append(ISYLightDevice(node)) devices.append(ISYLightEntity(node))
add_entities(devices) add_entities(devices)
class ISYLightDevice(ISYDevice, LightEntity, RestoreEntity): class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity):
"""Representation of an ISY994 light device.""" """Representation of an ISY994 light device."""
def __init__(self, node) -> None: def __init__(self, node) -> None:

View File

@ -5,8 +5,9 @@ from homeassistant.components.lock import DOMAIN as LOCK, LockEntity
from homeassistant.const import STATE_LOCKED, STATE_UNKNOWN, STATE_UNLOCKED from homeassistant.const import STATE_LOCKED, STATE_UNKNOWN, STATE_UNLOCKED
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice from . import ISY994_NODES, ISY994_PROGRAMS
from .const import _LOGGER from .const import _LOGGER
from .entity import ISYNodeEntity, ISYProgramEntity
VALUE_TO_STATE = {0: STATE_UNLOCKED, 100: STATE_LOCKED} VALUE_TO_STATE = {0: STATE_UNLOCKED, 100: STATE_LOCKED}
@ -17,15 +18,15 @@ def setup_platform(
"""Set up the ISY994 lock platform.""" """Set up the ISY994 lock platform."""
devices = [] devices = []
for node in hass.data[ISY994_NODES][LOCK]: for node in hass.data[ISY994_NODES][LOCK]:
devices.append(ISYLockDevice(node)) devices.append(ISYLockEntity(node))
for name, status, actions in hass.data[ISY994_PROGRAMS][LOCK]: for name, status, actions in hass.data[ISY994_PROGRAMS][LOCK]:
devices.append(ISYLockProgram(name, status, actions)) devices.append(ISYLockProgramEntity(name, status, actions))
add_entities(devices) add_entities(devices)
class ISYLockDevice(ISYDevice, LockEntity): class ISYLockEntity(ISYNodeEntity, LockEntity):
"""Representation of an ISY994 lock device.""" """Representation of an ISY994 lock device."""
def __init__(self, node) -> None: def __init__(self, node) -> None:
@ -68,15 +69,9 @@ class ISYLockDevice(ISYDevice, LockEntity):
self._node.update(0.5) self._node.update(0.5)
class ISYLockProgram(ISYLockDevice): class ISYLockProgramEntity(ISYProgramEntity, LockEntity):
"""Representation of a ISY lock program.""" """Representation of a ISY lock program."""
def __init__(self, name: str, node, actions) -> None:
"""Initialize the lock."""
super().__init__(node)
self._name = name
self._actions = actions
@property @property
def is_locked(self) -> bool: def is_locked(self) -> bool:
"""Return true if the device is locked.""" """Return true if the device is locked."""

View File

@ -5,8 +5,9 @@ from homeassistant.components.sensor import DOMAIN as SENSOR
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from . import ISY994_NODES, ISY994_WEATHER, ISYDevice from . import ISY994_NODES, ISY994_WEATHER
from .const import _LOGGER, UOM_FRIENDLY_NAME, UOM_TO_STATES from .const import _LOGGER, UOM_FRIENDLY_NAME, UOM_TO_STATES
from .entity import ISYEntity, ISYNodeEntity
def setup_platform( def setup_platform(
@ -17,7 +18,7 @@ def setup_platform(
for node in hass.data[ISY994_NODES][SENSOR]: for node in hass.data[ISY994_NODES][SENSOR]:
_LOGGER.debug("Loading %s", node.name) _LOGGER.debug("Loading %s", node.name)
devices.append(ISYSensorDevice(node)) devices.append(ISYSensorEntity(node))
for node in hass.data[ISY994_WEATHER]: for node in hass.data[ISY994_WEATHER]:
devices.append(ISYWeatherDevice(node)) devices.append(ISYWeatherDevice(node))
@ -25,7 +26,7 @@ def setup_platform(
add_entities(devices) add_entities(devices)
class ISYSensorDevice(ISYDevice): class ISYSensorEntity(ISYNodeEntity):
"""Representation of an ISY994 sensor device.""" """Representation of an ISY994 sensor device."""
@property @property
@ -77,7 +78,8 @@ class ISYSensorDevice(ISYDevice):
return raw_units return raw_units
class ISYWeatherDevice(ISYDevice): # Depreciated, not renaming. Will be removed in next PR.
class ISYWeatherDevice(ISYEntity):
"""Representation of an ISY994 weather device.""" """Representation of an ISY994 weather device."""
@property @property

View File

@ -4,8 +4,9 @@ from typing import Callable
from homeassistant.components.switch import DOMAIN as SWITCH, SwitchEntity from homeassistant.components.switch import DOMAIN as SWITCH, SwitchEntity
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice from . import ISY994_NODES, ISY994_PROGRAMS
from .const import _LOGGER from .const import _LOGGER
from .entity import ISYNodeEntity, ISYProgramEntity
def setup_platform( def setup_platform(
@ -15,15 +16,15 @@ def setup_platform(
devices = [] devices = []
for node in hass.data[ISY994_NODES][SWITCH]: for node in hass.data[ISY994_NODES][SWITCH]:
if not node.dimmable: if not node.dimmable:
devices.append(ISYSwitchDevice(node)) devices.append(ISYSwitchEntity(node))
for name, status, actions in hass.data[ISY994_PROGRAMS][SWITCH]: for name, status, actions in hass.data[ISY994_PROGRAMS][SWITCH]:
devices.append(ISYSwitchProgram(name, status, actions)) devices.append(ISYSwitchProgramEntity(name, status, actions))
add_entities(devices) add_entities(devices)
class ISYSwitchDevice(ISYDevice, SwitchEntity): class ISYSwitchEntity(ISYNodeEntity, SwitchEntity):
"""Representation of an ISY994 switch device.""" """Representation of an ISY994 switch device."""
@property @property
@ -42,15 +43,9 @@ class ISYSwitchDevice(ISYDevice, SwitchEntity):
_LOGGER.debug("Unable to turn on switch.") _LOGGER.debug("Unable to turn on switch.")
class ISYSwitchProgram(ISYSwitchDevice): class ISYSwitchProgramEntity(ISYProgramEntity, SwitchEntity):
"""A representation of an ISY994 program switch.""" """A representation of an ISY994 program switch."""
def __init__(self, name: str, node, actions) -> None:
"""Initialize the ISY994 switch program."""
super().__init__(node)
self._name = name
self._actions = actions
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:
"""Get whether the ISY994 switch program is on.""" """Get whether the ISY994 switch program is on."""
@ -65,3 +60,8 @@ class ISYSwitchProgram(ISYSwitchDevice):
"""Send the turn off command to the ISY994 switch program.""" """Send the turn off command to the ISY994 switch program."""
if not self._actions.runElse(): if not self._actions.runElse():
_LOGGER.error("Unable to turn off switch") _LOGGER.error("Unable to turn off switch")
@property
def icon(self) -> str:
"""Get the icon for programs."""
return "mdi:script-text-outline" # Matches isy program icon