Home Assistant Unofficial Reference 2024.12.1
properties.py
Go to the documentation of this file.
1 """Property update methods and schemas."""
2 
3 from typing import Any
4 
5 from pyinsteon import devices
6 from pyinsteon.config import (
7  LOAD_BUTTON,
8  RADIO_BUTTON_GROUPS,
9  RAMP_RATE_IN_SEC,
10  get_usable_value,
11 )
12 from pyinsteon.constants import (
13  RAMP_RATES_SEC,
14  PropertyType,
15  RelayMode,
16  ResponseStatus,
17  ToggleMode,
18 )
19 from pyinsteon.device_types.device_base import Device
20 import voluptuous as vol
21 import voluptuous_serialize
22 
23 from homeassistant.components import websocket_api
24 from homeassistant.core import HomeAssistant
26 
27 from ..const import (
28  DEVICE_ADDRESS,
29  ID,
30  INSTEON_DEVICE_NOT_FOUND,
31  PROPERTY_NAME,
32  PROPERTY_VALUE,
33  TYPE,
34 )
35 from .device import notify_device_not_found
36 
37 SHOW_ADVANCED = "show_advanced"
38 RAMP_RATE_SECONDS = list(dict.fromkeys(RAMP_RATES_SEC))
39 RAMP_RATE_SECONDS.sort()
40 RAMP_RATE_LIST = [str(seconds) for seconds in RAMP_RATE_SECONDS]
41 TOGGLE_MODES = [str(ToggleMode(v)).lower() for v in list(ToggleMode)]
42 RELAY_MODES = [str(RelayMode(v)).lower() for v in list(RelayMode)]
43 
44 
45 def _bool_schema(name):
46  return voluptuous_serialize.convert(vol.Schema({vol.Required(name): bool}))[0]
47 
48 
49 def _byte_schema(name):
50  return voluptuous_serialize.convert(vol.Schema({vol.Required(name): cv.byte}))[0]
51 
52 
53 def _float_schema(name):
54  return voluptuous_serialize.convert(vol.Schema({vol.Required(name): float}))[0]
55 
56 
57 def _list_schema(name, values):
58  return voluptuous_serialize.convert(
59  vol.Schema({vol.Required(name): vol.In(values)}),
60  custom_serializer=cv.custom_serializer,
61  )[0]
62 
63 
64 def _multi_select_schema(name, values):
65  return voluptuous_serialize.convert(
66  vol.Schema({vol.Optional(name): cv.multi_select(values)}),
67  custom_serializer=cv.custom_serializer,
68  )[0]
69 
70 
71 def _read_only_schema(name, value):
72  """Return a constant value schema."""
73  return voluptuous_serialize.convert(vol.Schema({vol.Required(name): value}))[0]
74 
75 
76 def get_schema(prop, name, groups):
77  """Return the correct schema type."""
78  if prop.is_read_only:
79  return _read_only_schema(name, prop.value)
80  if name == RAMP_RATE_IN_SEC:
81  return _list_schema(name, RAMP_RATE_LIST)
82  if name == RADIO_BUTTON_GROUPS:
83  button_list = {str(group): groups[group].name for group in groups}
84  return _multi_select_schema(name, button_list)
85  if name == LOAD_BUTTON:
86  button_list = {group: groups[group].name for group in groups}
87  return _list_schema(name, button_list)
88  if prop.value_type is bool:
89  return _bool_schema(name)
90  if prop.value_type is int:
91  return _byte_schema(name)
92  if prop.value_type is float:
93  return _float_schema(name)
94  if prop.value_type == ToggleMode:
95  return _list_schema(name, TOGGLE_MODES)
96  if prop.value_type == RelayMode:
97  return _list_schema(name, RELAY_MODES)
98  return None
99 
100 
101 def get_properties(device: Device, show_advanced=False):
102  """Get the properties of an Insteon device and return the records and schema."""
103 
104  properties = []
105  schema = {}
106 
107  for name, prop in device.configuration.items():
108  if prop.is_read_only and not show_advanced:
109  continue
110 
111  prop_schema = get_schema(prop, name, device.groups)
112  if prop_schema is None:
113  continue
114  schema[name] = prop_schema
115  properties.append(property_to_dict(prop))
116 
117  if show_advanced:
118  for name, prop in device.operating_flags.items():
119  if prop.property_type != PropertyType.ADVANCED:
120  continue
121  prop_schema = get_schema(prop, name, device.groups)
122  if prop_schema is not None:
123  schema[name] = prop_schema
124  properties.append(property_to_dict(prop))
125  for name, prop in device.properties.items():
126  if prop.property_type != PropertyType.ADVANCED:
127  continue
128  prop_schema = get_schema(prop, name, device.groups)
129  if prop_schema is not None:
130  schema[name] = prop_schema
131  properties.append(property_to_dict(prop))
132 
133  return properties, schema
134 
135 
137  """Return a property data row."""
138  value = get_usable_value(prop)
139  modified = value == prop.new_value
140  if prop.value_type in [ToggleMode, RelayMode] or prop.name == RAMP_RATE_IN_SEC:
141  value = str(value).lower()
142  return {"name": prop.name, "value": value, "modified": modified}
143 
144 
145 def update_property(device, prop_name, value):
146  """Update the value of a device property."""
147  prop = device.configuration[prop_name]
148  if prop.value_type == ToggleMode:
149  toggle_mode = getattr(ToggleMode, value.upper())
150  prop.new_value = toggle_mode
151  elif prop.value_type == RelayMode:
152  relay_mode = getattr(RelayMode, value.upper())
153  prop.new_value = relay_mode
154  else:
155  prop.new_value = value
156 
157 
158 @websocket_api.websocket_command( { vol.Required(TYPE): "insteon/properties/get",
159  vol.Required(DEVICE_ADDRESS): str,
160  vol.Required(SHOW_ADVANCED): bool,
161  }
162 )
163 @websocket_api.require_admin
164 @websocket_api.async_response
165 async def websocket_get_properties(
166  hass: HomeAssistant,
168  msg: dict[str, Any],
169 ) -> None:
170  """Add the default All-Link Database records for an Insteon device."""
171  if not (device := devices[msg[DEVICE_ADDRESS]]):
172  notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
173  return
174 
175  properties, schema = get_properties(device, msg[SHOW_ADVANCED])
176 
177  connection.send_result(msg[ID], {"properties": properties, "schema": schema})
178 
179 
180 @websocket_api.websocket_command( { vol.Required(TYPE): "insteon/properties/change",
181  vol.Required(DEVICE_ADDRESS): str,
182  vol.Required(PROPERTY_NAME): str,
183  vol.Required(PROPERTY_VALUE): vol.Any(list, int, float, bool, str),
184  }
185 )
186 @websocket_api.require_admin
187 @websocket_api.async_response
189  hass: HomeAssistant,
191  msg: dict[str, Any],
192 ) -> None:
193  """Add the default All-Link Database records for an Insteon device."""
194  if not (device := devices[msg[DEVICE_ADDRESS]]):
195  notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
196  return
197 
198  update_property(device, msg[PROPERTY_NAME], msg[PROPERTY_VALUE])
199  connection.send_result(msg[ID])
200 
201 
202 @websocket_api.websocket_command( { vol.Required(TYPE): "insteon/properties/write",
203  vol.Required(DEVICE_ADDRESS): str,
204  }
205 )
206 @websocket_api.require_admin
207 @websocket_api.async_response
209  hass: HomeAssistant,
211  msg: dict[str, Any],
212 ) -> None:
213  """Add the default All-Link Database records for an Insteon device."""
214  if not (device := devices[msg[DEVICE_ADDRESS]]):
215  notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
216  return
217 
218  result = await device.async_write_config()
219  await devices.async_save(workdir=hass.config.config_dir)
220  if result not in [ResponseStatus.SUCCESS, ResponseStatus.RUN_ON_WAKE]:
221  connection.send_message(
222  websocket_api.error_message(
223  msg[ID], "write_failed", "properties not written to device"
224  )
225  )
226  return
227  connection.send_result(msg[ID])
228 
229 
230 @websocket_api.websocket_command( { vol.Required(TYPE): "insteon/properties/load",
231  vol.Required(DEVICE_ADDRESS): str,
232  }
233 )
234 @websocket_api.require_admin
235 @websocket_api.async_response
236 async def websocket_load_properties(
237  hass: HomeAssistant,
239  msg: dict[str, Any],
240 ) -> None:
241  """Add the default All-Link Database records for an Insteon device."""
242  if not (device := devices[msg[DEVICE_ADDRESS]]):
243  notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
244  return
245 
246  result = await device.async_read_config(read_aldb=False)
247  await devices.async_save(workdir=hass.config.config_dir)
248 
249  if result not in [ResponseStatus.SUCCESS, ResponseStatus.RUN_ON_WAKE]:
250  connection.send_message(
251  websocket_api.error_message(
252  msg[ID], "load_failed", "properties not loaded from device"
253  )
254  )
255  return
256  connection.send_result(msg[ID])
257 
258 
259 @websocket_api.websocket_command( { vol.Required(TYPE): "insteon/properties/reset",
260  vol.Required(DEVICE_ADDRESS): str,
261  }
262 )
263 @websocket_api.require_admin
264 @websocket_api.async_response
266  hass: HomeAssistant,
268  msg: dict[str, Any],
269 ) -> None:
270  """Add the default All-Link Database records for an Insteon device."""
271  if not (device := devices[msg[DEVICE_ADDRESS]]):
272  notify_device_not_found(connection, msg, INSTEON_DEVICE_NOT_FOUND)
273  return
274 
275  for prop in device.operating_flags:
276  device.operating_flags[prop].new_value = None
277  for prop in device.properties:
278  device.properties[prop].new_value = None
279  connection.send_result(msg[ID])
280 
def notify_device_not_found(connection, msg, text)
Definition: device.py:51
None websocket_reset_properties(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
Definition: properties.py:279
None websocket_write_properties(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
Definition: properties.py:218
None websocket_get_properties(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
Definition: properties.py:171
None websocket_load_properties(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
Definition: properties.py:248
def get_properties(Device device, show_advanced=False)
Definition: properties.py:101
None websocket_change_properties_record(HomeAssistant hass, websocket_api.connection.ActiveConnection connection, dict[str, Any] msg)
Definition: properties.py:196
def update_property(device, prop_name, value)
Definition: properties.py:145