mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
Add more vacuum features for tplink (#136580)
This commit is contained in:
parent
e07e8b8706
commit
c2cbbf1e1c
@ -113,6 +113,9 @@
|
||||
"state": {
|
||||
"on": "mdi:baby-face"
|
||||
}
|
||||
},
|
||||
"carpet_boost": {
|
||||
"default": "mdi:rug"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
@ -130,6 +133,9 @@
|
||||
},
|
||||
"water_alert_timestamp": {
|
||||
"default": "mdi:clock-alert-outline"
|
||||
},
|
||||
"vacuum_error": {
|
||||
"default": "mdi:alert-circle"
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
@ -150,6 +156,9 @@
|
||||
},
|
||||
"tilt_step": {
|
||||
"default": "mdi:unfold-more-horizontal"
|
||||
},
|
||||
"clean_count": {
|
||||
"default": "mdi:counter"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -65,6 +65,10 @@ NUMBER_DESCRIPTIONS: Final = (
|
||||
key="tilt_step",
|
||||
mode=NumberMode.BOX,
|
||||
),
|
||||
TPLinkNumberEntityDescription(
|
||||
key="clean_count",
|
||||
mode=NumberMode.SLIDER,
|
||||
),
|
||||
)
|
||||
|
||||
NUMBER_DESCRIPTIONS_MAP = {desc.key: desc for desc in NUMBER_DESCRIPTIONS}
|
||||
|
@ -2,10 +2,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, cast
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
from kasa import Feature
|
||||
from kasa.smart.modules.clean import ErrorCode as VacuumError
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
DOMAIN as SENSOR_DOMAIN,
|
||||
@ -28,6 +30,9 @@ class TPLinkSensorEntityDescription(
|
||||
):
|
||||
"""Base class for a TPLink feature based sensor entity description."""
|
||||
|
||||
#: Optional callable to convert the value
|
||||
convert_fn: Callable[[Any], Any] | None = None
|
||||
|
||||
|
||||
# Coordinator is used to centralize the data updates
|
||||
PARALLEL_UPDATES = 0
|
||||
@ -115,6 +120,12 @@ SENSOR_DESCRIPTIONS: tuple[TPLinkSensorEntityDescription, ...] = (
|
||||
TPLinkSensorEntityDescription(
|
||||
key="alarm_source",
|
||||
),
|
||||
TPLinkSensorEntityDescription(
|
||||
key="vacuum_error",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=[name.lower() for name in VacuumError._member_names_],
|
||||
convert_fn=lambda x: x.name.lower(),
|
||||
),
|
||||
)
|
||||
|
||||
SENSOR_DESCRIPTIONS_MAP = {desc.key: desc for desc in SENSOR_DESCRIPTIONS}
|
||||
@ -165,6 +176,9 @@ class TPLinkSensorEntity(CoordinatedTPLinkFeatureEntity, SensorEntity):
|
||||
# We probably do not need this, when we are rounding already?
|
||||
self._attr_suggested_display_precision = self._feature.precision_hint
|
||||
|
||||
if self.entity_description.convert_fn:
|
||||
value = self.entity_description.convert_fn(value)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
from datetime import date, datetime
|
||||
|
@ -198,6 +198,23 @@
|
||||
},
|
||||
"alarm_source": {
|
||||
"name": "Alarm source"
|
||||
},
|
||||
"vacuum_error": {
|
||||
"name": "Error",
|
||||
"state": {
|
||||
"ok": "No error",
|
||||
"sidebrushstuck": "Side brush stuck",
|
||||
"mainbrushstuck": "Main brush stuck",
|
||||
"wheelblocked": "Wheel blocked",
|
||||
"trapped": "Unable to move",
|
||||
"trappedcliff": "Unable to move (cliff sensor)",
|
||||
"dustbinremoved": "Missing dust bin",
|
||||
"unabletomove": "Unable to move",
|
||||
"lidarblocked": "Lidar blocked",
|
||||
"unabletofinddock": "Unable to find dock",
|
||||
"batterylow": "Low on battery",
|
||||
"unknowninternal": "Unknown error, report to upstream"
|
||||
}
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
@ -233,6 +250,9 @@
|
||||
},
|
||||
"baby_cry_detection": {
|
||||
"name": "Baby cry detection"
|
||||
},
|
||||
"carpet_boost": {
|
||||
"name": "Carpet boost"
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
@ -253,6 +273,9 @@
|
||||
},
|
||||
"tilt_step": {
|
||||
"name": "Tilt degrees"
|
||||
},
|
||||
"clean_count": {
|
||||
"name": "Clean count"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -74,6 +74,9 @@ SWITCH_DESCRIPTIONS: tuple[TPLinkSwitchEntityDescription, ...] = (
|
||||
TPLinkSwitchEntityDescription(
|
||||
key="baby_cry_detection",
|
||||
),
|
||||
TPLinkSwitchEntityDescription(
|
||||
key="carpet_boost",
|
||||
),
|
||||
)
|
||||
|
||||
SWITCH_DESCRIPTIONS_MAP = {desc.key: desc for desc in SWITCH_DESCRIPTIONS}
|
||||
|
@ -60,6 +60,7 @@ def _load_feature_fixtures():
|
||||
|
||||
|
||||
FEATURES_FIXTURE = _load_feature_fixtures()
|
||||
FIXTURE_ENUM_TYPES = {"CleanErrorCode": ErrorCode}
|
||||
|
||||
|
||||
async def setup_platform_for_device(
|
||||
@ -275,6 +276,10 @@ 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
|
||||
|
||||
else:
|
||||
assert require_fixture is False, (
|
||||
f"No fixture defined for feature {id} and require_fixture is True"
|
||||
|
@ -371,6 +371,22 @@
|
||||
"type": "Number",
|
||||
"category": "Config"
|
||||
},
|
||||
"clean_count": {
|
||||
"value": 1,
|
||||
"type": "Number",
|
||||
"category": "Config"
|
||||
},
|
||||
"carpet_boost": {
|
||||
"value": true,
|
||||
"type": "Switch",
|
||||
"category": "Config"
|
||||
},
|
||||
"vacuum_error": {
|
||||
"value": 0,
|
||||
"type": "Sensor",
|
||||
"category": "Info",
|
||||
"enum_type": "CleanErrorCode"
|
||||
},
|
||||
"pair": {
|
||||
"value": "<Action>",
|
||||
"type": "Action",
|
||||
|
@ -35,6 +35,61 @@
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_states[number.my_device_clean_count-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 65536,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.SLIDER: 'slider'>,
|
||||
'step': 1.0,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.my_device_clean_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': 'Clean count',
|
||||
'platform': 'tplink',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'clean_count',
|
||||
'unique_id': '123456789ABCDEFGH_clean_count',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_states[number.my_device_clean_count-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'my_device Clean count',
|
||||
'max': 65536,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.SLIDER: 'slider'>,
|
||||
'step': 1.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.my_device_clean_count',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '1',
|
||||
})
|
||||
# ---
|
||||
# name: test_states[number.my_device_pan_degrees-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
@ -307,6 +307,82 @@
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_states[sensor.my_device_error-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'ok',
|
||||
'sidebrushstuck',
|
||||
'mainbrushstuck',
|
||||
'wheelblocked',
|
||||
'trapped',
|
||||
'trappedcliff',
|
||||
'dustbinremoved',
|
||||
'unabletomove',
|
||||
'lidarblocked',
|
||||
'unabletofinddock',
|
||||
'batterylow',
|
||||
'unknowninternal',
|
||||
]),
|
||||
}),
|
||||
'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_error',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Error',
|
||||
'platform': 'tplink',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'vacuum_error',
|
||||
'unique_id': '123456789ABCDEFGH_vacuum_error',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_states[sensor.my_device_error-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'my_device Error',
|
||||
'options': list([
|
||||
'ok',
|
||||
'sidebrushstuck',
|
||||
'mainbrushstuck',
|
||||
'wheelblocked',
|
||||
'trapped',
|
||||
'trappedcliff',
|
||||
'dustbinremoved',
|
||||
'unabletomove',
|
||||
'lidarblocked',
|
||||
'unabletofinddock',
|
||||
'batterylow',
|
||||
'unknowninternal',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.my_device_error',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'ok',
|
||||
})
|
||||
# ---
|
||||
# name: test_states[sensor.my_device_humidity-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
@ -219,6 +219,52 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_states[switch.my_device_carpet_boost-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.my_device_carpet_boost',
|
||||
'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': 'Carpet boost',
|
||||
'platform': 'tplink',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'carpet_boost',
|
||||
'unique_id': '123456789ABCDEFGH_carpet_boost',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_states[switch.my_device_carpet_boost-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'my_device Carpet boost',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.my_device_carpet_boost',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_states[switch.my_device_child_lock-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
Loading…
x
Reference in New Issue
Block a user