From 4696e428cf8bdd13a80563e36d79210e23bd3adb Mon Sep 17 00:00:00 2001 From: LG-ThinQ-Integration Date: Thu, 14 May 2026 23:37:33 +0900 Subject: [PATCH] Fix ValueError for non-numeric value in LG ThinQ (#166300) Co-authored-by: YunseonPark-LGE --- homeassistant/components/lg_thinq/sensor.py | 69 ++++- tests/components/lg_thinq/conftest.py | 1 + .../fixtures/kimchi_refrigerator/device.json | 9 + .../kimchi_refrigerator/energy_profile.json | 6 + .../fixtures/kimchi_refrigerator/profile.json | 29 ++ .../fixtures/kimchi_refrigerator/status.json | 12 + .../lg_thinq/snapshots/test_sensor.ambr | 287 ++++++++++++++++++ tests/components/lg_thinq/test_sensor.py | 2 +- tests/components/lg_thinq/test_switch.py | 5 + 9 files changed, 414 insertions(+), 6 deletions(-) create mode 100644 tests/components/lg_thinq/fixtures/kimchi_refrigerator/device.json create mode 100644 tests/components/lg_thinq/fixtures/kimchi_refrigerator/energy_profile.json create mode 100644 tests/components/lg_thinq/fixtures/kimchi_refrigerator/profile.json create mode 100644 tests/components/lg_thinq/fixtures/kimchi_refrigerator/status.json diff --git a/homeassistant/components/lg_thinq/sensor.py b/homeassistant/components/lg_thinq/sensor.py index d9e60cd6a507..8718f880168d 100644 --- a/homeassistant/components/lg_thinq/sensor.py +++ b/homeassistant/components/lg_thinq/sensor.py @@ -518,10 +518,6 @@ DEVICE_TYPE_SENSOR_MAP: dict[DeviceType, tuple[SensorEntityDescription, ...]] = ), DeviceType.KIMCHI_REFRIGERATOR: ( REFRIGERATION_SENSOR_DESC[ThinQProperty.FRESH_AIR_FILTER], - SensorEntityDescription( - key=ThinQProperty.TARGET_TEMPERATURE, - translation_key=ThinQProperty.TARGET_TEMPERATURE, - ), ), DeviceType.MICROWAVE_OVEN: (RUN_STATE_SENSOR_DESC[ThinQProperty.CURRENT_STATE],), DeviceType.OVEN: ( @@ -592,6 +588,17 @@ DEVICE_TYPE_SENSOR_MAP: dict[DeviceType, tuple[SensorEntityDescription, ...]] = } +ENUM_TEMPERATURE_SENSOR_MAP: dict[DeviceType, tuple[SensorEntityDescription, ...]] = { + DeviceType.KIMCHI_REFRIGERATOR: ( + SensorEntityDescription( + key=ThinQProperty.TARGET_TEMPERATURE, + native_unit_of_measurement=UnitOfTemperature.CELSIUS, + translation_key=ThinQProperty.TARGET_TEMPERATURE, + ), + ), +} + + @dataclass(frozen=True, kw_only=True) class ThinQEnergySensorEntityDescription(SensorEntityDescription): """Describes ThinQ energy sensor entity.""" @@ -639,7 +646,9 @@ async def async_setup_entry( async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Set up an entry for sensor platform.""" - entities: list[ThinQSensorEntity | ThinQEnergySensorEntity] = [] + entities: list[ + ThinQSensorEntity | ThinQEnergySensorEntity | ThinQEnumTempSensorEntity + ] = [] for coordinator in entry.runtime_data.coordinators.values(): if ( descriptions := DEVICE_TYPE_SENSOR_MAP.get( @@ -661,6 +670,21 @@ async def async_setup_entry( ), ) ) + + if ( + descriptions := ENUM_TEMPERATURE_SENSOR_MAP.get( + coordinator.api.device.device_type + ) + ) is not None: + for description in descriptions: + entities.extend( + ThinQEnumTempSensorEntity(coordinator, description, property_id) + for property_id in coordinator.api.get_active_idx( + description.key, + ActiveMode.READ_ONLY, + ) + ) + for energy_description in ENERGY_USAGE_SENSORS: entities.extend( ThinQEnergySensorEntity( @@ -860,3 +884,38 @@ class ThinQEnergySensorEntity(ThinQEntity, SensorEntity): self.async_update, next_update, ) + + +class ThinQEnumTempSensorEntity(ThinQEntity, SensorEntity): + """Represent a thinq sensor platform.""" + + def __init__( + self, + coordinator: DeviceDataUpdateCoordinator, + entity_description: SensorEntityDescription, + property_id: str, + ) -> None: + """Initialize a sensor entity.""" + super().__init__(coordinator, entity_description, property_id) + + if self.data.options: + # some kimchi refrigerator's target temperature have data in the form of string with enum options. + # Set options to display the correct value in the UI. + self._attr_options = self.data.options + self._attr_device_class = SensorDeviceClass.ENUM + self._attr_native_unit_of_measurement = None + + def _update_status(self) -> None: + """Update status itself.""" + super()._update_status() + self._attr_native_value = self.data.value + + _LOGGER.debug( + "[%s:%s] update status: %s -> %s, options:%s, unit:%s", + self.coordinator.device_name, + self.property_id, + self.data.value, + self.native_value, + self.options, + self.native_unit_of_measurement, + ) diff --git a/tests/components/lg_thinq/conftest.py b/tests/components/lg_thinq/conftest.py index 1a5bafe77d08..fb8faf7d0697 100644 --- a/tests/components/lg_thinq/conftest.py +++ b/tests/components/lg_thinq/conftest.py @@ -117,6 +117,7 @@ def mock_thinq_mqtt_client() -> Generator[None]: "air_conditioner", "washer", "dehumidifier", + "kimchi_refrigerator", ] ) def device_fixture(request: pytest.FixtureRequest) -> Generator[str]: diff --git a/tests/components/lg_thinq/fixtures/kimchi_refrigerator/device.json b/tests/components/lg_thinq/fixtures/kimchi_refrigerator/device.json new file mode 100644 index 000000000000..9d58a14132f8 --- /dev/null +++ b/tests/components/lg_thinq/fixtures/kimchi_refrigerator/device.json @@ -0,0 +1,9 @@ +{ + "deviceId": "MW2-FD94EF23-EF86-4D65-92AB-5399A90C228F", + "deviceInfo": { + "deviceType": "DEVICE_KIMCHI_REFRIGERATOR", + "modelName": "2REB1KRS11__H", + "alias": "Test kimchi refrigerator", + "reportable": true + } +} diff --git a/tests/components/lg_thinq/fixtures/kimchi_refrigerator/energy_profile.json b/tests/components/lg_thinq/fixtures/kimchi_refrigerator/energy_profile.json new file mode 100644 index 000000000000..b27926136448 --- /dev/null +++ b/tests/components/lg_thinq/fixtures/kimchi_refrigerator/energy_profile.json @@ -0,0 +1,6 @@ +{ + "resultCode": "0000", + "result": { + "property": ["energyUsage"] + } +} diff --git a/tests/components/lg_thinq/fixtures/kimchi_refrigerator/profile.json b/tests/components/lg_thinq/fixtures/kimchi_refrigerator/profile.json new file mode 100644 index 000000000000..da86bfe305d4 --- /dev/null +++ b/tests/components/lg_thinq/fixtures/kimchi_refrigerator/profile.json @@ -0,0 +1,29 @@ +{ + "notification": { + "push": ["DOOR_IS_OPEN"] + }, + "property": { + "temperature": [ + { + "locationName": "TOP", + "targetTemperature": { + "mode": ["r"], + "type": "enum", + "value": { + "r": ["TEMPERATURE_NUMBER"] + } + } + }, + { + "locationName": "MIDDLE", + "targetTemperature": { + "mode": ["r"], + "type": "enum", + "value": { + "r": ["KIMCHI", "MEAT_FISH", "VEGETABLE_FRUIT"] + } + } + } + ] + } +} diff --git a/tests/components/lg_thinq/fixtures/kimchi_refrigerator/status.json b/tests/components/lg_thinq/fixtures/kimchi_refrigerator/status.json new file mode 100644 index 000000000000..9c89f41729d2 --- /dev/null +++ b/tests/components/lg_thinq/fixtures/kimchi_refrigerator/status.json @@ -0,0 +1,12 @@ +{ + "temperature": [ + { + "locationName": "TOP", + "targetTemperature": 6 + }, + { + "locationName": "MIDDLE", + "targetTemperature": "KIMCHI" + } + ] +} diff --git a/tests/components/lg_thinq/snapshots/test_sensor.ambr b/tests/components/lg_thinq/snapshots/test_sensor.ambr index e618f9a6462b..50d53c9d3b4e 100644 --- a/tests/components/lg_thinq/snapshots/test_sensor.ambr +++ b/tests/components/lg_thinq/snapshots/test_sensor.ambr @@ -605,3 +605,290 @@ 'state': '2024-10-10T13:14:00+00:00', }) # --- +# name: test_sensor_entities[kimchi_refrigerator][sensor.test_kimchi_refrigerator_energy_last_month-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_kimchi_refrigerator_energy_last_month', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Energy last month', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Energy last month', + 'platform': 'lg_thinq', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'energy_usage_last_month', + 'unique_id': 'MW2-FD94EF23-EF86-4D65-92AB-5399A90C228F_energyUsage_last_month', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor_entities[kimchi_refrigerator][sensor.test_kimchi_refrigerator_energy_last_month-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Test kimchi refrigerator Energy last month', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_kimchi_refrigerator_energy_last_month', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor_entities[kimchi_refrigerator][sensor.test_kimchi_refrigerator_energy_this_month-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_kimchi_refrigerator_energy_this_month', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Energy this month', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Energy this month', + 'platform': 'lg_thinq', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'energy_usage_this_month', + 'unique_id': 'MW2-FD94EF23-EF86-4D65-92AB-5399A90C228F_energyUsage_this_month', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor_entities[kimchi_refrigerator][sensor.test_kimchi_refrigerator_energy_this_month-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Test kimchi refrigerator Energy this month', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_kimchi_refrigerator_energy_this_month', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor_entities[kimchi_refrigerator][sensor.test_kimchi_refrigerator_energy_yesterday-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_kimchi_refrigerator_energy_yesterday', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Energy yesterday', + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 0, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Energy yesterday', + 'platform': 'lg_thinq', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'energy_usage_yesterday', + 'unique_id': 'MW2-FD94EF23-EF86-4D65-92AB-5399A90C228F_energyUsage_yesterday', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor_entities[kimchi_refrigerator][sensor.test_kimchi_refrigerator_energy_yesterday-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Test kimchi refrigerator Energy yesterday', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_kimchi_refrigerator_energy_yesterday', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor_entities[kimchi_refrigerator][sensor.test_kimchi_refrigerator_middle_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'kimchi', + 'meat_fish', + 'vegetable_fruit', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_kimchi_refrigerator_middle_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'middle temperature', + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'middle temperature', + 'platform': 'lg_thinq', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'target_temperature_for_location', + 'unique_id': 'MW2-FD94EF23-EF86-4D65-92AB-5399A90C228F_middle_target_temperature', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_entities[kimchi_refrigerator][sensor.test_kimchi_refrigerator_middle_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Test kimchi refrigerator middle temperature', + 'options': list([ + 'kimchi', + 'meat_fish', + 'vegetable_fruit', + ]), + }), + 'context': , + 'entity_id': 'sensor.test_kimchi_refrigerator_middle_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'kimchi', + }) +# --- +# name: test_sensor_entities[kimchi_refrigerator][sensor.test_kimchi_refrigerator_top_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_kimchi_refrigerator_top_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'top temperature', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'top temperature', + 'platform': 'lg_thinq', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'target_temperature_for_location', + 'unique_id': 'MW2-FD94EF23-EF86-4D65-92AB-5399A90C228F_top_target_temperature', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor_entities[kimchi_refrigerator][sensor.test_kimchi_refrigerator_top_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Test kimchi refrigerator top temperature', + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_kimchi_refrigerator_top_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '6', + }) +# --- diff --git a/tests/components/lg_thinq/test_sensor.py b/tests/components/lg_thinq/test_sensor.py index fa986c37f484..e495c2cbfb30 100644 --- a/tests/components/lg_thinq/test_sensor.py +++ b/tests/components/lg_thinq/test_sensor.py @@ -23,7 +23,7 @@ from tests.common import ( ) -@pytest.mark.parametrize("device_fixture", ["air_conditioner"]) +@pytest.mark.parametrize("device_fixture", ["air_conditioner", "kimchi_refrigerator"]) @pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.freeze_time(datetime(2024, 10, 10, tzinfo=UTC)) async def test_sensor_entities( diff --git a/tests/components/lg_thinq/test_switch.py b/tests/components/lg_thinq/test_switch.py index 0e2039f49d00..e0c5b0986971 100644 --- a/tests/components/lg_thinq/test_switch.py +++ b/tests/components/lg_thinq/test_switch.py @@ -2,6 +2,7 @@ from unittest.mock import AsyncMock, patch +import pytest from syrupy.assertion import SnapshotAssertion from homeassistant.const import Platform @@ -13,6 +14,10 @@ from . import setup_integration from tests.common import MockConfigEntry, snapshot_platform +@pytest.mark.usefixtures("entity_registry_enabled_by_default") +@pytest.mark.parametrize( + "device_fixture", ["air_conditioner", "dehumidifier", "washer"] +) async def test_switch_entities( hass: HomeAssistant, snapshot: SnapshotAssertion,