Code conformance and sensor value clean-up on ISY994 (#35514)

* Consolidate value conversion functions

* Update to sensor

* Fix property name

* Revise sensors and state reporting per code standards

* Update uom function and revert to property
This commit is contained in:
shbatm 2020-05-11 21:32:19 -05:00 committed by GitHub
parent 2f73361381
commit b1d59679e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 123 additions and 170 deletions

View file

@ -24,7 +24,6 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN
from homeassistant.core import callback
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.helpers.typing import HomeAssistantType
@ -136,7 +135,7 @@ async def async_setup_entry(
# the initial state is forced "OFF"/"NORMAL" if the
# parent device has a valid state. This is corrected
# upon connection to the ISY event stream if subnode has a valid state.
initial_state = None if parent_device.state == STATE_UNKNOWN else False
initial_state = None if parent_device.state is None else False
if subnode_id == SUBNODE_DUSK_DAWN:
# Subnode 2 is the Dusk/Dawn sensor
device = ISYInsteonBinarySensorEntity(node, DEVICE_CLASS_LIGHT)
@ -216,18 +215,10 @@ class ISYBinarySensorEntity(ISYNodeEntity, BinarySensorEntity):
@property
def is_on(self) -> bool:
"""Get whether the ISY994 binary sensor device is on.
Note: This method will return false if the current state is UNKNOWN
"""
return bool(self.value)
@property
def state(self):
"""Return the state of the binary sensor."""
if self.value == ISY_VALUE_UNKNOWN:
return STATE_UNKNOWN
return STATE_ON if self.is_on else STATE_OFF
"""Get whether the ISY994 binary sensor device is on."""
if self._node.status == ISY_VALUE_UNKNOWN:
return None
return bool(self._node.status)
@property
def device_class(self) -> str:
@ -354,8 +345,8 @@ class ISYInsteonBinarySensorEntity(ISYBinarySensorEntity):
self._heartbeat()
@property
def value(self) -> object:
"""Get the current value of the device.
def is_on(self) -> bool:
"""Get whether the ISY994 binary sensor device is on.
Insteon leak sensors set their primary node to On when the state is
DRY, not WET, so we invert the binary state if the user indicates
@ -370,13 +361,6 @@ class ISYInsteonBinarySensorEntity(ISYBinarySensorEntity):
return self._computed_state
@property
def state(self):
"""Return the state of the binary sensor."""
if self._computed_state is None:
return STATE_UNKNOWN
return STATE_ON if self.is_on else STATE_OFF
class ISYBinarySensorHeartbeat(ISYNodeEntity, BinarySensorEntity):
"""Representation of the battery state of an ISY994 sensor."""
@ -394,7 +378,7 @@ class ISYBinarySensorHeartbeat(ISYNodeEntity, BinarySensorEntity):
self._parent_device = parent_device
self._heartbeat_timer = None
self._computed_state = None
if self.state != STATE_UNKNOWN:
if self.state is None:
self._computed_state = False
async def async_added_to_hass(self) -> None:
@ -459,25 +443,15 @@ class ISYBinarySensorHeartbeat(ISYNodeEntity, BinarySensorEntity):
We listen directly to the Control events for this device.
"""
@property
def value(self) -> object:
"""Get the current value of this sensor."""
return self._computed_state
@property
def is_on(self) -> bool:
"""Get whether the ISY994 binary sensor device is on.
Note: This method will return false if the current state is UNKNOWN
which occurs after a restart until the first heartbeat or control
parent control event is received.
"""
return bool(self.value)
@property
def state(self):
"""Return the state of the binary sensor."""
if self._computed_state is None:
return None
return STATE_ON if self.is_on else STATE_OFF
return bool(self._computed_state)
@property
def device_class(self) -> str:
@ -502,4 +476,4 @@ class ISYBinarySensorProgramEntity(ISYProgramEntity, BinarySensorEntity):
@property
def is_on(self) -> bool:
"""Get whether the ISY994 binary sensor device is on."""
return bool(self.value)
return bool(self._node.status)

View file

@ -1,10 +1,9 @@
"""Support for Insteon Thermostats via ISY994 Platform."""
from typing import Callable, List, Optional, Union
from typing import Callable, List, Optional
from pyisy.constants import (
CMD_CLIMATE_FAN_SETTING,
CMD_CLIMATE_MODE,
ISY_VALUE_UNKNOWN,
PROP_HEAT_COOL_STATE,
PROP_HUMIDITY,
PROP_SETPOINT_COOL,
@ -42,19 +41,17 @@ from .const import (
HA_HVAC_TO_ISY,
ISY994_NODES,
ISY_HVAC_MODES,
UOM_DOUBLE_TEMP,
UOM_FAN_MODES,
UOM_HVAC_ACTIONS,
UOM_HVAC_MODE_GENERIC,
UOM_HVAC_MODE_INSTEON,
UOM_ISY_CELSIUS,
UOM_ISY_FAHRENHEIT,
UOM_ISYV4_DEGREES,
UOM_ISYV4_NONE,
UOM_TO_STATES,
)
from .entity import ISYNodeEntity
from .helpers import migrate_old_unique_ids
from .helpers import convert_isy_value_to_hass, migrate_old_unique_ids
from .services import async_setup_device_services
ISY_SUPPORTED_FEATURES = (
@ -79,26 +76,6 @@ async def async_setup_entry(
async_setup_device_services(hass)
def convert_isy_temp_to_hass(
temp: Union[int, float, None], uom: str, precision: str
) -> float:
"""Fix Insteon Thermostats' Reported Temperature.
Insteon Thermostats report temperature in 0.5-deg precision as an int
by sending a value of 2 times the Temp. Correct by dividing by 2 here.
Z-Wave Thermostats report temps in tenths as an integer and precision.
Correct by shifting the decimal place left by the value of precision.
"""
if temp is None or temp == ISY_VALUE_UNKNOWN:
return None
if uom in [UOM_DOUBLE_TEMP, UOM_ISYV4_DEGREES]:
return round(float(temp) / 2.0, 1)
if precision != "0":
return round(float(temp) / 10 ** int(precision), int(precision))
return round(float(temp), 1)
class ISYThermostatEntity(ISYNodeEntity, ClimateEntity):
"""Representation of an ISY994 thermostat entity."""
@ -180,7 +157,9 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity):
@property
def current_temperature(self) -> Optional[float]:
"""Return the current temperature."""
return convert_isy_temp_to_hass(self._node.status, self._uom, self._node.prec)
return convert_isy_value_to_hass(
self._node.status, self._uom, self._node.prec, 1
)
@property
def target_temperature_step(self) -> Optional[float]:
@ -202,7 +181,7 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity):
target = self._node.aux_properties.get(PROP_SETPOINT_COOL)
if not target:
return None
return convert_isy_temp_to_hass(target.value, target.uom, target.prec)
return convert_isy_value_to_hass(target.value, target.uom, target.prec, 1)
@property
def target_temperature_low(self) -> Optional[float]:
@ -210,7 +189,7 @@ class ISYThermostatEntity(ISYNodeEntity, ClimateEntity):
target = self._node.aux_properties.get(PROP_SETPOINT_HEAT)
if not target:
return None
return convert_isy_temp_to_hass(target.value, target.uom, target.prec)
return convert_isy_value_to_hass(target.value, target.uom, target.prec, 1)
@property
def fan_modes(self):

View file

@ -301,6 +301,8 @@ UOM_HVAC_ACTIONS = "66"
UOM_HVAC_MODE_GENERIC = "67"
UOM_HVAC_MODE_INSTEON = "98"
UOM_FAN_MODES = "99"
UOM_INDEX = "25"
UOM_ON_OFF = "2"
UOM_FRIENDLY_NAME = {
"1": "A",
@ -324,7 +326,7 @@ UOM_FRIENDLY_NAME = {
"22": "%RH",
"23": PRESSURE_INHG,
"24": f"{LENGTH_INCHES}/{TIME_HOURS}",
"25": "index",
UOM_INDEX: "index", # Index type. Use "node.formatted" for value
"26": TEMP_KELVIN,
"27": "keyword",
"28": MASS_KILOGRAMS,
@ -383,7 +385,7 @@ UOM_FRIENDLY_NAME = {
"91": DEGREE,
"92": f"{DEGREE} South",
"100": "", # Range 0-255, no unit.
"101": f"{DEGREE} (x2)",
UOM_DOUBLE_TEMP: UOM_DOUBLE_TEMP,
"102": "kWs",
"103": "$",
"104": "¢",
@ -398,7 +400,7 @@ UOM_FRIENDLY_NAME = {
"113": "", # raw 3-byte signed value
"114": "", # raw 4-byte signed value
"116": LENGTH_MILES,
"117": "mb",
"117": "mbar",
"118": "hPa",
"119": f"{POWER_WATT}{TIME_HOURS}",
"120": f"{LENGTH_INCHES}/{TIME_DAYS}",

View file

@ -5,16 +5,9 @@ from pyisy.constants import ISY_VALUE_UNKNOWN
from homeassistant.components.cover import DOMAIN as COVER, CoverEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_CLOSED, STATE_OPEN, STATE_UNKNOWN
from homeassistant.helpers.typing import HomeAssistantType
from .const import (
_LOGGER,
DOMAIN as ISY994_DOMAIN,
ISY994_NODES,
ISY994_PROGRAMS,
UOM_TO_STATES,
)
from .const import _LOGGER, DOMAIN as ISY994_DOMAIN, ISY994_NODES, ISY994_PROGRAMS
from .entity import ISYNodeEntity, ISYProgramEntity
from .helpers import migrate_old_unique_ids
from .services import async_setup_device_services
@ -45,21 +38,16 @@ class ISYCoverEntity(ISYNodeEntity, CoverEntity):
@property
def current_cover_position(self) -> int:
"""Return the current cover position."""
if self.value in [None, ISY_VALUE_UNKNOWN]:
return STATE_UNKNOWN
return sorted((0, self.value, 100))[1]
if self._node.status == ISY_VALUE_UNKNOWN:
return None
return sorted((0, self._node.status, 100))[1]
@property
def is_closed(self) -> bool:
"""Get whether the ISY994 cover device is closed."""
return self.state == STATE_CLOSED
@property
def state(self) -> str:
"""Get the state of the ISY994 cover device."""
if self.value == ISY_VALUE_UNKNOWN:
return STATE_UNKNOWN
return UOM_TO_STATES["97"].get(self.value, STATE_OPEN)
if self._node.status == ISY_VALUE_UNKNOWN:
return None
return self._node.status == 0
def open_cover(self, **kwargs) -> None:
"""Send the open cover command to the ISY994 cover device."""
@ -76,9 +64,9 @@ class ISYCoverProgramEntity(ISYProgramEntity, CoverEntity):
"""Representation of an ISY994 cover program."""
@property
def state(self) -> str:
"""Get the state of the ISY994 cover program."""
return STATE_CLOSED if bool(self.value) else STATE_OPEN
def is_closed(self) -> bool:
"""Get whether the ISY994 cover program is closed."""
return bool(self._node.status)
def open_cover(self, **kwargs) -> None:
"""Send the open cover command to the ISY994 cover program."""

View file

@ -4,13 +4,12 @@ from pyisy.constants import (
COMMAND_FRIENDLY_NAME,
EMPTY_TIME,
EVENT_PROPS_IGNORED,
ISY_VALUE_UNKNOWN,
PROTO_GROUP,
PROTO_ZWAVE,
)
from pyisy.helpers import NodeProperty
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import Dict
@ -51,7 +50,7 @@ class ISYEntity(Entity):
"precision": event.prec,
}
if event.value is None or event.control not in EVENT_PROPS_IGNORED:
if event.control not in EVENT_PROPS_IGNORED:
# New state attributes may be available, update the state.
self.schedule_update_ha_state()
@ -128,18 +127,6 @@ class ISYEntity(Entity):
"""No polling required since we're using the subscription."""
return False
@property
def value(self) -> int:
"""Get the current value of the device."""
return self._node.status
@property
def state(self):
"""Return the state of the ISY device."""
if self.value == ISY_VALUE_UNKNOWN:
return STATE_UNKNOWN
return super().state
class ISYNodeEntity(ISYEntity):
"""Representation of a ISY Nodebase (Node/Group) entity."""

View file

@ -1,6 +1,8 @@
"""Support for ISY994 fans."""
from typing import Callable
from pyisy.constants import ISY_VALUE_UNKNOWN
from homeassistant.components.fan import (
DOMAIN as FAN,
SPEED_HIGH,
@ -58,12 +60,14 @@ class ISYFanEntity(ISYNodeEntity, FanEntity):
@property
def speed(self) -> str:
"""Return the current speed."""
return VALUE_TO_STATE.get(self.value)
return VALUE_TO_STATE.get(self._node.status)
@property
def is_on(self) -> bool:
"""Get if the fan is on."""
return self.value != 0
if self._node.status == ISY_VALUE_UNKNOWN:
return None
return self._node.status != 0
def set_speed(self, speed: str) -> None:
"""Send the set speed command to the ISY994 fan device."""
@ -94,12 +98,12 @@ class ISYFanProgramEntity(ISYProgramEntity, FanEntity):
@property
def speed(self) -> str:
"""Return the current speed."""
return VALUE_TO_STATE.get(self.value)
return VALUE_TO_STATE.get(self._node.status)
@property
def is_on(self) -> bool:
"""Get if the fan is on."""
return self.value != 0
return self._node.status != 0
def turn_off(self, **kwargs) -> None:
"""Send the turn on command to ISY994 fan program."""

View file

@ -2,6 +2,7 @@
from typing import Any, List, Optional, Union
from pyisy.constants import (
ISY_VALUE_UNKNOWN,
PROTO_GROUP,
PROTO_INSTEON,
PROTO_PROGRAM,
@ -46,6 +47,8 @@ from .const import (
SUPPORTED_PROGRAM_PLATFORMS,
TYPE_CATEGORY_SENSOR_ACTUATORS,
TYPE_EZIO2X4,
UOM_DOUBLE_TEMP,
UOM_ISYV4_DEGREES,
)
BINARY_SENSOR_UOMS = ["2", "78"]
@ -394,3 +397,29 @@ async def migrate_old_unique_ids(
registry.async_update_entity(
old_entity_id_2, new_unique_id=device.unique_id
)
def convert_isy_value_to_hass(
value: Union[int, float, None],
uom: str,
precision: str,
fallback_precision: Optional[int] = None,
) -> Union[float, int]:
"""Fix ISY Reported Values.
ISY provides float values as an integer and precision component.
Correct by shifting the decimal place left by the value of precision.
(e.g. value=2345, prec="2" == 23.45)
Insteon Thermostats report temperature in 0.5-deg precision as an int
by sending a value of 2 times the Temp. Correct by dividing by 2 here.
"""
if value is None or value == ISY_VALUE_UNKNOWN:
return None
if uom in [UOM_DOUBLE_TEMP, UOM_ISYV4_DEGREES]:
return round(float(value) / 2.0, 1)
if precision != "0":
return round(float(value) / 10 ** int(precision), int(precision))
if fallback_precision:
return round(float(value), fallback_precision)
return value

View file

@ -9,7 +9,6 @@ from homeassistant.components.light import (
LightEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_UNKNOWN
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import HomeAssistantType
@ -58,14 +57,16 @@ class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity):
@property
def is_on(self) -> bool:
"""Get whether the ISY994 light is on."""
if self.value == ISY_VALUE_UNKNOWN:
if self._node.status == ISY_VALUE_UNKNOWN:
return False
return int(self.value) != 0
return int(self._node.status) != 0
@property
def brightness(self) -> float:
"""Get the brightness of the ISY994 light."""
return STATE_UNKNOWN if self.value == ISY_VALUE_UNKNOWN else int(self.value)
if self._node.status == ISY_VALUE_UNKNOWN:
return None
return int(self._node.status)
def turn_off(self, **kwargs) -> None:
"""Send the turn off command to the ISY994 light device."""
@ -75,8 +76,8 @@ class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity):
def on_update(self, event: object) -> None:
"""Save brightness in the update event from the ISY994 Node."""
if self.value not in (0, ISY_VALUE_UNKNOWN):
self._last_brightness = self.value
if self._node.status not in (0, ISY_VALUE_UNKNOWN):
self._last_brightness = self._node.status
super().on_update(event)
# pylint: disable=arguments-differ

View file

@ -5,7 +5,6 @@ from pyisy.constants import ISY_VALUE_UNKNOWN
from homeassistant.components.lock import DOMAIN as LOCK, LockEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_LOCKED, STATE_UNKNOWN, STATE_UNLOCKED
from homeassistant.helpers.typing import HomeAssistantType
from .const import _LOGGER, DOMAIN as ISY994_DOMAIN, ISY994_NODES, ISY994_PROGRAMS
@ -13,7 +12,7 @@ from .entity import ISYNodeEntity, ISYProgramEntity
from .helpers import migrate_old_unique_ids
from .services import async_setup_device_services
VALUE_TO_STATE = {0: STATE_UNLOCKED, 100: STATE_LOCKED}
VALUE_TO_STATE = {0: False, 100: True}
async def async_setup_entry(
@ -41,29 +40,20 @@ class ISYLockEntity(ISYNodeEntity, LockEntity):
@property
def is_locked(self) -> bool:
"""Get whether the lock is in locked state."""
return self.state == STATE_LOCKED
@property
def state(self) -> str:
"""Get the state of the lock."""
if self.value == ISY_VALUE_UNKNOWN:
return STATE_UNKNOWN
return VALUE_TO_STATE.get(self.value, STATE_UNKNOWN)
if self._node.status == ISY_VALUE_UNKNOWN:
return None
return VALUE_TO_STATE.get(self._node.status)
def lock(self, **kwargs) -> None:
"""Send the lock command to the ISY994 device."""
if not self._node.secure_lock():
_LOGGER.error("Unable to lock device")
self._node.update(0.5)
def unlock(self, **kwargs) -> None:
"""Send the unlock command to the ISY994 device."""
if not self._node.secure_unlock():
_LOGGER.error("Unable to lock device")
self._node.update(0.5)
class ISYLockProgramEntity(ISYProgramEntity, LockEntity):
"""Representation of a ISY lock program."""
@ -71,12 +61,7 @@ class ISYLockProgramEntity(ISYProgramEntity, LockEntity):
@property
def is_locked(self) -> bool:
"""Return true if the device is locked."""
return bool(self.value)
@property
def state(self) -> str:
"""Return the state of the lock."""
return STATE_LOCKED if self.is_locked else STATE_UNLOCKED
return bool(self._node.status)
def lock(self, **kwargs) -> None:
"""Lock the device."""

View file

@ -1,11 +1,11 @@
"""Support for ISY994 sensors."""
from typing import Callable, Dict
from typing import Callable, Dict, Union
from pyisy.constants import ISY_VALUE_UNKNOWN
from homeassistant.components.sensor import DOMAIN as SENSOR
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.helpers.typing import HomeAssistantType
from .const import (
@ -13,11 +13,12 @@ from .const import (
DOMAIN as ISY994_DOMAIN,
ISY994_NODES,
ISY994_VARIABLES,
UOM_DOUBLE_TEMP,
UOM_FRIENDLY_NAME,
UOM_TO_STATES,
)
from .entity import ISYEntity, ISYNodeEntity
from .helpers import migrate_old_unique_ids
from .helpers import convert_isy_value_to_hass, migrate_old_unique_ids
from .services import async_setup_device_services
@ -46,48 +47,52 @@ class ISYSensorEntity(ISYNodeEntity):
"""Representation of an ISY994 sensor device."""
@property
def raw_unit_of_measurement(self) -> str:
def raw_unit_of_measurement(self) -> Union[dict, str]:
"""Get the raw unit of measurement for the ISY994 sensor device."""
uom = self._node.uom
# Backwards compatibility for ISYv4 Firmware:
if isinstance(uom, list):
return UOM_FRIENDLY_NAME.get(uom[0], uom[0])
# Special cases for ISY UOM index units:
isy_states = UOM_TO_STATES.get(uom)
if isy_states:
return isy_states
return UOM_FRIENDLY_NAME.get(uom)
@property
def state(self) -> str:
"""Get the state of the ISY994 sensor device."""
if self.value == ISY_VALUE_UNKNOWN:
return STATE_UNKNOWN
value = self._node.status
if value == ISY_VALUE_UNKNOWN:
return None
uom = self._node.uom
# Backwards compatibility for ISYv4 Firmware:
if isinstance(uom, list):
uom = uom[0]
if not uom:
return STATE_UNKNOWN
# Get the translated ISY Unit of Measurement
uom = self.raw_unit_of_measurement
states = UOM_TO_STATES.get(uom)
if states and states.get(self.value):
return states.get(self.value)
if self._node.prec and int(self._node.prec) != 0:
str_val = str(self.value)
int_prec = int(self._node.prec)
decimal_part = str_val[-int_prec:]
whole_part = str_val[: len(str_val) - int_prec]
val = float(f"{whole_part}.{decimal_part}")
raw_units = self.raw_unit_of_measurement
if raw_units in (TEMP_CELSIUS, TEMP_FAHRENHEIT):
val = self.hass.config.units.temperature(val, raw_units)
return val
return self.value
# Check if this is a known index pair UOM
if isinstance(uom, dict):
return uom.get(value, value)
# Handle ISY precision and rounding
value = convert_isy_value_to_hass(value, uom, self._node.prec)
# Convert temperatures to Home Assistant's unit
if uom in (TEMP_CELSIUS, TEMP_FAHRENHEIT):
value = self.hass.config.units.temperature(value, uom)
return value
@property
def unit_of_measurement(self) -> str:
"""Get the unit of measurement for the ISY994 sensor device."""
"""Get the Home Assistant unit of measurement for the device."""
raw_units = self.raw_unit_of_measurement
if raw_units in (TEMP_FAHRENHEIT, TEMP_CELSIUS):
# Check if this is a known index pair UOM
if isinstance(raw_units, dict):
return None
if raw_units in (TEMP_FAHRENHEIT, TEMP_CELSIUS, UOM_DOUBLE_TEMP):
return self.hass.config.units.temperature_unit
return raw_units
@ -103,7 +108,7 @@ class ISYSensorVariableEntity(ISYEntity):
@property
def state(self):
"""Return the state of the variable."""
return self.value
return self._node.status
@property
def device_state_attributes(self) -> Dict:

View file

@ -5,7 +5,6 @@ from pyisy.constants import ISY_VALUE_UNKNOWN, PROTO_GROUP
from homeassistant.components.switch import DOMAIN as SWITCH, SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_UNKNOWN
from homeassistant.helpers.typing import HomeAssistantType
from .const import _LOGGER, DOMAIN as ISY994_DOMAIN, ISY994_NODES, ISY994_PROGRAMS
@ -39,9 +38,9 @@ class ISYSwitchEntity(ISYNodeEntity, SwitchEntity):
@property
def is_on(self) -> bool:
"""Get whether the ISY994 device is in the on state."""
if self.value == ISY_VALUE_UNKNOWN:
return STATE_UNKNOWN
return bool(self.value)
if self._node.status == ISY_VALUE_UNKNOWN:
return None
return bool(self._node.status)
def turn_off(self, **kwargs) -> None:
"""Send the turn off command to the ISY994 switch."""
@ -67,7 +66,7 @@ class ISYSwitchProgramEntity(ISYProgramEntity, SwitchEntity):
@property
def is_on(self) -> bool:
"""Get whether the ISY994 switch program is on."""
return bool(self.value)
return bool(self._node.status)
def turn_on(self, **kwargs) -> None:
"""Send the turn on command to the ISY994 switch program."""