From e1c6ccb1988e02e80e65e61ef36d7d4d4e2b0ead Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Wed, 28 Jul 2021 17:15:27 -0400 Subject: [PATCH] Add zwave_js.reset_meter service (#53390) * Add zwave_js.meter_reset service * fix log statement * Add endpoint attribute to service call and rename service * Make service an entity service * remove endpoint from service description --- homeassistant/components/zwave_js/const.py | 22 +++---- homeassistant/components/zwave_js/sensor.py | 38 +++++++++++- .../components/zwave_js/services.yaml | 23 ++++++++ tests/components/zwave_js/test_sensor.py | 59 +++++++++++++++++++ 4 files changed, 130 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/zwave_js/const.py b/homeassistant/components/zwave_js/const.py index ae5607745f6..7848af146b5 100644 --- a/homeassistant/components/zwave_js/const.py +++ b/homeassistant/components/zwave_js/const.py @@ -49,24 +49,24 @@ ATTR_NODE = "node" ATTR_ZWAVE_VALUE = "zwave_value" # service constants -ATTR_NODES = "nodes" - +SERVICE_SET_VALUE = "set_value" +SERVICE_RESET_METER = "reset_meter" +SERVICE_MULTICAST_SET_VALUE = "multicast_set_value" +SERVICE_PING = "ping" +SERVICE_REFRESH_VALUE = "refresh_value" SERVICE_SET_CONFIG_PARAMETER = "set_config_parameter" SERVICE_BULK_SET_PARTIAL_CONFIG_PARAMETERS = "bulk_set_partial_config_parameters" +ATTR_NODES = "nodes" +# config parameter ATTR_CONFIG_PARAMETER = "parameter" ATTR_CONFIG_PARAMETER_BITMASK = "bitmask" ATTR_CONFIG_VALUE = "value" - -SERVICE_REFRESH_VALUE = "refresh_value" - +# refresh value ATTR_REFRESH_ALL_VALUES = "refresh_all_values" - -SERVICE_SET_VALUE = "set_value" -SERVICE_MULTICAST_SET_VALUE = "multicast_set_value" - +# multicast ATTR_BROADCAST = "broadcast" - -SERVICE_PING = "ping" +# meter reset +ATTR_METER_TYPE = "meter_type" ADDON_SLUG = "core_zwave_js" diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index f20a12a519a..7c41ad035be 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -4,6 +4,7 @@ from __future__ import annotations import logging from typing import cast +import voluptuous as vol from zwave_js_server.client import Client as ZwaveClient from zwave_js_server.const import CommandClass, ConfigurationValueType from zwave_js_server.model.node import Node as ZwaveNode @@ -26,10 +27,11 @@ from homeassistant.const import ( TEMP_FAHRENHEIT, ) from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_platform from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DATA_CLIENT, DOMAIN +from .const import ATTR_METER_TYPE, ATTR_VALUE, DATA_CLIENT, DOMAIN, SERVICE_RESET_METER from .discovery import ZwaveDiscoveryInfo from .entity import ZWaveBaseEntity from .helpers import get_device_id @@ -89,6 +91,16 @@ async def async_setup_entry( ) ) + platform = entity_platform.async_get_current_platform() + platform.async_register_entity_service( + SERVICE_RESET_METER, + { + vol.Optional(ATTR_METER_TYPE): vol.Coerce(int), + vol.Optional(ATTR_VALUE): vol.Coerce(int), + }, + "async_reset_meter", + ) + class ZwaveSensorBase(ZWaveBaseEntity, SensorEntity): """Basic Representation of a Z-Wave sensor.""" @@ -218,6 +230,30 @@ class ZWaveNumericSensor(ZwaveSensorBase): return str(self.info.primary_value.metadata.unit) + async def async_reset_meter( + self, meter_type: int | None = None, value: int | None = None + ) -> None: + """Reset meter(s) on device.""" + node = self.info.node + primary_value = self.info.primary_value + if primary_value.command_class != CommandClass.METER: + raise TypeError("Reset only available for Meter sensors") + options = {} + if meter_type is not None: + options["type"] = meter_type + if value is not None: + options["targetValue"] = value + args = [options] if options else [] + await node.endpoints[primary_value.endpoint].async_invoke_cc_api( + CommandClass.METER, "reset", *args, wait_for_result=False + ) + LOGGER.debug( + "Meters on node %s endpoint %s reset with the following options: %s", + node, + primary_value.endpoint, + options, + ) + class ZWaveListSensor(ZwaveSensorBase): """Representation of a Z-Wave Numeric sensor with multiple states.""" diff --git a/homeassistant/components/zwave_js/services.yaml b/homeassistant/components/zwave_js/services.yaml index f737f159806..b41a893c7e4 100644 --- a/homeassistant/components/zwave_js/services.yaml +++ b/homeassistant/components/zwave_js/services.yaml @@ -229,3 +229,26 @@ ping: target: entity: integration: zwave_js + +reset_meter: + name: Reset meter(s) on a node + description: Resets the meter(s) on a node. + target: + entity: + domain: sensor + integration: zwave_js + fields: + meter_type: + name: Meter Type + description: The type of meter to reset. Not all meters support the ability to pick a meter type to reset. + example: 1 + required: false + selector: + text: + value: + name: Target Value + description: The value that meter(s) should be reset to. Not all meters support the ability to be reset to a specific value. + example: 5 + required: false + selector: + text: diff --git a/tests/components/zwave_js/test_sensor.py b/tests/components/zwave_js/test_sensor.py index fc6d274235d..e368ec1b026 100644 --- a/tests/components/zwave_js/test_sensor.py +++ b/tests/components/zwave_js/test_sensor.py @@ -1,7 +1,14 @@ """Test the Z-Wave JS sensor platform.""" from zwave_js_server.event import Event +from homeassistant.components.zwave_js.const import ( + ATTR_METER_TYPE, + ATTR_VALUE, + DOMAIN, + SERVICE_RESET_METER, +) from homeassistant.const import ( + ATTR_ENTITY_ID, DEVICE_CLASS_ENERGY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_POWER, @@ -131,3 +138,55 @@ async def test_node_status_sensor(hass, lock_id_lock_as_id150, integration): ) node.receive_event(event) assert hass.states.get(NODE_STATUS_ENTITY).state == "alive" + + +async def test_reset_meter( + hass, + client, + aeon_smart_switch_6, + integration, +): + """Test reset_meter service.""" + SENSOR = "sensor.smart_switch_6_electric_consumed_v" + client.async_send_command.return_value = {} + client.async_send_command_no_wait.return_value = {} + + # Test successful meter reset call + await hass.services.async_call( + DOMAIN, + SERVICE_RESET_METER, + { + ATTR_ENTITY_ID: SENSOR, + }, + blocking=True, + ) + + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] + assert args["command"] == "endpoint.invoke_cc_api" + assert args["nodeId"] == aeon_smart_switch_6.node_id + assert args["endpoint"] == 0 + assert args["args"] == [] + + client.async_send_command_no_wait.reset_mock() + + # Test successful meter reset call with options + await hass.services.async_call( + DOMAIN, + SERVICE_RESET_METER, + { + ATTR_ENTITY_ID: SENSOR, + ATTR_METER_TYPE: 1, + ATTR_VALUE: 2, + }, + blocking=True, + ) + + assert len(client.async_send_command_no_wait.call_args_list) == 1 + args = client.async_send_command_no_wait.call_args[0][0] + assert args["command"] == "endpoint.invoke_cc_api" + assert args["nodeId"] == aeon_smart_switch_6.node_id + assert args["endpoint"] == 0 + assert args["args"] == [{"type": 1, "targetValue": 2}] + + client.async_send_command_no_wait.reset_mock()