mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
Handle incomplete power consumption reports in SmartThings (#140370)
This commit is contained in:
parent
13e9906929
commit
0e7a083847
@ -228,28 +228,6 @@ KEEP_CAPABILITY_QUIRK: dict[
|
||||
Capability.DEMAND_RESPONSE_LOAD_CONTROL: lambda _: True,
|
||||
}
|
||||
|
||||
POWER_CONSUMPTION_FIELDS = {
|
||||
"energy",
|
||||
"power",
|
||||
"deltaEnergy",
|
||||
"powerEnergy",
|
||||
"energySaved",
|
||||
}
|
||||
|
||||
CAPABILITY_VALIDATION: dict[
|
||||
Capability | str, Callable[[dict[Attribute | str, Status]], bool]
|
||||
] = {
|
||||
Capability.POWER_CONSUMPTION_REPORT: (
|
||||
lambda status: (
|
||||
(power_consumption := status[Attribute.POWER_CONSUMPTION].value) is not None
|
||||
and all(
|
||||
field in cast(dict, power_consumption)
|
||||
for field in POWER_CONSUMPTION_FIELDS
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def process_status(
|
||||
status: dict[str, dict[Capability | str, dict[Attribute | str, Status]]],
|
||||
@ -273,8 +251,4 @@ def process_status(
|
||||
or not KEEP_CAPABILITY_QUIRK[capability](main_component[capability])
|
||||
):
|
||||
del main_component[capability]
|
||||
for capability in list(main_component):
|
||||
if capability in CAPABILITY_VALIDATION:
|
||||
if not CAPABILITY_VALIDATION[capability](main_component[capability]):
|
||||
del main_component[capability]
|
||||
return status
|
||||
|
@ -5,9 +5,9 @@ from __future__ import annotations
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
from pysmartthings import Attribute, Capability, SmartThings
|
||||
from pysmartthings import Attribute, Capability, SmartThings, Status
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
@ -131,6 +131,7 @@ class SmartThingsSensorEntityDescription(SensorEntityDescription):
|
||||
unique_id_separator: str = "."
|
||||
capability_ignore_list: list[set[Capability]] | None = None
|
||||
options_attribute: Attribute | None = None
|
||||
exists_fn: Callable[[Status], bool] | None = None
|
||||
|
||||
|
||||
CAPABILITY_TO_SENSORS: dict[
|
||||
@ -583,6 +584,10 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
value_fn=lambda value: value["energy"] / 1000,
|
||||
suggested_display_precision=2,
|
||||
exists_fn=lambda status: (
|
||||
(value := cast(dict | None, status.value)) is not None
|
||||
and "energy" in value
|
||||
),
|
||||
),
|
||||
SmartThingsSensorEntityDescription(
|
||||
key="power_meter",
|
||||
@ -592,6 +597,10 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
value_fn=lambda value: value["power"],
|
||||
extra_state_attributes_fn=power_attributes,
|
||||
suggested_display_precision=2,
|
||||
exists_fn=lambda status: (
|
||||
(value := cast(dict | None, status.value)) is not None
|
||||
and "power" in value
|
||||
),
|
||||
),
|
||||
SmartThingsSensorEntityDescription(
|
||||
key="deltaEnergy_meter",
|
||||
@ -601,6 +610,10 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
value_fn=lambda value: value["deltaEnergy"] / 1000,
|
||||
suggested_display_precision=2,
|
||||
exists_fn=lambda status: (
|
||||
(value := cast(dict | None, status.value)) is not None
|
||||
and "deltaEnergy" in value
|
||||
),
|
||||
),
|
||||
SmartThingsSensorEntityDescription(
|
||||
key="powerEnergy_meter",
|
||||
@ -610,6 +623,10 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
value_fn=lambda value: value["powerEnergy"] / 1000,
|
||||
suggested_display_precision=2,
|
||||
exists_fn=lambda status: (
|
||||
(value := cast(dict | None, status.value)) is not None
|
||||
and "powerEnergy" in value
|
||||
),
|
||||
),
|
||||
SmartThingsSensorEntityDescription(
|
||||
key="energySaved_meter",
|
||||
@ -619,6 +636,10 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
value_fn=lambda value: value["energySaved"] / 1000,
|
||||
suggested_display_precision=2,
|
||||
exists_fn=lambda status: (
|
||||
(value := cast(dict | None, status.value)) is not None
|
||||
and "energySaved" in value
|
||||
),
|
||||
),
|
||||
]
|
||||
},
|
||||
@ -980,6 +1001,10 @@ async def async_setup_entry(
|
||||
for capability_list in description.capability_ignore_list
|
||||
)
|
||||
)
|
||||
and (
|
||||
not description.exists_fn
|
||||
or description.exists_fn(device.status[MAIN][capability][attribute])
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
@ -127,6 +127,7 @@ def mock_smartthings() -> Generator[AsyncMock]:
|
||||
"generic_ef00_v1",
|
||||
"bosch_radiator_thermostat_ii",
|
||||
"im_speaker_ai_0001",
|
||||
"tplink_p110",
|
||||
]
|
||||
)
|
||||
def device_fixture(
|
||||
|
@ -0,0 +1,46 @@
|
||||
{
|
||||
"components": {
|
||||
"main": {
|
||||
"powerConsumptionReport": {
|
||||
"powerConsumption": {
|
||||
"value": {
|
||||
"start": "2025-03-10T14:43:42.500Z",
|
||||
"end": "2025-03-10T14:59:42.500Z",
|
||||
"energy": 15720,
|
||||
"deltaEnergy": 0
|
||||
},
|
||||
"timestamp": "2025-03-10T14:59:50.010Z"
|
||||
}
|
||||
},
|
||||
"healthCheck": {
|
||||
"checkInterval": {
|
||||
"value": 60,
|
||||
"unit": "s",
|
||||
"data": {
|
||||
"deviceScheme": "UNTRACKED",
|
||||
"protocol": "cloud"
|
||||
},
|
||||
"timestamp": "2024-03-07T21:14:59.839Z"
|
||||
},
|
||||
"healthStatus": {
|
||||
"value": null
|
||||
},
|
||||
"DeviceWatch-Enroll": {
|
||||
"value": null
|
||||
},
|
||||
"DeviceWatch-DeviceStatus": {
|
||||
"value": "online",
|
||||
"data": {},
|
||||
"timestamp": "2025-03-10T14:14:37.232Z"
|
||||
}
|
||||
},
|
||||
"refresh": {},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"value": "on",
|
||||
"timestamp": "2025-03-10T14:14:37.232Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"deviceId": "6602696a-1e48-49e4-919f-69406f5b5da1",
|
||||
"name": "plug-energy-usage-report",
|
||||
"label": "Sp\u00fclmaschine",
|
||||
"manufacturerName": "0AI2",
|
||||
"presentationId": "ST_8f2be0ec-1113-46e0-ad56-3e92eb27410f",
|
||||
"deviceManufacturerCode": "TP-Link",
|
||||
"locationId": "70da36b0-bd25-410c-beed-7f0dbf658448",
|
||||
"ownerId": "be5d4173-dd49-1eee-56f5-f98306ee872c",
|
||||
"roomId": "bd13616d-b7e2-44ff-914c-eb38ea18c4b4",
|
||||
"components": [
|
||||
{
|
||||
"id": "main",
|
||||
"label": "main",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "healthCheck",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "refresh",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "switch",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "powerConsumptionReport",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "SmartPlug",
|
||||
"categoryType": "manufacturer"
|
||||
},
|
||||
{
|
||||
"name": "SmartPlug",
|
||||
"categoryType": "user"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"createTime": "2024-03-07T21:14:59.762Z",
|
||||
"profile": {
|
||||
"id": "a25b207e-cbb9-40ae-8a88-906637c22ab6"
|
||||
},
|
||||
"viper": {
|
||||
"uniqueIdentifier": "8022F7F6FE0A6EACA52B5D89C0D667352136D8C6",
|
||||
"manufacturerName": "TP-Link",
|
||||
"modelName": "P110",
|
||||
"swVersion": "1.3.1 Build 240621 Rel.162048",
|
||||
"hwVersion": "1.0",
|
||||
"endpointAppId": "viper_7ea6bb80-b876-11eb-be42-952f31ab3f7b"
|
||||
},
|
||||
"type": "VIPER",
|
||||
"restrictionTier": 0,
|
||||
"allowed": null,
|
||||
"indoorMap": {
|
||||
"coordinates": [0.0, 0.0, 0.0],
|
||||
"rotation": [0.0, 180.0, 0.0],
|
||||
"visible": false,
|
||||
"data": null
|
||||
},
|
||||
"executionContext": "CLOUD",
|
||||
"relationships": []
|
||||
}
|
||||
],
|
||||
"_links": {}
|
||||
}
|
@ -1124,6 +1124,39 @@
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[tplink_p110]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
'config_entries': <ANY>,
|
||||
'config_entries_subentries': <ANY>,
|
||||
'configuration_url': 'https://account.smartthings.com',
|
||||
'connections': set({
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'entry_type': None,
|
||||
'hw_version': '1.0',
|
||||
'id': <ANY>,
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'smartthings',
|
||||
'6602696a-1e48-49e4-919f-69406f5b5da1',
|
||||
),
|
||||
}),
|
||||
'is_new': False,
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'TP-Link',
|
||||
'model': 'P110',
|
||||
'model_id': None,
|
||||
'name': 'Spülmaschine',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': '1.3.1 Build 240621 Rel.162048',
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[vd_network_audio_002s]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': 'theater',
|
||||
|
@ -6212,6 +6212,116 @@
|
||||
'state': '15',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[tplink_p110][sensor.spulmaschine_energy-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.spulmaschine_energy',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Energy',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '6602696a-1e48-49e4-919f-69406f5b5da1.energy_meter',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[tplink_p110][sensor.spulmaschine_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Spülmaschine Energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.spulmaschine_energy',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '15.72',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[tplink_p110][sensor.spulmaschine_energy_difference-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.spulmaschine_energy_difference',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Energy difference',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'energy_difference',
|
||||
'unique_id': '6602696a-1e48-49e4-919f-69406f5b5da1.deltaEnergy_meter',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[tplink_p110][sensor.spulmaschine_energy_difference-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Spülmaschine Energy difference',
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.spulmaschine_energy_difference',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[vd_network_audio_002s][sensor.soundbar_living_media_playback_status-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
@ -516,6 +516,53 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[tplink_p110][switch.spulmaschine-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': None,
|
||||
'entity_id': 'switch.spulmaschine',
|
||||
'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': None,
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '6602696a-1e48-49e4-919f-69406f5b5da1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[tplink_p110][switch.spulmaschine-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Spülmaschine',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.spulmaschine',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[vd_network_audio_002s][switch.soundbar_living-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
Loading…
x
Reference in New Issue
Block a user