11"""Support for ISY number entities."""
22from __future__ import annotations
33
4+ from dataclasses import replace
45from typing import Any
56
7+ from pyisy .constants import COMMAND_FRIENDLY_NAME , ISY_VALUE_UNKNOWN , PROP_ON_LEVEL
68from pyisy .helpers import EventListener , NodeProperty
9+ from pyisy .nodes import Node
710from pyisy .variables import Variable
811
9- from homeassistant .components .number import NumberEntity , NumberEntityDescription
12+ from homeassistant .components .number import (
13+ NumberEntity ,
14+ NumberEntityDescription ,
15+ NumberMode ,
16+ )
1017from homeassistant .config_entries import ConfigEntry
11- from homeassistant .const import CONF_VARIABLES , Platform
18+ from homeassistant .const import CONF_VARIABLES , PERCENTAGE , Platform
1219from homeassistant .core import HomeAssistant , callback
1320from homeassistant .helpers .entity import DeviceInfo , EntityCategory
1421from homeassistant .helpers .entity_platform import AddEntitiesCallback
15-
16- from .const import CONF_VAR_SENSOR_STRING , DEFAULT_VAR_SENSOR_STRING , DOMAIN
22+ from homeassistant .util .percentage import (
23+ percentage_to_ranged_value ,
24+ ranged_value_to_percentage ,
25+ )
26+
27+ from .const import (
28+ CONF_VAR_SENSOR_STRING ,
29+ DEFAULT_VAR_SENSOR_STRING ,
30+ DOMAIN ,
31+ UOM_8_BIT_RANGE ,
32+ )
1733from .helpers import convert_isy_value_to_hass
1834
1935ISY_MAX_SIZE = (2 ** 32 ) / 2
36+ ON_RANGE = (1 , 255 ) # Off is not included
37+ CONTROL_DESC = {
38+ PROP_ON_LEVEL : NumberEntityDescription (
39+ key = PROP_ON_LEVEL ,
40+ native_unit_of_measurement = PERCENTAGE ,
41+ entity_category = EntityCategory .CONFIG ,
42+ native_min_value = 1.0 ,
43+ native_max_value = 100.0 ,
44+ native_step = 1.0 ,
45+ )
46+ }
2047
2148
2249async def async_setup_entry (
@@ -27,7 +54,7 @@ async def async_setup_entry(
2754 """Set up ISY/IoX number entities from config entry."""
2855 isy_data = hass .data [DOMAIN ][config_entry .entry_id ]
2956 device_info = isy_data .devices
30- entities : list [ISYVariableNumberEntity ] = []
57+ entities : list [ISYVariableNumberEntity | ISYAuxControlNumberEntity ] = []
3158 var_id = config_entry .options .get (CONF_VAR_SENSOR_STRING , DEFAULT_VAR_SENSOR_STRING )
3259
3360 for node in isy_data .variables [Platform .NUMBER ]:
@@ -43,15 +70,10 @@ async def async_setup_entry(
4370 native_min_value = - min_max ,
4471 native_max_value = min_max ,
4572 )
46- description_init = NumberEntityDescription (
73+ description_init = replace (
74+ description ,
4775 key = f"{ node .address } _init" ,
4876 name = f"{ node .name } Initial Value" ,
49- icon = "mdi:counter" ,
50- entity_registry_enabled_default = False ,
51- native_unit_of_measurement = None ,
52- native_step = step ,
53- native_min_value = - min_max ,
54- native_max_value = min_max ,
5577 entity_category = EntityCategory .CONFIG ,
5678 )
5779
@@ -73,9 +95,88 @@ async def async_setup_entry(
7395 )
7496 )
7597
98+ for node , control in isy_data .aux_properties [Platform .NUMBER ]:
99+ entities .append (
100+ ISYAuxControlNumberEntity (
101+ node = node ,
102+ control = control ,
103+ unique_id = f"{ isy_data .uid_base (node )} _{ control } " ,
104+ description = CONTROL_DESC [control ],
105+ device_info = device_info .get (node .primary_node ),
106+ )
107+ )
76108 async_add_entities (entities )
77109
78110
111+ class ISYAuxControlNumberEntity (NumberEntity ):
112+ """Representation of a ISY/IoX Aux Control Number entity."""
113+
114+ _attr_mode = NumberMode .SLIDER
115+ _attr_should_poll = False
116+
117+ def __init__ (
118+ self ,
119+ node : Node ,
120+ control : str ,
121+ unique_id : str ,
122+ description : NumberEntityDescription ,
123+ device_info : DeviceInfo | None ,
124+ ) -> None :
125+ """Initialize the ISY Aux Control Number entity."""
126+ self ._node = node
127+ name = COMMAND_FRIENDLY_NAME .get (control , control ).replace ("_" , " " ).title ()
128+ if node .address != node .primary_node :
129+ name = f"{ node .name } { name } "
130+ self ._attr_name = name
131+ self ._control = control
132+ self .entity_description = description
133+ self ._attr_has_entity_name = node .address == node .primary_node
134+ self ._attr_unique_id = unique_id
135+ self ._attr_device_info = device_info
136+ self ._change_handler : EventListener | None = None
137+
138+ async def async_added_to_hass (self ) -> None :
139+ """Subscribe to the node control change events."""
140+ self ._change_handler = self ._node .control_events .subscribe (self .async_on_update )
141+
142+ @callback
143+ def async_on_update (self , event : NodeProperty ) -> None :
144+ """Handle a control event from the ISY Node."""
145+ if event .control != self ._control :
146+ return
147+ self .async_write_ha_state ()
148+
149+ @property
150+ def native_value (self ) -> float | int | None :
151+ """Return the state of the variable."""
152+ node_prop : NodeProperty = self ._node .aux_properties [self ._control ]
153+ if node_prop .value == ISY_VALUE_UNKNOWN :
154+ return None
155+
156+ if (
157+ self .entity_description .native_unit_of_measurement == PERCENTAGE
158+ and node_prop .uom == UOM_8_BIT_RANGE # Insteon 0-255
159+ ):
160+ return ranged_value_to_percentage (ON_RANGE , node_prop .value )
161+ return int (node_prop .value )
162+
163+ async def async_set_native_value (self , value : float ) -> None :
164+ """Update the current value."""
165+ node_prop : NodeProperty = self ._node .aux_properties [self ._control ]
166+
167+ if self .entity_description .native_unit_of_measurement == PERCENTAGE :
168+ value = (
169+ percentage_to_ranged_value (ON_RANGE , round (value ))
170+ if node_prop .uom == UOM_8_BIT_RANGE
171+ else value
172+ )
173+ if self ._control == PROP_ON_LEVEL :
174+ await self ._node .set_on_level (value )
175+ return
176+
177+ await self ._node .send_cmd (self ._control , val = value , uom = node_prop .uom )
178+
179+
79180class ISYVariableNumberEntity (NumberEntity ):
80181 """Representation of an ISY variable as a number entity device."""
81182
0 commit comments