From 14bb90097117d5df3fd385b2c9416f82337704cc Mon Sep 17 00:00:00 2001 From: raman325 <7243222+raman325@users.noreply.github.com> Date: Tue, 23 Mar 2021 00:10:02 -0400 Subject: [PATCH 01/15] Add utility function to bulk set partial config parameters --- test/util/test_node.py | 68 +++++++++++- zwave_js_server/model/node.py | 14 +-- zwave_js_server/util/lock.py | 2 +- zwave_js_server/util/node.py | 199 +++++++++++++++++++++++++--------- 4 files changed, 224 insertions(+), 59 deletions(-) diff --git a/test/util/test_node.py b/test/util/test_node.py index c25e815b8..f14a0aa60 100644 --- a/test/util/test_node.py +++ b/test/util/test_node.py @@ -1,10 +1,14 @@ """Test node utility functions.""" +from zwave_js_server.const import CommandClass import pytest from zwave_js_server.exceptions import InvalidNewValue, NotFoundError from zwave_js_server.model.node import Node from zwave_js_server.model.value import ConfigurationValue -from zwave_js_server.util.node import async_set_config_parameter +from zwave_js_server.util.node import ( + async_bulk_set_partial_config_parameters, + async_set_config_parameter, +) async def test_configuration_parameter_values( @@ -101,3 +105,65 @@ async def test_configuration_parameter_values( "value": 4, "messageId": uuid4, } + + +async def test_bulk_set_partial_config_parameters(multisensor_6, uuid4, mock_command): + """Test bulk setting partial config parameters.""" + node: Node = multisensor_6 + ack_commands = mock_command( + {"command": "node.set_value", "nodeId": node.node_id}, + {"success": True}, + ) + await async_bulk_set_partial_config_parameters(node, 241, 101) + assert len(ack_commands) == 1 + assert ack_commands[0] == { + "command": "node.set_value", + "nodeId": node.node_id, + "valueId": { + "commandClass": CommandClass.CONFIGURATION.value, + "property": 101, + }, + "value": 241, + "messageId": uuid4, + } + + await async_bulk_set_partial_config_parameters( + node, {128: 1, 64: 1, 32: 1, 16: 1, 1: 1}, 101 + ) + assert len(ack_commands) == 2 + assert ack_commands[1] == { + "command": "node.set_value", + "nodeId": node.node_id, + "valueId": { + "commandClass": CommandClass.CONFIGURATION.value, + "property": 101, + }, + "value": 241, + "messageId": uuid4, + } + + # Only set some values so we use cached values for the rest + await async_bulk_set_partial_config_parameters( + node, {64: 1, 32: 1, 16: 1, 1: 1}, 101 + ) + assert len(ack_commands) == 3 + assert ack_commands[2] == { + "command": "node.set_value", + "nodeId": node.node_id, + "valueId": { + "commandClass": CommandClass.CONFIGURATION.value, + "property": 101, + }, + "value": 241, + "messageId": uuid4, + } + + # Use an invalid property + with pytest.raises(NotFoundError): + await async_bulk_set_partial_config_parameters(node, 99, 999) + + # use an invalid bitmask + with pytest.raises(NotFoundError): + await async_bulk_set_partial_config_parameters( + node, {128: 1, 64: 1, 32: 1, 16: 1, 2: 1}, 101 + ) diff --git a/zwave_js_server/model/node.py b/zwave_js_server/model/node.py index 1c2a2cae5..4fe10330b 100644 --- a/zwave_js_server/model/node.py +++ b/zwave_js_server/model/node.py @@ -325,7 +325,7 @@ def receive_event(self, event: Event) -> None: self.emit(event.type, event.data) - async def _async_send_command( + async def async_send_command( self, cmd: str, require_schema: Optional[int] = None, @@ -367,7 +367,7 @@ async def async_set_value( raise UnwriteableValue # the value object needs to be send to the server - result = await self._async_send_command( + result = await self.async_send_command( "set_value", valueId=val.data, value=new_value, @@ -381,11 +381,11 @@ async def async_set_value( async def async_refresh_info(self) -> None: """Send refreshInfo command to Node.""" - await self._async_send_command("refresh_info", wait_for_result=False) + await self.async_send_command("refresh_info", wait_for_result=False) async def async_get_defined_value_ids(self) -> List[Value]: """Send getDefinedValueIDs command to Node.""" - data = await self._async_send_command( + data = await self.async_send_command( "get_defined_value_ids", wait_for_result=True ) @@ -403,21 +403,21 @@ async def async_get_value_metadata(self, val: Union[Value, str]) -> ValueMetadat if not isinstance(val, Value): val = self.values[val] # the value object needs to be send to the server - data = await self._async_send_command( + data = await self.async_send_command( "get_value_metadata", valueId=val.data, wait_for_result=True ) return ValueMetadata(cast(MetaDataType, data)) async def async_abort_firmware_update(self) -> None: """Send abortFirmwareUpdate command to Node.""" - await self._async_send_command("abort_firmware_update", wait_for_result=False) + await self.async_send_command("abort_firmware_update", wait_for_result=False) async def async_poll_value(self, val: Union[Value, str]) -> None: """Send pollValue command to Node for given value (or value_id).""" # a value may be specified as value_id or the value itself if not isinstance(val, Value): val = self.values[val] - await self._async_send_command("poll_value", valueId=val.data, require_schema=1) + await self.async_send_command("poll_value", valueId=val.data, require_schema=1) def handle_wake_up(self, event: Event) -> None: """Process a node wake up event.""" diff --git a/zwave_js_server/util/lock.py b/zwave_js_server/util/lock.py index 5025385b2..958c99724 100644 --- a/zwave_js_server/util/lock.py +++ b/zwave_js_server/util/lock.py @@ -13,7 +13,7 @@ ) from ..exceptions import NotFoundError from ..model.node import Node -from ..model.value import get_value_id, Value +from ..model.value import Value, get_value_id def get_code_slot_value(node: Node, code_slot: int, property_name: str) -> Value: diff --git a/zwave_js_server/util/node.py b/zwave_js_server/util/node.py index f84ea2348..192c5f02b 100644 --- a/zwave_js_server/util/node.py +++ b/zwave_js_server/util/node.py @@ -5,54 +5,13 @@ from ..const import CommandClass, ConfigurationValueType from ..exceptions import InvalidNewValue, NotFoundError, SetValueFailed from ..model.node import Node -from ..model.value import ConfigurationValue, get_value_id +from ..model.value import ConfigurationValue, Value, get_value_id -async def async_set_config_parameter( - node: Node, - new_value: Union[int, str], - property_or_property_name: Union[int, str], - property_key: Optional[Union[int, str]] = None, -) -> ConfigurationValue: - """ - Set a value for a config parameter on this node. - - new_value and property_ can be provided as labels, so we need to resolve them to - the appropriate key - """ - config_values = node.get_configuration_values() - - # If a property name is provided, we have to search for the correct value since - # we can't use value ID - if isinstance(property_or_property_name, str): - try: - zwave_value = next( - config_value - for config_value in config_values.values() - if config_value.property_name == property_or_property_name - ) - except StopIteration: - raise NotFoundError( - "Configuration parameter with parameter name " - f"{property_or_property_name} could not be found" - ) from None - else: - value_id = get_value_id( - node, - CommandClass.CONFIGURATION, - property_or_property_name, - endpoint=0, - property_key=property_key, - ) - - try: - zwave_value = config_values[value_id] - except KeyError: - raise NotFoundError( - f"Configuration parameter with value ID {value_id} could not be " - "found" - ) from None - +def _validate_and_transform_new_value( + zwave_value: Value, new_value: Union[int, str] +) -> int: + """Validate a new value and return the integer value to set.""" # Validate that new value for enumerated configuration parameter is a valid state # key or label if ( @@ -64,8 +23,8 @@ async def async_set_config_parameter( ] ): raise InvalidNewValue( - "Must provide a value that represents a valid state key or label from " - f"{json.dumps(zwave_value.metadata.states)}" + f"Must provide a value for {zwave_value.value_id} that represents a valid " + f"state key or label from {json.dumps(zwave_value.metadata.states)}" ) # Validate that new value for manual entry configuration parameter is a valid state @@ -76,8 +35,8 @@ async def async_set_config_parameter( and str(new_value) not in zwave_value.metadata.states.values() ): raise InvalidNewValue( - "Must provide a value that represents a valid state from " - f"{list(zwave_value.metadata.states.values())}" + f"Must provide a value for {zwave_value.value_id} that represents a valid " + f"state from {list(zwave_value.metadata.states.values())}" ) # If needed, convert a state label to its key. We know the state exists because @@ -115,6 +74,146 @@ async def async_set_config_parameter( f"Must provide a value within the target range ({', '.join(bounds)})" ) + return new_value + + +def partial_param_bit_shift(property_key: int) -> int: + """Get the number of bits to shift the value for a given property key.""" + # We can get the binary representation of the property key, reverse it, + # and find the first 1 + return bin(property_key)[::-1].index("1") + + +async def async_bulk_set_partial_config_parameters( + node: Node, + new_value: Union[int, dict[str, Union[int, str]]], + property_: int, +) -> None: + """Bulk set partial configuration values on this node.""" + config_values = node.get_configuration_values() + property_values = [ + value for value in config_values.values() if value.property_ == property_ + ] + if len(property_values) == 0: + raise NotFoundError( + f"Configuration parameter {property_} for node {node.node_id} not found" + ) + + # If new_value is a dictionary, we need to calculate the full value to send + if isinstance(new_value, dict): + temp_value = 0 + # For each property key provided, we multiply the partial value by the multiplication factor + # and send them to get the number to send + for property_key, partial_value in new_value.items(): + value_id = get_value_id( + node, CommandClass.CONFIGURATION, property_, property_key=property_key + ) + if value_id not in node.values: + raise NotFoundError( + f"Bitmask {property_key} ({hex(property_key)}) not found for " + f"parameter {property_}" + ) + zwave_value = node.values[value_id] + partial_value = _validate_and_transform_new_value( + zwave_value, partial_value + ) + temp_value += partial_value << partial_param_bit_shift(property_key) + + # To set partial parameters in bulk, we also have to include cached values for + # property keys that haven't been specified + for property_value in property_values: + if property_value.property_key not in new_value: + temp_value += property_value.value << partial_param_bit_shift( + property_value.property_key + ) + + new_value = temp_value + else: + property_keys = sorted( + [value.property_key for value in property_values], reverse=True + ) + temp_value = new_value + + # Break down the bulk value into partial values and validate them against each partial parameter's metadata + for property_key in property_keys: + multiplication_factor = 2 ** partial_param_bit_shift(property_key) + partial_value = int(temp_value / multiplication_factor) + temp_value = temp_value % multiplication_factor + zwave_value = node.values[ + get_value_id( + node, + CommandClass.CONFIGURATION, + property_, + property_key=property_key, + ) + ] + _validate_and_transform_new_value(zwave_value, partial_value) + + if ( + await node.async_send_command( + "set_value", + valueId={ + "commandClass": CommandClass.CONFIGURATION.value, + "property": property_, + }, + value=new_value, + ) + is False + ): + raise SetValueFailed( + "Unable to set value, refer to " + "https://zwave-js.github.io/node-zwave-js/#/api/node?id=setvalue for " + "possible reasons" + ) + + +async def async_set_config_parameter( + node: Node, + new_value: Union[int, str], + property_or_property_name: Union[int, str], + property_key: Optional[Union[int, str]] = None, +) -> ConfigurationValue: + """ + Set a value for a config parameter on this node. + + new_value and property_ can be provided as labels, so we need to resolve them to + the appropriate key + """ + config_values = node.get_configuration_values() + + # If a property name is provided, we have to search for the correct value since + # we can't use value ID + if isinstance(property_or_property_name, str): + try: + zwave_value = next( + config_value + for config_value in config_values.values() + if config_value.property_name == property_or_property_name + ) + value_id = zwave_value.value_id + except StopIteration: + raise NotFoundError( + "Configuration parameter with parameter name " + f"{property_or_property_name} could not be found" + ) from None + else: + value_id = get_value_id( + node, + CommandClass.CONFIGURATION, + property_or_property_name, + endpoint=0, + property_key=property_key, + ) + + if value_id not in config_values: + raise NotFoundError( + f"Configuration parameter with value ID {value_id} could not be " + "found" + ) from None + zwave_value = config_values[value_id] + + new_value = _validate_and_transform_new_value(zwave_value, new_value) + # Finally attempt to set the value and return the Value object if successful if await node.async_set_value(zwave_value, new_value) is False: raise SetValueFailed( From 5f17f864ded3ab3e6d019c7ca9c21fb962348c56 Mon Sep 17 00:00:00 2001 From: raman325 <7243222+raman325@users.noreply.github.com> Date: Tue, 23 Mar 2021 00:40:54 -0400 Subject: [PATCH 02/15] fix lint errors --- zwave_js_server/util/node.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/zwave_js_server/util/node.py b/zwave_js_server/util/node.py index 192c5f02b..7c49db6d8 100644 --- a/zwave_js_server/util/node.py +++ b/zwave_js_server/util/node.py @@ -1,6 +1,6 @@ """Utility functions for Z-Wave JS nodes.""" import json -from typing import Optional, Union +from typing import Dict, Optional, Union from ..const import CommandClass, ConfigurationValueType from ..exceptions import InvalidNewValue, NotFoundError, SetValueFailed @@ -86,7 +86,7 @@ def partial_param_bit_shift(property_key: int) -> int: async def async_bulk_set_partial_config_parameters( node: Node, - new_value: Union[int, dict[str, Union[int, str]]], + new_value: Union[int, Dict[str, Union[int, str]]], property_: int, ) -> None: """Bulk set partial configuration values on this node.""" @@ -134,7 +134,8 @@ async def async_bulk_set_partial_config_parameters( ) temp_value = new_value - # Break down the bulk value into partial values and validate them against each partial parameter's metadata + # Break down the bulk value into partial values and validate them against + # each partial parameter's metadata for property_key in property_keys: multiplication_factor = 2 ** partial_param_bit_shift(property_key) partial_value = int(temp_value / multiplication_factor) From bc3419bac0f48c9c061226d89ffbe130c60280c7 Mon Sep 17 00:00:00 2001 From: raman325 <7243222+raman325@users.noreply.github.com> Date: Tue, 23 Mar 2021 00:47:41 -0400 Subject: [PATCH 03/15] fix typing --- zwave_js_server/util/node.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/zwave_js_server/util/node.py b/zwave_js_server/util/node.py index 7c49db6d8..936ff10d4 100644 --- a/zwave_js_server/util/node.py +++ b/zwave_js_server/util/node.py @@ -1,15 +1,15 @@ """Utility functions for Z-Wave JS nodes.""" import json -from typing import Dict, Optional, Union +from typing import Dict, Optional, Union, cast from ..const import CommandClass, ConfigurationValueType from ..exceptions import InvalidNewValue, NotFoundError, SetValueFailed from ..model.node import Node -from ..model.value import ConfigurationValue, Value, get_value_id +from ..model.value import ConfigurationValue, get_value_id def _validate_and_transform_new_value( - zwave_value: Value, new_value: Union[int, str] + zwave_value: ConfigurationValue, new_value: Union[int, str] ) -> int: """Validate a new value and return the integer value to set.""" # Validate that new value for enumerated configuration parameter is a valid state @@ -86,7 +86,7 @@ def partial_param_bit_shift(property_key: int) -> int: async def async_bulk_set_partial_config_parameters( node: Node, - new_value: Union[int, Dict[str, Union[int, str]]], + new_value: Union[int, Dict[int, Union[int, str]]], property_: int, ) -> None: """Bulk set partial configuration values on this node.""" @@ -99,6 +99,12 @@ async def async_bulk_set_partial_config_parameters( f"Configuration parameter {property_} for node {node.node_id} not found" ) + if len(property_values) == 1: + raise TypeError( + f"Configuration parameter {property_} for node {node.node_id} does not " + "have partials" + ) + # If new_value is a dictionary, we need to calculate the full value to send if isinstance(new_value, dict): temp_value = 0 @@ -113,7 +119,7 @@ async def async_bulk_set_partial_config_parameters( f"Bitmask {property_key} ({hex(property_key)}) not found for " f"parameter {property_}" ) - zwave_value = node.values[value_id] + zwave_value = cast(ConfigurationValue, node.values[value_id]) partial_value = _validate_and_transform_new_value( zwave_value, partial_value ) @@ -123,14 +129,14 @@ async def async_bulk_set_partial_config_parameters( # property keys that haven't been specified for property_value in property_values: if property_value.property_key not in new_value: - temp_value += property_value.value << partial_param_bit_shift( - property_value.property_key + temp_value += cast(int, property_value.value) << partial_param_bit_shift( + cast(int, property_value.property_key) ) new_value = temp_value else: property_keys = sorted( - [value.property_key for value in property_values], reverse=True + [cast(int, value.property_key) for value in property_values], reverse=True ) temp_value = new_value @@ -140,14 +146,14 @@ async def async_bulk_set_partial_config_parameters( multiplication_factor = 2 ** partial_param_bit_shift(property_key) partial_value = int(temp_value / multiplication_factor) temp_value = temp_value % multiplication_factor - zwave_value = node.values[ + zwave_value = cast(ConfigurationValue, node.values[ get_value_id( node, CommandClass.CONFIGURATION, property_, property_key=property_key, ) - ] + ]) _validate_and_transform_new_value(zwave_value, partial_value) if ( From 305b041b4f31847a6f255ac83bb64efd381d47e5 Mon Sep 17 00:00:00 2001 From: raman325 <7243222+raman325@users.noreply.github.com> Date: Tue, 23 Mar 2021 00:49:41 -0400 Subject: [PATCH 04/15] black --- zwave_js_server/util/node.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/zwave_js_server/util/node.py b/zwave_js_server/util/node.py index 936ff10d4..f0c6e0ccb 100644 --- a/zwave_js_server/util/node.py +++ b/zwave_js_server/util/node.py @@ -129,9 +129,9 @@ async def async_bulk_set_partial_config_parameters( # property keys that haven't been specified for property_value in property_values: if property_value.property_key not in new_value: - temp_value += cast(int, property_value.value) << partial_param_bit_shift( - cast(int, property_value.property_key) - ) + temp_value += cast( + int, property_value.value + ) << partial_param_bit_shift(cast(int, property_value.property_key)) new_value = temp_value else: @@ -146,14 +146,17 @@ async def async_bulk_set_partial_config_parameters( multiplication_factor = 2 ** partial_param_bit_shift(property_key) partial_value = int(temp_value / multiplication_factor) temp_value = temp_value % multiplication_factor - zwave_value = cast(ConfigurationValue, node.values[ - get_value_id( - node, - CommandClass.CONFIGURATION, - property_, - property_key=property_key, - ) - ]) + zwave_value = cast( + ConfigurationValue, + node.values[ + get_value_id( + node, + CommandClass.CONFIGURATION, + property_, + property_key=property_key, + ) + ], + ) _validate_and_transform_new_value(zwave_value, partial_value) if ( From e5f6791b92367da97d02cfc77d781bac94b993a7 Mon Sep 17 00:00:00 2001 From: raman325 <7243222+raman325@users.noreply.github.com> Date: Tue, 23 Mar 2021 00:51:50 -0400 Subject: [PATCH 05/15] fix import sort --- test/util/test_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/util/test_node.py b/test/util/test_node.py index f14a0aa60..a655dca09 100644 --- a/test/util/test_node.py +++ b/test/util/test_node.py @@ -1,7 +1,7 @@ """Test node utility functions.""" -from zwave_js_server.const import CommandClass import pytest +from zwave_js_server.const import CommandClass from zwave_js_server.exceptions import InvalidNewValue, NotFoundError from zwave_js_server.model.node import Node from zwave_js_server.model.value import ConfigurationValue From 191964b616cf26ec398f758747a6cae53361a6f1 Mon Sep 17 00:00:00 2001 From: raman325 <7243222+raman325@users.noreply.github.com> Date: Tue, 23 Mar 2021 01:06:38 -0400 Subject: [PATCH 06/15] add additional comments --- zwave_js_server/model/value.py | 6 ++++++ zwave_js_server/util/node.py | 15 +++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/zwave_js_server/model/value.py b/zwave_js_server/model/value.py index 38d7cb914..63d547987 100644 --- a/zwave_js_server/model/value.py +++ b/zwave_js_server/model/value.py @@ -23,6 +23,7 @@ class MetaDataType(TypedDict, total=False): states: Dict[int, str] ccSpecific: Dict[str, Any] allowManualEntry: bool + valueSize: int class ValueDataType(TypedDict, total=False): @@ -141,6 +142,11 @@ def allow_manual_entry(self) -> Optional[bool]: """Return allowManualEntry.""" return self.data.get("allowManualEntry") + @property + def value_size(self) -> Optional[int]: + """Return valueSize.""" + return self.data.get("valueSize") + def update(self, data: MetaDataType) -> None: """Update data.""" self.data.update(data) diff --git a/zwave_js_server/util/node.py b/zwave_js_server/util/node.py index f0c6e0ccb..7e047b04d 100644 --- a/zwave_js_server/util/node.py +++ b/zwave_js_server/util/node.py @@ -94,11 +94,15 @@ async def async_bulk_set_partial_config_parameters( property_values = [ value for value in config_values.values() if value.property_ == property_ ] + + # If we can't find any values with this property, the property is wrong if len(property_values) == 0: raise NotFoundError( f"Configuration parameter {property_} for node {node.node_id} not found" ) + # If we only find one value with this property_, we know this value isn't split + # into partial params if len(property_values) == 1: raise TypeError( f"Configuration parameter {property_} for node {node.node_id} does not " @@ -108,8 +112,8 @@ async def async_bulk_set_partial_config_parameters( # If new_value is a dictionary, we need to calculate the full value to send if isinstance(new_value, dict): temp_value = 0 - # For each property key provided, we multiply the partial value by the multiplication factor - # and send them to get the number to send + # For each property key provided, we bit shift the partial value using the + # property_key for property_key, partial_value in new_value.items(): value_id = get_value_id( node, CommandClass.CONFIGURATION, property_, property_key=property_key @@ -135,14 +139,13 @@ async def async_bulk_set_partial_config_parameters( new_value = temp_value else: - property_keys = sorted( - [cast(int, value.property_key) for value in property_values], reverse=True - ) temp_value = new_value # Break down the bulk value into partial values and validate them against # each partial parameter's metadata - for property_key in property_keys: + for property_key in sorted( + [cast(int, value.property_key) for value in property_values], reverse=True + ): multiplication_factor = 2 ** partial_param_bit_shift(property_key) partial_value = int(temp_value / multiplication_factor) temp_value = temp_value % multiplication_factor From 4d54a968c37d05c108a95f3dd7976236cc33ecc5 Mon Sep 17 00:00:00 2001 From: raman325 <7243222+raman325@users.noreply.github.com> Date: Tue, 23 Mar 2021 01:08:06 -0400 Subject: [PATCH 07/15] rename variable --- zwave_js_server/util/node.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zwave_js_server/util/node.py b/zwave_js_server/util/node.py index 7e047b04d..2eb26a2e3 100644 --- a/zwave_js_server/util/node.py +++ b/zwave_js_server/util/node.py @@ -139,7 +139,7 @@ async def async_bulk_set_partial_config_parameters( new_value = temp_value else: - temp_value = new_value + remaining_value = new_value # Break down the bulk value into partial values and validate them against # each partial parameter's metadata @@ -147,8 +147,8 @@ async def async_bulk_set_partial_config_parameters( [cast(int, value.property_key) for value in property_values], reverse=True ): multiplication_factor = 2 ** partial_param_bit_shift(property_key) - partial_value = int(temp_value / multiplication_factor) - temp_value = temp_value % multiplication_factor + partial_value = int(remaining_value / multiplication_factor) + remaining_value = remaining_value % multiplication_factor zwave_value = cast( ConfigurationValue, node.values[ From d90c9ea36d7eb560a2756ea13b8f00d95744cfd8 Mon Sep 17 00:00:00 2001 From: raman325 <7243222+raman325@users.noreply.github.com> Date: Tue, 23 Mar 2021 01:09:31 -0400 Subject: [PATCH 08/15] remove unused variable --- zwave_js_server/util/node.py | 1 - 1 file changed, 1 deletion(-) diff --git a/zwave_js_server/util/node.py b/zwave_js_server/util/node.py index 2eb26a2e3..c72e3c49f 100644 --- a/zwave_js_server/util/node.py +++ b/zwave_js_server/util/node.py @@ -203,7 +203,6 @@ async def async_set_config_parameter( for config_value in config_values.values() if config_value.property_name == property_or_property_name ) - value_id = zwave_value.value_id except StopIteration: raise NotFoundError( "Configuration parameter with parameter name " From db7322d799ed4ff0ec661592d0ae62d363e4e954 Mon Sep 17 00:00:00 2001 From: raman325 <7243222+raman325@users.noreply.github.com> Date: Tue, 23 Mar 2021 10:39:12 -0400 Subject: [PATCH 09/15] loop through values directly instead of property keys and swap input order --- zwave_js_server/util/node.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/zwave_js_server/util/node.py b/zwave_js_server/util/node.py index c72e3c49f..9f9cf685a 100644 --- a/zwave_js_server/util/node.py +++ b/zwave_js_server/util/node.py @@ -86,8 +86,8 @@ def partial_param_bit_shift(property_key: int) -> int: async def async_bulk_set_partial_config_parameters( node: Node, - new_value: Union[int, Dict[int, Union[int, str]]], property_: int, + new_value: Union[int, Dict[int, Union[int, str]]], ) -> None: """Bulk set partial configuration values on this node.""" config_values = node.get_configuration_values() @@ -142,24 +142,15 @@ async def async_bulk_set_partial_config_parameters( remaining_value = new_value # Break down the bulk value into partial values and validate them against - # each partial parameter's metadata - for property_key in sorted( - [cast(int, value.property_key) for value in property_values], reverse=True + # each partial parameter's metadata by looping through the property values + # starting with the highest property key + for zwave_value in sorted( + property_values, key=lambda val: cast(int, val.property_key), reverse=True ): + property_key = cast(int, zwave_value.property_key) multiplication_factor = 2 ** partial_param_bit_shift(property_key) partial_value = int(remaining_value / multiplication_factor) remaining_value = remaining_value % multiplication_factor - zwave_value = cast( - ConfigurationValue, - node.values[ - get_value_id( - node, - CommandClass.CONFIGURATION, - property_, - property_key=property_key, - ) - ], - ) _validate_and_transform_new_value(zwave_value, partial_value) if ( From 56a8c0cb63fdd2c698195e7ab558ae0cd150cc82 Mon Sep 17 00:00:00 2001 From: raman325 <7243222+raman325@users.noreply.github.com> Date: Tue, 23 Mar 2021 10:40:29 -0400 Subject: [PATCH 10/15] fix tests --- test/util/test_node.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/util/test_node.py b/test/util/test_node.py index a655dca09..6c3d44555 100644 --- a/test/util/test_node.py +++ b/test/util/test_node.py @@ -114,7 +114,7 @@ async def test_bulk_set_partial_config_parameters(multisensor_6, uuid4, mock_com {"command": "node.set_value", "nodeId": node.node_id}, {"success": True}, ) - await async_bulk_set_partial_config_parameters(node, 241, 101) + await async_bulk_set_partial_config_parameters(node, 101, 241) assert len(ack_commands) == 1 assert ack_commands[0] == { "command": "node.set_value", @@ -128,7 +128,7 @@ async def test_bulk_set_partial_config_parameters(multisensor_6, uuid4, mock_com } await async_bulk_set_partial_config_parameters( - node, {128: 1, 64: 1, 32: 1, 16: 1, 1: 1}, 101 + node, 101, {128: 1, 64: 1, 32: 1, 16: 1, 1: 1} ) assert len(ack_commands) == 2 assert ack_commands[1] == { @@ -144,7 +144,7 @@ async def test_bulk_set_partial_config_parameters(multisensor_6, uuid4, mock_com # Only set some values so we use cached values for the rest await async_bulk_set_partial_config_parameters( - node, {64: 1, 32: 1, 16: 1, 1: 1}, 101 + node, 101, {64: 1, 32: 1, 16: 1, 1: 1} ) assert len(ack_commands) == 3 assert ack_commands[2] == { @@ -160,10 +160,10 @@ async def test_bulk_set_partial_config_parameters(multisensor_6, uuid4, mock_com # Use an invalid property with pytest.raises(NotFoundError): - await async_bulk_set_partial_config_parameters(node, 99, 999) + await async_bulk_set_partial_config_parameters(node, 999, 99) # use an invalid bitmask with pytest.raises(NotFoundError): await async_bulk_set_partial_config_parameters( - node, {128: 1, 64: 1, 32: 1, 16: 1, 2: 1}, 101 + node, 101, {128: 1, 64: 1, 32: 1, 16: 1, 2: 1} ) From 10641a3218b198cccfadc6ac8b1292c0a1e56dff Mon Sep 17 00:00:00 2001 From: raman325 <7243222+raman325@users.noreply.github.com> Date: Tue, 23 Mar 2021 10:45:41 -0400 Subject: [PATCH 11/15] add additional test --- test/util/test_node.py | 6 +++++- zwave_js_server/exceptions.py | 4 ++++ zwave_js_server/util/node.py | 4 ++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/test/util/test_node.py b/test/util/test_node.py index 6c3d44555..f03eec4a9 100644 --- a/test/util/test_node.py +++ b/test/util/test_node.py @@ -2,7 +2,7 @@ import pytest from zwave_js_server.const import CommandClass -from zwave_js_server.exceptions import InvalidNewValue, NotFoundError +from zwave_js_server.exceptions import InvalidNewValue, NotFoundError, ValueTypeError from zwave_js_server.model.node import Node from zwave_js_server.model.value import ConfigurationValue from zwave_js_server.util.node import ( @@ -167,3 +167,7 @@ async def test_bulk_set_partial_config_parameters(multisensor_6, uuid4, mock_com await async_bulk_set_partial_config_parameters( node, 101, {128: 1, 64: 1, 32: 1, 16: 1, 2: 1} ) + + # Try to bulkset a property that isn't broken into partials + with pytest.raises(ValueTypeError): + await async_bulk_set_partial_config_parameters(node, 252, 1) diff --git a/zwave_js_server/exceptions.py b/zwave_js_server/exceptions.py index 0ef18c92d..6df86b9e2 100644 --- a/zwave_js_server/exceptions.py +++ b/zwave_js_server/exceptions.py @@ -82,6 +82,10 @@ class InvalidNewValue(BaseZwaveJSServerError): """Exception raised when target new value is invalid based on Value metadata.""" +class ValueTypeError(BaseZwaveJSServerError): + """Exception raised when target Zwave value is the wrong type.""" + + class SetValueFailed(BaseZwaveJSServerError): """ Exception raise when setting a value fails. diff --git a/zwave_js_server/util/node.py b/zwave_js_server/util/node.py index 9f9cf685a..1f9e1442e 100644 --- a/zwave_js_server/util/node.py +++ b/zwave_js_server/util/node.py @@ -3,7 +3,7 @@ from typing import Dict, Optional, Union, cast from ..const import CommandClass, ConfigurationValueType -from ..exceptions import InvalidNewValue, NotFoundError, SetValueFailed +from ..exceptions import InvalidNewValue, NotFoundError, SetValueFailed, ValueTypeError from ..model.node import Node from ..model.value import ConfigurationValue, get_value_id @@ -104,7 +104,7 @@ async def async_bulk_set_partial_config_parameters( # If we only find one value with this property_, we know this value isn't split # into partial params if len(property_values) == 1: - raise TypeError( + raise ValueTypeError( f"Configuration parameter {property_} for node {node.node_id} does not " "have partials" ) From 36a46a654bbcb2767f0a3c5e00881b7a755a671d Mon Sep 17 00:00:00 2001 From: raman325 <7243222+raman325@users.noreply.github.com> Date: Tue, 23 Mar 2021 17:15:58 -0400 Subject: [PATCH 12/15] fix command response logic --- zwave_js_server/util/node.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/zwave_js_server/util/node.py b/zwave_js_server/util/node.py index 1f9e1442e..ac36fe321 100644 --- a/zwave_js_server/util/node.py +++ b/zwave_js_server/util/node.py @@ -153,17 +153,15 @@ async def async_bulk_set_partial_config_parameters( remaining_value = remaining_value % multiplication_factor _validate_and_transform_new_value(zwave_value, partial_value) - if ( - await node.async_send_command( - "set_value", - valueId={ - "commandClass": CommandClass.CONFIGURATION.value, - "property": property_, - }, - value=new_value, - ) - is False - ): + response = await node.async_send_command( + "set_value", + valueId={ + "commandClass": CommandClass.CONFIGURATION.value, + "property": property_, + }, + value=new_value, + ) + if response and cast(bool, response["success"]) is False: raise SetValueFailed( "Unable to set value, refer to " "https://zwave-js.github.io/node-zwave-js/#/api/node?id=setvalue for " From 879f8e292e3e96b741278001ad1e54d2f865f3e4 Mon Sep 17 00:00:00 2001 From: raman325 <7243222+raman325@users.noreply.github.com> Date: Tue, 23 Mar 2021 21:41:55 -0400 Subject: [PATCH 13/15] add test for new value size property --- test/model/test_value.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/model/test_value.py b/test/model/test_value.py index 0a8ef627d..5c921aa5b 100644 --- a/test/model/test_value.py +++ b/test/model/test_value.py @@ -6,6 +6,13 @@ from zwave_js_server.model.value import get_value_id +def test_value_size(lock_schlage_be469): + """Test the value size property for a value.""" + node = lock_schlage_be469 + zwave_value = node.values["20-112-0-3"] + assert zwave_value.metadata.value_size == 1 + + def test_buffer_dict(client, idl_101_lock_state): """Test that we handle buffer dictionary correctly.""" node_data = deepcopy(idl_101_lock_state) From ca269aeedc7f52fc9a27075e9b547d04305bd6d5 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Wed, 24 Mar 2021 10:24:36 -0400 Subject: [PATCH 14/15] Update zwave_js_server/util/node.py Co-authored-by: Martin Hjelmare --- zwave_js_server/util/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zwave_js_server/util/node.py b/zwave_js_server/util/node.py index ac36fe321..a6fb323fb 100644 --- a/zwave_js_server/util/node.py +++ b/zwave_js_server/util/node.py @@ -96,7 +96,7 @@ async def async_bulk_set_partial_config_parameters( ] # If we can't find any values with this property, the property is wrong - if len(property_values) == 0: + if not property_values: raise NotFoundError( f"Configuration parameter {property_} for node {node.node_id} not found" ) From b52639d583bbe97b375d593d544531d7cc1837ba Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Wed, 24 Mar 2021 10:25:22 -0400 Subject: [PATCH 15/15] Update zwave_js_server/util/node.py Co-authored-by: Martin Hjelmare --- zwave_js_server/util/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zwave_js_server/util/node.py b/zwave_js_server/util/node.py index a6fb323fb..1ad7cddec 100644 --- a/zwave_js_server/util/node.py +++ b/zwave_js_server/util/node.py @@ -161,7 +161,7 @@ async def async_bulk_set_partial_config_parameters( }, value=new_value, ) - if response and cast(bool, response["success"]) is False: + if response and not cast(bool, response["success"]): raise SetValueFailed( "Unable to set value, refer to " "https://zwave-js.github.io/node-zwave-js/#/api/node?id=setvalue for "