mirror of
https://github.com/Electric-Special/ha-core.git
synced 2026-03-21 07:05:48 +01:00
Log warning for incorrect Tuya enum values (#156541)
This commit is contained in:
@@ -14,6 +14,7 @@ from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import TuyaConfigEntry
|
||||
from .const import DOMAIN, DPCode
|
||||
from .models import DEVICE_WARNINGS
|
||||
|
||||
_REDACTED_DPCODES = {
|
||||
DPCode.ALARM_MESSAGE,
|
||||
@@ -97,6 +98,7 @@ def _async_device_as_dict(
|
||||
"home_assistant": {},
|
||||
"set_up": device.set_up,
|
||||
"support_local": device.support_local,
|
||||
"warnings": DEVICE_WARNINGS.get(device.id),
|
||||
}
|
||||
|
||||
# Gather Tuya states
|
||||
|
||||
@@ -11,9 +11,27 @@ from tuya_sharing import CustomerDevice
|
||||
|
||||
from homeassistant.util.json import json_loads, json_loads_object
|
||||
|
||||
from .const import DPCode, DPType
|
||||
from .const import LOGGER, DPCode, DPType
|
||||
from .util import parse_dptype, remap_value
|
||||
|
||||
# Dictionary to track logged warnings to avoid spamming logs
|
||||
# Keyed by device ID
|
||||
DEVICE_WARNINGS: dict[str, set[str]] = {}
|
||||
|
||||
|
||||
def _should_log_warning(device_id: str, warning_key: str) -> bool:
|
||||
"""Check if a warning has already been logged for a device and add it if not.
|
||||
|
||||
Returns: False if the warning was already logged, True if it was added.
|
||||
"""
|
||||
if (device_warnings := DEVICE_WARNINGS.get(device_id)) is None:
|
||||
device_warnings = set()
|
||||
DEVICE_WARNINGS[device_id] = device_warnings
|
||||
if warning_key in device_warnings:
|
||||
return False
|
||||
DEVICE_WARNINGS[device_id].add(warning_key)
|
||||
return True
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class TypeInformation:
|
||||
@@ -285,11 +303,22 @@ class DPCodeEnumWrapper(DPCodeTypeInformationWrapper[EnumTypeData]):
|
||||
Values outside of the list defined by the Enum type information will
|
||||
return None.
|
||||
"""
|
||||
if (
|
||||
raw_value := self._read_device_status_raw(device)
|
||||
) in self.type_information.range:
|
||||
return raw_value
|
||||
return None
|
||||
if (raw_value := self._read_device_status_raw(device)) is None:
|
||||
return None
|
||||
if raw_value not in self.type_information.range:
|
||||
if _should_log_warning(
|
||||
device.id, f"enum_out_range|{self.dpcode}|{raw_value}"
|
||||
):
|
||||
LOGGER.warning(
|
||||
"Found invalid enum value `%s` for datapoint `%s` in product id `%s`,"
|
||||
" expected one of `%s`; please report this defect to Tuya support",
|
||||
raw_value,
|
||||
self.dpcode,
|
||||
device.product_id,
|
||||
self.type_information.range,
|
||||
)
|
||||
return None
|
||||
return raw_value
|
||||
|
||||
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
|
||||
"""Convert a Home Assistant value back to a raw device value."""
|
||||
|
||||
@@ -298,6 +298,7 @@
|
||||
'terminal_id': '7cd96aff-6ec8-4006-b093-3dbff7947591',
|
||||
'time_zone': '+02:00',
|
||||
'update_time': '2024-12-02T20:08:56+00:00',
|
||||
'warnings': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_device_diagnostics[rqbj_4iqe2hsfyd86kwwc]
|
||||
@@ -413,6 +414,167 @@
|
||||
'terminal_id': '7cd96aff-6ec8-4006-b093-3dbff7947591',
|
||||
'time_zone': '-04:00',
|
||||
'update_time': '2025-06-24T20:33:10+00:00',
|
||||
'warnings': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_device_diagnostics[tdq_9htyiowaf5rtdhrv]
|
||||
dict({
|
||||
'active_time': '2024-09-08T13:46:46+00:00',
|
||||
'category': 'tdq',
|
||||
'create_time': '2024-09-08T13:46:46+00:00',
|
||||
'disabled_by': None,
|
||||
'disabled_polling': False,
|
||||
'endpoint': 'https://apigw.tuyaeu.com',
|
||||
'function': dict({
|
||||
'countdown_1': dict({
|
||||
'type': 'Integer',
|
||||
'value': '{"unit":"s","min":0,"max":86400,"scale":0,"step":1}',
|
||||
}),
|
||||
'cycle_time': dict({
|
||||
'type': 'String',
|
||||
'value': '{"maxlen":255}',
|
||||
}),
|
||||
'random_time': dict({
|
||||
'type': 'String',
|
||||
'value': '{"maxlen":255}',
|
||||
}),
|
||||
'relay_status': dict({
|
||||
'type': 'Enum',
|
||||
'value': '{"range":["0","1","2"]}',
|
||||
}),
|
||||
'remote_add': dict({
|
||||
'type': 'Raw',
|
||||
'value': '{}',
|
||||
}),
|
||||
'remote_list': dict({
|
||||
'type': 'Raw',
|
||||
'value': '{}',
|
||||
}),
|
||||
'switch_1': dict({
|
||||
'type': 'Boolean',
|
||||
'value': '{}',
|
||||
}),
|
||||
'switch_inching': dict({
|
||||
'type': 'String',
|
||||
'value': '{"maxlen":255}',
|
||||
}),
|
||||
'switch_type': dict({
|
||||
'type': 'Enum',
|
||||
'value': '{"range":["flip","sync","button"]}',
|
||||
}),
|
||||
}),
|
||||
'home_assistant': dict({
|
||||
'disabled': False,
|
||||
'disabled_by': None,
|
||||
'entities': list([
|
||||
dict({
|
||||
'device_class': None,
|
||||
'disabled': False,
|
||||
'disabled_by': None,
|
||||
'entity_category': 'config',
|
||||
'icon': None,
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'friendly_name': 'Framboisiers Power on behavior',
|
||||
'options': list([
|
||||
'0',
|
||||
'1',
|
||||
'2',
|
||||
]),
|
||||
}),
|
||||
'entity_id': 'select.framboisiers_power_on_behavior',
|
||||
'state': 'unknown',
|
||||
}),
|
||||
'unit_of_measurement': None,
|
||||
}),
|
||||
dict({
|
||||
'device_class': None,
|
||||
'disabled': False,
|
||||
'disabled_by': None,
|
||||
'entity_category': None,
|
||||
'icon': None,
|
||||
'original_device_class': 'outlet',
|
||||
'original_icon': None,
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'device_class': 'outlet',
|
||||
'friendly_name': 'Framboisiers Switch 1',
|
||||
}),
|
||||
'entity_id': 'switch.framboisiers_switch_1',
|
||||
'state': 'off',
|
||||
}),
|
||||
'unit_of_measurement': None,
|
||||
}),
|
||||
]),
|
||||
'name': 'Framboisiers',
|
||||
'name_by_user': None,
|
||||
}),
|
||||
'id': 'vrhdtr5fawoiyth9qdt',
|
||||
'mqtt_connected': True,
|
||||
'name': 'Framboisiers',
|
||||
'online': True,
|
||||
'product_id': '9htyiowaf5rtdhrv',
|
||||
'product_name': '1-433',
|
||||
'set_up': True,
|
||||
'status': dict({
|
||||
'countdown_1': 0,
|
||||
'cycle_time': '',
|
||||
'random_time': '',
|
||||
'relay_status': 2,
|
||||
'remote_add': '',
|
||||
'remote_list': 'AA==',
|
||||
'switch_1': False,
|
||||
'switch_inching': 'AAAC',
|
||||
'switch_type': 'button',
|
||||
}),
|
||||
'status_range': dict({
|
||||
'countdown_1': dict({
|
||||
'type': 'Integer',
|
||||
'value': '{"unit":"s","min":0,"max":86400,"scale":0,"step":1}',
|
||||
}),
|
||||
'cycle_time': dict({
|
||||
'type': 'String',
|
||||
'value': '{"maxlen":255}',
|
||||
}),
|
||||
'random_time': dict({
|
||||
'type': 'String',
|
||||
'value': '{"maxlen":255}',
|
||||
}),
|
||||
'relay_status': dict({
|
||||
'type': 'Enum',
|
||||
'value': '{"range":["0","1","2"]}',
|
||||
}),
|
||||
'remote_add': dict({
|
||||
'type': 'Raw',
|
||||
'value': '{}',
|
||||
}),
|
||||
'remote_list': dict({
|
||||
'type': 'Raw',
|
||||
'value': '{}',
|
||||
}),
|
||||
'switch_1': dict({
|
||||
'type': 'Boolean',
|
||||
'value': '{}',
|
||||
}),
|
||||
'switch_inching': dict({
|
||||
'type': 'String',
|
||||
'value': '{"maxlen":255}',
|
||||
}),
|
||||
'switch_type': dict({
|
||||
'type': 'Enum',
|
||||
'value': '{"range":["flip","sync","button"]}',
|
||||
}),
|
||||
}),
|
||||
'sub': False,
|
||||
'support_local': True,
|
||||
'terminal_id': '7cd96aff-6ec8-4006-b093-3dbff7947591',
|
||||
'time_zone': '+02:00',
|
||||
'update_time': '2024-09-08T13:46:46+00:00',
|
||||
'warnings': list([
|
||||
'enum_out_range|relay_status|2',
|
||||
]),
|
||||
})
|
||||
# ---
|
||||
# name: test_entry_diagnostics[rqbj_4iqe2hsfyd86kwwc]
|
||||
@@ -525,6 +687,7 @@
|
||||
'support_local': True,
|
||||
'time_zone': '-04:00',
|
||||
'update_time': '2025-06-24T20:33:10+00:00',
|
||||
'warnings': None,
|
||||
}),
|
||||
]),
|
||||
'disabled_by': None,
|
||||
|
||||
@@ -45,8 +45,9 @@ async def test_entry_diagnostics(
|
||||
@pytest.mark.parametrize(
|
||||
"mock_device_code",
|
||||
[
|
||||
"mal_gyitctrjj1kefxp2",
|
||||
"rqbj_4iqe2hsfyd86kwwc",
|
||||
"mal_gyitctrjj1kefxp2", # with redacted dpcodes
|
||||
"tdq_9htyiowaf5rtdhrv", # with bad enum warnings
|
||||
],
|
||||
)
|
||||
async def test_device_diagnostics(
|
||||
|
||||
Reference in New Issue
Block a user