Esempio n. 1
0
    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
Esempio n. 2
0
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
Esempio n. 3
0
 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
Esempio n. 4
0
    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")
Esempio n. 5
0
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
Esempio n. 6
0
    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))),
        )
Esempio n. 7
0
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)
Esempio n. 9
0
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"
Esempio n. 10
0
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]
Esempio n. 11
0
    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")
Esempio n. 12
0
    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"
                )
Esempio n. 13
0
    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
Esempio n. 14
0
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
Esempio n. 15
0
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 {}
Esempio n. 16
0
            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()