From 0738f082156def8db0445fefd719d1b167497bb7 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Mon, 1 Aug 2022 09:52:51 +0200 Subject: [PATCH] Add missing sensors for Shelly Plus H&T (#76001) * Add missing sensors for Shelly Plus H&T * Cleanup * Fix * Add voltage to battery sensor * Apply review comments --- homeassistant/components/shelly/entity.py | 6 ++-- homeassistant/components/shelly/sensor.py | 37 ++++++++++++++++++++++- homeassistant/components/shelly/utils.py | 17 +++++++++-- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index 8cba4d2804e..a38bef54bea 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -190,9 +190,9 @@ def async_setup_entry_rpc( ] and not description.supported(wrapper.device.status[key]): continue - # Filter and remove entities that according to settings should not create an entity + # Filter and remove entities that according to settings/status should not create an entity if description.removal_condition and description.removal_condition( - wrapper.device.config, key + wrapper.device.config, wrapper.device.status, key ): domain = sensor_class.__module__.split(".")[-1] unique_id = f"{wrapper.mac}-{key}-{sensor_id}" @@ -268,7 +268,7 @@ class RpcEntityDescription(EntityDescription, RpcEntityRequiredKeysMixin): value: Callable[[Any, Any], Any] | None = None available: Callable[[dict], bool] | None = None - removal_condition: Callable[[dict, str], bool] | None = None + removal_condition: Callable[[dict, dict, str], bool] | None = None extra_state_attributes: Callable[[dict, dict], dict | None] | None = None use_polling_wrapper: bool = False supported: Callable = lambda _: False diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 92c19734414..5a36b5e99bf 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -46,7 +46,12 @@ from .entity import ( async_setup_entry_rest, async_setup_entry_rpc, ) -from .utils import get_device_entry_gen, get_device_uptime, temperature_unit +from .utils import ( + get_device_entry_gen, + get_device_uptime, + is_rpc_device_externally_powered, + temperature_unit, +) @dataclass @@ -352,6 +357,15 @@ RPC_SENSORS: Final = { entity_category=EntityCategory.DIAGNOSTIC, use_polling_wrapper=True, ), + "temperature_0": RpcSensorDescription( + key="temperature:0", + sub_key="tC", + name="Temperature", + native_unit_of_measurement=TEMP_CELSIUS, + device_class=SensorDeviceClass.TEMPERATURE, + state_class=SensorStateClass.MEASUREMENT, + entity_registry_enabled_default=True, + ), "rssi": RpcSensorDescription( key="wifi", sub_key="rssi", @@ -373,6 +387,27 @@ RPC_SENSORS: Final = { entity_category=EntityCategory.DIAGNOSTIC, use_polling_wrapper=True, ), + "humidity_0": RpcSensorDescription( + key="humidity:0", + sub_key="rh", + name="Humidity", + native_unit_of_measurement=PERCENTAGE, + device_class=SensorDeviceClass.HUMIDITY, + state_class=SensorStateClass.MEASUREMENT, + entity_registry_enabled_default=True, + ), + "battery": RpcSensorDescription( + key="devicepower:0", + sub_key="battery", + name="Battery", + native_unit_of_measurement=PERCENTAGE, + value=lambda status, _: status["percent"], + device_class=SensorDeviceClass.BATTERY, + state_class=SensorStateClass.MEASUREMENT, + removal_condition=is_rpc_device_externally_powered, + entity_registry_enabled_default=True, + entity_category=EntityCategory.DIAGNOSTIC, + ), } diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index 6dfc2fb3be8..7eeb93f2918 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -271,7 +271,9 @@ def get_rpc_channel_name(device: RpcDevice, key: str) -> str: entity_name = device.config[key].get("name", device_name) if entity_name is None: - return f"{device_name} {key.replace(':', '_')}" + if [k for k in key if k.startswith(("input", "switch"))]: + return f"{device_name} {key.replace(':', '_')}" + return device_name return entity_name @@ -325,7 +327,9 @@ def get_rpc_key_ids(keys_dict: dict[str, Any], key: str) -> list[int]: return key_ids -def is_rpc_momentary_input(config: dict[str, Any], key: str) -> bool: +def is_rpc_momentary_input( + config: dict[str, Any], status: dict[str, Any], key: str +) -> bool: """Return true if rpc input button settings is set to a momentary type.""" return cast(bool, config[key]["type"] == "button") @@ -342,6 +346,13 @@ def is_rpc_channel_type_light(config: dict[str, Any], channel: int) -> bool: return con_types is not None and con_types[channel].lower().startswith("light") +def is_rpc_device_externally_powered( + config: dict[str, Any], status: dict[str, Any], key: str +) -> bool: + """Return true if device has external power instead of battery.""" + return cast(bool, status[key]["external"]["present"]) + + def get_rpc_input_triggers(device: RpcDevice) -> list[tuple[str, str]]: """Return list of input triggers for RPC device.""" triggers = [] @@ -350,7 +361,7 @@ def get_rpc_input_triggers(device: RpcDevice) -> list[tuple[str, str]]: for id_ in key_ids: key = f"input:{id_}" - if not is_rpc_momentary_input(device.config, key): + if not is_rpc_momentary_input(device.config, device.status, key): continue for trigger_type in RPC_INPUTS_EVENTS_TYPES: