diff --git a/homeassistant/components/zwave_js/services.py b/homeassistant/components/zwave_js/services.py index e09b0fb0c5e..fa0e93a72aa 100644 --- a/homeassistant/components/zwave_js/services.py +++ b/homeassistant/components/zwave_js/services.py @@ -283,6 +283,7 @@ class ZWaveServices: ), vol.Optional(const.ATTR_ENDPOINT): vol.Coerce(int), vol.Required(const.ATTR_VALUE): VALUE_SCHEMA, + vol.Optional(const.ATTR_OPTIONS): {cv.string: VALUE_SCHEMA}, }, vol.Any( cv.has_at_least_one_key(ATTR_DEVICE_ID, ATTR_ENTITY_ID), @@ -409,6 +410,7 @@ class ZWaveServices: """Set a value via multicast to multiple nodes.""" nodes = service.data[const.ATTR_NODES] broadcast: bool = service.data[const.ATTR_BROADCAST] + options = service.data.get(const.ATTR_OPTIONS) if not broadcast and len(nodes) == 1: const.LOGGER.warning( @@ -438,10 +440,11 @@ class ZWaveServices: client = self._hass.data[const.DOMAIN][entry_id][const.DATA_CLIENT] success = await async_multicast_set_value( - client, - new_value, - {k: v for k, v in value.items() if v is not None}, - None if broadcast else list(nodes), + client=client, + new_value=new_value, + value_data={k: v for k, v in value.items() if v is not None}, + nodes=None if broadcast else list(nodes), + options=options, ) if success is False: diff --git a/homeassistant/components/zwave_js/services.yaml b/homeassistant/components/zwave_js/services.yaml index 7d88f5f7830..f737f159806 100644 --- a/homeassistant/components/zwave_js/services.yaml +++ b/homeassistant/components/zwave_js/services.yaml @@ -156,7 +156,7 @@ set_value: object: options: name: Options - description: Set value options. Refer to the Z-Wave JS documentation for more information on what options can be set. + description: Set value options map. Refer to the Z-Wave JS documentation for more information on what options can be set. required: false selector: object: @@ -209,6 +209,12 @@ multicast_set_value: required: false selector: text: + options: + name: Options + description: Set value options map. Refer to the Z-Wave JS documentation for more information on what options can be set. + required: false + selector: + object: value: name: Value description: The new value to set. diff --git a/tests/components/zwave_js/test_light.py b/tests/components/zwave_js/test_light.py index 43250c3477d..fa3c73a9a42 100644 --- a/tests/components/zwave_js/test_light.py +++ b/tests/components/zwave_js/test_light.py @@ -569,6 +569,7 @@ async def test_rgbw_light(hass, client, zen_31, integration): "type": "any", "readable": True, "writeable": True, + "valueChangeOptions": ["transitionDuration"], }, "value": {"blue": 70, "green": 159, "red": 255, "warmWhite": 141}, } diff --git a/tests/components/zwave_js/test_services.py b/tests/components/zwave_js/test_services.py index 4b66e178e6f..3ee656e40c0 100644 --- a/tests/components/zwave_js/test_services.py +++ b/tests/components/zwave_js/test_services.py @@ -13,6 +13,7 @@ from homeassistant.components.zwave_js.const import ( ATTR_CONFIG_VALUE, ATTR_OPTIONS, ATTR_PROPERTY, + ATTR_PROPERTY_KEY, ATTR_REFRESH_ALL_VALUES, ATTR_VALUE, ATTR_WAIT_FOR_RESULT, @@ -34,6 +35,7 @@ from homeassistant.helpers.entity_registry import async_get as async_get_ent_reg from .common import ( AEON_SMART_SWITCH_LIGHT_ENTITY, AIR_TEMPERATURE_SENSOR, + BULB_6_MULTI_COLOR_LIGHT_ENTITY, CLIMATE_DANFOSS_LC13_ENTITY, CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY, CLIMATE_RADIO_THERMOSTAT_ENTITY, @@ -819,8 +821,9 @@ async def test_multicast_set_value( CLIMATE_DANFOSS_LC13_ENTITY, CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY, ], - ATTR_COMMAND_CLASS: 117, - ATTR_PROPERTY: "local", + ATTR_COMMAND_CLASS: 67, + ATTR_PROPERTY: "setpoint", + ATTR_PROPERTY_KEY: 1, ATTR_VALUE: 2, }, blocking=True, @@ -834,8 +837,9 @@ async def test_multicast_set_value( climate_danfoss_lc_13.node_id, ] assert args["valueId"] == { - "commandClass": 117, - "property": "local", + "commandClass": 67, + "property": "setpoint", + "propertyKey": 1, } assert args["value"] == 2 @@ -850,8 +854,9 @@ async def test_multicast_set_value( CLIMATE_DANFOSS_LC13_ENTITY, CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY, ], - ATTR_COMMAND_CLASS: 117, - ATTR_PROPERTY: "local", + ATTR_COMMAND_CLASS: 67, + ATTR_PROPERTY: "setpoint", + ATTR_PROPERTY_KEY: 1, ATTR_VALUE: "0x2", }, blocking=True, @@ -865,8 +870,9 @@ async def test_multicast_set_value( climate_danfoss_lc_13.node_id, ] assert args["valueId"] == { - "commandClass": 117, - "property": "local", + "commandClass": 67, + "property": "setpoint", + "propertyKey": 1, } assert args["value"] == 2 @@ -878,8 +884,9 @@ async def test_multicast_set_value( SERVICE_MULTICAST_SET_VALUE, { ATTR_BROADCAST: True, - ATTR_COMMAND_CLASS: 117, - ATTR_PROPERTY: "local", + ATTR_COMMAND_CLASS: 67, + ATTR_PROPERTY: "setpoint", + ATTR_PROPERTY_KEY: 1, ATTR_VALUE: 2, }, blocking=True, @@ -889,8 +896,9 @@ async def test_multicast_set_value( args = client.async_send_command.call_args[0][0] assert args["command"] == "broadcast_node.set_value" assert args["valueId"] == { - "commandClass": 117, - "property": "local", + "commandClass": 67, + "property": "setpoint", + "propertyKey": 1, } assert args["value"] == 2 @@ -902,8 +910,9 @@ async def test_multicast_set_value( SERVICE_MULTICAST_SET_VALUE, { ATTR_ENTITY_ID: CLIMATE_DANFOSS_LC13_ENTITY, - ATTR_COMMAND_CLASS: 117, - ATTR_PROPERTY: "local", + ATTR_COMMAND_CLASS: 67, + ATTR_PROPERTY: "setpoint", + ATTR_PROPERTY_KEY: 1, ATTR_VALUE: 2, }, blocking=True, @@ -921,8 +930,9 @@ async def test_multicast_set_value( DOMAIN, SERVICE_MULTICAST_SET_VALUE, { - ATTR_COMMAND_CLASS: 117, - ATTR_PROPERTY: "local", + ATTR_COMMAND_CLASS: 67, + ATTR_PROPERTY: "setpoint", + ATTR_PROPERTY_KEY: 1, ATTR_VALUE: 2, }, blocking=True, @@ -940,8 +950,9 @@ async def test_multicast_set_value( CLIMATE_DANFOSS_LC13_ENTITY, CLIMATE_EUROTRONICS_SPIRIT_Z_ENTITY, ], - ATTR_COMMAND_CLASS: 117, - ATTR_PROPERTY: "local", + ATTR_COMMAND_CLASS: 67, + ATTR_PROPERTY: "setpoint", + ATTR_PROPERTY_KEY: 1, ATTR_VALUE: 2, }, blocking=True, @@ -965,8 +976,9 @@ async def test_multicast_set_value( CLIMATE_DANFOSS_LC13_ENTITY, ], ATTR_DEVICE_ID: "fake_device_id", - ATTR_COMMAND_CLASS: 117, - ATTR_PROPERTY: "local", + ATTR_COMMAND_CLASS: 67, + ATTR_PROPERTY: "setpoint", + ATTR_PROPERTY_KEY: 1, ATTR_VALUE: 2, }, blocking=True, @@ -982,14 +994,58 @@ async def test_multicast_set_value( SERVICE_MULTICAST_SET_VALUE, { ATTR_BROADCAST: True, - ATTR_COMMAND_CLASS: 117, - ATTR_PROPERTY: "local", + ATTR_COMMAND_CLASS: 67, + ATTR_PROPERTY: "setpoint", + ATTR_PROPERTY_KEY: 1, ATTR_VALUE: 2, }, blocking=True, ) +async def test_multicast_set_value_options( + hass, + client, + bulb_6_multi_color, + light_color_null_values, + integration, +): + """Test multicast_set_value service with options.""" + await hass.services.async_call( + DOMAIN, + SERVICE_MULTICAST_SET_VALUE, + { + ATTR_ENTITY_ID: [ + BULB_6_MULTI_COLOR_LIGHT_ENTITY, + "light.repeater", + ], + ATTR_COMMAND_CLASS: 51, + ATTR_PROPERTY: "targetColor", + ATTR_PROPERTY_KEY: 2, + ATTR_VALUE: 2, + ATTR_OPTIONS: {"transitionDuration": 1}, + }, + blocking=True, + ) + + assert len(client.async_send_command.call_args_list) == 1 + args = client.async_send_command.call_args[0][0] + assert args["command"] == "multicast_group.set_value" + assert args["nodeIDs"] == [ + bulb_6_multi_color.node_id, + light_color_null_values.node_id, + ] + assert args["valueId"] == { + "commandClass": 51, + "property": "targetColor", + "propertyKey": 2, + } + assert args["value"] == 2 + assert args["options"] == {"transitionDuration": 1} + + client.async_send_command.reset_mock() + + async def test_ping( hass, client, diff --git a/tests/fixtures/zwave_js/light_color_null_values_state.json b/tests/fixtures/zwave_js/light_color_null_values_state.json index f60b61c7a5a..213b873f85c 100644 --- a/tests/fixtures/zwave_js/light_color_null_values_state.json +++ b/tests/fixtures/zwave_js/light_color_null_values_state.json @@ -1,5 +1,5 @@ { - "nodeId": 39, + "nodeId": 40, "index": 0, "installerIcon": 6912, "userIcon": 6912, @@ -297,7 +297,8 @@ "description": "The target value of the Red color.", "label": "Target value (Red)", "min": 0, - "max": 255 + "max": 255, + "valueChangeOptions": ["transitionDuration"] } }, { @@ -316,7 +317,8 @@ "description": "The target value of the Green color.", "label": "Target value (Green)", "min": 0, - "max": 255 + "max": 255, + "valueChangeOptions": ["transitionDuration"] } }, { @@ -335,7 +337,8 @@ "description": "The target value of the Blue color.", "label": "Target value (Blue)", "min": 0, - "max": 255 + "max": 255, + "valueChangeOptions": ["transitionDuration"] } }, { @@ -349,7 +352,8 @@ "type": "any", "readable": true, "writeable": true, - "label": "Target Color" + "label": "Target Color", + "valueChangeOptions": ["transitionDuration"] } }, { diff --git a/tests/fixtures/zwave_js/zen_31_state.json b/tests/fixtures/zwave_js/zen_31_state.json index f0c3c1759eb..7407607e086 100644 --- a/tests/fixtures/zwave_js/zen_31_state.json +++ b/tests/fixtures/zwave_js/zen_31_state.json @@ -1645,7 +1645,8 @@ "type": "any", "readable": true, "writeable": true, - "label": "Target Color" + "label": "Target Color", + "valueChangeOptions": ["transitionDuration"] }, "value": { "warmWhite": 141, @@ -1670,7 +1671,8 @@ "description": "The target value of the Warm White color.", "label": "Target value (Warm White)", "min": 0, - "max": 255 + "max": 255, + "valueChangeOptions": ["transitionDuration"] } }, { @@ -1689,7 +1691,8 @@ "description": "The target value of the Red color.", "label": "Target value (Red)", "min": 0, - "max": 255 + "max": 255, + "valueChangeOptions": ["transitionDuration"] } }, { @@ -1708,7 +1711,8 @@ "description": "The target value of the Green color.", "label": "Target value (Green)", "min": 0, - "max": 255 + "max": 255, + "valueChangeOptions": ["transitionDuration"] } }, { @@ -1727,7 +1731,8 @@ "description": "The target value of the Blue color.", "label": "Target value (Blue)", "min": 0, - "max": 255 + "max": 255, + "valueChangeOptions": ["transitionDuration"] } }, {