1 """Provides device actions for Z-Wave JS."""
3 from __future__
import annotations
5 from collections
import defaultdict
9 import voluptuous
as vol
10 from zwave_js_server.const
import CommandClass
11 from zwave_js_server.const.command_class.lock
import ATTR_CODE_SLOT, ATTR_USERCODE
12 from zwave_js_server.const.command_class.meter
import CC_SPECIFIC_METER_TYPE
13 from zwave_js_server.model.value
import get_value_id_str
14 from zwave_js_server.util.command_class.meter
import get_meter_type
33 from .config_validation
import VALUE_SCHEMA
36 ATTR_CONFIG_PARAMETER,
37 ATTR_CONFIG_PARAMETER_BITMASK,
42 ATTR_REFRESH_ALL_VALUES,
46 SERVICE_CLEAR_LOCK_USERCODE,
48 SERVICE_REFRESH_VALUE,
50 SERVICE_SET_CONFIG_PARAMETER,
51 SERVICE_SET_LOCK_USERCODE,
54 from .device_automation_helpers
import (
57 generate_config_parameter_subtype,
59 from .helpers
import async_get_node_from_device_id, get_value_state_schema
62 SERVICE_CLEAR_LOCK_USERCODE,
64 SERVICE_REFRESH_VALUE,
66 SERVICE_SET_CONFIG_PARAMETER,
67 SERVICE_SET_LOCK_USERCODE,
71 CLEAR_LOCK_USERCODE_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
73 vol.Required(CONF_TYPE): SERVICE_CLEAR_LOCK_USERCODE,
74 vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
75 vol.Required(ATTR_CODE_SLOT): vol.Coerce(int),
79 PING_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
81 vol.Required(CONF_TYPE): SERVICE_PING,
85 REFRESH_VALUE_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
87 vol.Required(CONF_TYPE): SERVICE_REFRESH_VALUE,
88 vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
89 vol.Optional(ATTR_REFRESH_ALL_VALUES, default=
False): cv.boolean,
93 RESET_METER_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
95 vol.Required(CONF_TYPE): SERVICE_RESET_METER,
96 vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
97 vol.Optional(ATTR_METER_TYPE): vol.Coerce(int),
98 vol.Optional(ATTR_VALUE): vol.Coerce(int),
102 SET_CONFIG_PARAMETER_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
104 vol.Required(CONF_TYPE): SERVICE_SET_CONFIG_PARAMETER,
105 vol.Required(ATTR_ENDPOINT, default=0): vol.Coerce(int),
106 vol.Required(ATTR_CONFIG_PARAMETER): vol.Any(int, str),
107 vol.Required(ATTR_CONFIG_PARAMETER_BITMASK): vol.Any(
None, int, str),
108 vol.Required(ATTR_VALUE): vol.Coerce(int),
109 vol.Required(CONF_SUBTYPE): cv.string,
113 SET_LOCK_USERCODE_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
115 vol.Required(CONF_TYPE): SERVICE_SET_LOCK_USERCODE,
116 vol.Required(CONF_ENTITY_ID): cv.entity_id_or_uuid,
117 vol.Required(ATTR_CODE_SLOT): vol.Coerce(int),
118 vol.Required(ATTR_USERCODE): cv.string,
122 SET_VALUE_SCHEMA = cv.DEVICE_ACTION_BASE_SCHEMA.extend(
124 vol.Required(CONF_TYPE): SERVICE_SET_VALUE,
125 vol.Required(ATTR_COMMAND_CLASS): vol.In([cc.value
for cc
in CommandClass]),
126 vol.Required(ATTR_PROPERTY): vol.Any(int, str),
127 vol.Optional(ATTR_PROPERTY_KEY): vol.Any(vol.Coerce(int), cv.string),
128 vol.Optional(ATTR_ENDPOINT): vol.Coerce(int),
129 vol.Required(ATTR_VALUE): VALUE_SCHEMA,
130 vol.Optional(ATTR_WAIT_FOR_RESULT, default=
False): cv.boolean,
134 _ACTION_SCHEMA = vol.Any(
135 CLEAR_LOCK_USERCODE_SCHEMA,
137 REFRESH_VALUE_SCHEMA,
139 SET_CONFIG_PARAMETER_SCHEMA,
140 SET_LOCK_USERCODE_SCHEMA,
146 hass: HomeAssistant, config: ConfigType
148 """Validate config."""
153 hass: HomeAssistant, device_id: str
154 ) -> list[dict[str, Any]]:
155 """List device actions for Z-Wave JS devices."""
156 registry = er.async_get(hass)
157 actions: list[dict] = []
161 if node.client.driver
and node.client.driver.controller.own_node == node:
165 CONF_DEVICE_ID: device_id,
171 {**base_action, CONF_TYPE: SERVICE_SET_VALUE},
172 {**base_action, CONF_TYPE: SERVICE_PING},
179 CONF_TYPE: SERVICE_SET_CONFIG_PARAMETER,
180 ATTR_ENDPOINT: config_value.endpoint,
181 ATTR_CONFIG_PARAMETER: config_value.property_,
182 ATTR_CONFIG_PARAMETER_BITMASK: config_value.property_key,
185 for config_value
in node.get_configuration_values().values()
189 meter_endpoints: dict[int, dict[str, Any]] = defaultdict(dict)
191 for entry
in er.async_entries_for_device(
192 registry, device_id, include_disabled_entities=
False
199 not (state := hass.states.get(entry.entity_id))
200 or state.state == STATE_UNAVAILABLE
203 entity_action = {**base_action, CONF_ENTITY_ID: entry.id}
204 actions.append({**entity_action, CONF_TYPE: SERVICE_REFRESH_VALUE})
205 if entry.domain == LOCK_DOMAIN:
208 {**entity_action, CONF_TYPE: SERVICE_SET_LOCK_USERCODE},
209 {**entity_action, CONF_TYPE: SERVICE_CLEAR_LOCK_USERCODE},
213 if entry.domain == SENSOR_DOMAIN:
214 value_id = entry.unique_id.split(
".")[1]
217 if not re.match(VALUE_ID_REGEX, value_id):
219 value = node.values[value_id]
222 if CC_SPECIFIC_METER_TYPE
in value.metadata.cc_specific:
223 endpoint_idx = value.endpoint
or 0
224 meter_endpoints[endpoint_idx].setdefault(CONF_ENTITY_ID, entry.id)
225 meter_endpoints[endpoint_idx].setdefault(ATTR_METER_TYPE, set()).
add(
226 get_meter_type(value)
229 if not meter_endpoints:
232 for endpoint, endpoint_data
in meter_endpoints.items():
233 base_action[CONF_ENTITY_ID] = endpoint_data[CONF_ENTITY_ID]
237 CONF_TYPE: SERVICE_RESET_METER,
238 CONF_SUBTYPE: f
"Endpoint {endpoint} (All)",
244 CONF_TYPE: SERVICE_RESET_METER,
245 ATTR_METER_TYPE: meter_type,
246 CONF_SUBTYPE: f
"Endpoint {endpoint} ({meter_type.name})",
248 for meter_type
in endpoint_data[ATTR_METER_TYPE]
257 variables: TemplateVarsType,
258 context: Context |
None,
260 """Execute a device action."""
261 action_type = service = config[CONF_TYPE]
262 if action_type
not in ACTION_TYPES:
268 for k, v
in config.items()
269 if k
not in (ATTR_DOMAIN, CONF_TYPE, CONF_SUBTYPE)
and v
not in (
None,
"")
275 SERVICE_REFRESH_VALUE,
276 SERVICE_SET_LOCK_USERCODE,
277 SERVICE_CLEAR_LOCK_USERCODE,
280 service_data.pop(ATTR_DEVICE_ID)
281 await hass.services.async_call(
282 DOMAIN, service, service_data, blocking=
True, context=context
287 hass: HomeAssistant, config: ConfigType
288 ) -> dict[str, vol.Schema]:
289 """List action capabilities."""
290 action_type = config[CONF_TYPE]
294 if action_type == SERVICE_CLEAR_LOCK_USERCODE:
296 "extra_fields": vol.Schema(
298 vol.Required(ATTR_CODE_SLOT): cv.string,
303 if action_type == SERVICE_SET_LOCK_USERCODE:
305 "extra_fields": vol.Schema(
307 vol.Required(ATTR_CODE_SLOT): cv.string,
308 vol.Required(ATTR_USERCODE): cv.string,
313 if action_type == SERVICE_RESET_METER:
315 "extra_fields": vol.Schema(
317 vol.Optional(ATTR_VALUE): cv.string,
322 if action_type == SERVICE_REFRESH_VALUE:
324 "extra_fields": vol.Schema(
326 vol.Optional(ATTR_REFRESH_ALL_VALUES): cv.boolean,
331 if action_type == SERVICE_SET_VALUE:
333 "extra_fields": vol.Schema(
335 vol.Required(ATTR_COMMAND_CLASS): vol.In(
337 CommandClass(cc.id).value: cc.name
339 node.command_classes, key=
lambda cc: cc.name
343 vol.Required(ATTR_PROPERTY): cv.string,
344 vol.Optional(ATTR_PROPERTY_KEY): cv.string,
345 vol.Optional(ATTR_ENDPOINT): cv.string,
346 vol.Required(ATTR_VALUE): cv.string,
347 vol.Optional(ATTR_WAIT_FOR_RESULT): cv.boolean,
352 if action_type == SERVICE_SET_CONFIG_PARAMETER:
353 value_id = get_value_id_str(
355 CommandClass.CONFIGURATION,
356 config[ATTR_CONFIG_PARAMETER],
357 property_key=config[ATTR_CONFIG_PARAMETER_BITMASK],
358 endpoint=config[ATTR_ENDPOINT],
361 if value_schema
is None:
363 return {
"extra_fields": vol.Schema({vol.Required(ATTR_VALUE): value_schema})}
bool add(self, _T matcher)
ConfigType async_validate_entity_schema(HomeAssistant hass, ConfigType config, VolSchemaType schema)
ConfigType async_validate_action_config(HomeAssistant hass, ConfigType config)
dict[str, vol.Schema] async_get_action_capabilities(HomeAssistant hass, ConfigType config)
list[dict[str, Any]] async_get_actions(HomeAssistant hass, str device_id)
None async_call_action_from_config(HomeAssistant hass, ConfigType config, TemplateVarsType variables, Context|None context)
str generate_config_parameter_subtype(ConfigurationValue config_value)
VolSchemaType|vol.Coerce|vol.In|None get_value_state_schema(ZwaveValue value)
ZwaveNode async_get_node_from_device_id(HomeAssistant hass, str device_id, dr.DeviceRegistry|None dev_reg=None)