Add ISY994 variables as number entities (#85511)

Co-authored-by: J. Nick Koston <nick@koston.org>
pull/85641/head^2
shbatm 2023-01-10 16:29:11 -06:00 committed by GitHub
parent 7af23698bc
commit d3249432c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 201 additions and 5 deletions

View File

@ -607,6 +607,7 @@ omit =
homeassistant/components/isy994/helpers.py
homeassistant/components/isy994/light.py
homeassistant/components/isy994/lock.py
homeassistant/components/isy994/number.py
homeassistant/components/isy994/sensor.py
homeassistant/components/isy994/services.py
homeassistant/components/isy994/switch.py

View File

@ -16,6 +16,7 @@ from homeassistant.const import (
CONF_PASSWORD,
CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP,
Platform,
)
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
@ -141,7 +142,9 @@ async def async_setup_entry(
for platform in PROGRAM_PLATFORMS:
hass_isy_data[ISY994_PROGRAMS][platform] = []
hass_isy_data[ISY994_VARIABLES] = []
hass_isy_data[ISY994_VARIABLES] = {}
hass_isy_data[ISY994_VARIABLES][Platform.NUMBER] = []
hass_isy_data[ISY994_VARIABLES][Platform.SENSOR] = []
isy_config = entry.data
isy_options = entry.options
@ -212,7 +215,12 @@ async def async_setup_entry(
_categorize_nodes(hass_isy_data, isy.nodes, ignore_identifier, sensor_identifier)
_categorize_programs(hass_isy_data, isy.programs)
# Categorize variables call to be removed with variable sensors in 2023.5.0
_categorize_variables(hass_isy_data, isy.variables, variable_identifier)
# Gather ISY Variables to be added. Identifier used to enable by default.
numbers = hass_isy_data[ISY994_VARIABLES][Platform.NUMBER]
for vtype, vname, vid in isy.variables.children:
numbers.append((isy.variables[vtype][vid], variable_identifier in vname))
if isy.configuration[ISY_CONF_NETWORKING]:
for resource in isy.networking.nobjs:
hass_isy_data[ISY994_NODES][PROTO_NETWORK_RESOURCE].append(resource)

View File

@ -82,6 +82,7 @@ PLATFORMS = [
Platform.FAN,
Platform.LIGHT,
Platform.LOCK,
Platform.NUMBER,
Platform.SENSOR,
Platform.SWITCH,
]
@ -307,6 +308,14 @@ NODE_FILTERS: dict[Platform, dict[str, list[str]]] = {
FILTER_INSTEON_TYPE: ["4.8", TYPE_CATEGORY_CLIMATE],
FILTER_ZWAVE_CAT: ["140"],
},
Platform.NUMBER: {
# No devices automatically sorted as numbers at this time.
FILTER_UOM: [],
FILTER_STATES: [],
FILTER_NODE_DEF_ID: [],
FILTER_INSTEON_TYPE: [],
FILTER_ZWAVE_CAT: [],
},
}
UOM_FRIENDLY_NAME = {

View File

@ -376,8 +376,9 @@ def _categorize_variables(
except KeyError as err:
_LOGGER.error("Error adding ISY Variables: %s", err)
return
variable_entities = hass_isy_data[ISY994_VARIABLES]
for vtype, vname, vid in var_to_add:
hass_isy_data[ISY994_VARIABLES].append((vname, variables[vtype][vid]))
variable_entities[Platform.SENSOR].append((vname, variables[vtype][vid]))
async def migrate_old_unique_ids(

View File

@ -0,0 +1,162 @@
"""Support for ISY number entities."""
from __future__ import annotations
from typing import Any
from pyisy import ISY
from pyisy.helpers import EventListener, NodeProperty
from pyisy.variables import Variable
from homeassistant.components.number import NumberEntity, NumberEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import _async_isy_to_configuration_url
from .const import (
DOMAIN as ISY994_DOMAIN,
ISY994_ISY,
ISY994_VARIABLES,
ISY_CONF_FIRMWARE,
ISY_CONF_MODEL,
ISY_CONF_NAME,
ISY_CONF_UUID,
MANUFACTURER,
)
from .helpers import convert_isy_value_to_hass
ISY_MAX_SIZE = (2**32) / 2
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up ISY/IoX number entities from config entry."""
hass_isy_data = hass.data[ISY994_DOMAIN][config_entry.entry_id]
isy: ISY = hass_isy_data[ISY994_ISY]
uuid = isy.configuration[ISY_CONF_UUID]
entities: list[ISYVariableNumberEntity] = []
for node, enable_by_default in hass_isy_data[ISY994_VARIABLES][Platform.NUMBER]:
step = 10 ** (-1 * node.prec)
min_max = ISY_MAX_SIZE / (10**node.prec)
description = NumberEntityDescription(
key=node.address,
name=node.name,
icon="mdi:counter",
entity_registry_enabled_default=enable_by_default,
native_unit_of_measurement=None,
native_step=step,
native_min_value=-min_max,
native_max_value=min_max,
)
description_init = NumberEntityDescription(
key=f"{node.address}_init",
name=f"{node.name} Initial Value",
icon="mdi:counter",
entity_registry_enabled_default=False,
native_unit_of_measurement=None,
native_step=step,
native_min_value=-min_max,
native_max_value=min_max,
entity_category=EntityCategory.CONFIG,
)
entities.append(
ISYVariableNumberEntity(
node,
unique_id=f"{uuid}_{node.address}",
description=description,
)
)
entities.append(
ISYVariableNumberEntity(
node=node,
unique_id=f"{uuid}_{node.address}_init",
description=description_init,
init_entity=True,
)
)
async_add_entities(entities)
class ISYVariableNumberEntity(NumberEntity):
"""Representation of an ISY variable as a number entity device."""
_attr_has_entity_name = True
_attr_should_poll = False
_init_entity: bool
_node: Variable
entity_description: NumberEntityDescription
def __init__(
self,
node: Variable,
unique_id: str,
description: NumberEntityDescription,
init_entity: bool = False,
) -> None:
"""Initialize the ISY variable number."""
self._node = node
self._name = description.name
self.entity_description = description
self._change_handler: EventListener | None = None
# Two entities are created for each variable, one for current value and one for initial.
# Initial value entities are disabled by default
self._init_entity = init_entity
self._attr_unique_id = unique_id
url = _async_isy_to_configuration_url(node.isy)
config = node.isy.configuration
self._attr_device_info = DeviceInfo(
identifiers={
(
ISY994_DOMAIN,
f"{config[ISY_CONF_UUID]}_variables",
)
},
manufacturer=MANUFACTURER,
name=f"{config[ISY_CONF_NAME]} Variables",
model=config[ISY_CONF_MODEL],
sw_version=config[ISY_CONF_FIRMWARE],
configuration_url=url,
via_device=(ISY994_DOMAIN, config[ISY_CONF_UUID]),
entry_type=DeviceEntryType.SERVICE,
)
async def async_added_to_hass(self) -> None:
"""Subscribe to the node change events."""
self._change_handler = self._node.status_events.subscribe(self.async_on_update)
@callback
def async_on_update(self, event: NodeProperty) -> None:
"""Handle the update event from the ISY Node."""
self.async_write_ha_state()
@property
def native_value(self) -> float | int | None:
"""Return the state of the variable."""
return convert_isy_value_to_hass(
self._node.init if self._init_entity else self._node.status,
"",
self._node.prec,
)
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Get the state attributes for the device."""
return {
"last_edited": self._node.last_edited,
}
async def async_set_native_value(self, value: float) -> None:
"""Set new value."""
await self._node.set_value(value, init=self._init_entity)

View File

@ -132,7 +132,7 @@ async def async_setup_entry(
# Any node in SENSOR_AUX can potentially have communication errors
entities.append(ISYAuxSensorEntity(node, PROP_COMMS_ERROR, False))
for vname, vobj in hass_isy_data[ISY994_VARIABLES]:
for vname, vobj in hass_isy_data[ISY994_VARIABLES][Platform.SENSOR]:
entities.append(ISYSensorVariableEntity(vname, vobj))
await migrate_old_unique_ids(hass, Platform.SENSOR, entities)
@ -269,6 +269,9 @@ class ISYAuxSensorEntity(ISYSensorEntity):
class ISYSensorVariableEntity(ISYEntity, SensorEntity):
"""Representation of an ISY variable as a sensor device."""
# Depreceted sensors, will be removed in 2023.5.0
_attr_entity_registry_enabled_default = False
def __init__(self, vname: str, vobj: object) -> None:
"""Initialize the ISY binary sensor program."""
super().__init__(vobj)

View File

@ -307,6 +307,18 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901
variable = isy.variables.vobjs[vtype].get(address)
if variable is not None:
await variable.set_value(value, init)
entity_registry = er.async_get(hass)
async_log_deprecated_service_call(
hass,
call=service,
alternate_service="number.set_value",
alternate_target=entity_registry.async_get_entity_id(
Platform.NUMBER,
DOMAIN,
f"{isy.configuration[ISY_CONF_UUID]}_{address}{'_init' if init else ''}",
),
breaks_in_ha_version="2023.5.0",
)
return
_LOGGER.error("Could not set variable value; not found or enabled on the ISY")

View File

@ -184,8 +184,8 @@ system_query:
selector:
text:
set_variable:
name: Set variable
description: Set an ISY variable's current or initial value. Variables can be set by either type/address or by name.
name: Set variable (Deprecated)
description: "Set an ISY variable's current or initial value. Variables can be set by either type/address or by name. Deprecated: Use number entities instead."
fields:
address:
name: Address