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": {
|
"state": {
|
||||||
"on": "mdi:baby-face"
|
"on": "mdi:baby-face"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"carpet_boost": {
|
||||||
|
"default": "mdi:rug"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sensor": {
|
"sensor": {
|
||||||
@ -130,6 +133,9 @@
|
|||||||
},
|
},
|
||||||
"water_alert_timestamp": {
|
"water_alert_timestamp": {
|
||||||
"default": "mdi:clock-alert-outline"
|
"default": "mdi:clock-alert-outline"
|
||||||
|
},
|
||||||
|
"vacuum_error": {
|
||||||
|
"default": "mdi:alert-circle"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"number": {
|
"number": {
|
||||||
@ -150,6 +156,9 @@
|
|||||||
},
|
},
|
||||||
"tilt_step": {
|
"tilt_step": {
|
||||||
"default": "mdi:unfold-more-horizontal"
|
"default": "mdi:unfold-more-horizontal"
|
||||||
|
},
|
||||||
|
"clean_count": {
|
||||||
|
"default": "mdi:counter"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -65,6 +65,10 @@ NUMBER_DESCRIPTIONS: Final = (
|
|||||||
key="tilt_step",
|
key="tilt_step",
|
||||||
mode=NumberMode.BOX,
|
mode=NumberMode.BOX,
|
||||||
),
|
),
|
||||||
|
TPLinkNumberEntityDescription(
|
||||||
|
key="clean_count",
|
||||||
|
mode=NumberMode.SLIDER,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
NUMBER_DESCRIPTIONS_MAP = {desc.key: desc for desc in NUMBER_DESCRIPTIONS}
|
NUMBER_DESCRIPTIONS_MAP = {desc.key: desc for desc in NUMBER_DESCRIPTIONS}
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING, cast
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
|
|
||||||
from kasa import Feature
|
from kasa import Feature
|
||||||
|
from kasa.smart.modules.clean import ErrorCode as VacuumError
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
DOMAIN as SENSOR_DOMAIN,
|
DOMAIN as SENSOR_DOMAIN,
|
||||||
@ -28,6 +30,9 @@ class TPLinkSensorEntityDescription(
|
|||||||
):
|
):
|
||||||
"""Base class for a TPLink feature based sensor entity description."""
|
"""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
|
# Coordinator is used to centralize the data updates
|
||||||
PARALLEL_UPDATES = 0
|
PARALLEL_UPDATES = 0
|
||||||
@ -115,6 +120,12 @@ SENSOR_DESCRIPTIONS: tuple[TPLinkSensorEntityDescription, ...] = (
|
|||||||
TPLinkSensorEntityDescription(
|
TPLinkSensorEntityDescription(
|
||||||
key="alarm_source",
|
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}
|
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?
|
# We probably do not need this, when we are rounding already?
|
||||||
self._attr_suggested_display_precision = self._feature.precision_hint
|
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:
|
if TYPE_CHECKING:
|
||||||
# pylint: disable-next=import-outside-toplevel
|
# pylint: disable-next=import-outside-toplevel
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
|
@ -198,6 +198,23 @@
|
|||||||
},
|
},
|
||||||
"alarm_source": {
|
"alarm_source": {
|
||||||
"name": "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": {
|
"switch": {
|
||||||
@ -233,6 +250,9 @@
|
|||||||
},
|
},
|
||||||
"baby_cry_detection": {
|
"baby_cry_detection": {
|
||||||
"name": "Baby cry detection"
|
"name": "Baby cry detection"
|
||||||
|
},
|
||||||
|
"carpet_boost": {
|
||||||
|
"name": "Carpet boost"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"number": {
|
"number": {
|
||||||
@ -253,6 +273,9 @@
|
|||||||
},
|
},
|
||||||
"tilt_step": {
|
"tilt_step": {
|
||||||
"name": "Tilt degrees"
|
"name": "Tilt degrees"
|
||||||
|
},
|
||||||
|
"clean_count": {
|
||||||
|
"name": "Clean count"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -74,6 +74,9 @@ SWITCH_DESCRIPTIONS: tuple[TPLinkSwitchEntityDescription, ...] = (
|
|||||||
TPLinkSwitchEntityDescription(
|
TPLinkSwitchEntityDescription(
|
||||||
key="baby_cry_detection",
|
key="baby_cry_detection",
|
||||||
),
|
),
|
||||||
|
TPLinkSwitchEntityDescription(
|
||||||
|
key="carpet_boost",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
SWITCH_DESCRIPTIONS_MAP = {desc.key: desc for desc in SWITCH_DESCRIPTIONS}
|
SWITCH_DESCRIPTIONS_MAP = {desc.key: desc for desc in SWITCH_DESCRIPTIONS}
|
||||||
|
@ -60,6 +60,7 @@ def _load_feature_fixtures():
|
|||||||
|
|
||||||
|
|
||||||
FEATURES_FIXTURE = _load_feature_fixtures()
|
FEATURES_FIXTURE = _load_feature_fixtures()
|
||||||
|
FIXTURE_ENUM_TYPES = {"CleanErrorCode": ErrorCode}
|
||||||
|
|
||||||
|
|
||||||
async def setup_platform_for_device(
|
async def setup_platform_for_device(
|
||||||
@ -275,6 +276,10 @@ def _mocked_feature(
|
|||||||
if fixture := FEATURES_FIXTURE.get(id):
|
if fixture := FEATURES_FIXTURE.get(id):
|
||||||
# copy the fixture so tests do not interfere with each other
|
# copy the fixture so tests do not interfere with each other
|
||||||
fixture = dict(fixture)
|
fixture = dict(fixture)
|
||||||
|
if enum_type := fixture.get("enum_type"):
|
||||||
|
val = FIXTURE_ENUM_TYPES[enum_type](fixture["value"])
|
||||||
|
fixture["value"] = val
|
||||||
|
|
||||||
else:
|
else:
|
||||||
assert require_fixture is False, (
|
assert require_fixture is False, (
|
||||||
f"No fixture defined for feature {id} and require_fixture is True"
|
f"No fixture defined for feature {id} and require_fixture is True"
|
||||||
|
@ -371,6 +371,22 @@
|
|||||||
"type": "Number",
|
"type": "Number",
|
||||||
"category": "Config"
|
"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": {
|
"pair": {
|
||||||
"value": "<Action>",
|
"value": "<Action>",
|
||||||
"type": "Action",
|
"type": "Action",
|
||||||
|
@ -35,6 +35,61 @@
|
|||||||
'via_device_id': None,
|
'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]
|
# name: test_states[number.my_device_pan_degrees-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
@ -307,6 +307,82 @@
|
|||||||
'unit_of_measurement': None,
|
'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]
|
# name: test_states[sensor.my_device_humidity-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
@ -219,6 +219,52 @@
|
|||||||
'state': 'on',
|
'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]
|
# name: test_states[switch.my_device_child_lock-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user