Add cleaning statistics for tplink (#135784)

Co-authored-by: Steven B <51370195+sdb9696@users.noreply.github.com>
This commit is contained in:
Teemu R.
2025-01-29 15:56:47 +01:00
committed by GitHub
parent c7176f6849
commit 653ff47171
6 changed files with 497 additions and 3 deletions

View File

@@ -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,
}

View File

@@ -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,

View File

@@ -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"
},

View File

@@ -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"

View File

@@ -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",

View File

@@ -238,6 +238,155 @@
'unit_of_measurement': <UnitOfTime.HOURS: 'h'>,
})
# ---
# name: test_states[sensor.my_device_cleaning_area-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.my_device_cleaning_area',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfArea.SQUARE_METERS: 'm²'>,
}),
}),
'original_device_class': <SensorDeviceClass.AREA: 'area'>,
'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': <UnitOfArea.SQUARE_METERS: 'm²'>,
})
# ---
# name: test_states[sensor.my_device_cleaning_area-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'area',
'friendly_name': 'my_device Cleaning area',
'unit_of_measurement': <UnitOfArea.SQUARE_METERS: 'm²'>,
}),
'context': <ANY>,
'entity_id': 'sensor.my_device_cleaning_area',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0.2',
})
# ---
# name: test_states[sensor.my_device_cleaning_progress-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.my_device_cleaning_progress',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <ANY>,
'entity_id': 'sensor.my_device_cleaning_progress',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '30',
})
# ---
# name: test_states[sensor.my_device_cleaning_time-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.my_device_cleaning_time',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}),
}),
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
'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': <UnitOfTime.MINUTES: 'min'>,
})
# ---
# name: test_states[sensor.my_device_cleaning_time-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'duration',
'friendly_name': 'my_device Cleaning time',
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}),
'context': <ANY>,
'entity_id': 'sensor.my_device_cleaning_time',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'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': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': <RegistryEntryDisabler.INTEGRATION: 'integration'>,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.my_device_last_clean_start',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
'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': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': <RegistryEntryDisabler.INTEGRATION: 'integration'>,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.my_device_last_cleaned_area',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfArea.SQUARE_METERS: 'm²'>,
}),
}),
'original_device_class': <SensorDeviceClass.AREA: 'area'>,
'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': <UnitOfArea.SQUARE_METERS: 'm²'>,
})
# ---
# name: test_states[sensor.my_device_last_cleaned_time-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': <RegistryEntryDisabler.INTEGRATION: 'integration'>,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.my_device_last_cleaned_time',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}),
}),
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
'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': <UnitOfTime.MINUTES: 'min'>,
})
# ---
# 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': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': <RegistryEntryDisabler.INTEGRATION: 'integration'>,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.my_device_total_cleaning_area',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfArea.SQUARE_METERS: 'm²'>,
}),
}),
'original_device_class': <SensorDeviceClass.AREA: 'area'>,
'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': <UnitOfArea.SQUARE_METERS: 'm²'>,
})
# ---
# name: test_states[sensor.my_device_total_cleaning_count-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': <RegistryEntryDisabler.INTEGRATION: 'integration'>,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.my_device_total_cleaning_count',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': <RegistryEntryDisabler.INTEGRATION: 'integration'>,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.my_device_total_cleaning_time',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
'sensor.private': dict({
'suggested_unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
}),
}),
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
'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': <UnitOfTime.MINUTES: 'min'>,
})
# ---
# name: test_states[sensor.my_device_total_consumption-entry]
EntityRegistryEntrySnapshot({
'aliases': set({