From 5d3d66e47d066c74d596a326631165dea8411081 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 10 Aug 2023 01:28:08 -0400 Subject: [PATCH] Bump zwave-js-server-python to 0.50.1 (#94760) * 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 --- homeassistant/components/zwave_js/__init__.py | 6 +-- homeassistant/components/zwave_js/api.py | 1 + homeassistant/components/zwave_js/climate.py | 5 +- .../components/zwave_js/device_action.py | 5 +- .../zwave_js/device_automation_helpers.py | 23 -------- .../components/zwave_js/device_condition.py | 4 +- .../components/zwave_js/diagnostics.py | 30 +++++++---- .../components/zwave_js/discovery.py | 53 ++++++++----------- homeassistant/components/zwave_js/entity.py | 14 +++-- homeassistant/components/zwave_js/helpers.py | 9 ++-- .../components/zwave_js/manifest.json | 2 +- homeassistant/components/zwave_js/services.py | 30 +++++------ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/zwave_js/conftest.py | 3 ++ .../nortek_thermostat_removed_event.json | 2 +- tests/components/zwave_js/test_api.py | 8 +-- tests/components/zwave_js/test_climate.py | 3 ++ tests/components/zwave_js/test_cover.py | 4 ++ tests/components/zwave_js/test_diagnostics.py | 14 ++++- tests/components/zwave_js/test_discovery.py | 2 + tests/components/zwave_js/test_fan.py | 6 +++ tests/components/zwave_js/test_helpers.py | 14 +++++ tests/components/zwave_js/test_init.py | 24 ++++----- tests/components/zwave_js/test_services.py | 12 +++-- tests/components/zwave_js/test_switch.py | 2 + tests/components/zwave_js/test_trigger.py | 4 +- tests/components/zwave_js/test_update.py | 15 ++++-- 28 files changed, 171 insertions(+), 128 deletions(-) diff --git a/homeassistant/components/zwave_js/__init__.py b/homeassistant/components/zwave_js/__init__.py index 7ff351893b1bae..d477964d229849 100644 --- a/homeassistant/components/zwave_js/__init__.py +++ b/homeassistant/components/zwave_js/__init__.py @@ -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 @@ -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( diff --git a/homeassistant/components/zwave_js/api.py b/homeassistant/components/zwave_js/api.py index 5fc7da68e99c4f..6d2461df3e42e4 100644 --- a/homeassistant/components/zwave_js/api.py +++ b/homeassistant/components/zwave_js/api.py @@ -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( diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 327db05cb00a5e..d511a030fb1a21 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -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, diff --git a/homeassistant/components/zwave_js/device_action.py b/homeassistant/components/zwave_js/device_action.py index 04db33fdff6596..b9b0c3a6e86206 100644 --- a/homeassistant/components/zwave_js/device_action.py +++ b/homeassistant/components/zwave_js/device_action.py @@ -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, @@ -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})} diff --git a/homeassistant/components/zwave_js/device_automation_helpers.py b/homeassistant/components/zwave_js/device_automation_helpers.py index 7a60d491b3cb0f..2c375485e6b648 100644 --- a/homeassistant/components/zwave_js/device_automation_helpers.py +++ b/homeassistant/components/zwave_js/device_automation_helpers.py @@ -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 @@ -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_) diff --git a/homeassistant/components/zwave_js/device_condition.py b/homeassistant/components/zwave_js/device_condition.py index 3e089362d0b12c..26b4c637b6e837 100644 --- a/homeassistant/components/zwave_js/device_condition.py +++ b/homeassistant/components/zwave_js/device_condition.py @@ -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, ) @@ -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})} diff --git a/homeassistant/components/zwave_js/diagnostics.py b/homeassistant/components/zwave_js/diagnostics.py index 2fe2b17fe1b939..afae214ab2bad2 100644 --- a/homeassistant/components/zwave_js/diagnostics.py +++ b/homeassistant/components/zwave_js/diagnostics.py @@ -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 @@ -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 @@ -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]} @@ -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, diff --git a/homeassistant/components/zwave_js/discovery.py b/homeassistant/components/zwave_js/discovery.py index 9569ba97167e73..c879cc1f5b459a 100644 --- a/homeassistant/components/zwave_js/discovery.py +++ b/homeassistant/components/zwave_js/discovery.py @@ -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( @@ -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, diff --git a/homeassistant/components/zwave_js/entity.py b/homeassistant/components/zwave_js/entity.py index 2a0f5ff4e7233f..6cf2a402f3f8a2 100644 --- a/homeassistant/components/zwave_js/entity.py +++ b/homeassistant/components/zwave_js/entity.py @@ -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 @@ -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: @@ -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( diff --git a/homeassistant/components/zwave_js/helpers.py b/homeassistant/components/zwave_js/helpers.py index 6c54a464837238..adce141f91ced9 100644 --- a/homeassistant/components/zwave_js/helpers.py +++ b/homeassistant/components/zwave_js/helpers.py @@ -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 @@ -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 @@ -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()}) diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index b163ace1d24f01..43dddd08a1acd5 100644 --- a/homeassistant/components/zwave_js/manifest.json +++ b/homeassistant/components/zwave_js/manifest.json @@ -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", diff --git a/homeassistant/components/zwave_js/services.py b/homeassistant/components/zwave_js/services.py index 133cb40740558c..44ef3a2269cec2 100644 --- a/homeassistant/components/zwave_js/services.py +++ b/homeassistant/components/zwave_js/services.py @@ -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 @@ -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]] @@ -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: @@ -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, @@ -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).""" diff --git a/requirements_all.txt b/requirements_all.txt index f3daac4ff14aa7..be5cbbfdf6f0eb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2785,7 +2785,7 @@ zigpy==0.56.4 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.49.0 +zwave-js-server-python==0.50.1 # homeassistant.components.zwave_me zwave-me-ws==0.4.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f0dae95b7c1318..8eb00604a77049 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -2049,7 +2049,7 @@ zigpy-znp==0.11.4 zigpy==0.56.4 # homeassistant.components.zwave_js -zwave-js-server-python==0.49.0 +zwave-js-server-python==0.50.1 # homeassistant.components.zwave_me zwave-me-ws==0.4.3 diff --git a/tests/components/zwave_js/conftest.py b/tests/components/zwave_js/conftest.py index 0eb4ec775f932a..8bb55e3949b881 100644 --- a/tests/components/zwave_js/conftest.py +++ b/tests/components/zwave_js/conftest.py @@ -687,6 +687,9 @@ async def disconnect(): client.version = VersionInfo.from_message(version_state) client.ws_server_url = "ws://test:3000/zjs" + client.async_send_command.return_value = { + "result": {"success": True, "status": 255} + } yield client diff --git a/tests/components/zwave_js/fixtures/nortek_thermostat_removed_event.json b/tests/components/zwave_js/fixtures/nortek_thermostat_removed_event.json index 8491e65c037ad0..e30e0297e7d7d8 100644 --- a/tests/components/zwave_js/fixtures/nortek_thermostat_removed_event.json +++ b/tests/components/zwave_js/fixtures/nortek_thermostat_removed_event.json @@ -270,5 +270,5 @@ } ] }, - "replaced": false + "reason": 0 } diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index ebdf21124357fe..5bafe9323625dd 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -276,14 +276,16 @@ async def test_subscribe_node_status( msg = await ws_client.receive_json() assert msg["success"] - node.data["ready"] = True + new_node_data = deepcopy(multisensor_6_state) + new_node_data["ready"] = True + event = Event( "ready", { "source": "node", "event": "ready", "nodeId": node.node_id, - "nodeState": node.data, + "nodeState": new_node_data, }, ) node.receive_event(event) @@ -1715,7 +1717,7 @@ async def test_remove_node( assert len(client.async_send_command.call_args_list) == 1 assert client.async_send_command.call_args[0][0] == { "command": "controller.begin_exclusion", - "strategy": 0, + "options": {"strategy": 0}, } # Test FailedZWaveCommand is caught diff --git a/tests/components/zwave_js/test_climate.py b/tests/components/zwave_js/test_climate.py index 23d34c131b8690..e9040dfd397e6c 100644 --- a/tests/components/zwave_js/test_climate.py +++ b/tests/components/zwave_js/test_climate.py @@ -731,6 +731,8 @@ async def test_thermostat_raise_repair_issue_and_warning_when_setting_dry_preset caplog: pytest.LogCaptureFixture, ) -> None: """Test raise of repair issue and warning when setting Dry preset.""" + client.async_send_command.return_value = {"result": {"status": 1}} + state = hass.states.get(CLIMATE_AIDOO_HVAC_UNIT_ENTITY) assert state @@ -765,6 +767,7 @@ async def test_thermostat_raise_repair_issue_and_warning_when_setting_fan_preset caplog: pytest.LogCaptureFixture, ) -> None: """Test raise of repair issue and warning when setting Fan preset.""" + client.async_send_command.return_value = {"result": {"status": 1}} state = hass.states.get(CLIMATE_AIDOO_HVAC_UNIT_ENTITY) assert state diff --git a/tests/components/zwave_js/test_cover.py b/tests/components/zwave_js/test_cover.py index 502f2413c992cc..e51b3751ac8e85 100644 --- a/tests/components/zwave_js/test_cover.py +++ b/tests/components/zwave_js/test_cover.py @@ -126,6 +126,7 @@ async def test_window_cover( assert args["value"] client.async_send_command.reset_mock() + # Test stop after opening await hass.services.async_call( DOMAIN, @@ -265,6 +266,7 @@ async def test_fibaro_fgr222_shutter_cover( assert args["value"] == 99 client.async_send_command.reset_mock() + # Test closing tilts await hass.services.async_call( DOMAIN, @@ -286,6 +288,7 @@ async def test_fibaro_fgr222_shutter_cover( assert args["value"] == 0 client.async_send_command.reset_mock() + # Test setting tilt position await hass.services.async_call( DOMAIN, @@ -365,6 +368,7 @@ async def test_aeotec_nano_shutter_cover( assert args["value"] client.async_send_command.reset_mock() + # Test stop after opening await hass.services.async_call( DOMAIN, diff --git a/tests/components/zwave_js/test_diagnostics.py b/tests/components/zwave_js/test_diagnostics.py index 4454e38e0d8c85..2510143695cfba 100644 --- a/tests/components/zwave_js/test_diagnostics.py +++ b/tests/components/zwave_js/test_diagnostics.py @@ -125,7 +125,13 @@ async def test_device_diagnostics( entity["entity_id"] == "test.unrelated_entity" for entity in diagnostics_data["entities"] ) - assert diagnostics_data["state"] == multisensor_6.data + assert diagnostics_data["state"] == { + **multisensor_6.data, + "values": {id: val.data for id, val in multisensor_6.values.items()}, + "endpoints": { + str(idx): endpoint.data for idx, endpoint in multisensor_6.endpoints.items() + }, + } async def test_device_diagnostics_error(hass: HomeAssistant, integration) -> None: @@ -230,7 +236,11 @@ def _find_ultraviolet_val(data: dict) -> dict: """Find ultraviolet property value in data.""" return next( val - for val in data["values"] + for val in ( + data["values"] + if isinstance(data["values"], list) + else data["values"].values() + ) if val["commandClass"] == CommandClass.SENSOR_MULTILEVEL and val["property"] == PROPERTY_ULTRAVIOLET ) diff --git a/tests/components/zwave_js/test_discovery.py b/tests/components/zwave_js/test_discovery.py index 1c4a69d32e39c6..99a46eaadf9137 100644 --- a/tests/components/zwave_js/test_discovery.py +++ b/tests/components/zwave_js/test_discovery.py @@ -171,6 +171,7 @@ async def test_zooz_zen72( state = hass.states.get(entity_id) assert state assert state.state == STATE_UNKNOWN + await hass.services.async_call( NUMBER_DOMAIN, SERVICE_SET_VALUE, @@ -256,6 +257,7 @@ async def test_indicator_test( state = hass.states.get(entity_id) assert state assert state.state == STATE_OFF + await hass.services.async_call( SWITCH_DOMAIN, SERVICE_TURN_ON, diff --git a/tests/components/zwave_js/test_fan.py b/tests/components/zwave_js/test_fan.py index 2b50870041391d..92141eec3ff0b7 100644 --- a/tests/components/zwave_js/test_fan.py +++ b/tests/components/zwave_js/test_fan.py @@ -231,6 +231,7 @@ async def test_configurable_speeds_fan( async def get_zwave_speed_from_percentage(percentage): """Set the fan to a particular percentage and get the resulting Zwave speed.""" client.async_send_command.reset_mock() + await hass.services.async_call( "fan", "turn_on", @@ -356,6 +357,7 @@ async def test_ge_12730_fan(hass: HomeAssistant, client, ge_12730, integration) async def get_zwave_speed_from_percentage(percentage): """Set the fan to a particular percentage and get the resulting Zwave speed.""" client.async_send_command.reset_mock() + await hass.services.async_call( "fan", "turn_on", @@ -448,6 +450,7 @@ async def test_inovelli_lzw36( async def get_zwave_speed_from_percentage(percentage): """Set the fan to a particular percentage and get the resulting Zwave speed.""" client.async_send_command.reset_mock() + await hass.services.async_call( "fan", "turn_on", @@ -518,6 +521,7 @@ async def get_percentage_from_zwave_speed(zwave_speed): assert state.attributes[ATTR_PERCENTAGE] is None client.async_send_command.reset_mock() + await hass.services.async_call( "fan", "turn_on", @@ -553,6 +557,7 @@ async def test_leviton_zw4sf_fan( async def get_zwave_speed_from_percentage(percentage): """Set the fan to a particular percentage and get the resulting Zwave speed.""" client.async_send_command.reset_mock() + await hass.services.async_call( "fan", "turn_on", @@ -951,6 +956,7 @@ async def test_honeywell_39358_fan( async def get_zwave_speed_from_percentage(percentage): """Set the fan to a particular percentage and get the resulting Zwave speed.""" client.async_send_command.reset_mock() + await hass.services.async_call( "fan", "turn_on", diff --git a/tests/components/zwave_js/test_helpers.py b/tests/components/zwave_js/test_helpers.py index aaa2907d30a3d2..e38873322ae24a 100644 --- a/tests/components/zwave_js/test_helpers.py +++ b/tests/components/zwave_js/test_helpers.py @@ -1,7 +1,10 @@ """Test the Z-Wave JS helpers module.""" +import voluptuous as vol + from homeassistant.components.zwave_js.helpers import ( async_get_node_status_sensor_entity_id, async_get_nodes_from_area_id, + get_value_state_schema, ) from homeassistant.core import HomeAssistant from homeassistant.helpers import area_registry as ar, device_registry as dr @@ -22,3 +25,14 @@ async def test_async_get_nodes_from_area_id(hass: HomeAssistant) -> None: area_reg = ar.async_get(hass) area = area_reg.async_create("test") assert not async_get_nodes_from_area_id(hass, area.id) + + +async def test_get_value_state_schema_boolean_config_value( + hass: HomeAssistant, client, aeon_smart_switch_6 +) -> None: + """Test get_value_state_schema for boolean config value.""" + schema_validator = get_value_state_schema( + aeon_smart_switch_6.values["102-112-0-255"] + ) + assert isinstance(schema_validator, vol.Coerce) + assert schema_validator.type == bool diff --git a/tests/components/zwave_js/test_init.py b/tests/components/zwave_js/test_init.py index 3ec1f113b3e086..c421e0434130dc 100644 --- a/tests/components/zwave_js/test_init.py +++ b/tests/components/zwave_js/test_init.py @@ -1005,7 +1005,7 @@ async def test_node_removed( event = { "source": "controller", "event": "node added", - "node": node.data, + "node": multisensor_6_state, "result": {}, } @@ -1014,7 +1014,7 @@ async def test_node_removed( old_device = dev_reg.async_get_device(identifiers={(DOMAIN, device_id)}) assert old_device.id - event = {"node": node, "replaced": False} + event = {"node": node, "reason": 0} client.driver.controller.emit("node removed", event) await hass.async_block_till_done() @@ -1047,14 +1047,14 @@ async def test_replace_same_node( assert hass.states.get(AIR_TEMPERATURE_SENSOR) - # A replace node event has the extra field "replaced" set to True + # A replace node event has the extra field "reason" # to distinguish it from an exclusion event = Event( type="node removed", data={ "source": "controller", "event": "node removed", - "replaced": True, + "reason": 3, "node": multisensor_6_state, }, ) @@ -1139,8 +1139,8 @@ async def test_replace_different_node( """Test when a node is replaced with a different node.""" dev_reg = dr.async_get(hass) node_id = multisensor_6.node_id - hank_binary_switch_state = deepcopy(hank_binary_switch_state) - hank_binary_switch_state["nodeId"] = node_id + state = deepcopy(hank_binary_switch_state) + state["nodeId"] = node_id device_id = f"{client.driver.controller.home_id}-{node_id}" multisensor_6_device_id = ( @@ -1148,9 +1148,9 @@ async def test_replace_different_node( f"{multisensor_6.product_type}:{multisensor_6.product_id}" ) hank_device_id = ( - f"{device_id}-{hank_binary_switch_state['manufacturerId']}:" - f"{hank_binary_switch_state['productType']}:" - f"{hank_binary_switch_state['productId']}" + f"{device_id}-{state['manufacturerId']}:" + f"{state['productType']}:" + f"{state['productId']}" ) device = dev_reg.async_get_device(identifiers={(DOMAIN, device_id)}) @@ -1171,7 +1171,7 @@ async def test_replace_different_node( data={ "source": "controller", "event": "node removed", - "replaced": True, + "reason": 3, "node": multisensor_6_state, }, ) @@ -1228,7 +1228,7 @@ async def test_replace_different_node( "source": "node", "event": "ready", "nodeId": node_id, - "nodeState": hank_binary_switch_state, + "nodeState": state, }, ) client.driver.receive_event(event) @@ -1345,7 +1345,7 @@ async def test_disabled_node_status_entity_on_node_replaced( data={ "source": "controller", "event": "node removed", - "replaced": True, + "reason": 3, "node": zp3111_state, }, ) diff --git a/tests/components/zwave_js/test_services.py b/tests/components/zwave_js/test_services.py index 54638358fe7d1c..ccbe956fbe57f3 100644 --- a/tests/components/zwave_js/test_services.py +++ b/tests/components/zwave_js/test_services.py @@ -414,6 +414,7 @@ async def test_bulk_set_config_parameters( identifiers={get_device_id(client.driver, multisensor_6)} ) assert device + # Test setting config parameter by property and property_key await hass.services.async_call( DOMAIN, @@ -875,7 +876,9 @@ async def test_set_value( client.async_send_command.reset_mock() # Test that when a command fails we raise an exception - client.async_send_command.return_value = {"success": False} + client.async_send_command.return_value = { + "result": {"status": 2, "message": "test"} + } with pytest.raises(HomeAssistantError): await hass.services.async_call( @@ -924,7 +927,6 @@ async def test_set_value_string( hass: HomeAssistant, client, climate_danfoss_lc_13, lock_schlage_be469, integration ) -> None: """Test set_value service converts number to string when needed.""" - client.async_send_command.return_value = {"success": True} # Test that number gets converted to a string when needed await hass.services.async_call( @@ -1240,7 +1242,9 @@ async def test_multicast_set_value( ) # Test that when a command is unsuccessful we raise an exception - client.async_send_command.return_value = {"success": False} + client.async_send_command.return_value = { + "result": {"status": 2, "message": "test"} + } with pytest.raises(HomeAssistantError): await hass.services.async_call( @@ -1381,7 +1385,7 @@ async def test_multicast_set_value_string( integration, ) -> None: """Test multicast_set_value service converts number to string when needed.""" - client.async_send_command.return_value = {"success": True} + client.async_send_command.return_value = {"result": {"status": 255}} # Test that number gets converted to a string when needed await hass.services.async_call( diff --git a/tests/components/zwave_js/test_switch.py b/tests/components/zwave_js/test_switch.py index ebf7d9f441fa1e..fd5c626bdd2276 100644 --- a/tests/components/zwave_js/test_switch.py +++ b/tests/components/zwave_js/test_switch.py @@ -63,6 +63,8 @@ async def test_switch( state = hass.states.get(SWITCH_ENTITY) assert state.state == "on" + client.async_send_command.reset_mock() + # Test turning off await hass.services.async_call( "switch", "turn_off", {"entity_id": SWITCH_ENTITY}, blocking=True diff --git a/tests/components/zwave_js/test_trigger.py b/tests/components/zwave_js/test_trigger.py index 501ad13cbaa4db..25553489b4ed84 100644 --- a/tests/components/zwave_js/test_trigger.py +++ b/tests/components/zwave_js/test_trigger.py @@ -1158,7 +1158,7 @@ async def test_server_reconnect_event( data={ "source": "controller", "event": "node removed", - "replaced": False, + "reason": 0, "node": lock_schlage_be469_state, }, ) @@ -1238,7 +1238,7 @@ async def test_server_reconnect_value_updated( data={ "source": "controller", "event": "node removed", - "replaced": False, + "reason": 0, "node": lock_schlage_be469_state, }, ) diff --git a/tests/components/zwave_js/test_update.py b/tests/components/zwave_js/test_update.py index dcd71789e840bb..5234460bb5188c 100644 --- a/tests/components/zwave_js/test_update.py +++ b/tests/components/zwave_js/test_update.py @@ -264,6 +264,8 @@ async def test_update_entity_ha_not_running( """Test update occurs only after HA is running.""" await hass.async_stop() + client.async_send_command.return_value = {"updates": []} + entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"}) entry.add_to_hass(hass) await hass.config_entries.async_setup(entry.entry_id) @@ -341,7 +343,9 @@ async def test_update_entity_progress( assert attrs[ATTR_LATEST_VERSION] == "11.2.4" client.async_send_command.reset_mock() - client.async_send_command.return_value = {"success": False} + client.async_send_command.return_value = { + "result": {"status": 2, "success": False, "reInterview": False} + } # Test successful install call without a version install_task = hass.async_create_task( @@ -437,7 +441,9 @@ async def test_update_entity_install_failed( assert attrs[ATTR_LATEST_VERSION] == "11.2.4" client.async_send_command.reset_mock() - client.async_send_command.return_value = {"success": False} + client.async_send_command.return_value = { + "result": {"status": 2, "success": False, "reInterview": False} + } # Test install call - we expect it to finish fail install_task = hass.async_create_task( @@ -577,6 +583,7 @@ async def test_update_entity_delay( ) -> None: """Test update occurs on a delay after HA starts.""" client.async_send_command.reset_mock() + client.async_send_command.return_value = {"updates": []} await hass.async_stop() entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"}) @@ -710,7 +717,9 @@ async def test_update_entity_full_restore_data_update_available( assert state.attributes[ATTR_SKIPPED_VERSION] is None assert state.attributes[ATTR_LATEST_VERSION] == "11.2.4" - client.async_send_command.return_value = {"success": True} + client.async_send_command.return_value = { + "result": {"status": 255, "success": True, "reInterview": False} + } # Test successful install call without a version install_task = hass.async_create_task(