Migrate ISY994 to PyISY v2 (#35338)

* Remove unnecessary pylint exceptions

* Move up some change to binary_sensors and switch. Fix program d.s.a's.

* ISY994 Basic support for PyISYv2

- Bare minimum changes to be able to support PyISYv2.
- Renaming imports and functions to new names.
- Use necessary constants from module.
- **BREAKING CHANGE** Remove ISY Climate Module support.
    - Climate module was retired on 3/30/2020: [UDI Annoucement](https://www.universal-devices.com/byebyeclimatemodule/)
- **BREAKING CHANGE** Device State Attributes use NodeProperty
    - Some attributes names and types will have changed as part of the changes to PyISY. If a user relied on a device state attribute for a given entity, they should check that it is still there and formatted the same. In general, *more* state attributes should be getting picked up now that the underlying changes have been made.
- **BREAKING CHANGE** `isy994_control` event changes (using NodeProperty)
    - Control events now return an object with additional information. Control events are now parsed to the friendly names and will need to be updated in automations.
Remove cast

* PyISY v2.0.2, add extra UOMs, omit EMPTY_TIME attributes

* Fix typo in function doc string.

Co-authored-by: J. Nick Koston <nick@koston.org>

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
shbatm 2020-05-07 23:15:42 -05:00 committed by GitHub
parent 7ac547a6e0
commit 4ec88b41dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 268 additions and 245 deletions

View file

@ -1,5 +1,14 @@
"""Representation of ISYEntity Types."""
from pyisy.constants import (
COMMAND_FRIENDLY_NAME,
EMPTY_TIME,
EVENT_PROPS_IGNORED,
ISY_VALUE_UNKNOWN,
)
from pyisy.helpers import NodeProperty
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import Dict
@ -7,39 +16,48 @@ 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._attrs = {}
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)
self._change_handler = self._node.status_events.subscribe(self.on_update)
if hasattr(self._node, "controlEvents"):
self._control_handler = self._node.controlEvents.subscribe(self.on_control)
if hasattr(self._node, "control_events"):
self._control_handler = self._node.control_events.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:
def on_control(self, event: NodeProperty) -> None:
"""Handle a control event from the ISY994 Node."""
self.hass.bus.fire(
"isy994_control", {"entity_id": self.entity_id, "control": event}
)
event_data = {
"entity_id": self.entity_id,
"control": event.control,
"value": event.value,
"formatted": event.formatted,
"uom": event.uom,
"precision": event.prec,
}
if event.value is None or event.control not in EVENT_PROPS_IGNORED:
# New state attributes may be available, update the state.
self.schedule_update_ha_state()
self.hass.bus.fire("isy994_control", event_data)
@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
if hasattr(self._node, "address"):
return self._node.address
return None
@property
@ -55,21 +73,13 @@ class ISYEntity(Entity):
@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")
return self._node.status
@property
def state(self):
"""Return the state of the ISY device."""
if self.is_unknown():
return None
if self.value == ISY_VALUE_UNKNOWN:
return STATE_UNKNOWN
return super().state
@ -78,12 +88,25 @@ class ISYNodeEntity(ISYEntity):
@property
def device_state_attributes(self) -> Dict:
"""Get the state attributes for the device."""
"""Get the state attributes for the device.
The 'aux_properties' in the pyisy Node class are combined with the
other attributes which have been picked up from the event stream and
the combined result are returned as the device state attributes.
"""
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
# Cast as list due to RuntimeError if a new property is added while running.
for name, value in list(self._node.aux_properties.items()):
attr_name = COMMAND_FRIENDLY_NAME.get(name, name)
attr[attr_name] = str(value.formatted).lower()
# If a Group/Scene, set a property if the entire scene is on/off
if hasattr(self._node, "group_all_on"):
attr["group_all_on"] = STATE_ON if self._node.group_all_on else STATE_OFF
self._attrs.update(attr)
return self._attrs
class ISYProgramEntity(ISYEntity):
@ -94,3 +117,28 @@ class ISYProgramEntity(ISYEntity):
super().__init__(status)
self._name = name
self._actions = actions
@property
def device_state_attributes(self) -> Dict:
"""Get the state attributes for the device."""
attr = {}
if self._actions:
attr["actions_enabled"] = self._actions.enabled
if self._actions.last_finished != EMPTY_TIME:
attr["actions_last_finished"] = self._actions.last_finished
if self._actions.last_run != EMPTY_TIME:
attr["actions_last_run"] = self._actions.last_run
if self._actions.last_update != EMPTY_TIME:
attr["actions_last_update"] = self._actions.last_update
attr["ran_else"] = self._actions.ran_else
attr["ran_then"] = self._actions.ran_then
attr["run_at_startup"] = self._actions.run_at_startup
attr["running"] = self._actions.running
attr["status_enabled"] = self._node.enabled
if self._node.last_finished != EMPTY_TIME:
attr["status_last_finished"] = self._node.last_finished
if self._node.last_run != EMPTY_TIME:
attr["status_last_run"] = self._node.last_run
if self._node.last_update != EMPTY_TIME:
attr["status_last_update"] = self._node.last_update
return attr