Home Assistant Unofficial Reference 2024.12.1
services.py
Go to the documentation of this file.
1 """ISY Services and Commands."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
7 from pyisy.constants import COMMAND_FRIENDLY_NAME
8 import voluptuous as vol
9 
10 from homeassistant.const import (
11  CONF_ADDRESS,
12  CONF_COMMAND,
13  CONF_NAME,
14  CONF_UNIT_OF_MEASUREMENT,
15 )
16 from homeassistant.core import HomeAssistant, ServiceCall, callback
18 from homeassistant.helpers.entity import Entity
19 from homeassistant.helpers.entity_platform import async_get_platforms
20 from homeassistant.helpers.service import entity_service_call
21 from homeassistant.helpers.typing import VolDictType
22 
23 from .const import _LOGGER, DOMAIN
24 
25 # Common Services for All Platforms:
26 SERVICE_SEND_PROGRAM_COMMAND = "send_program_command"
27 
28 # Entity specific methods (valid for most Groups/ISY Scenes, Lights, Switches, Fans)
29 SERVICE_SEND_RAW_NODE_COMMAND = "send_raw_node_command"
30 SERVICE_SEND_NODE_COMMAND = "send_node_command"
31 SERVICE_GET_ZWAVE_PARAMETER = "get_zwave_parameter"
32 SERVICE_SET_ZWAVE_PARAMETER = "set_zwave_parameter"
33 SERVICE_RENAME_NODE = "rename_node"
34 
35 # Services valid only for Z-Wave Locks
36 SERVICE_SET_ZWAVE_LOCK_USER_CODE = "set_zwave_lock_user_code"
37 SERVICE_DELETE_ZWAVE_LOCK_USER_CODE = "delete_zwave_lock_user_code"
38 
39 CONF_PARAMETER = "parameter"
40 CONF_PARAMETERS = "parameters"
41 CONF_USER_NUM = "user_num"
42 CONF_CODE = "code"
43 CONF_VALUE = "value"
44 CONF_INIT = "init"
45 CONF_ISY = "isy"
46 CONF_SIZE = "size"
47 
48 VALID_NODE_COMMANDS = [
49  "beep",
50  "brighten",
51  "dim",
52  "disable",
53  "enable",
54  "fade_down",
55  "fade_stop",
56  "fade_up",
57  "fast_off",
58  "fast_on",
59  "query",
60 ]
61 VALID_PROGRAM_COMMANDS = [
62  "run",
63  "run_then",
64  "run_else",
65  "stop",
66  "enable",
67  "disable",
68  "enable_run_at_startup",
69  "disable_run_at_startup",
70 ]
71 VALID_PARAMETER_SIZES = [1, 2, 4]
72 
73 
74 def valid_isy_commands(value: Any) -> str:
75  """Validate the command is valid."""
76  value = str(value).upper()
77  if value in COMMAND_FRIENDLY_NAME:
78  assert isinstance(value, str)
79  return value
80  raise vol.Invalid("Invalid ISY Command.")
81 
82 
83 SCHEMA_GROUP = "name-address"
84 
85 SERVICE_SEND_RAW_NODE_COMMAND_SCHEMA = {
86  vol.Required(CONF_COMMAND): vol.All(cv.string, valid_isy_commands),
87  vol.Optional(CONF_VALUE): vol.All(vol.Coerce(int), vol.Range(0, 255)),
88  vol.Optional(CONF_UNIT_OF_MEASUREMENT): vol.All(vol.Coerce(int), vol.Range(0, 120)),
89  vol.Optional(CONF_PARAMETERS, default={}): {cv.string: cv.string},
90 }
91 
92 SERVICE_SEND_NODE_COMMAND_SCHEMA = {
93  vol.Required(CONF_COMMAND): vol.In(VALID_NODE_COMMANDS)
94 }
95 
96 SERVICE_RENAME_NODE_SCHEMA = {vol.Required(CONF_NAME): cv.string}
97 
98 SERVICE_GET_ZWAVE_PARAMETER_SCHEMA = {vol.Required(CONF_PARAMETER): vol.Coerce(int)}
99 
100 SERVICE_SET_ZWAVE_PARAMETER_SCHEMA = {
101  vol.Required(CONF_PARAMETER): vol.Coerce(int),
102  vol.Required(CONF_VALUE): vol.Coerce(int),
103  vol.Required(CONF_SIZE): vol.All(vol.Coerce(int), vol.In(VALID_PARAMETER_SIZES)),
104 }
105 
106 SERVICE_SET_USER_CODE_SCHEMA: VolDictType = {
107  vol.Required(CONF_USER_NUM): vol.Coerce(int),
108  vol.Required(CONF_CODE): vol.Coerce(int),
109 }
110 
111 SERVICE_DELETE_USER_CODE_SCHEMA: VolDictType = {
112  vol.Required(CONF_USER_NUM): vol.Coerce(int)
113 }
114 
115 SERVICE_SEND_PROGRAM_COMMAND_SCHEMA = vol.All(
116  cv.has_at_least_one_key(CONF_ADDRESS, CONF_NAME),
117  vol.Schema(
118  {
119  vol.Exclusive(CONF_NAME, SCHEMA_GROUP): cv.string,
120  vol.Exclusive(CONF_ADDRESS, SCHEMA_GROUP): cv.string,
121  vol.Required(CONF_COMMAND): vol.In(VALID_PROGRAM_COMMANDS),
122  vol.Optional(CONF_ISY): cv.string,
123  }
124  ),
125 )
126 
127 
128 def async_get_entities(hass: HomeAssistant) -> dict[str, Entity]:
129  """Get entities for a domain."""
130  entities: dict[str, Entity] = {}
131  for platform in async_get_platforms(hass, DOMAIN):
132  entities.update(platform.entities)
133  return entities
134 
135 
136 @callback
137 def async_setup_services(hass: HomeAssistant) -> None:
138  """Create and register services for the ISY integration."""
139  existing_services = hass.services.async_services_for_domain(DOMAIN)
140  if existing_services and SERVICE_SEND_PROGRAM_COMMAND in existing_services:
141  # Integration-level services have already been added. Return.
142  return
143 
144  async def async_send_program_command_service_handler(service: ServiceCall) -> None:
145  """Handle a send program command service call."""
146  address = service.data.get(CONF_ADDRESS)
147  name = service.data.get(CONF_NAME)
148  command = service.data[CONF_COMMAND]
149  isy_name = service.data.get(CONF_ISY)
150 
151  for config_entry_id in hass.data[DOMAIN]:
152  isy_data = hass.data[DOMAIN][config_entry_id]
153  isy = isy_data.root
154  if isy_name and isy_name != isy.conf["name"]:
155  continue
156  program = None
157  if address:
158  program = isy.programs.get_by_id(address)
159  if name:
160  program = isy.programs.get_by_name(name)
161  if program is not None:
162  await getattr(program, command)()
163  return
164  _LOGGER.error("Could not send program command; not found or enabled on the ISY")
165 
166  hass.services.async_register(
167  domain=DOMAIN,
168  service=SERVICE_SEND_PROGRAM_COMMAND,
169  service_func=async_send_program_command_service_handler,
170  schema=SERVICE_SEND_PROGRAM_COMMAND_SCHEMA,
171  )
172 
173  async def _async_send_raw_node_command(call: ServiceCall) -> None:
174  await entity_service_call(
175  hass, async_get_entities(hass), "async_send_raw_node_command", call
176  )
177 
178  hass.services.async_register(
179  domain=DOMAIN,
180  service=SERVICE_SEND_RAW_NODE_COMMAND,
181  schema=cv.make_entity_service_schema(SERVICE_SEND_RAW_NODE_COMMAND_SCHEMA),
182  service_func=_async_send_raw_node_command,
183  )
184 
185  async def _async_send_node_command(call: ServiceCall) -> None:
186  await entity_service_call(
187  hass, async_get_entities(hass), "async_send_node_command", call
188  )
189 
190  hass.services.async_register(
191  domain=DOMAIN,
192  service=SERVICE_SEND_NODE_COMMAND,
193  schema=cv.make_entity_service_schema(SERVICE_SEND_NODE_COMMAND_SCHEMA),
194  service_func=_async_send_node_command,
195  )
196 
197  async def _async_get_zwave_parameter(call: ServiceCall) -> None:
198  await entity_service_call(
199  hass, async_get_entities(hass), "async_get_zwave_parameter", call
200  )
201 
202  hass.services.async_register(
203  domain=DOMAIN,
204  service=SERVICE_GET_ZWAVE_PARAMETER,
205  schema=cv.make_entity_service_schema(SERVICE_GET_ZWAVE_PARAMETER_SCHEMA),
206  service_func=_async_get_zwave_parameter,
207  )
208 
209  async def _async_set_zwave_parameter(call: ServiceCall) -> None:
210  await entity_service_call(
211  hass, async_get_entities(hass), "async_set_zwave_parameter", call
212  )
213 
214  hass.services.async_register(
215  domain=DOMAIN,
216  service=SERVICE_SET_ZWAVE_PARAMETER,
217  schema=cv.make_entity_service_schema(SERVICE_SET_ZWAVE_PARAMETER_SCHEMA),
218  service_func=_async_set_zwave_parameter,
219  )
220 
221  async def _async_rename_node(call: ServiceCall) -> None:
222  await entity_service_call(
223  hass, async_get_entities(hass), "async_rename_node", call
224  )
225 
226  hass.services.async_register(
227  domain=DOMAIN,
228  service=SERVICE_RENAME_NODE,
229  schema=cv.make_entity_service_schema(SERVICE_RENAME_NODE_SCHEMA),
230  service_func=_async_rename_node,
231  )
232 
233 
234 @callback
235 def async_unload_services(hass: HomeAssistant) -> None:
236  """Unload services for the ISY integration."""
237  if hass.data[DOMAIN]:
238  # There is still another config entry for this domain, don't remove services.
239  return
240 
241  existing_services = hass.services.async_services_for_domain(DOMAIN)
242  if not existing_services or SERVICE_SEND_PROGRAM_COMMAND not in existing_services:
243  return
244 
245  _LOGGER.debug("Unloading ISY994 Services")
246  hass.services.async_remove(domain=DOMAIN, service=SERVICE_SEND_PROGRAM_COMMAND)
247  hass.services.async_remove(domain=DOMAIN, service=SERVICE_SEND_RAW_NODE_COMMAND)
248  hass.services.async_remove(domain=DOMAIN, service=SERVICE_SEND_NODE_COMMAND)
249  hass.services.async_remove(domain=DOMAIN, service=SERVICE_GET_ZWAVE_PARAMETER)
250  hass.services.async_remove(domain=DOMAIN, service=SERVICE_SET_ZWAVE_PARAMETER)
None async_unload_services(HomeAssistant hass)
Definition: services.py:235
None async_setup_services(HomeAssistant hass)
Definition: services.py:137
dict[str, Entity] async_get_entities(HomeAssistant hass)
Definition: services.py:128
list[EntityPlatform] async_get_platforms(HomeAssistant hass, str integration_name)
EntityServiceResponse|None entity_service_call(HomeAssistant hass, dict[str, Entity] registered_entities, str|HassJob func, ServiceCall call, Iterable[int]|None required_features=None)
Definition: service.py:925