From e45f2cf5a298251b511105019b357aae0db53f46 Mon Sep 17 00:00:00 2001 From: shbatm Date: Mon, 4 May 2020 23:03:12 -0500 Subject: [PATCH] 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. --- homeassistant/components/isy994/__init__.py | 317 +----------------- .../components/isy994/binary_sensor.py | 16 +- homeassistant/components/isy994/cover.py | 15 +- homeassistant/components/isy994/entity.py | 96 ++++++ homeassistant/components/isy994/fan.py | 32 +- homeassistant/components/isy994/light.py | 7 +- homeassistant/components/isy994/lock.py | 17 +- homeassistant/components/isy994/sensor.py | 10 +- homeassistant/components/isy994/switch.py | 22 +- 9 files changed, 153 insertions(+), 379 deletions(-) create mode 100644 homeassistant/components/isy994/entity.py diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index 61bdebadd2b..8076debc2d1 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -1,15 +1,9 @@ """Support the ISY-994 controllers.""" -from collections import namedtuple from urllib.parse import urlparse import PyISY -from PyISY.Nodes import Group 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 ( CONF_HOST, CONF_PASSWORD, @@ -18,8 +12,7 @@ from homeassistant.const import ( ) from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv, discovery -from homeassistant.helpers.entity import Entity -from homeassistant.helpers.typing import ConfigType, Dict +from homeassistant.helpers.typing import ConfigType from .const import ( _LOGGER, @@ -33,15 +26,10 @@ from .const import ( ISY994_NODES, ISY994_PROGRAMS, ISY994_WEATHER, - ISY_GROUP_PLATFORM, - KEY_ACTIONS, - KEY_FOLDER, - KEY_MY_PROGRAMS, - KEY_STATUS, - NODE_FILTERS, SUPPORTED_PLATFORMS, SUPPORTED_PROGRAM_PLATFORMS, ) +from .helpers import _categorize_nodes, _categorize_programs, _categorize_weather CONFIG_SCHEMA = vol.Schema( { @@ -64,229 +52,6 @@ CONFIG_SCHEMA = vol.Schema( 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: """Set up the ISY 994 platform.""" @@ -352,81 +117,3 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool: isy.auto_update = 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 diff --git a/homeassistant/components/isy994/binary_sensor.py b/homeassistant/components/isy994/binary_sensor.py index 093f0f0574e..a7c9d1b7943 100644 --- a/homeassistant/components/isy994/binary_sensor.py +++ b/homeassistant/components/isy994/binary_sensor.py @@ -12,8 +12,9 @@ from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.typing import ConfigType 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 .entity import ISYNodeEntity, ISYProgramEntity def setup_platform( @@ -65,7 +66,7 @@ def setup_platform( devices.append(device) for name, status, _ in hass.data[ISY994_PROGRAMS][BINARY_SENSOR]: - devices.append(ISYBinarySensorProgram(name, status)) + devices.append(ISYBinarySensorProgramEntity(name, status)) add_entities(devices) @@ -90,7 +91,7 @@ def _is_val_unknown(val): return val == -1 * float("inf") -class ISYBinarySensorEntity(ISYDevice, BinarySensorEntity): +class ISYBinarySensorEntity(ISYNodeEntity, BinarySensorEntity): """Representation of an ISY994 binary sensor device. 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 -class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorEntity): +class ISYBinarySensorHeartbeat(ISYNodeEntity, BinarySensorEntity): """Representation of the battery state of an ISY994 sensor.""" def __init__(self, node, parent_device) -> None: @@ -348,18 +349,13 @@ class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorEntity): return attr -class ISYBinarySensorProgram(ISYDevice, BinarySensorEntity): +class ISYBinarySensorProgramEntity(ISYProgramEntity, BinarySensorEntity): """Representation of an ISY994 binary sensor program. This does not need all of the subnode logic in the device version of binary sensors. """ - def __init__(self, name, node) -> None: - """Initialize the ISY994 binary sensor program.""" - super().__init__(node) - self._name = name - @property def is_on(self) -> bool: """Get whether the ISY994 binary sensor device is on.""" diff --git a/homeassistant/components/isy994/cover.py b/homeassistant/components/isy994/cover.py index d5246d1a134..1661bfe37ab 100644 --- a/homeassistant/components/isy994/cover.py +++ b/homeassistant/components/isy994/cover.py @@ -5,8 +5,9 @@ from homeassistant.components.cover import DOMAIN as COVER, CoverEntity from homeassistant.const import STATE_CLOSED, STATE_OPEN 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 .entity import ISYNodeEntity, ISYProgramEntity def setup_platform( @@ -18,12 +19,12 @@ def setup_platform( devices.append(ISYCoverEntity(node)) 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) -class ISYCoverEntity(ISYDevice, CoverEntity): +class ISYCoverEntity(ISYNodeEntity, CoverEntity): """Representation of an ISY994 cover device.""" @property @@ -57,15 +58,9 @@ class ISYCoverEntity(ISYDevice, CoverEntity): _LOGGER.error("Unable to close the cover") -class ISYCoverProgram(ISYCoverEntity): +class ISYCoverProgramEntity(ISYProgramEntity, CoverEntity): """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 def state(self) -> str: """Get the state of the ISY994 cover program.""" diff --git a/homeassistant/components/isy994/entity.py b/homeassistant/components/isy994/entity.py new file mode 100644 index 00000000000..f592de6c9b2 --- /dev/null +++ b/homeassistant/components/isy994/entity.py @@ -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 diff --git a/homeassistant/components/isy994/fan.py b/homeassistant/components/isy994/fan.py index cf8bd2cb308..2315610dcf8 100644 --- a/homeassistant/components/isy994/fan.py +++ b/homeassistant/components/isy994/fan.py @@ -12,8 +12,9 @@ from homeassistant.components.fan import ( ) from homeassistant.helpers.typing import ConfigType -from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice +from . import ISY994_NODES, ISY994_PROGRAMS from .const import _LOGGER +from .entity import ISYNodeEntity, ISYProgramEntity VALUE_TO_STATE = { 0: SPEED_OFF, @@ -36,15 +37,15 @@ def setup_platform( devices = [] 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]: - devices.append(ISYFanProgram(name, status, actions)) + devices.append(ISYFanProgramEntity(name, status, actions)) add_entities(devices) -class ISYFanDevice(ISYDevice, FanEntity): +class ISYFanEntity(ISYNodeEntity, FanEntity): """Representation of an ISY994 fan device.""" @property @@ -80,14 +81,20 @@ class ISYFanDevice(ISYDevice, FanEntity): return SUPPORT_SET_SPEED -class ISYFanProgram(ISYFanDevice): +class ISYFanProgramEntity(ISYProgramEntity, FanEntity): """Representation of an ISY994 fan program.""" - def __init__(self, name: str, node, actions) -> None: - """Initialize the ISY994 fan program.""" - super().__init__(node) - self._name = name - self._actions = actions + @property + def speed(self) -> str: + """Return the current speed.""" + # TEMPORARY: Cast value to int until PyISYv2. + 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: """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.""" if not self._actions.runElse(): _LOGGER.error("Unable to turn on the fan") - - @property - def supported_features(self) -> int: - """Flag supported features.""" - return 0 diff --git a/homeassistant/components/isy994/light.py b/homeassistant/components/isy994/light.py index 4a6c9ad8612..e5f35bc62fb 100644 --- a/homeassistant/components/isy994/light.py +++ b/homeassistant/components/isy994/light.py @@ -9,8 +9,9 @@ from homeassistant.components.light import ( from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.typing import ConfigType -from . import ISY994_NODES, ISYDevice +from . import ISY994_NODES from .const import _LOGGER +from .entity import ISYNodeEntity ATTR_LAST_BRIGHTNESS = "last_brightness" @@ -21,12 +22,12 @@ def setup_platform( """Set up the ISY994 light platform.""" devices = [] for node in hass.data[ISY994_NODES][LIGHT]: - devices.append(ISYLightDevice(node)) + devices.append(ISYLightEntity(node)) add_entities(devices) -class ISYLightDevice(ISYDevice, LightEntity, RestoreEntity): +class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity): """Representation of an ISY994 light device.""" def __init__(self, node) -> None: diff --git a/homeassistant/components/isy994/lock.py b/homeassistant/components/isy994/lock.py index 122123be71f..b1e94cadac8 100644 --- a/homeassistant/components/isy994/lock.py +++ b/homeassistant/components/isy994/lock.py @@ -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.helpers.typing import ConfigType -from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice +from . import ISY994_NODES, ISY994_PROGRAMS from .const import _LOGGER +from .entity import ISYNodeEntity, ISYProgramEntity VALUE_TO_STATE = {0: STATE_UNLOCKED, 100: STATE_LOCKED} @@ -17,15 +18,15 @@ def setup_platform( """Set up the ISY994 lock platform.""" devices = [] 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]: - devices.append(ISYLockProgram(name, status, actions)) + devices.append(ISYLockProgramEntity(name, status, actions)) add_entities(devices) -class ISYLockDevice(ISYDevice, LockEntity): +class ISYLockEntity(ISYNodeEntity, LockEntity): """Representation of an ISY994 lock device.""" def __init__(self, node) -> None: @@ -68,15 +69,9 @@ class ISYLockDevice(ISYDevice, LockEntity): self._node.update(0.5) -class ISYLockProgram(ISYLockDevice): +class ISYLockProgramEntity(ISYProgramEntity, LockEntity): """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 def is_locked(self) -> bool: """Return true if the device is locked.""" diff --git a/homeassistant/components/isy994/sensor.py b/homeassistant/components/isy994/sensor.py index d98020c6db8..bf787582910 100644 --- a/homeassistant/components/isy994/sensor.py +++ b/homeassistant/components/isy994/sensor.py @@ -5,8 +5,9 @@ from homeassistant.components.sensor import DOMAIN as SENSOR from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT 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 .entity import ISYEntity, ISYNodeEntity def setup_platform( @@ -17,7 +18,7 @@ def setup_platform( for node in hass.data[ISY994_NODES][SENSOR]: _LOGGER.debug("Loading %s", node.name) - devices.append(ISYSensorDevice(node)) + devices.append(ISYSensorEntity(node)) for node in hass.data[ISY994_WEATHER]: devices.append(ISYWeatherDevice(node)) @@ -25,7 +26,7 @@ def setup_platform( add_entities(devices) -class ISYSensorDevice(ISYDevice): +class ISYSensorEntity(ISYNodeEntity): """Representation of an ISY994 sensor device.""" @property @@ -77,7 +78,8 @@ class ISYSensorDevice(ISYDevice): return raw_units -class ISYWeatherDevice(ISYDevice): +# Depreciated, not renaming. Will be removed in next PR. +class ISYWeatherDevice(ISYEntity): """Representation of an ISY994 weather device.""" @property diff --git a/homeassistant/components/isy994/switch.py b/homeassistant/components/isy994/switch.py index 564d7cbaeac..e87ed846fd9 100644 --- a/homeassistant/components/isy994/switch.py +++ b/homeassistant/components/isy994/switch.py @@ -4,8 +4,9 @@ from typing import Callable from homeassistant.components.switch import DOMAIN as SWITCH, SwitchEntity from homeassistant.helpers.typing import ConfigType -from . import ISY994_NODES, ISY994_PROGRAMS, ISYDevice +from . import ISY994_NODES, ISY994_PROGRAMS from .const import _LOGGER +from .entity import ISYNodeEntity, ISYProgramEntity def setup_platform( @@ -15,15 +16,15 @@ def setup_platform( devices = [] for node in hass.data[ISY994_NODES][SWITCH]: if not node.dimmable: - devices.append(ISYSwitchDevice(node)) + devices.append(ISYSwitchEntity(node)) 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) -class ISYSwitchDevice(ISYDevice, SwitchEntity): +class ISYSwitchEntity(ISYNodeEntity, SwitchEntity): """Representation of an ISY994 switch device.""" @property @@ -42,15 +43,9 @@ class ISYSwitchDevice(ISYDevice, SwitchEntity): _LOGGER.debug("Unable to turn on switch.") -class ISYSwitchProgram(ISYSwitchDevice): +class ISYSwitchProgramEntity(ISYProgramEntity, SwitchEntity): """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 def is_on(self) -> bool: """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.""" if not self._actions.runElse(): _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