Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Huge ISY994 platform cleanup, fixes support for 5.0.10 firmware #11243

Merged
merged 10 commits into from
Dec 26, 2017
28 changes: 11 additions & 17 deletions homeassistant/components/binary_sensor/isy994.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,15 @@

from homeassistant.core import callback
from homeassistant.components.binary_sensor import BinarySensorDevice, DOMAIN
import homeassistant.components.isy994 as isy
from homeassistant.components.isy994 import (ISY994_NODES, ISY994_PROGRAMS,
KEY_STATUS, ISYDevice)
from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util import dt as dt_util

_LOGGER = logging.getLogger(__name__)

UOM = ['2', '78']
STATES = [STATE_OFF, STATE_ON, 'true', 'false']

ISY_DEVICE_TYPES = {
'moisture': ['16.8', '16.13', '16.14'],
'opening': ['16.9', '16.6', '16.7', '16.2', '16.17', '16.20', '16.21'],
Expand All @@ -34,16 +32,11 @@
def setup_platform(hass, config: ConfigType,
add_devices: Callable[[list], None], discovery_info=None):
"""Set up the ISY994 binary sensor platform."""
if isy.ISY is None or not isy.ISY.connected:
_LOGGER.error("A connection has not been made to the ISY controller")
return False

devices = []
devices_by_nid = {}
child_nodes = []

for node in isy.filter_nodes(isy.SENSOR_NODES, units=UOM,
states=STATES):
for node in hass.data[ISY994_NODES][DOMAIN]:
if node.parent_node is None:
device = ISYBinarySensorDevice(node)
devices.append(device)
Expand Down Expand Up @@ -80,11 +73,12 @@ def setup_platform(hass, config: ConfigType,
device = ISYBinarySensorDevice(node)
devices.append(device)

for program in isy.PROGRAMS.get(DOMAIN, []):
for program in hass.data[ISY994_PROGRAMS][DOMAIN]:
try:
status = program[isy.KEY_STATUS]
except (KeyError, AssertionError):
pass
status = program[KEY_STATUS]
except (AttributeError, KeyError, AssertionError):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There doesn't seem to be an assertion check within this try...except.

Would it be possible to move all these try...except blocks for program out of the platforms and into the _categorize_programs function in the component? The aim would be that all programs in ISY994_PROGRAMS should be programs that should be added as entities. That would save a lot of near duplicated code. I see the block here in the binary sensor doesn't access the KEY_ACTIONS key on program. But that should be solvable in _categorize_programs by checking the domain.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree that's a much better way for this to work. I'll move it over.

_LOGGER.warning("Program entity '%s' not loaded due to"
"incompatible folder structure.", program.name)
else:
devices.append(ISYBinarySensorProgram(program.name, status))

Expand All @@ -111,7 +105,7 @@ def _is_val_unknown(val):
return val == -1*float('inf')


class ISYBinarySensorDevice(isy.ISYDevice, BinarySensorDevice):
class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice):
"""Representation of an ISY994 binary sensor device.

Often times, a single device is represented by multiple nodes in the ISY,
Expand Down Expand Up @@ -250,7 +244,7 @@ def device_class(self) -> str:
return self._device_class_from_type


class ISYBinarySensorHeartbeat(isy.ISYDevice, BinarySensorDevice):
class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorDevice):
"""Representation of the battery state of an ISY994 sensor."""

def __init__(self, node, parent_device) -> None:
Expand Down Expand Up @@ -353,7 +347,7 @@ def device_state_attributes(self):
return attr


class ISYBinarySensorProgram(isy.ISYDevice, BinarySensorDevice):
class ISYBinarySensorProgram(ISYDevice, BinarySensorDevice):
"""Representation of an ISY994 binary sensor program.

This does not need all of the subnode logic in the device version of binary
Expand Down
35 changes: 17 additions & 18 deletions homeassistant/components/cover/isy994.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,53 +8,52 @@
from typing import Callable # noqa

from homeassistant.components.cover import CoverDevice, DOMAIN
import homeassistant.components.isy994 as isy
from homeassistant.const import STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN
from homeassistant.components.isy994 import (ISY994_NODES, ISY994_PROGRAMS,
KEY_STATUS, KEY_ACTIONS,
ISYDevice)
from homeassistant.const import (
STATE_OPEN, STATE_CLOSED, STATE_OPENING, STATE_CLOSING, STATE_UNKNOWN)
from homeassistant.helpers.typing import ConfigType

_LOGGER = logging.getLogger(__name__)

VALUE_TO_STATE = {
0: STATE_CLOSED,
101: STATE_UNKNOWN,
102: 'stopped',
103: STATE_CLOSING,
104: STATE_OPENING
}

UOM = ['97']
STATES = [STATE_OPEN, STATE_CLOSED, 'closing', 'opening', 'stopped']


# pylint: disable=unused-argument
def setup_platform(hass, config: ConfigType,
add_devices: Callable[[list], None], discovery_info=None):
"""Set up the ISY994 cover platform."""
if isy.ISY is None or not isy.ISY.connected:
_LOGGER.error("A connection has not been made to the ISY controller")
return False

devices = []

for node in isy.filter_nodes(isy.NODES, units=UOM, states=STATES):
for node in hass.data[ISY994_NODES][DOMAIN]:
devices.append(ISYCoverDevice(node))

for program in isy.PROGRAMS.get(DOMAIN, []):
for program in hass.data[ISY994_PROGRAMS].get(DOMAIN, []):
try:
status = program[isy.KEY_STATUS]
actions = program[isy.KEY_ACTIONS]
status = program[KEY_STATUS]
actions = program[KEY_ACTIONS]
assert actions.dtype == 'program', 'Not a program'
except (KeyError, AssertionError):
pass
except (AttributeError, KeyError, AssertionError):
_LOGGER.warning("Program entity '%s' not loaded due to"
"incompatible folder structure.", program.name)
else:
devices.append(ISYCoverProgram(program.name, status, actions))

add_devices(devices)


class ISYCoverDevice(isy.ISYDevice, CoverDevice):
class ISYCoverDevice(ISYDevice, CoverDevice):
"""Representation of an ISY994 cover device."""

def __init__(self, node: object):
"""Initialize the ISY994 cover device."""
isy.ISYDevice.__init__(self, node)
ISYDevice.__init__(self, node)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super().__init__(node)


@property
def current_cover_position(self) -> int:
Expand Down
57 changes: 24 additions & 33 deletions homeassistant/components/fan/isy994.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,14 @@

from homeassistant.components.fan import (FanEntity, DOMAIN, SPEED_OFF,
SPEED_LOW, SPEED_MEDIUM,
SPEED_HIGH)
import homeassistant.components.isy994 as isy
from homeassistant.const import STATE_ON, STATE_OFF
SPEED_HIGH, SUPPORT_SET_SPEED)
from homeassistant.components.isy994 import (ISY994_NODES, ISY994_PROGRAMS,
KEY_STATUS, KEY_ACTIONS,
ISYDevice)
from homeassistant.helpers.typing import ConfigType

_LOGGER = logging.getLogger(__name__)

# Define term used for medium speed. This must be set as the fan component uses
# 'medium' which the ISY does not understand
ISY_SPEED_MEDIUM = 'med'


VALUE_TO_STATE = {
0: SPEED_OFF,
63: SPEED_LOW,
Expand All @@ -34,49 +30,44 @@
for key in VALUE_TO_STATE:
STATE_TO_VALUE[VALUE_TO_STATE[key]] = key

STATES = [SPEED_OFF, SPEED_LOW, ISY_SPEED_MEDIUM, SPEED_HIGH]


# pylint: disable=unused-argument
def setup_platform(hass, config: ConfigType,
add_devices: Callable[[list], None], discovery_info=None):
"""Set up the ISY994 fan platform."""
if isy.ISY is None or not isy.ISY.connected:
_LOGGER.error("A connection has not been made to the ISY controller")
return False

devices = []

for node in isy.filter_nodes(isy.NODES, states=STATES):
for node in hass.data[ISY994_NODES][DOMAIN]:
devices.append(ISYFanDevice(node))

for program in isy.PROGRAMS.get(DOMAIN, []):
for program in hass.data[ISY994_PROGRAMS].get(DOMAIN, []):
try:
status = program[isy.KEY_STATUS]
actions = program[isy.KEY_ACTIONS]
status = program[KEY_STATUS]
actions = program[KEY_ACTIONS]
assert actions.dtype == 'program', 'Not a program'
except (KeyError, AssertionError):
pass
except (AttributeError, KeyError, AssertionError):
_LOGGER.warning("Program entity '%s' not loaded due to"
"incompatible folder structure.", program.name)
else:
devices.append(ISYFanProgram(program.name, status, actions))

add_devices(devices)


class ISYFanDevice(isy.ISYDevice, FanEntity):
class ISYFanDevice(ISYDevice, FanEntity):
"""Representation of an ISY994 fan device."""

def __init__(self, node) -> None:
"""Initialize the ISY994 fan device."""
isy.ISYDevice.__init__(self, node)
ISYDevice.__init__(self, node)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.


@property
def speed(self) -> str:
"""Return the current speed."""
return VALUE_TO_STATE.get(self.value)

@property
def is_on(self) -> str:
def is_on(self) -> bool:
"""Get if the fan is on."""
return self.value != 0

Expand All @@ -97,6 +88,11 @@ def speed_list(self) -> list:
"""Get the list of available speeds."""
return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]

@property
def supported_features(self) -> int:
"""Flag supported features."""
return SUPPORT_SET_SPEED


class ISYFanProgram(ISYFanDevice):
"""Representation of an ISY994 fan program."""
Expand All @@ -106,23 +102,18 @@ def __init__(self, name: str, node, actions) -> None:
ISYFanDevice.__init__(self, node)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

self._name = name
self._actions = actions
self.speed = STATE_ON if self.is_on else STATE_OFF

@property
def state(self) -> str:
"""Get the state of the ISY994 fan program."""
return STATE_ON if bool(self.value) else STATE_OFF

def turn_off(self, **kwargs) -> None:
"""Send the turn on command to ISY994 fan program."""
if not self._actions.runThen():
_LOGGER.error("Unable to turn off the fan")
else:
self.speed = STATE_ON if self.is_on else STATE_OFF

def turn_on(self, **kwargs) -> None:
"""Send the turn off command to ISY994 fan program."""
if not self._actions.runElse():
_LOGGER.error("Unable to turn on the fan")
else:
self.speed = STATE_ON if self.is_on else STATE_OFF

@property
def supported_features(self) -> int:
"""Flag supported features."""
return 0
Loading