Skip to content

Commit

Permalink
Bump zwave-js-server-python to 0.50.1 (#94760)
Browse files Browse the repository at this point in the history
* Bump zwave-js-server-python to 0.50.0

* handle additional upstream changes

* Additional changes

* fix tests

* Convert two similar functions to be one function

* Fix docstring

* Remove conditional pydantic import

* Revert scope change

* Bump zwave-js-server-python

* Set default return value for command

* Remove line breaks

* Add coverage
  • Loading branch information
raman325 authored Aug 10, 2023
1 parent 2841cbb commit 5d3d66e
Show file tree
Hide file tree
Showing 28 changed files with 171 additions and 128 deletions.
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
):
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

0 comments on commit 5d3d66e

Please sign in to comment.