mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Add On Level number entities to ISY994 Insteon Devices (#85798)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
5f67e79ad9
commit
1fcd25130f
@ -87,7 +87,7 @@ NODE_PLATFORMS = [
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
]
|
||||
NODE_AUX_PROP_PLATFORMS = [Platform.SENSOR]
|
||||
NODE_AUX_PROP_PLATFORMS = [Platform.SENSOR, Platform.NUMBER]
|
||||
PROGRAM_PLATFORMS = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.COVER,
|
||||
@ -308,7 +308,7 @@ NODE_FILTERS: dict[Platform, dict[str, list[str]]] = {
|
||||
},
|
||||
}
|
||||
NODE_AUX_FILTERS: dict[str, Platform] = {
|
||||
PROP_ON_LEVEL: Platform.SENSOR,
|
||||
PROP_ON_LEVEL: Platform.NUMBER,
|
||||
PROP_RAMP_RATE: Platform.SENSOR,
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,7 @@ from .const import (
|
||||
ISY_GROUP_PLATFORM,
|
||||
KEY_ACTIONS,
|
||||
KEY_STATUS,
|
||||
NODE_AUX_FILTERS,
|
||||
NODE_FILTERS,
|
||||
NODE_PLATFORMS,
|
||||
PROGRAM_PLATFORMS,
|
||||
@ -331,7 +332,10 @@ def _categorize_nodes(
|
||||
if getattr(node, "is_dimmable", False):
|
||||
aux_controls = ROOT_AUX_CONTROLS.intersection(node.aux_properties)
|
||||
for control in aux_controls:
|
||||
# Deprecated all aux properties as sensors. Update in 2023.5.0 to remove extras.
|
||||
isy_data.aux_properties[Platform.SENSOR].append((node, control))
|
||||
platform = NODE_AUX_FILTERS[control]
|
||||
isy_data.aux_properties[platform].append((node, control))
|
||||
|
||||
if node.protocol == PROTO_GROUP:
|
||||
isy_data.nodes[ISY_GROUP_PLATFORM].append(node)
|
||||
|
@ -10,14 +10,19 @@ from pyisy.nodes import Node
|
||||
from homeassistant.components.light import ColorMode, LightEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
import homeassistant.helpers.entity_registry as er
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
from .const import _LOGGER, CONF_RESTORE_LIGHT_STATE, DOMAIN, UOM_PERCENTAGE
|
||||
from .entity import ISYNodeEntity
|
||||
from .services import async_setup_light_services
|
||||
from .services import (
|
||||
SERVICE_SET_ON_LEVEL,
|
||||
async_log_deprecated_service_call,
|
||||
async_setup_light_services,
|
||||
)
|
||||
|
||||
ATTR_LAST_BRIGHTNESS = "last_brightness"
|
||||
|
||||
@ -125,6 +130,18 @@ class ISYLightEntity(ISYNodeEntity, LightEntity, RestoreEntity):
|
||||
|
||||
async def async_set_on_level(self, value: int) -> None:
|
||||
"""Set the ON Level for a device."""
|
||||
entity_registry = er.async_get(self.hass)
|
||||
async_log_deprecated_service_call(
|
||||
self.hass,
|
||||
call=ServiceCall(domain=DOMAIN, service=SERVICE_SET_ON_LEVEL),
|
||||
alternate_service="number.set_value",
|
||||
alternate_target=entity_registry.async_get_entity_id(
|
||||
Platform.NUMBER,
|
||||
DOMAIN,
|
||||
f"{self._node.isy.uuid}_{self._node.address}_OL",
|
||||
),
|
||||
breaks_in_ha_version="2023.5.0",
|
||||
)
|
||||
await self._node.set_on_level(value)
|
||||
|
||||
async def async_set_ramp_rate(self, value: int) -> None:
|
||||
|
@ -1,22 +1,49 @@
|
||||
"""Support for ISY number entities."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import replace
|
||||
from typing import Any
|
||||
|
||||
from pyisy.constants import COMMAND_FRIENDLY_NAME, ISY_VALUE_UNKNOWN, PROP_ON_LEVEL
|
||||
from pyisy.helpers import EventListener, NodeProperty
|
||||
from pyisy.nodes import Node
|
||||
from pyisy.variables import Variable
|
||||
|
||||
from homeassistant.components.number import NumberEntity, NumberEntityDescription
|
||||
from homeassistant.components.number import (
|
||||
NumberEntity,
|
||||
NumberEntityDescription,
|
||||
NumberMode,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_VARIABLES, Platform
|
||||
from homeassistant.const import CONF_VARIABLES, PERCENTAGE, Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util.percentage import (
|
||||
percentage_to_ranged_value,
|
||||
ranged_value_to_percentage,
|
||||
)
|
||||
|
||||
from .const import CONF_VAR_SENSOR_STRING, DEFAULT_VAR_SENSOR_STRING, DOMAIN
|
||||
from .const import (
|
||||
CONF_VAR_SENSOR_STRING,
|
||||
DEFAULT_VAR_SENSOR_STRING,
|
||||
DOMAIN,
|
||||
UOM_8_BIT_RANGE,
|
||||
)
|
||||
from .helpers import convert_isy_value_to_hass
|
||||
|
||||
ISY_MAX_SIZE = (2**32) / 2
|
||||
ON_RANGE = (1, 255) # Off is not included
|
||||
CONTROL_DESC = {
|
||||
PROP_ON_LEVEL: NumberEntityDescription(
|
||||
key=PROP_ON_LEVEL,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_min_value=1.0,
|
||||
native_max_value=100.0,
|
||||
native_step=1.0,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@ -27,7 +54,7 @@ async def async_setup_entry(
|
||||
"""Set up ISY/IoX number entities from config entry."""
|
||||
isy_data = hass.data[DOMAIN][config_entry.entry_id]
|
||||
device_info = isy_data.devices
|
||||
entities: list[ISYVariableNumberEntity] = []
|
||||
entities: list[ISYVariableNumberEntity | ISYAuxControlNumberEntity] = []
|
||||
var_id = config_entry.options.get(CONF_VAR_SENSOR_STRING, DEFAULT_VAR_SENSOR_STRING)
|
||||
|
||||
for node in isy_data.variables[Platform.NUMBER]:
|
||||
@ -43,15 +70,10 @@ async def async_setup_entry(
|
||||
native_min_value=-min_max,
|
||||
native_max_value=min_max,
|
||||
)
|
||||
description_init = NumberEntityDescription(
|
||||
description_init = replace(
|
||||
description,
|
||||
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,
|
||||
)
|
||||
|
||||
@ -73,9 +95,88 @@ async def async_setup_entry(
|
||||
)
|
||||
)
|
||||
|
||||
for node, control in isy_data.aux_properties[Platform.NUMBER]:
|
||||
entities.append(
|
||||
ISYAuxControlNumberEntity(
|
||||
node=node,
|
||||
control=control,
|
||||
unique_id=f"{isy_data.uid_base(node)}_{control}",
|
||||
description=CONTROL_DESC[control],
|
||||
device_info=device_info.get(node.primary_node),
|
||||
)
|
||||
)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class ISYAuxControlNumberEntity(NumberEntity):
|
||||
"""Representation of a ISY/IoX Aux Control Number entity."""
|
||||
|
||||
_attr_mode = NumberMode.SLIDER
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
node: Node,
|
||||
control: str,
|
||||
unique_id: str,
|
||||
description: NumberEntityDescription,
|
||||
device_info: DeviceInfo | None,
|
||||
) -> None:
|
||||
"""Initialize the ISY Aux Control Number entity."""
|
||||
self._node = node
|
||||
name = COMMAND_FRIENDLY_NAME.get(control, control).replace("_", " ").title()
|
||||
if node.address != node.primary_node:
|
||||
name = f"{node.name} {name}"
|
||||
self._attr_name = name
|
||||
self._control = control
|
||||
self.entity_description = description
|
||||
self._attr_has_entity_name = node.address == node.primary_node
|
||||
self._attr_unique_id = unique_id
|
||||
self._attr_device_info = device_info
|
||||
self._change_handler: EventListener | None = None
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Subscribe to the node control change events."""
|
||||
self._change_handler = self._node.control_events.subscribe(self.async_on_update)
|
||||
|
||||
@callback
|
||||
def async_on_update(self, event: NodeProperty) -> None:
|
||||
"""Handle a control event from the ISY Node."""
|
||||
if event.control != self._control:
|
||||
return
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def native_value(self) -> float | int | None:
|
||||
"""Return the state of the variable."""
|
||||
node_prop: NodeProperty = self._node.aux_properties[self._control]
|
||||
if node_prop.value == ISY_VALUE_UNKNOWN:
|
||||
return None
|
||||
|
||||
if (
|
||||
self.entity_description.native_unit_of_measurement == PERCENTAGE
|
||||
and node_prop.uom == UOM_8_BIT_RANGE # Insteon 0-255
|
||||
):
|
||||
return ranged_value_to_percentage(ON_RANGE, node_prop.value)
|
||||
return int(node_prop.value)
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Update the current value."""
|
||||
node_prop: NodeProperty = self._node.aux_properties[self._control]
|
||||
|
||||
if self.entity_description.native_unit_of_measurement == PERCENTAGE:
|
||||
value = (
|
||||
percentage_to_ranged_value(ON_RANGE, round(value))
|
||||
if node_prop.uom == UOM_8_BIT_RANGE
|
||||
else value
|
||||
)
|
||||
if self._control == PROP_ON_LEVEL:
|
||||
await self._node.set_on_level(value)
|
||||
return
|
||||
|
||||
await self._node.send_cmd(self._control, val=value, uom=node_prop.uom)
|
||||
|
||||
|
||||
class ISYVariableNumberEntity(NumberEntity):
|
||||
"""Representation of an ISY variable as a number entity device."""
|
||||
|
||||
|
@ -7,7 +7,6 @@ from pyisy.constants import (
|
||||
COMMAND_FRIENDLY_NAME,
|
||||
ISY_VALUE_UNKNOWN,
|
||||
PROP_BATTERY_LEVEL,
|
||||
PROP_BUSY,
|
||||
PROP_COMMS_ERROR,
|
||||
PROP_ENERGY_MODE,
|
||||
PROP_HEAT_COOL_STATE,
|
||||
@ -28,7 +27,7 @@ from homeassistant.components.sensor import (
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
@ -53,7 +52,6 @@ AUX_DISABLED_BY_DEFAULT_EXACT = {
|
||||
PROP_RAMP_RATE,
|
||||
PROP_STATUS,
|
||||
}
|
||||
SKIP_AUX_PROPERTIES = {PROP_BUSY, PROP_COMMS_ERROR, PROP_STATUS}
|
||||
|
||||
# Reference pyisy.constants.COMMAND_FRIENDLY_NAME for API details.
|
||||
# Note: "LUMIN"/Illuminance removed, some devices use non-conformant "%" unit
|
||||
@ -260,6 +258,22 @@ class ISYAuxSensorEntity(ISYSensorEntity):
|
||||
"""Return the target value."""
|
||||
return None if self.target is None else self.target.value
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Subscribe to the node control change events.
|
||||
|
||||
Overloads the default ISYNodeEntity updater to only update when
|
||||
this control is changed on the device and prevent duplicate firing
|
||||
of `isy994_control` events.
|
||||
"""
|
||||
self._change_handler = self._node.control_events.subscribe(self.async_on_update)
|
||||
|
||||
@callback
|
||||
def async_on_update(self, event: NodeProperty) -> None:
|
||||
"""Handle a control event from the ISY Node."""
|
||||
if event.control != self._control:
|
||||
return
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class ISYSensorVariableEntity(ISYEntity, SensorEntity):
|
||||
"""Representation of an ISY variable as a sensor device."""
|
||||
|
@ -136,8 +136,8 @@ rename_node:
|
||||
selector:
|
||||
text:
|
||||
set_on_level:
|
||||
name: Set On Level
|
||||
description: Send a ISY set_on_level command to a Node.
|
||||
name: Set On Level (Deprecated)
|
||||
description: "Send a ISY set_on_level command to a Node. Deprecated: Use On Level Number entity instead."
|
||||
target:
|
||||
entity:
|
||||
integration: isy994
|
||||
|
Loading…
x
Reference in New Issue
Block a user