From 653ff4717105b9c33d5312eeba65c75073af35bb Mon Sep 17 00:00:00 2001 From: "Teemu R." Date: Wed, 29 Jan 2025 15:56:47 +0100 Subject: [PATCH] Add cleaning statistics for tplink (#135784) Co-authored-by: Steven B <51370195+sdb9696@users.noreply.github.com> --- homeassistant/components/tplink/const.py | 6 +- homeassistant/components/tplink/sensor.py | 44 +++ homeassistant/components/tplink/strings.json | 27 ++ tests/components/tplink/__init__.py | 9 +- .../components/tplink/fixtures/features.json | 55 +++ .../tplink/snapshots/test_sensor.ambr | 359 ++++++++++++++++++ 6 files changed, 497 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tplink/const.py b/homeassistant/components/tplink/const.py index ad17aadeb5b..2df7101791a 100644 --- a/homeassistant/components/tplink/const.py +++ b/homeassistant/components/tplink/const.py @@ -4,7 +4,9 @@ from __future__ import annotations from typing import Final -from homeassistant.const import Platform, UnitOfTemperature +from kasa.smart.modules.clean import AreaUnit + +from homeassistant.const import Platform, UnitOfArea, UnitOfTemperature DOMAIN = "tplink" @@ -47,4 +49,6 @@ PLATFORMS: Final = [ UNIT_MAPPING = { "celsius": UnitOfTemperature.CELSIUS, "fahrenheit": UnitOfTemperature.FAHRENHEIT, + AreaUnit.Sqm: UnitOfArea.SQUARE_METERS, + AreaUnit.Sqft: UnitOfArea.SQUARE_FEET, } diff --git a/homeassistant/components/tplink/sensor.py b/homeassistant/components/tplink/sensor.py index 4c38591b64d..38aab26cf8b 100644 --- a/homeassistant/components/tplink/sensor.py +++ b/homeassistant/components/tplink/sensor.py @@ -124,6 +124,50 @@ SENSOR_DESCRIPTIONS: tuple[TPLinkSensorEntityDescription, ...] = ( TPLinkSensorEntityDescription( key="alarm_source", ), + # Vacuum cleaning records + TPLinkSensorEntityDescription( + key="clean_time", + device_class=SensorDeviceClass.DURATION, + native_unit_of_measurement=UnitOfTime.SECONDS, + suggested_unit_of_measurement=UnitOfTime.MINUTES, + convert_fn=_TOTAL_SECONDS_METHOD_CALLER, + ), + TPLinkSensorEntityDescription( + key="clean_area", + device_class=SensorDeviceClass.AREA, + ), + TPLinkSensorEntityDescription( + key="clean_progress", + ), + TPLinkSensorEntityDescription( + key="last_clean_time", + device_class=SensorDeviceClass.DURATION, + native_unit_of_measurement=UnitOfTime.SECONDS, + suggested_unit_of_measurement=UnitOfTime.MINUTES, + convert_fn=_TOTAL_SECONDS_METHOD_CALLER, + ), + TPLinkSensorEntityDescription( + key="last_clean_area", + device_class=SensorDeviceClass.AREA, + ), + TPLinkSensorEntityDescription( + key="last_clean_timestamp", + device_class=SensorDeviceClass.TIMESTAMP, + ), + TPLinkSensorEntityDescription( + key="total_clean_time", + device_class=SensorDeviceClass.DURATION, + native_unit_of_measurement=UnitOfTime.SECONDS, + suggested_unit_of_measurement=UnitOfTime.MINUTES, + convert_fn=_TOTAL_SECONDS_METHOD_CALLER, + ), + TPLinkSensorEntityDescription( + key="total_clean_area", + device_class=SensorDeviceClass.AREA, + ), + TPLinkSensorEntityDescription( + key="total_clean_count", + ), TPLinkSensorEntityDescription( key="main_brush_remaining", device_class=SensorDeviceClass.DURATION, diff --git a/homeassistant/components/tplink/strings.json b/homeassistant/components/tplink/strings.json index 2714e92bd5c..ded4806a726 100644 --- a/homeassistant/components/tplink/strings.json +++ b/homeassistant/components/tplink/strings.json @@ -217,6 +217,33 @@ "alarm_source": { "name": "Alarm source" }, + "clean_area": { + "name": "Cleaning area" + }, + "clean_time": { + "name": "Cleaning time" + }, + "clean_progress": { + "name": "Cleaning progress" + }, + "total_clean_area": { + "name": "Total cleaning area" + }, + "total_clean_time": { + "name": "Total cleaning time" + }, + "total_clean_count": { + "name": "Total cleaning count" + }, + "last_clean_area": { + "name": "Last cleaned area" + }, + "last_clean_time": { + "name": "Last cleaned time" + }, + "last_clean_timestamp": { + "name": "Last clean start" + }, "main_brush_remaining": { "name": "Main brush remaining" }, diff --git a/tests/components/tplink/__init__.py b/tests/components/tplink/__init__.py index 851d05636b0..ac5bb347765 100644 --- a/tests/components/tplink/__init__.py +++ b/tests/components/tplink/__init__.py @@ -18,7 +18,7 @@ from kasa import ( from kasa.interfaces import Fan, Light, LightEffect, LightState, Thermostat from kasa.smart.modules import Speaker from kasa.smart.modules.alarm import Alarm -from kasa.smart.modules.clean import Clean, ErrorCode, Status +from kasa.smart.modules.clean import AreaUnit, Clean, ErrorCode, Status from kasa.smartcam.modules.camera import LOCAL_STREAMING_PORT, Camera from syrupy import SnapshotAssertion @@ -60,7 +60,7 @@ def _load_feature_fixtures(): FEATURES_FIXTURE = _load_feature_fixtures() -FIXTURE_ENUM_TYPES = {"CleanErrorCode": ErrorCode} +FIXTURE_ENUM_TYPES = {"CleanErrorCode": ErrorCode, "CleanAreaUnit": AreaUnit} async def setup_platform_for_device( @@ -276,12 +276,17 @@ def _mocked_feature( if fixture := FEATURES_FIXTURE.get(id): # copy the fixture so tests do not interfere with each other fixture = dict(fixture) + if enum_type := fixture.get("enum_type"): val = FIXTURE_ENUM_TYPES[enum_type](fixture["value"]) fixture["value"] = val if timedelta_type := fixture.get("timedelta_type"): fixture["value"] = timedelta(**{timedelta_type: fixture["value"]}) + if unit_enum_type := fixture.get("unit_enum_type"): + val = FIXTURE_ENUM_TYPES[unit_enum_type](fixture["unit"]) + fixture["unit"] = val + else: assert require_fixture is False, ( f"No fixture defined for feature {id} and require_fixture is True" diff --git a/tests/components/tplink/fixtures/features.json b/tests/components/tplink/fixtures/features.json index 45b85da4583..81277ddd3ae 100644 --- a/tests/components/tplink/fixtures/features.json +++ b/tests/components/tplink/fixtures/features.json @@ -341,6 +341,61 @@ "Connection 2" ] }, + "clean_time": { + "type": "Sensor", + "category": "Info", + "value": 12, + "timedelta_type": "minutes" + }, + "clean_area": { + "type": "Sensor", + "category": "Info", + "value": 2, + "unit": 1, + "unit_enum_type": "CleanAreaUnit" + }, + "clean_progress": { + "type": "Sensor", + "category": "Info", + "value": 30, + "unit": "%" + }, + "total_clean_time": { + "type": "Sensor", + "category": "Debug", + "value": 120, + "timedelta_type": "minutes" + }, + "total_clean_area": { + "type": "Sensor", + "category": "Debug", + "value": 2, + "unit": 1, + "unit_enum_type": "CleanAreaUnit" + }, + "last_clean_time": { + "type": "Sensor", + "category": "Debug", + "value": 60, + "timedelta_type": "minutes" + }, + "last_clean_area": { + "type": "Sensor", + "category": "Debug", + "value": 2, + "unit": 1, + "unit_enum_type": "CleanAreaUnit" + }, + "last_clean_timestamp": { + "type": "Sensor", + "category": "Debug", + "value": "2024-06-24 10:03:11.046643+01:00" + }, + "total_clean_count": { + "type": "Sensor", + "category": "Debug", + "value": 12 + }, "alarm_volume": { "value": "normal", "type": "Choice", diff --git a/tests/components/tplink/snapshots/test_sensor.ambr b/tests/components/tplink/snapshots/test_sensor.ambr index 344b9e28b98..0d1cc9a03e4 100644 --- a/tests/components/tplink/snapshots/test_sensor.ambr +++ b/tests/components/tplink/snapshots/test_sensor.ambr @@ -238,6 +238,155 @@ 'unit_of_measurement': , }) # --- +# name: test_states[sensor.my_device_cleaning_area-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.my_device_cleaning_area', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Cleaning area', + 'platform': 'tplink', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'clean_area', + 'unique_id': '123456789ABCDEFGH_clean_area', + 'unit_of_measurement': , + }) +# --- +# name: test_states[sensor.my_device_cleaning_area-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'area', + 'friendly_name': 'my_device Cleaning area', + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.my_device_cleaning_area', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.2', + }) +# --- +# name: test_states[sensor.my_device_cleaning_progress-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.my_device_cleaning_progress', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Cleaning progress', + 'platform': 'tplink', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'clean_progress', + 'unique_id': '123456789ABCDEFGH_clean_progress', + 'unit_of_measurement': '%', + }) +# --- +# name: test_states[sensor.my_device_cleaning_progress-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'my_device Cleaning progress', + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.my_device_cleaning_progress', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '30', + }) +# --- +# name: test_states[sensor.my_device_cleaning_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.my_device_cleaning_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Cleaning time', + 'platform': 'tplink', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'clean_time', + 'unique_id': '123456789ABCDEFGH_clean_time', + 'unit_of_measurement': , + }) +# --- +# name: test_states[sensor.my_device_cleaning_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'duration', + 'friendly_name': 'my_device Cleaning time', + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.my_device_cleaning_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '12.00', + }) +# --- # name: test_states[sensor.my_device_current-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -578,6 +727,111 @@ 'state': '12', }) # --- +# name: test_states[sensor.my_device_last_clean_start-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.my_device_last_clean_start', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Last clean start', + 'platform': 'tplink', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'last_clean_timestamp', + 'unique_id': '123456789ABCDEFGH_last_clean_timestamp', + 'unit_of_measurement': None, + }) +# --- +# name: test_states[sensor.my_device_last_cleaned_area-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.my_device_last_cleaned_area', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Last cleaned area', + 'platform': 'tplink', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'last_clean_area', + 'unique_id': '123456789ABCDEFGH_last_clean_area', + 'unit_of_measurement': , + }) +# --- +# name: test_states[sensor.my_device_last_cleaned_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.my_device_last_cleaned_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Last cleaned time', + 'platform': 'tplink', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'last_clean_time', + 'unique_id': '123456789ABCDEFGH_last_clean_time', + 'unit_of_measurement': , + }) +# --- # name: test_states[sensor.my_device_last_water_leak_alert-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -1167,6 +1421,111 @@ 'state': '5.23', }) # --- +# name: test_states[sensor.my_device_total_cleaning_area-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.my_device_total_cleaning_area', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Total cleaning area', + 'platform': 'tplink', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'total_clean_area', + 'unique_id': '123456789ABCDEFGH_total_clean_area', + 'unit_of_measurement': , + }) +# --- +# name: test_states[sensor.my_device_total_cleaning_count-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.my_device_total_cleaning_count', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Total cleaning count', + 'platform': 'tplink', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'total_clean_count', + 'unique_id': '123456789ABCDEFGH_total_clean_count', + 'unit_of_measurement': None, + }) +# --- +# name: test_states[sensor.my_device_total_cleaning_time-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': , + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.my_device_total_cleaning_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor.private': dict({ + 'suggested_unit_of_measurement': , + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Total cleaning time', + 'platform': 'tplink', + 'previous_unique_id': None, + 'supported_features': 0, + 'translation_key': 'total_clean_time', + 'unique_id': '123456789ABCDEFGH_total_clean_time', + 'unit_of_measurement': , + }) +# --- # name: test_states[sensor.my_device_total_consumption-entry] EntityRegistryEntrySnapshot({ 'aliases': set({