Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bump zwave-js-server-python to 0.50.1 #94760

Merged
merged 12 commits into from
Aug 10, 2023
6 changes: 3 additions & 3 deletions homeassistant/components/zwave_js/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from async_timeout import timeout
from zwave_js_server.client import Client as ZwaveClient
from zwave_js_server.const import CommandClass
from zwave_js_server.const import CommandClass, RemoveNodeReason
from zwave_js_server.exceptions import BaseZwaveJSServerError, InvalidServerVersion
from zwave_js_server.model.driver import Driver
from zwave_js_server.model.node import Node as ZwaveNode
Expand Down Expand Up @@ -398,13 +398,13 @@ async def async_on_node_added(self, node: ZwaveNode) -> None:
def async_on_node_removed(self, event: dict) -> None:
"""Handle node removed event."""
node: ZwaveNode = event["node"]
replaced: bool = event.get("replaced", False)
reason: RemoveNodeReason = event["reason"]
# grab device in device registry attached to this node
dev_id = get_device_id(self.driver_events.driver, node)
device = self.dev_reg.async_get_device(identifiers={dev_id})
# We assert because we know the device exists
assert device
if replaced:
if reason in (RemoveNodeReason.REPLACED, RemoveNodeReason.PROXY_REPLACED):
self.discovered_value_ids.pop(device.id, None)

async_dispatcher_send(
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/zwave_js/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,7 @@ def node_removed(event: dict) -> None:
node = event["node"]
node_details = {
"node_id": node.node_id,
"reason": event["reason"],
}

connection.send_message(
Expand Down
5 changes: 3 additions & 2 deletions homeassistant/components/zwave_js/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,8 +507,9 @@ async def async_set_preset_mode(self, preset_mode: str) -> None:
# Please use Dry and Fan HVAC modes instead.
if preset_mode_value in (ThermostatMode.DRY, ThermostatMode.FAN):
LOGGER.warning(
"Dry and Fan preset modes are deprecated and will be removed in Home Assistant 2024.2. "
"Please use the corresponding Dry and Fan HVAC modes instead"
"Dry and Fan preset modes are deprecated and will be removed in Home "
"Assistant 2024.2. Please use the corresponding Dry and Fan HVAC "
"modes instead"
)
async_create_issue(
self.hass,
Expand Down
5 changes: 2 additions & 3 deletions homeassistant/components/zwave_js/device_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,8 @@
CONF_SUBTYPE,
VALUE_ID_REGEX,
generate_config_parameter_subtype,
get_config_parameter_value_schema,
)
from .helpers import async_get_node_from_device_id
from .helpers import async_get_node_from_device_id, get_value_state_schema

ACTION_TYPES = {
SERVICE_CLEAR_LOCK_USERCODE,
Expand Down Expand Up @@ -357,7 +356,7 @@ async def async_get_action_capabilities(
property_key=config[ATTR_CONFIG_PARAMETER_BITMASK],
endpoint=config[ATTR_ENDPOINT],
)
value_schema = get_config_parameter_value_schema(node, value_id)
value_schema = get_value_state_schema(node.values[value_id])
if value_schema is None:
return {}
return {"extra_fields": vol.Schema({vol.Required(ATTR_VALUE): value_schema})}
Expand Down
23 changes: 0 additions & 23 deletions homeassistant/components/zwave_js/device_automation_helpers.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
"""Provides helpers for Z-Wave JS device automations."""
from __future__ import annotations

from typing import cast

import voluptuous as vol
from zwave_js_server.client import Client as ZwaveClient
from zwave_js_server.const import ConfigurationValueType
from zwave_js_server.model.node import Node
from zwave_js_server.model.value import ConfigurationValue

from homeassistant.config_entries import ConfigEntryState
Expand All @@ -23,24 +18,6 @@
VALUE_ID_REGEX = r"([0-9]+-[0-9]+-[0-9]+-).+"


def get_config_parameter_value_schema(node: Node, value_id: str) -> vol.Schema | None:
"""Get the extra fields schema for a config parameter value."""
config_value = cast(ConfigurationValue, node.values[value_id])
min_ = config_value.metadata.min
max_ = config_value.metadata.max

if config_value.configuration_value_type in (
ConfigurationValueType.RANGE,
ConfigurationValueType.MANUAL_ENTRY,
):
return vol.All(vol.Coerce(int), vol.Range(min=min_, max=max_))

if config_value.configuration_value_type == ConfigurationValueType.ENUMERATED:
return vol.In({int(k): v for k, v in config_value.metadata.states.items()})

return None


def generate_config_parameter_subtype(config_value: ConfigurationValue) -> str:
"""Generate the config parameter name used in a device automation subtype."""
parameter = str(config_value.property_)
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/zwave_js/device_condition.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@
NODE_STATUSES,
async_bypass_dynamic_config_validation,
generate_config_parameter_subtype,
get_config_parameter_value_schema,
)
from .helpers import (
async_get_node_from_device_id,
check_type_schema_map,
get_value_state_schema,
get_zwave_value_from_config,
remove_keys_with_empty_values,
)
Expand Down Expand Up @@ -209,7 +209,7 @@ async def async_get_condition_capabilities(
# Add additional fields to the automation trigger UI
if config[CONF_TYPE] == CONFIG_PARAMETER_TYPE:
value_id = config[CONF_VALUE_ID]
value_schema = get_config_parameter_value_schema(node, value_id)
value_schema = get_value_state_schema(node.values[value_id])
if value_schema is None:
return {}
return {"extra_fields": vol.Schema({vol.Required(ATTR_VALUE): value_schema})}
Expand Down
30 changes: 20 additions & 10 deletions homeassistant/components/zwave_js/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
from zwave_js_server.client import Client
from zwave_js_server.const import CommandClass
from zwave_js_server.dump import dump_msgs
from zwave_js_server.model.node import Node, NodeDataType
from zwave_js_server.model.node import Node
from zwave_js_server.model.value import ValueDataType
from zwave_js_server.util.node import dump_node_state

from homeassistant.components.diagnostics import REDACTED
from homeassistant.components.diagnostics.util import async_redact_data
Expand Down Expand Up @@ -54,13 +55,20 @@ def optionally_redact_value_of_zwave_value(zwave_value: ValueDataType) -> ValueD
return zwave_value


def redact_node_state(node_state: NodeDataType) -> NodeDataType:
def redact_node_state(node_state: dict) -> dict:
"""Redact node state."""
redacted_state: NodeDataType = deepcopy(node_state)
redacted_state["values"] = [
optionally_redact_value_of_zwave_value(zwave_value)
for zwave_value in node_state["values"]
]
redacted_state: dict = deepcopy(node_state)
# dump_msgs returns values in a list but dump_node_state returns them in a dict
if isinstance(node_state["values"], list):
redacted_state["values"] = [
optionally_redact_value_of_zwave_value(zwave_value)
for zwave_value in node_state["values"]
]
else:
redacted_state["values"] = {
value_id: optionally_redact_value_of_zwave_value(zwave_value)
for value_id, zwave_value in node_state["values"].items()
}
return redacted_state


Expand Down Expand Up @@ -129,8 +137,8 @@ async def async_get_config_entry_diagnostics(
handshake_msgs = msgs[:-1]
network_state = msgs[-1]
network_state["result"]["state"]["nodes"] = [
redact_node_state(async_redact_data(node, KEYS_TO_REDACT))
for node in network_state["result"]["state"]["nodes"]
redact_node_state(async_redact_data(node_data, KEYS_TO_REDACT))
for node_data in network_state["result"]["state"]["nodes"]
]
return {"messages": [*handshake_msgs, network_state]}

Expand All @@ -148,7 +156,9 @@ async def async_get_device_diagnostics(
node = driver.controller.nodes[node_id]
entities = get_device_entities(hass, node, config_entry, device)
assert client.version
node_state = redact_node_state(async_redact_data(node.data, KEYS_TO_REDACT))
node_state = redact_node_state(
async_redact_data(dump_node_state(node), KEYS_TO_REDACT)
)
return {
"versionInfo": {
"driverVersion": client.version.driver_version,
Expand Down
53 changes: 23 additions & 30 deletions homeassistant/components/zwave_js/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -1108,7 +1108,7 @@ def async_discover_single_value(
def async_discover_single_configuration_value(
value: ConfigurationValue,
) -> Generator[ZwaveDiscoveryInfo, None, None]:
"""Run discovery on a single ZWave configuration value and return matching schema info."""
"""Run discovery on single Z-Wave configuration value and return schema matches."""
if value.metadata.writeable and value.metadata.readable:
if value.configuration_value_type == ConfigurationValueType.ENUMERATED:
yield ZwaveDiscoveryInfo(
Expand All @@ -1125,36 +1125,29 @@ def async_discover_single_configuration_value(
ConfigurationValueType.RANGE,
ConfigurationValueType.MANUAL_ENTRY,
):
if value.metadata.type == ValueType.BOOLEAN or (
value.metadata.min == 0 and value.metadata.max == 1
MartinHjelmare marked this conversation as resolved.
Show resolved Hide resolved
):
yield ZwaveDiscoveryInfo(
node=value.node,
primary_value=value,
assumed_state=False,
platform=Platform.SWITCH,
platform_hint="config_parameter",
platform_data=None,
additional_value_ids_to_watch=set(),
entity_registry_enabled_default=False,
)
else:
yield ZwaveDiscoveryInfo(
node=value.node,
primary_value=value,
assumed_state=False,
platform=Platform.NUMBER,
platform_hint="config_parameter",
platform_data=None,
additional_value_ids_to_watch=set(),
entity_registry_enabled_default=False,
)
yield ZwaveDiscoveryInfo(
node=value.node,
primary_value=value,
assumed_state=False,
platform=Platform.NUMBER,
platform_hint="config_parameter",
platform_data=None,
additional_value_ids_to_watch=set(),
entity_registry_enabled_default=False,
)
elif value.configuration_value_type == ConfigurationValueType.BOOLEAN:
yield ZwaveDiscoveryInfo(
node=value.node,
primary_value=value,
assumed_state=False,
platform=Platform.SWITCH,
platform_hint="config_parameter",
platform_data=None,
additional_value_ids_to_watch=set(),
entity_registry_enabled_default=False,
)
elif not value.metadata.writeable and value.metadata.readable:
if value.metadata.type == ValueType.BOOLEAN or (
value.metadata.min == 0
and value.metadata.max == 1
and not value.metadata.states
):
if value.configuration_value_type == ConfigurationValueType.BOOLEAN:
yield ZwaveDiscoveryInfo(
node=value.node,
primary_value=value,
Expand Down
14 changes: 9 additions & 5 deletions homeassistant/components/zwave_js/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
from zwave_js_server.const import NodeStatus
from zwave_js_server.exceptions import BaseZwaveJSServerError
from zwave_js_server.model.driver import Driver
from zwave_js_server.model.value import Value as ZwaveValue, get_value_id_str
from zwave_js_server.model.value import (
SetValueResult,
Value as ZwaveValue,
get_value_id_str,
)

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
Expand Down Expand Up @@ -70,9 +74,9 @@ def on_value_update(self) -> None:

async def _async_poll_value(self, value_or_id: str | ZwaveValue) -> None:
"""Poll a value."""
# We log an error instead of raising an exception because this service call occurs
# in a separate task and we don't want to raise the exception in that separate task
# because it is confusing to the user.
# We log an error instead of raising an exception because this service call
# occurs in a separate task and we don't want to raise the exception in that
# separate task because it is confusing to the user.
try:
await self.info.node.async_poll_value(value_or_id)
except BaseZwaveJSServerError as err:
Expand Down Expand Up @@ -312,7 +316,7 @@ async def _async_set_value(
new_value: Any,
options: dict | None = None,
wait_for_result: bool | None = None,
) -> bool | None:
) -> SetValueResult | None:
"""Set value on node."""
try:
return await self.info.node.async_set_value(
Expand Down
9 changes: 5 additions & 4 deletions homeassistant/components/zwave_js/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ def async_get_node_from_entity_id(
entity_entry = ent_reg.async_get(entity_id)

if entity_entry is None or entity_entry.platform != DOMAIN:
raise ValueError(f"Entity {entity_id} is not a valid {DOMAIN} entity.")
raise ValueError(f"Entity {entity_id} is not a valid {DOMAIN} entity")

# Assert for mypy, safe because we know that zwave_js entities are always
# tied to a device
Expand Down Expand Up @@ -414,9 +414,7 @@ def copy_available_params(
)


def get_value_state_schema(
value: ZwaveValue,
) -> vol.Schema | None:
def get_value_state_schema(value: ZwaveValue) -> vol.Schema | None:
"""Return device automation schema for a config entry."""
if isinstance(value, ConfigurationValue):
min_ = value.metadata.min
Expand All @@ -427,6 +425,9 @@ def get_value_state_schema(
):
return vol.All(vol.Coerce(int), vol.Range(min=min_, max=max_))

if value.configuration_value_type == ConfigurationValueType.BOOLEAN:
return vol.Coerce(bool)

if value.configuration_value_type == ConfigurationValueType.ENUMERATED:
return vol.In({int(k): v for k, v in value.metadata.states.items()})

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/zwave_js/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"iot_class": "local_push",
"loggers": ["zwave_js_server"],
"quality_scale": "platinum",
"requirements": ["pyserial==3.5", "zwave-js-server-python==0.49.0"],
"requirements": ["pyserial==3.5", "zwave-js-server-python==0.50.1"],
"usb": [
{
"vid": "0658",
Expand Down
30 changes: 14 additions & 16 deletions homeassistant/components/zwave_js/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import voluptuous as vol
from zwave_js_server.client import Client as ZwaveClient
from zwave_js_server.const import CommandClass, CommandStatus
from zwave_js_server.const import SET_VALUE_SUCCESS, CommandClass, CommandStatus
from zwave_js_server.exceptions import FailedZWaveCommand, SetValueFailed
from zwave_js_server.model.endpoint import Endpoint
from zwave_js_server.model.node import Node as ZwaveNode
Expand Down Expand Up @@ -39,12 +39,6 @@

_LOGGER = logging.getLogger(__name__)

SET_VALUE_FAILED_EXC = SetValueFailed(
"Unable to set value, refer to "
"https://zwave-js.github.io/node-zwave-js/#/api/node?id=setvalue for "
"possible reasons"
)


def parameter_name_does_not_need_bitmask(
val: dict[str, int | str | list[str]]
Expand Down Expand Up @@ -538,16 +532,20 @@ async def async_set_value(self, service: ServiceCall) -> None:
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_error_list: list[SetValueFailed] = []
for node_, result in get_valid_responses_from_results(nodes_list, results):
if result and result.status not in SET_VALUE_SUCCESS:
# If we failed to set a value, add node to exception list
set_value_failed_nodes_list.append(node_)
set_value_failed_error_list.append(
SetValueFailed(f"{result.status} {result.message}")
)

# Add the SetValueFailed exception to the results and the nodes to the node
# list. No-op if there are no SetValueFailed exceptions
# Add the exception to the results and the nodes to the node list. No-op if
# no set value commands failed
raise_exceptions_from_results(
(*nodes_list, *set_value_failed_nodes_list),
(*results, *([SET_VALUE_FAILED_EXC] * len(set_value_failed_nodes_list))),
(*results, *set_value_failed_error_list),
)

async def async_multicast_set_value(self, service: ServiceCall) -> None:
Expand Down Expand Up @@ -611,7 +609,7 @@ async def async_multicast_set_value(self, service: ServiceCall) -> None:
new_value = str(new_value)

try:
success = await async_multicast_set_value(
result = await async_multicast_set_value(
client=client,
new_value=new_value,
value_data=value,
Expand All @@ -621,10 +619,10 @@ async def async_multicast_set_value(self, service: ServiceCall) -> None:
except FailedZWaveCommand as err:
raise HomeAssistantError("Unable to set value via multicast") from err

if success is False:
if result.status not in SET_VALUE_SUCCESS:
raise HomeAssistantError(
"Unable to set value via multicast"
) from SetValueFailed
) from SetValueFailed(f"{result.status} {result.message}")

async def async_ping(self, service: ServiceCall) -> None:
"""Ping node(s)."""
Expand Down
Loading