def get_zwave_value( self, value_property: Union[str, int], command_class: Optional[int] = None, endpoint: Optional[int] = None, value_property_key: Optional[int] = None, value_property_key_name: Optional[str] = None, add_to_watched_value_ids: bool = True, check_all_endpoints: bool = False, ) -> Optional[ZwaveValue]: """Return specific ZwaveValue on this ZwaveNode.""" # use commandclass and endpoint from primary value if omitted return_value = None if command_class is None: command_class = self.info.primary_value.command_class if endpoint is None: endpoint = self.info.primary_value.endpoint # lookup value by value_id value_id = get_value_id( self.info.node, command_class, value_property, endpoint=endpoint, property_key=value_property_key, property_key_name=value_property_key_name, ) return_value = self.info.node.values.get(value_id) # If we haven't found a value and check_all_endpoints is True, we should # return the first value we can find on any other endpoint if return_value is None and check_all_endpoints: for endpoint_ in self.info.node.endpoints: if endpoint_.index != self.info.primary_value.endpoint: value_id = get_value_id( self.info.node, command_class, value_property, endpoint=endpoint_.index, property_key=value_property_key, property_key_name=value_property_key_name, ) return_value = self.info.node.values.get(value_id) if return_value: break # add to watched_ids list so we will be triggered when the value updates if ( return_value and return_value.value_id not in self.watched_value_ids and add_to_watched_value_ids ): self.watched_value_ids.add(return_value.value_id) return return_value
def test_allow_manual_entry(client, inovelli_switch_state): """Test that allow_manaual_entry works correctly.""" node = Node(client, inovelli_switch_state) config_values = node.get_configuration_values() value_id = get_value_id(node, 112, 8, 0, 255) zwave_value = config_values[value_id] assert zwave_value.configuration_value_type == ConfigurationValueType.MANUAL_ENTRY value_id = get_value_id(node, 112, 8, 0, 65280) zwave_value = config_values[value_id] assert zwave_value.configuration_value_type == ConfigurationValueType.ENUMERATED
def get_zwave_value( self, value_property: Union[str, int], command_class: Optional[int] = None, endpoint: Optional[int] = None, value_property_key_name: Optional[str] = None, add_to_watched_value_ids: bool = True, ) -> Optional[ZwaveValue]: """Return specific ZwaveValue on this ZwaveNode.""" # use commandclass and endpoint from primary value if omitted return_value = None if command_class is None: command_class = self.info.primary_value.command_class if endpoint is None: endpoint = self.info.primary_value.endpoint # lookup value by value_id value_id = get_value_id( self.info.node, { "commandClass": command_class, "endpoint": endpoint, "property": value_property, "propertyKeyName": value_property_key_name, }, ) return_value = self.info.node.values.get(value_id) # add to watched_ids list so we will be triggered when the value updates if (return_value and return_value.value_id not in self.watched_value_ids and add_to_watched_value_ids): self.watched_value_ids.add(return_value.value_id) return return_value
async def async_set_value(self, service: ServiceCall) -> None: """Set a value on a node.""" nodes = service.data[const.ATTR_NODES] command_class = service.data[const.ATTR_COMMAND_CLASS] property_ = service.data[const.ATTR_PROPERTY] property_key = service.data.get(const.ATTR_PROPERTY_KEY) endpoint = service.data.get(const.ATTR_ENDPOINT) new_value = service.data[const.ATTR_VALUE] wait_for_result = service.data.get(const.ATTR_WAIT_FOR_RESULT) for node in nodes: success = await node.async_set_value( get_value_id( node, command_class, property_, endpoint=endpoint, property_key=property_key, ), new_value, wait_for_result=wait_for_result, ) if success is False: raise SetValueFailed( "Unable to set value, refer to " "https://zwave-js.github.io/node-zwave-js/#/api/node?id=setvalue " "for possible reasons")
def test_unparseable_value(client, unparseable_json_string_value_state): """Test that we handle string value with unparseable format.""" node = Node(client, unparseable_json_string_value_state) value_id = get_value_id(node, 99, "userCode", 0, 4) assert value_id == "20-99-0-userCode-4" assert value_id not in node.values
async def async_set_value(self, service: ServiceCall) -> None: """Set a value on a node.""" nodes: set[ZwaveNode] = service.data[const.ATTR_NODES] command_class: CommandClass = service.data[const.ATTR_COMMAND_CLASS] property_: int | str = service.data[const.ATTR_PROPERTY] property_key: int | str | None = service.data.get( const.ATTR_PROPERTY_KEY) endpoint: int | None = service.data.get(const.ATTR_ENDPOINT) new_value = service.data[const.ATTR_VALUE] wait_for_result = service.data.get(const.ATTR_WAIT_FOR_RESULT) options = service.data.get(const.ATTR_OPTIONS) coros = [] for node in nodes: value_id = get_value_id( node, command_class, property_, endpoint=endpoint, property_key=property_key, ) # If value has a string type but the new value is not a string, we need to # convert it to one. We use new variable `new_value_` to convert the data # so we can preserve the original `new_value` for every node. if (value_id in node.values and node.values[value_id].metadata.type == "string" and not isinstance(new_value, str)): new_value_ = str(new_value) else: new_value_ = new_value coros.append( node.async_set_value( value_id, new_value_, options=options, wait_for_result=wait_for_result, )) results = await asyncio.gather(*coros, return_exceptions=True) nodes_list = list(nodes) # multiple set_values my fail so we will track the entire list set_value_failed_nodes_list: list[ZwaveNode | Endpoint] = [] for node_, success in get_valid_responses_from_results( nodes_list, results): if success is False: # If we failed to set a value, add node to SetValueFailed exception list set_value_failed_nodes_list.append(node_) # Add the SetValueFailed exception to the results and the nodes to the node # list. No-op if there are no SetValueFailed exceptions raise_exceptions_from_results( (*nodes_list, *set_value_failed_nodes_list), (*results, *([SET_VALUE_FAILED_EXC] * len(set_value_failed_nodes_list))), )
async def test_device_diagnostics( hass, client, multisensor_6, integration, hass_client, version_state, ): """Test the device level diagnostics data dump.""" dev_reg = async_get(hass) device = dev_reg.async_get_device({get_device_id(client, multisensor_6)}) assert device # Update a value and ensure it is reflected in the node state value_id = get_value_id(multisensor_6, CommandClass.SENSOR_MULTILEVEL, PROPERTY_ULTRAVIOLET) event = Event( type="value updated", data={ "source": "node", "event": "value updated", "nodeId": multisensor_6.node_id, "args": { "commandClassName": "Multilevel Sensor", "commandClass": 49, "endpoint": 0, "property": PROPERTY_ULTRAVIOLET, "newValue": 1, "prevValue": 0, "propertyName": PROPERTY_ULTRAVIOLET, }, }, ) multisensor_6.receive_event(event) diagnostics_data = await get_diagnostics_for_device( hass, hass_client, integration, device) assert diagnostics_data["versionInfo"] == { "driverVersion": version_state["driverVersion"], "serverVersion": version_state["serverVersion"], "minSchemaVersion": 0, "maxSchemaVersion": 0, } # Assert that the data returned doesn't match the stale node state data assert diagnostics_data["state"] != multisensor_6.data # Replace data for the value we updated and assert the new node data is the same # as what's returned updated_node_data = multisensor_6.data.copy() for idx, value in enumerate(updated_node_data["values"]): if _get_value_id_from_dict(multisensor_6, value) == value_id: updated_node_data["values"][idx] = multisensor_6.values[ value_id].data.copy() assert diagnostics_data["state"] == updated_node_data
def _get_value_from_id(node: ZwaveNode, value_id_obj: ZwaveValueID) -> ZwaveValue | None: """Get a ZwaveValue from a node using a ZwaveValueDict.""" value_id = get_value_id( node, value_id_obj.command_class, value_id_obj.property_, endpoint=value_id_obj.endpoint, property_key=value_id_obj.property_key, ) return node.values.get(value_id)
def test_buffer_dict(client, idl_101_lock_state): """Test that we handle buffer dictionary correctly.""" node = Node(client, idl_101_lock_state) value_id = get_value_id(node, 99, "userCode", 0, 3) assert value_id == "26-99-0-userCode-3" zwave_value = node.values[value_id] assert zwave_value.metadata.type == "string" assert zwave_value.value == "¤\x0eªV"
def get_zwave_value_from_config(node: ZwaveNode, config: ConfigType) -> ZwaveValue: """Get a Z-Wave JS Value from a config.""" endpoint = None if config.get(ATTR_ENDPOINT): endpoint = config[ATTR_ENDPOINT] property_key = None if config.get(ATTR_PROPERTY_KEY): property_key = config[ATTR_PROPERTY_KEY] value_id = get_value_id( node, config[ATTR_COMMAND_CLASS], config[ATTR_PROPERTY], endpoint, property_key, ) if value_id not in node.values: raise vol.Invalid(f"Value {value_id} can't be found on node {node}") return node.values[value_id]
async def async_set_value(self, service: ServiceCall) -> None: """Set a value on a node.""" # pylint: disable=no-self-use nodes: set[ZwaveNode] = service.data[const.ATTR_NODES] command_class = service.data[const.ATTR_COMMAND_CLASS] property_ = service.data[const.ATTR_PROPERTY] property_key = service.data.get(const.ATTR_PROPERTY_KEY) endpoint = service.data.get(const.ATTR_ENDPOINT) new_value = service.data[const.ATTR_VALUE] wait_for_result = service.data.get(const.ATTR_WAIT_FOR_RESULT) options = service.data.get(const.ATTR_OPTIONS) for node in nodes: value_id = get_value_id( node, command_class, property_, endpoint=endpoint, property_key=property_key, ) # If value has a string type but the new value is not a string, we need to # convert it to one. We use new variable `new_value_` to convert the data # so we can preserve the original `new_value` for every node. if (value_id in node.values and node.values[value_id].metadata.type == "string" and not isinstance(new_value, str)): new_value_ = str(new_value) else: new_value_ = new_value success = await node.async_set_value( value_id, new_value_, options=options, wait_for_result=wait_for_result, ) if success is False: raise SetValueFailed( "Unable to set value, refer to " "https://zwave-js.github.io/node-zwave-js/#/api/node?id=setvalue " "for possible reasons")
async def async_set_value(self, service: ServiceCall) -> None: """Set a value on a node.""" nodes: set[ZwaveNode] = set() if ATTR_ENTITY_ID in service.data: nodes |= { async_get_node_from_entity_id(self._opp, entity_id) for entity_id in service.data[ATTR_ENTITY_ID] } if ATTR_DEVICE_ID in service.data: nodes |= { async_get_node_from_device_id(self._opp, device_id) for device_id in service.data[ATTR_DEVICE_ID] } command_class = service.data[const.ATTR_COMMAND_CLASS] property_ = service.data[const.ATTR_PROPERTY] property_key = service.data.get(const.ATTR_PROPERTY_KEY) endpoint = service.data.get(const.ATTR_ENDPOINT) new_value = service.data[const.ATTR_VALUE] wait_for_result = service.data.get(const.ATTR_WAIT_FOR_RESULT) for node in nodes: success = await node.async_set_value( get_value_id( node, command_class, property_, endpoint=endpoint, property_key=property_key, ), new_value, wait_for_result=wait_for_result, ) if success is False: raise SetValueFailed( "Unable to set value, refer to " "https://zwave-js.github.io/node-zwave-js/#/api/node?id=setvalue " "for possible reasons" )
async def async_multicast_set_value(self, service: ServiceCall) -> None: """Set a value via multicast to multiple nodes.""" nodes = service.data[const.ATTR_NODES] broadcast: bool = service.data[const.ATTR_BROADCAST] options = service.data.get(const.ATTR_OPTIONS) if not broadcast and len(nodes) == 1: _LOGGER.info( "Passing the zwave_js.multicast_set_value service call to the " "zwave_js.set_value service since only one node was targeted") await self.async_set_value(service) return command_class = service.data[const.ATTR_COMMAND_CLASS] property_ = service.data[const.ATTR_PROPERTY] property_key = service.data.get(const.ATTR_PROPERTY_KEY) endpoint = service.data.get(const.ATTR_ENDPOINT) value = { "commandClass": command_class, "property": property_, "propertyKey": property_key, "endpoint": endpoint, } new_value = service.data[const.ATTR_VALUE] # If there are no nodes, we can assume there is only one config entry due to # schema validation and can use that to get the client, otherwise we can just # get the client from the node. client: ZwaveClient = None first_node: ZwaveNode = next((node for node in nodes), None) if first_node: client = first_node.client else: entry_id = self._hass.config_entries.async_entries( const.DOMAIN)[0].entry_id client = self._hass.data[const.DOMAIN][entry_id][const.DATA_CLIENT] first_node = next( node for node in client.driver.controller.nodes.values() if get_value_id(node, command_class, property_, endpoint, property_key) in node.values) # If value has a string type but the new value is not a string, we need to # convert it to one value_id = get_value_id(first_node, command_class, property_, endpoint, property_key) if (value_id in first_node.values and first_node.values[value_id].metadata.type == "string" and not isinstance(new_value, str)): new_value = str(new_value) success = await async_multicast_set_value( client=client, new_value=new_value, value_data={k: v for k, v in value.items() if v is not None}, nodes=None if broadcast else list(nodes), options=options, ) if success is False: raise HomeAssistantError( "Unable to set value via multicast") from SetValueFailed
async def async_attach_trigger( hass: HomeAssistant, config: ConfigType, action: Callable, automation_info: dict[str, Any], *, platform_type: str = PLATFORM_TYPE, ) -> CALLBACK_TYPE: """Listen for state changes based on configuration.""" nodes: set[Node] = set() if ATTR_DEVICE_ID in config: nodes.update({ async_get_node_from_device_id(hass, device_id) for device_id in config.get(ATTR_DEVICE_ID, []) }) if ATTR_ENTITY_ID in config: nodes.update({ async_get_node_from_entity_id(hass, entity_id) for entity_id in config.get(ATTR_ENTITY_ID, []) }) from_value = config[ATTR_FROM] to_value = config[ATTR_TO] command_class = config[ATTR_COMMAND_CLASS] property_ = config[ATTR_PROPERTY] endpoint = config.get(ATTR_ENDPOINT) property_key = config.get(ATTR_PROPERTY_KEY) unsubs = [] job = HassJob(action) trigger_data: dict = {} if automation_info: trigger_data = automation_info.get("trigger_data", {}) @callback def async_on_value_updated(value: Value, device: dr.DeviceEntry, event: Event) -> None: """Handle value update.""" event_value: Value = event["value"] if event_value != value: return # Get previous value and its state value if it exists prev_value_raw = event["args"]["prevValue"] prev_value = value.metadata.states.get(str(prev_value_raw), prev_value_raw) # Get current value and its state value if it exists curr_value_raw = event["args"]["newValue"] curr_value = value.metadata.states.get(str(curr_value_raw), curr_value_raw) # Check from and to values against previous and current values respectively for value_to_eval, raw_value_to_eval, match in ( (prev_value, prev_value_raw, from_value), (curr_value, curr_value_raw, to_value), ): if (match != MATCH_ALL and value_to_eval != match and not (isinstance(match, list) and (value_to_eval in match or raw_value_to_eval in match)) and raw_value_to_eval != match): return device_name = device.name_by_user or device.name payload = { **trigger_data, CONF_PLATFORM: platform_type, ATTR_DEVICE_ID: device.id, ATTR_NODE_ID: value.node.node_id, ATTR_COMMAND_CLASS: value.command_class, ATTR_COMMAND_CLASS_NAME: value.command_class_name, ATTR_PROPERTY: value.property_, ATTR_PROPERTY_NAME: value.property_name, ATTR_ENDPOINT: endpoint, ATTR_PROPERTY_KEY: value.property_key, ATTR_PROPERTY_KEY_NAME: value.property_key_name, ATTR_PREVIOUS_VALUE: prev_value, ATTR_PREVIOUS_VALUE_RAW: prev_value_raw, ATTR_CURRENT_VALUE: curr_value, ATTR_CURRENT_VALUE_RAW: curr_value_raw, "description": f"Z-Wave value {value_id} updated on {device_name}", } hass.async_run_hass_job(job, {"trigger": payload}) dev_reg = dr.async_get(hass) for node in nodes: device_identifier = get_device_id(node.client, node) device = dev_reg.async_get_device({device_identifier}) assert device value_id = get_value_id(node, command_class, property_, endpoint, property_key) value = node.values[value_id] # We need to store the current value and device for the callback unsubs.append( node.on( "value updated", functools.partial(async_on_value_updated, value, device), )) @callback def async_remove() -> None: """Remove state listeners async.""" for unsub in unsubs: unsub() unsubs.clear() return async_remove
async def async_get_action_capabilities( hass: HomeAssistant, config: ConfigType) -> dict[str, vol.Schema]: """List action capabilities.""" action_type = config[CONF_TYPE] node = async_get_node_from_device_id(hass, config[CONF_DEVICE_ID]) # Add additional fields to the automation action UI if action_type == SERVICE_CLEAR_LOCK_USERCODE: return { "extra_fields": vol.Schema({ vol.Required(ATTR_CODE_SLOT): cv.string, }) } if action_type == SERVICE_SET_LOCK_USERCODE: return { "extra_fields": vol.Schema({ vol.Required(ATTR_CODE_SLOT): cv.string, vol.Required(ATTR_USERCODE): cv.string, }) } if action_type == SERVICE_RESET_METER: return { "extra_fields": vol.Schema({ vol.Optional(ATTR_VALUE): cv.string, }) } if action_type == SERVICE_REFRESH_VALUE: return { "extra_fields": vol.Schema({ vol.Optional(ATTR_REFRESH_ALL_VALUES): cv.boolean, }) } if action_type == SERVICE_SET_VALUE: return { "extra_fields": vol.Schema({ vol.Required(ATTR_COMMAND_CLASS): vol.In({ CommandClass(cc.id).value: cc.name for cc in sorted(node.command_classes, key=lambda cc: cc.name) }), vol.Required(ATTR_PROPERTY): cv.string, vol.Optional(ATTR_PROPERTY_KEY): cv.string, vol.Optional(ATTR_ENDPOINT): cv.string, vol.Required(ATTR_VALUE): cv.string, vol.Optional(ATTR_WAIT_FOR_RESULT): cv.boolean, }) } if action_type == SERVICE_SET_CONFIG_PARAMETER: value_id = get_value_id( node, CommandClass.CONFIGURATION, config[ATTR_CONFIG_PARAMETER], property_key=config[ATTR_CONFIG_PARAMETER_BITMASK], ) value_schema = get_config_parameter_value_schema(node, value_id) if value_schema is None: return {} return { "extra_fields": vol.Schema({vol.Required(ATTR_VALUE): value_schema}) } return {}
ATTR_PREVIOUS_VALUE: prev_value, ATTR_PREVIOUS_VALUE_RAW: prev_value_raw, ATTR_CURRENT_VALUE: curr_value, ATTR_CURRENT_VALUE_RAW: curr_value_raw, "description": f"Z-Wave value {value_id} updated on {device_name}", } hass.async_run_hass_job(job, {"trigger": payload}) for node in nodes: driver = node.client.driver assert driver is not None # The node comes from the driver. device_identifier = get_device_id(driver, node) device = dev_reg.async_get_device({device_identifier}) assert device value_id = get_value_id(node, command_class, property_, endpoint, property_key) value = node.values[value_id] # We need to store the current value and device for the callback unsubs.append( node.on( "value updated", functools.partial(async_on_value_updated, value, device), )) @callback def async_remove() -> None: """Remove state listeners async.""" for unsub in unsubs: unsub() unsubs.clear()