From 22828568e2831031a85b10a94505f6d030881889 Mon Sep 17 00:00:00 2001 From: Hessel Date: Fri, 11 Jul 2025 11:37:24 +0200 Subject: [PATCH 1/7] Wallbox Integration - Type Config Entry (#148594) --- homeassistant/components/wallbox/__init__.py | 12 +++++++----- homeassistant/components/wallbox/coordinator.py | 6 ++++-- homeassistant/components/wallbox/lock.py | 5 ++--- homeassistant/components/wallbox/number.py | 7 +++---- homeassistant/components/wallbox/select.py | 5 ++--- homeassistant/components/wallbox/sensor.py | 5 ++--- homeassistant/components/wallbox/switch.py | 5 ++--- 7 files changed, 22 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/wallbox/__init__.py b/homeassistant/components/wallbox/__init__.py index c2983d540df66..43b5d3ef91fac 100644 --- a/homeassistant/components/wallbox/__init__.py +++ b/homeassistant/components/wallbox/__init__.py @@ -4,13 +4,17 @@ from wallbox import Wallbox -from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed from .const import UPDATE_INTERVAL -from .coordinator import InvalidAuth, WallboxCoordinator, async_validate_input +from .coordinator import ( + InvalidAuth, + WallboxConfigEntry, + WallboxCoordinator, + async_validate_input, +) PLATFORMS = [ Platform.LOCK, @@ -20,8 +24,6 @@ Platform.SWITCH, ] -type WallboxConfigEntry = ConfigEntry[WallboxCoordinator] - async def async_setup_entry(hass: HomeAssistant, entry: WallboxConfigEntry) -> bool: """Set up Wallbox from a config entry.""" @@ -45,6 +47,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: WallboxConfigEntry) -> b return True -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: +async def async_unload_entry(hass: HomeAssistant, entry: WallboxConfigEntry) -> bool: """Unload a config entry.""" return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/homeassistant/components/wallbox/coordinator.py b/homeassistant/components/wallbox/coordinator.py index ffd235157acd6..82a807e4d09b5 100644 --- a/homeassistant/components/wallbox/coordinator.py +++ b/homeassistant/components/wallbox/coordinator.py @@ -77,6 +77,8 @@ 210: ChargerStatus.LOCKED_CAR_CONNECTED, } +type WallboxConfigEntry = ConfigEntry[WallboxCoordinator] + def _require_authentication[_WallboxCoordinatorT: WallboxCoordinator, **_P]( func: Callable[Concatenate[_WallboxCoordinatorT, _P], Any], @@ -118,10 +120,10 @@ async def async_validate_input(hass: HomeAssistant, wallbox: Wallbox) -> None: class WallboxCoordinator(DataUpdateCoordinator[dict[str, Any]]): """Wallbox Coordinator class.""" - config_entry: ConfigEntry + config_entry: WallboxConfigEntry def __init__( - self, hass: HomeAssistant, config_entry: ConfigEntry, wallbox: Wallbox + self, hass: HomeAssistant, config_entry: WallboxConfigEntry, wallbox: Wallbox ) -> None: """Initialize.""" self._station = config_entry.data[CONF_STATION] diff --git a/homeassistant/components/wallbox/lock.py b/homeassistant/components/wallbox/lock.py index 6ba9058db9690..f48ac0001101d 100644 --- a/homeassistant/components/wallbox/lock.py +++ b/homeassistant/components/wallbox/lock.py @@ -5,7 +5,6 @@ from typing import Any from homeassistant.components.lock import LockEntity, LockEntityDescription -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback @@ -14,7 +13,7 @@ CHARGER_LOCKED_UNLOCKED_KEY, CHARGER_SERIAL_NUMBER_KEY, ) -from .coordinator import WallboxCoordinator +from .coordinator import WallboxConfigEntry, WallboxCoordinator from .entity import WallboxEntity LOCK_TYPES: dict[str, LockEntityDescription] = { @@ -27,7 +26,7 @@ async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + entry: WallboxConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Create wallbox lock entities in HASS.""" diff --git a/homeassistant/components/wallbox/number.py b/homeassistant/components/wallbox/number.py index af4fbe2c38bb3..6bc37778a613f 100644 --- a/homeassistant/components/wallbox/number.py +++ b/homeassistant/components/wallbox/number.py @@ -10,7 +10,6 @@ from typing import cast from homeassistant.components.number import NumberEntity, NumberEntityDescription -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback @@ -24,7 +23,7 @@ CHARGER_PART_NUMBER_KEY, CHARGER_SERIAL_NUMBER_KEY, ) -from .coordinator import WallboxCoordinator +from .coordinator import WallboxConfigEntry, WallboxCoordinator from .entity import WallboxEntity @@ -79,7 +78,7 @@ class WallboxNumberEntityDescription(NumberEntityDescription): async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + entry: WallboxConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Create wallbox number entities in HASS.""" @@ -103,7 +102,7 @@ class WallboxNumber(WallboxEntity, NumberEntity): def __init__( self, coordinator: WallboxCoordinator, - entry: ConfigEntry, + entry: WallboxConfigEntry, description: WallboxNumberEntityDescription, ) -> None: """Initialize a Wallbox number entity.""" diff --git a/homeassistant/components/wallbox/select.py b/homeassistant/components/wallbox/select.py index 10ac4e6118950..8d4cf252344e5 100644 --- a/homeassistant/components/wallbox/select.py +++ b/homeassistant/components/wallbox/select.py @@ -8,7 +8,6 @@ from requests import HTTPError from homeassistant.components.select import SelectEntity, SelectEntityDescription -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback @@ -23,7 +22,7 @@ DOMAIN, EcoSmartMode, ) -from .coordinator import WallboxCoordinator +from .coordinator import WallboxConfigEntry, WallboxCoordinator from .entity import WallboxEntity @@ -58,7 +57,7 @@ class WallboxSelectEntityDescription(SelectEntityDescription): async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + entry: WallboxConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Create wallbox select entities in HASS.""" diff --git a/homeassistant/components/wallbox/sensor.py b/homeassistant/components/wallbox/sensor.py index 7d5e5b56309a0..b59e1e5319dd5 100644 --- a/homeassistant/components/wallbox/sensor.py +++ b/homeassistant/components/wallbox/sensor.py @@ -11,7 +11,6 @@ SensorEntityDescription, SensorStateClass, ) -from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( PERCENTAGE, UnitOfElectricCurrent, @@ -44,7 +43,7 @@ CHARGER_STATE_OF_CHARGE_KEY, CHARGER_STATUS_DESCRIPTION_KEY, ) -from .coordinator import WallboxCoordinator +from .coordinator import WallboxConfigEntry, WallboxCoordinator from .entity import WallboxEntity @@ -169,7 +168,7 @@ class WallboxSensorEntityDescription(SensorEntityDescription): async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + entry: WallboxConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Create wallbox sensor entities in HASS.""" diff --git a/homeassistant/components/wallbox/switch.py b/homeassistant/components/wallbox/switch.py index 7a28f863c4d9b..74f1783f539ed 100644 --- a/homeassistant/components/wallbox/switch.py +++ b/homeassistant/components/wallbox/switch.py @@ -5,7 +5,6 @@ from typing import Any from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback @@ -16,7 +15,7 @@ CHARGER_STATUS_DESCRIPTION_KEY, ChargerStatus, ) -from .coordinator import WallboxCoordinator +from .coordinator import WallboxConfigEntry, WallboxCoordinator from .entity import WallboxEntity SWITCH_TYPES: dict[str, SwitchEntityDescription] = { @@ -29,7 +28,7 @@ async def async_setup_entry( hass: HomeAssistant, - entry: ConfigEntry, + entry: WallboxConfigEntry, async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Create wallbox sensor entities in HASS.""" From 0b2ce73eac477659d3dd2a502b6313fe3757411e Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Fri, 11 Jul 2025 11:43:29 +0200 Subject: [PATCH 2/7] Fix description of `html5.dismiss` action (#148591) --- homeassistant/components/html5/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/html5/strings.json b/homeassistant/components/html5/strings.json index 2c68223581ac7..ee844f320bcb0 100644 --- a/homeassistant/components/html5/strings.json +++ b/homeassistant/components/html5/strings.json @@ -29,7 +29,7 @@ "services": { "dismiss": { "name": "Dismiss", - "description": "Dismisses a html5 notification.", + "description": "Dismisses an HTML5 notification.", "fields": { "target": { "name": "Target", From 87aecf0ed966039609932d09916e560e18d4e3c5 Mon Sep 17 00:00:00 2001 From: Arjan <44190435+vingerha@users.noreply.github.com> Date: Fri, 11 Jul 2025 12:45:21 +0200 Subject: [PATCH 3/7] Linkplay: add select entity to set Audio Output hardware (#143329) --- homeassistant/components/linkplay/const.py | 2 +- homeassistant/components/linkplay/icons.json | 5 + homeassistant/components/linkplay/select.py | 112 ++++++++++++++++++ .../components/linkplay/strings.json | 11 ++ 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/linkplay/select.py diff --git a/homeassistant/components/linkplay/const.py b/homeassistant/components/linkplay/const.py index 74b87f4aae9ef..ec85e5af97c22 100644 --- a/homeassistant/components/linkplay/const.py +++ b/homeassistant/components/linkplay/const.py @@ -19,5 +19,5 @@ class LinkPlaySharedData: DOMAIN = "linkplay" SHARED_DATA = "shared_data" SHARED_DATA_KEY: HassKey[LinkPlaySharedData] = HassKey(SHARED_DATA) -PLATFORMS = [Platform.BUTTON, Platform.MEDIA_PLAYER] +PLATFORMS = [Platform.BUTTON, Platform.MEDIA_PLAYER, Platform.SELECT] DATA_SESSION = "session" diff --git a/homeassistant/components/linkplay/icons.json b/homeassistant/components/linkplay/icons.json index c0fe86d9ac73a..26f7202943fe2 100644 --- a/homeassistant/components/linkplay/icons.json +++ b/homeassistant/components/linkplay/icons.json @@ -4,6 +4,11 @@ "timesync": { "default": "mdi:clock" } + }, + "select": { + "audio_output_hardware_mode": { + "default": "mdi:transit-connection-horizontal" + } } }, "services": { diff --git a/homeassistant/components/linkplay/select.py b/homeassistant/components/linkplay/select.py new file mode 100644 index 0000000000000..ebf5a05512a6e --- /dev/null +++ b/homeassistant/components/linkplay/select.py @@ -0,0 +1,112 @@ +"""Support for LinkPlay select.""" + +from __future__ import annotations + +from collections.abc import Awaitable, Callable, Coroutine +from dataclasses import dataclass +import logging +from typing import Any + +from linkplay.bridge import LinkPlayBridge, LinkPlayPlayer +from linkplay.consts import AudioOutputHwMode +from linkplay.manufacturers import MANUFACTURER_WIIM + +from homeassistant.components.select import SelectEntity, SelectEntityDescription +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback + +from . import LinkPlayConfigEntry +from .entity import LinkPlayBaseEntity, exception_wrap + +_LOGGER = logging.getLogger(__name__) + +AUDIO_OUTPUT_HW_MODE_MAP: dict[AudioOutputHwMode, str] = { + AudioOutputHwMode.OPTICAL: "optical", + AudioOutputHwMode.LINE_OUT: "line_out", + AudioOutputHwMode.COAXIAL: "coaxial", + AudioOutputHwMode.HEADPHONES: "headphones", +} + +AUDIO_OUTPUT_HW_MODE_MAP_INV: dict[str, AudioOutputHwMode] = { + v: k for k, v in AUDIO_OUTPUT_HW_MODE_MAP.items() +} + + +async def _get_current_option(bridge: LinkPlayBridge) -> str: + """Get the current hardware mode.""" + modes = await bridge.player.get_audio_output_hw_mode() + return AUDIO_OUTPUT_HW_MODE_MAP[modes.hardware] + + +@dataclass(frozen=True, kw_only=True) +class LinkPlaySelectEntityDescription(SelectEntityDescription): + """Class describing LinkPlay select entities.""" + + set_option_fn: Callable[[LinkPlayPlayer, str], Coroutine[Any, Any, None]] + current_option_fn: Callable[[LinkPlayPlayer], Awaitable[str]] + + +SELECT_TYPES_WIIM: tuple[LinkPlaySelectEntityDescription, ...] = ( + LinkPlaySelectEntityDescription( + key="audio_output_hardware_mode", + translation_key="audio_output_hardware_mode", + current_option_fn=_get_current_option, + set_option_fn=( + lambda linkplay_bridge, + option: linkplay_bridge.player.set_audio_output_hw_mode( + AUDIO_OUTPUT_HW_MODE_MAP_INV[option] + ) + ), + options=list(AUDIO_OUTPUT_HW_MODE_MAP_INV), + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: LinkPlayConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set up the LinkPlay select from config entry.""" + + # add entities + if config_entry.runtime_data.bridge.device.manufacturer == MANUFACTURER_WIIM: + async_add_entities( + LinkPlaySelect(config_entry.runtime_data.bridge, description) + for description in SELECT_TYPES_WIIM + ) + + +class LinkPlaySelect(LinkPlayBaseEntity, SelectEntity): + """Representation of LinkPlay select.""" + + entity_description: LinkPlaySelectEntityDescription + + def __init__( + self, + bridge: LinkPlayPlayer, + description: LinkPlaySelectEntityDescription, + ) -> None: + """Initialize LinkPlay select.""" + super().__init__(bridge) + self.entity_description = description + self._attr_unique_id = f"{bridge.device.uuid}-{description.key}" + + async def async_update(self) -> None: + """Get the current value from the device.""" + try: + # modes = await self.entity_description.current_option_fn(self._bridge) + self._attr_current_option = await self.entity_description.current_option_fn( + self._bridge + ) + + except ValueError as ex: + _LOGGER.debug( + "Cannot retrieve hardware mode value from device with error:, %s", ex + ) + self._attr_current_option = None + + @exception_wrap + async def async_select_option(self, option: str) -> None: + """Set the option.""" + await self.entity_description.set_option_fn(self._bridge, option) diff --git a/homeassistant/components/linkplay/strings.json b/homeassistant/components/linkplay/strings.json index 5d68754879cf2..7b0a6cbefe14b 100644 --- a/homeassistant/components/linkplay/strings.json +++ b/homeassistant/components/linkplay/strings.json @@ -40,6 +40,17 @@ "timesync": { "name": "Sync time" } + }, + "select": { + "audio_output_hardware_mode": { + "name": "Audio output hardware mode", + "state": { + "optical": "Optical", + "line_out": "Line out", + "coaxial": "Coaxial", + "headphones": "Headphones" + } + } } }, "exceptions": { From ec5991bc686c3ed74185bb581abe4cc84ce0ab1e Mon Sep 17 00:00:00 2001 From: Avi Miller Date: Fri, 11 Jul 2025 21:42:50 +1000 Subject: [PATCH 4/7] Add support for LIFX 26"x13" Ceiling (#148459) Signed-off-by: Avi Miller --- homeassistant/components/lifx/const.py | 1 + homeassistant/components/lifx/coordinator.py | 50 +- tests/components/lifx/__init__.py | 11 + .../lifx/snapshots/test_diagnostics.ambr | 1276 +++++++++++++++++ tests/components/lifx/test_diagnostics.py | 100 ++ 5 files changed, 1437 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/lifx/const.py b/homeassistant/components/lifx/const.py index ecc572aa00603..f0505f9a4fd59 100644 --- a/homeassistant/components/lifx/const.py +++ b/homeassistant/components/lifx/const.py @@ -70,6 +70,7 @@ } LIFX_CEILING_PRODUCT_IDS = {176, 177, 201, 202} +LIFX_128ZONE_CEILING_PRODUCT_IDS = {201, 202} _LOGGER = logging.getLogger(__package__) diff --git a/homeassistant/components/lifx/coordinator.py b/homeassistant/components/lifx/coordinator.py index 79ce843b339ed..c96f53d8f77a8 100644 --- a/homeassistant/components/lifx/coordinator.py +++ b/homeassistant/components/lifx/coordinator.py @@ -41,6 +41,7 @@ DEFAULT_ATTEMPTS, DOMAIN, IDENTIFY_WAVEFORM, + LIFX_128ZONE_CEILING_PRODUCT_IDS, MAX_ATTEMPTS_PER_UPDATE_REQUEST_MESSAGE, MAX_UPDATE_TIME, MESSAGE_RETRIES, @@ -183,6 +184,11 @@ def is_matrix(self) -> bool: """Return true if this is a matrix device.""" return bool(lifx_features(self.device)["matrix"]) + @cached_property + def is_128zone_matrix(self) -> bool: + """Return true if this is a 128-zone matrix device.""" + return bool(self.device.product in LIFX_128ZONE_CEILING_PRODUCT_IDS) + async def diagnostics(self) -> dict[str, Any]: """Return diagnostic information about the device.""" features = lifx_features(self.device) @@ -216,6 +222,16 @@ async def diagnostics(self) -> dict[str, Any]: "last_result": self.device.last_hev_cycle_result, } + if features["matrix"] is True: + device_data["matrix"] = { + "effect": self.device.effect, + "chain": self.device.chain, + "chain_length": self.device.chain_length, + "tile_devices": self.device.tile_devices, + "tile_devices_count": self.device.tile_devices_count, + "tile_device_width": self.device.tile_device_width, + } + if features["infrared"] is True: device_data["infrared"] = {"brightness": self.device.infrared_brightness} @@ -291,6 +307,37 @@ def _wrapped_callback( return calls + @callback + def _async_build_get64_update_requests(self) -> list[Callable]: + """Build one or more get64 update requests.""" + if self.device.tile_device_width == 0: + return [] + + calls: list[Callable] = [] + calls.append( + partial( + self.device.get64, + tile_index=0, + length=1, + x=0, + y=0, + width=self.device.tile_device_width, + ) + ) + if self.is_128zone_matrix: + # For 128-zone ceiling devices, we need another get64 request for the next set of zones + calls.append( + partial( + self.device.get64, + tile_index=0, + length=1, + x=0, + y=4, + width=self.device.tile_device_width, + ) + ) + return calls + async def _async_update_data(self) -> None: """Fetch all device data from the api.""" device = self.device @@ -312,9 +359,9 @@ async def _async_update_data(self) -> None: [ self.device.get_tile_effect, self.device.get_device_chain, - self.device.get64, ] ) + methods.extend(self._async_build_get64_update_requests()) if self.is_extended_multizone: methods.append(self.device.get_extended_color_zones) elif self.is_legacy_multizone: @@ -339,6 +386,7 @@ async def _async_update_data(self) -> None: if self.is_matrix or self.is_extended_multizone or self.is_legacy_multizone: self.active_effect = FirmwareEffect[self.device.effect.get("effect", "OFF")] + if self.is_legacy_multizone and num_zones != self.get_number_of_zones(): # The number of zones has changed so we need # to update the zones again. This happens rarely. diff --git a/tests/components/lifx/__init__.py b/tests/components/lifx/__init__.py index 81b913da6ce67..95f6154030b8c 100644 --- a/tests/components/lifx/__init__.py +++ b/tests/components/lifx/__init__.py @@ -199,6 +199,17 @@ def _mocked_ceiling() -> Light: return bulb +def _mocked_128zone_ceiling() -> Light: + bulb = _mocked_bulb() + bulb.product = 201 # LIFX 26"x13" Ceiling + bulb.effect = {"effect": "OFF"} + bulb.get_tile_effect = MockLifxCommand(bulb) + bulb.set_tile_effect = MockLifxCommand(bulb) + bulb.get64 = MockLifxCommand(bulb) + bulb.get_device_chain = MockLifxCommand(bulb) + return bulb + + def _mocked_bulb_old_firmware() -> Light: bulb = _mocked_bulb() bulb.host_firmware_version = "2.77" diff --git a/tests/components/lifx/snapshots/test_diagnostics.ambr b/tests/components/lifx/snapshots/test_diagnostics.ambr index 82499c3632e4f..3e095252159ed 100644 --- a/tests/components/lifx/snapshots/test_diagnostics.ambr +++ b/tests/components/lifx/snapshots/test_diagnostics.ambr @@ -1,4 +1,834 @@ # serializer version: 1 +# name: test_128zone_matrix_diagnostics + dict({ + 'data': dict({ + 'brightness': 3, + 'features': dict({ + 'buttons': False, + 'chain': False, + 'color': True, + 'extended_multizone': False, + 'hev': False, + 'infrared': False, + 'matrix': True, + 'max_kelvin': 9000, + 'min_kelvin': 1500, + 'multizone': False, + 'relays': False, + }), + 'firmware': '3.00', + 'hue': 1, + 'kelvin': 4, + 'matrix': dict({ + 'chain': dict({ + '0': list([ + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + ]), + }), + 'chain_length': 1, + 'effect': dict({ + 'effect': 'OFF', + }), + 'tile_device_width': 16, + 'tile_devices': list([ + dict({ + 'accel_meas_x': 0, + 'accel_meas_y': 0, + 'accel_meas_z': 2000, + 'device_version_product': 201, + 'device_version_vendor': 1, + 'firmware_build': 1729829374000000000, + 'firmware_version_major': 4, + 'firmware_version_minor': 10, + 'height': 16, + 'supported_frame_buffers': 5, + 'user_x': 0.0, + 'user_y': 0.0, + 'width': 8, + }), + ]), + 'tile_devices_count': 1, + }), + 'power': 0, + 'product_id': 201, + 'saturation': 2, + 'vendor': None, + }), + 'entry': dict({ + 'data': dict({ + 'host': '**REDACTED**', + }), + 'title': 'My Bulb', + }), + }) +# --- # name: test_bulb_diagnostics dict({ 'data': dict({ @@ -199,6 +1029,452 @@ }), }) # --- +# name: test_matrix_diagnostics + dict({ + 'data': dict({ + 'brightness': 3, + 'features': dict({ + 'buttons': False, + 'chain': False, + 'color': True, + 'extended_multizone': False, + 'hev': False, + 'infrared': False, + 'matrix': True, + 'max_kelvin': 9000, + 'min_kelvin': 1500, + 'multizone': False, + 'relays': False, + }), + 'firmware': '3.00', + 'hue': 1, + 'kelvin': 4, + 'matrix': dict({ + 'chain': dict({ + '0': list([ + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + list([ + 0, + 0, + 0, + 3500, + ]), + ]), + }), + 'chain_length': 1, + 'effect': dict({ + 'effect': 'OFF', + }), + 'tile_device_width': 8, + 'tile_devices': list([ + dict({ + 'accel_meas_x': 0, + 'accel_meas_y': 0, + 'accel_meas_z': 2000, + 'device_version_product': 176, + 'device_version_vendor': 1, + 'firmware_build': 1729829374000000000, + 'firmware_version_major': 4, + 'firmware_version_minor': 10, + 'height': 8, + 'supported_frame_buffers': 5, + 'user_x': 0.0, + 'user_y': 0.0, + 'width': 8, + }), + ]), + 'tile_devices_count': 1, + }), + 'power': 0, + 'product_id': 176, + 'saturation': 2, + 'vendor': None, + }), + 'entry': dict({ + 'data': dict({ + 'host': '**REDACTED**', + }), + 'title': 'My Bulb', + }), + }) +# --- # name: test_multizone_bulb_diagnostics dict({ 'data': dict({ diff --git a/tests/components/lifx/test_diagnostics.py b/tests/components/lifx/test_diagnostics.py index 5883ac046e713..830dc26829a21 100644 --- a/tests/components/lifx/test_diagnostics.py +++ b/tests/components/lifx/test_diagnostics.py @@ -12,7 +12,9 @@ IP_ADDRESS, SERIAL, MockLifxCommand, + _mocked_128zone_ceiling, _mocked_bulb, + _mocked_ceiling, _mocked_clean_bulb, _mocked_infrared_bulb, _mocked_light_strip, @@ -209,3 +211,101 @@ async def test_multizone_bulb_diagnostics( diag = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) assert diag == snapshot + + +async def test_matrix_diagnostics( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + snapshot: SnapshotAssertion, +) -> None: + """Test diagnostics for a standard bulb.""" + config_entry = MockConfigEntry( + domain=lifx.DOMAIN, + title=DEFAULT_ENTRY_TITLE, + data={CONF_HOST: IP_ADDRESS}, + unique_id=SERIAL, + ) + config_entry.add_to_hass(hass) + bulb = _mocked_ceiling() + bulb.effect = {"effect": "OFF"} + bulb.tile_devices_count = 1 + bulb.tile_device_width = 8 + bulb.tile_devices = [ + { + "accel_meas_x": 0, + "accel_meas_y": 0, + "accel_meas_z": 2000, + "user_x": 0.0, + "user_y": 0.0, + "width": 8, + "height": 8, + "supported_frame_buffers": 5, + "device_version_vendor": 1, + "device_version_product": 176, + "firmware_build": 1729829374000000000, + "firmware_version_minor": 10, + "firmware_version_major": 4, + } + ] + bulb.chain = {0: [(0, 0, 0, 3500)] * 64} + bulb.chain_length = 1 + + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): + await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) + await hass.async_block_till_done() + + diag = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) + assert diag == snapshot + + +async def test_128zone_matrix_diagnostics( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + snapshot: SnapshotAssertion, +) -> None: + """Test diagnostics for a standard bulb.""" + config_entry = MockConfigEntry( + domain=lifx.DOMAIN, + title=DEFAULT_ENTRY_TITLE, + data={CONF_HOST: IP_ADDRESS}, + unique_id=SERIAL, + ) + config_entry.add_to_hass(hass) + bulb = _mocked_128zone_ceiling() + bulb.effect = {"effect": "OFF"} + bulb.tile_devices_count = 1 + bulb.tile_device_width = 16 + bulb.tile_devices = [ + { + "accel_meas_x": 0, + "accel_meas_y": 0, + "accel_meas_z": 2000, + "user_x": 0.0, + "user_y": 0.0, + "width": 8, + "height": 16, + "supported_frame_buffers": 5, + "device_version_vendor": 1, + "device_version_product": 201, + "firmware_build": 1729829374000000000, + "firmware_version_minor": 10, + "firmware_version_major": 4, + } + ] + bulb.chain = {0: [(0, 0, 0, 3500)] * 128} + bulb.chain_length = 1 + + with ( + _patch_discovery(device=bulb), + _patch_config_flow_try_connect(device=bulb), + _patch_device(device=bulb), + ): + await async_setup_component(hass, lifx.DOMAIN, {lifx.DOMAIN: {}}) + await hass.async_block_till_done() + + diag = await get_diagnostics_for_config_entry(hass, hass_client, config_entry) + assert diag == snapshot From 73c9d99abf11e88d3ef65f5b856711c19cc81a55 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Fri, 11 Jul 2025 13:55:01 +0200 Subject: [PATCH 5/7] Add tuya snapshot tests for wxkg category (#148609) --- tests/components/tuya/__init__.py | 5 + .../tuya/fixtures/wxkg_wireless_switch.json | 50 ++++++++ .../components/tuya/snapshots/test_event.ambr | 119 ++++++++++++++++++ .../tuya/snapshots/test_sensor.ambr | 53 ++++++++ tests/components/tuya/test_event.py | 57 +++++++++ 5 files changed, 284 insertions(+) create mode 100644 tests/components/tuya/fixtures/wxkg_wireless_switch.json create mode 100644 tests/components/tuya/snapshots/test_event.ambr create mode 100644 tests/components/tuya/test_event.py diff --git a/tests/components/tuya/__init__.py b/tests/components/tuya/__init__.py index bf8af8835cf77..90a49fc2372e5 100644 --- a/tests/components/tuya/__init__.py +++ b/tests/components/tuya/__init__.py @@ -88,6 +88,11 @@ # https://github.com/home-assistant/core/issues/102769 Platform.SENSOR, ], + "wxkg_wireless_switch": [ + # https://github.com/home-assistant/core/issues/93975 + Platform.EVENT, + Platform.SENSOR, + ], "zndb_smart_meter": [ # https://github.com/home-assistant/core/issues/138372 Platform.SENSOR, diff --git a/tests/components/tuya/fixtures/wxkg_wireless_switch.json b/tests/components/tuya/fixtures/wxkg_wireless_switch.json new file mode 100644 index 0000000000000..376276099ccce --- /dev/null +++ b/tests/components/tuya/fixtures/wxkg_wireless_switch.json @@ -0,0 +1,50 @@ +{ + "endpoint": "https://openapi.tuyaeu.com", + "auth_type": 0, + "country_code": "44", + "app_type": "smartlife", + "mqtt_connected": true, + "disabled_by": null, + "disabled_polling": false, + "name": "Bathroom Smart Switch", + "model": "LKWSW201", + "category": "wxkg", + "product_id": "l8yaz4um5b3pwyvf", + "product_name": "Wireless Switch", + "online": true, + "sub": false, + "time_zone": "+00:00", + "active_time": "2023-01-05T20:12:39+00:00", + "create_time": "2023-01-05T20:12:39+00:00", + "update_time": "2023-05-30T17:17:47+00:00", + "function": {}, + "status_range": { + "switch_mode1": { + "type": "Enum", + "value": { + "range": ["click", "press"] + } + }, + "switch_mode2": { + "type": "Enum", + "value": { + "range": ["click", "press"] + } + }, + "battery_percentage": { + "type": "Integer", + "value": { + "unit": "%", + "min": 0, + "max": 100, + "scale": 0, + "step": 1 + } + } + }, + "status": { + "switch_mode1": "click", + "switch_mode2": "click", + "battery_percentage": 100 + } +} diff --git a/tests/components/tuya/snapshots/test_event.ambr b/tests/components/tuya/snapshots/test_event.ambr new file mode 100644 index 0000000000000..085ebd3ec8ba8 --- /dev/null +++ b/tests/components/tuya/snapshots/test_event.ambr @@ -0,0 +1,119 @@ +# serializer version: 1 +# name: test_platform_setup_and_discovery[wxkg_wireless_switch][event.bathroom_smart_switch_button_1-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'event_types': list([ + 'click', + 'press', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'event', + 'entity_category': None, + 'entity_id': 'event.bathroom_smart_switch_button_1', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Button 1', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'numbered_button', + 'unique_id': 'tuya.mocked_device_idswitch_mode1', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[wxkg_wireless_switch][event.bathroom_smart_switch_button_1-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'button', + 'event_type': None, + 'event_types': list([ + 'click', + 'press', + ]), + 'friendly_name': 'Bathroom Smart Switch Button 1', + }), + 'context': , + 'entity_id': 'event.bathroom_smart_switch_button_1', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_platform_setup_and_discovery[wxkg_wireless_switch][event.bathroom_smart_switch_button_2-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'event_types': list([ + 'click', + 'press', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'event', + 'entity_category': None, + 'entity_id': 'event.bathroom_smart_switch_button_2', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Button 2', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'numbered_button', + 'unique_id': 'tuya.mocked_device_idswitch_mode2', + 'unit_of_measurement': None, + }) +# --- +# name: test_platform_setup_and_discovery[wxkg_wireless_switch][event.bathroom_smart_switch_button_2-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'button', + 'event_type': None, + 'event_types': list([ + 'click', + 'press', + ]), + 'friendly_name': 'Bathroom Smart Switch Button 2', + }), + 'context': , + 'entity_id': 'event.bathroom_smart_switch_button_2', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- diff --git a/tests/components/tuya/snapshots/test_sensor.ambr b/tests/components/tuya/snapshots/test_sensor.ambr index 5e52c0e063c49..3704aa4f067d5 100644 --- a/tests/components/tuya/snapshots/test_sensor.ambr +++ b/tests/components/tuya/snapshots/test_sensor.ambr @@ -1467,6 +1467,59 @@ 'state': '18.5', }) # --- +# name: test_platform_setup_and_discovery[wxkg_wireless_switch][sensor.bathroom_smart_switch_battery-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + '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': , + 'entity_id': 'sensor.bathroom_smart_switch_battery', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Battery', + 'platform': 'tuya', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'battery', + 'unique_id': 'tuya.mocked_device_idbattery_percentage', + 'unit_of_measurement': '%', + }) +# --- +# name: test_platform_setup_and_discovery[wxkg_wireless_switch][sensor.bathroom_smart_switch_battery-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': 'Bathroom Smart Switch Battery', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.bathroom_smart_switch_battery', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '100.0', + }) +# --- # name: test_platform_setup_and_discovery[zndb_smart_meter][sensor.meter_phase_a_current-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tuya/test_event.py b/tests/components/tuya/test_event.py new file mode 100644 index 0000000000000..3a332dbe5c725 --- /dev/null +++ b/tests/components/tuya/test_event.py @@ -0,0 +1,57 @@ +"""Test Tuya event platform.""" + +from __future__ import annotations + +from unittest.mock import patch + +import pytest +from syrupy.assertion import SnapshotAssertion +from tuya_sharing import CustomerDevice + +from homeassistant.components.tuya import ManagerCompat +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import DEVICE_MOCKS, initialize_entry + +from tests.common import MockConfigEntry, snapshot_platform + + +@pytest.mark.parametrize( + "mock_device_code", + [k for k, v in DEVICE_MOCKS.items() if Platform.EVENT in v], +) +@patch("homeassistant.components.tuya.PLATFORMS", [Platform.EVENT]) +async def test_platform_setup_and_discovery( + hass: HomeAssistant, + mock_manager: ManagerCompat, + mock_config_entry: MockConfigEntry, + mock_device: CustomerDevice, + entity_registry: er.EntityRegistry, + snapshot: SnapshotAssertion, +) -> None: + """Test platform setup and discovery.""" + await initialize_entry(hass, mock_manager, mock_config_entry, mock_device) + + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) + + +@pytest.mark.parametrize( + "mock_device_code", + [k for k, v in DEVICE_MOCKS.items() if Platform.EVENT not in v], +) +@patch("homeassistant.components.tuya.PLATFORMS", [Platform.EVENT]) +async def test_platform_setup_no_discovery( + hass: HomeAssistant, + mock_manager: ManagerCompat, + mock_config_entry: MockConfigEntry, + mock_device: CustomerDevice, + entity_registry: er.EntityRegistry, +) -> None: + """Test platform setup without discovery.""" + await initialize_entry(hass, mock_manager, mock_config_entry, mock_device) + + assert not er.async_entries_for_config_entry( + entity_registry, mock_config_entry.entry_id + ) From a34264f345ff6445ccfc3c57976eb9691a183910 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Fri, 11 Jul 2025 14:01:11 +0200 Subject: [PATCH 6/7] Add SmartThings RVC fixture (#148552) --- tests/components/smartthings/conftest.py | 1 + .../device_status/da_rvc_map_01011.json | 994 ++++++++++++++++++ .../fixtures/devices/da_rvc_map_01011.json | 353 +++++++ .../smartthings/snapshots/test_init.ambr | 33 + .../smartthings/snapshots/test_select.ambr | 57 + .../smartthings/snapshots/test_sensor.ambr | 534 ++++++++++ .../smartthings/snapshots/test_switch.ambr | 48 + 7 files changed, 2020 insertions(+) create mode 100644 tests/components/smartthings/fixtures/device_status/da_rvc_map_01011.json create mode 100644 tests/components/smartthings/fixtures/devices/da_rvc_map_01011.json diff --git a/tests/components/smartthings/conftest.py b/tests/components/smartthings/conftest.py index e8cde67122ba9..93f505872f477 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -130,6 +130,7 @@ def mock_smartthings() -> Generator[AsyncMock]: "da_wm_wm_000001_1", "da_wm_sc_000001", "da_rvc_normal_000001", + "da_rvc_map_01011", "da_ks_microwave_0101x", "da_ks_cooktop_31001", "da_ks_range_0101x", diff --git a/tests/components/smartthings/fixtures/device_status/da_rvc_map_01011.json b/tests/components/smartthings/fixtures/device_status/da_rvc_map_01011.json new file mode 100644 index 0000000000000..1424493530890 --- /dev/null +++ b/tests/components/smartthings/fixtures/device_status/da_rvc_map_01011.json @@ -0,0 +1,994 @@ +{ + "components": { + "refill-drainage-kit": { + "samsungce.connectionState": { + "connectionState": { + "value": null + } + }, + "samsungce.activationState": { + "activationState": { + "value": null + } + }, + "custom.disabledCapabilities": { + "disabledCapabilities": { + "value": [ + "samsungce.drainFilter", + "samsungce.connectionState", + "samsungce.activationState" + ], + "timestamp": "2025-06-20T14:12:57.135Z" + } + }, + "samsungce.drainFilter": { + "drainFilterUsageStep": { + "value": null + }, + "drainFilterStatus": { + "value": null + }, + "drainFilterLastResetDate": { + "value": null + }, + "drainFilterResetType": { + "value": null + }, + "drainFilterUsage": { + "value": null + } + } + }, + "station": { + "custom.hepaFilter": { + "hepaFilterCapacity": { + "value": null + }, + "hepaFilterStatus": { + "value": "normal", + "timestamp": "2025-07-02T04:35:14.449Z" + }, + "hepaFilterResetType": { + "value": ["replaceable"], + "timestamp": "2025-07-02T04:35:14.449Z" + }, + "hepaFilterUsageStep": { + "value": null + }, + "hepaFilterUsage": { + "value": null + }, + "hepaFilterLastResetDate": { + "value": null + } + }, + "samsungce.robotCleanerDustBag": { + "supportedStatus": { + "value": ["full", "normal"], + "timestamp": "2025-07-02T04:35:14.620Z" + }, + "status": { + "value": "normal", + "timestamp": "2025-07-02T04:35:14.620Z" + } + } + }, + "main": { + "mediaPlayback": { + "supportedPlaybackCommands": { + "value": null + }, + "playbackStatus": { + "value": null + } + }, + "robotCleanerTurboMode": { + "robotCleanerTurboMode": { + "value": "extraSilence", + "timestamp": "2025-07-10T11:00:38.909Z" + } + }, + "ocf": { + "st": { + "value": "2024-01-01T09:00:15Z", + "timestamp": "2025-06-20T14:12:57.924Z" + }, + "mndt": { + "value": "", + "timestamp": "2025-06-20T14:12:57.924Z" + }, + "mnfv": { + "value": "20250123.105306", + "timestamp": "2025-06-20T14:12:57.924Z" + }, + "mnhw": { + "value": "", + "timestamp": "2025-06-20T14:12:57.924Z" + }, + "di": { + "value": "05accb39-2017-c98b-a5ab-04a81f4d3d9a", + "timestamp": "2025-06-20T14:12:57.924Z" + }, + "mnsl": { + "value": "", + "timestamp": "2025-06-20T14:12:57.924Z" + }, + "dmv": { + "value": "res.1.1.0,sh.1.1.0", + "timestamp": "2025-06-20T14:12:57.924Z" + }, + "n": { + "value": "[robot vacuum] Samsung", + "timestamp": "2025-06-20T14:12:57.924Z" + }, + "mnmo": { + "value": "JETBOT_COMBO_9X00_24K|50029141|80010b0002d8411f0100000000000000", + "timestamp": "2025-06-20T14:12:57.924Z" + }, + "vid": { + "value": "DA-RVC-MAP-01011", + "timestamp": "2025-06-20T14:12:57.924Z" + }, + "mnmn": { + "value": "Samsung Electronics", + "timestamp": "2025-06-20T14:12:57.924Z" + }, + "mnml": { + "value": "", + "timestamp": "2025-06-20T14:12:57.924Z" + }, + "mnpv": { + "value": "1.0", + "timestamp": "2025-06-20T14:12:57.924Z" + }, + "mnos": { + "value": "Tizen", + "timestamp": "2025-06-20T14:12:57.924Z" + }, + "pi": { + "value": "05accb39-2017-c98b-a5ab-04a81f4d3d9a", + "timestamp": "2025-06-20T14:12:57.924Z" + }, + "icv": { + "value": "core.1.1.0", + "timestamp": "2025-06-20T14:12:57.924Z" + } + }, + "custom.disabledCapabilities": { + "disabledCapabilities": { + "value": [ + "samsungce.robotCleanerAudioClip", + "custom.hepaFilter", + "imageCapture", + "mediaPlaybackRepeat", + "mediaPlayback", + "mediaTrackControl", + "samsungce.robotCleanerPatrol", + "samsungce.musicPlaylist", + "audioVolume", + "audioMute", + "videoCapture", + "samsungce.robotCleanerWelcome", + "samsungce.microphoneSettings", + "samsungce.robotCleanerGuidedPatrol", + "samsungce.robotCleanerSafetyPatrol", + "soundDetection", + "samsungce.soundDetectionSensitivity", + "audioTrackAddressing", + "samsungce.robotCleanerMonitoringAutomation" + ], + "timestamp": "2025-06-20T14:12:58.125Z" + } + }, + "logTrigger": { + "logState": { + "value": "idle", + "timestamp": "2025-07-02T04:35:14.401Z" + }, + "logRequestState": { + "value": "idle", + "timestamp": "2025-07-02T04:35:14.401Z" + }, + "logInfo": { + "value": null + } + }, + "samsungce.driverVersion": { + "versionNumber": { + "value": 25040102, + "timestamp": "2025-06-20T14:12:57.135Z" + } + }, + "sec.diagnosticsInformation": { + "logType": { + "value": ["errCode", "dump"], + "timestamp": "2025-07-02T04:35:13.556Z" + }, + "endpoint": { + "value": "PIPER", + "timestamp": "2025-07-02T04:35:13.556Z" + }, + "minVersion": { + "value": "3.0", + "timestamp": "2025-07-02T04:35:13.556Z" + }, + "signinPermission": { + "value": null + }, + "setupId": { + "value": "VR0", + "timestamp": "2025-07-02T04:35:13.556Z" + }, + "protocolType": { + "value": "ble_ocf", + "timestamp": "2025-07-02T04:35:13.556Z" + }, + "tsId": { + "value": "DA10", + "timestamp": "2025-07-02T04:35:13.556Z" + }, + "mnId": { + "value": "0AJT", + "timestamp": "2025-07-02T04:35:13.556Z" + }, + "dumpType": { + "value": "file", + "timestamp": "2025-07-02T04:35:13.556Z" + } + }, + "custom.hepaFilter": { + "hepaFilterCapacity": { + "value": null + }, + "hepaFilterStatus": { + "value": null + }, + "hepaFilterResetType": { + "value": null + }, + "hepaFilterUsageStep": { + "value": null + }, + "hepaFilterUsage": { + "value": null + }, + "hepaFilterLastResetDate": { + "value": null + } + }, + "samsungce.robotCleanerMapCleaningInfo": { + "area": { + "value": "None", + "timestamp": "2025-07-10T09:37:08.648Z" + }, + "cleanedExtent": { + "value": -1, + "unit": "m\u00b2", + "timestamp": "2025-07-10T09:37:08.648Z" + }, + "nearObject": { + "value": "None", + "timestamp": "2025-07-02T04:35:13.567Z" + }, + "remainingTime": { + "value": -1, + "unit": "minute", + "timestamp": "2025-07-10T06:42:57.820Z" + } + }, + "audioVolume": { + "volume": { + "value": null + } + }, + "powerConsumptionReport": { + "powerConsumption": { + "value": { + "energy": 981, + "deltaEnergy": 21, + "power": 0, + "powerEnergy": 0.0, + "persistedEnergy": 0, + "energySaved": 0, + "start": "2025-07-10T11:11:22Z", + "end": "2025-07-10T11:20:22Z" + }, + "timestamp": "2025-07-10T11:20:22.600Z" + } + }, + "samsungce.robotCleanerMapList": { + "maps": { + "value": [ + { + "id": "1", + "name": "Map1", + "userEdited": false, + "createdTime": "2025-07-01T08:23:29Z", + "updatedTime": "2025-07-01T08:23:29Z", + "areaInfo": [ + { + "id": "1", + "name": "Room", + "userEdited": false + }, + { + "id": "2", + "name": "Room 2", + "userEdited": false + }, + { + "id": "3", + "name": "Room 3", + "userEdited": false + }, + { + "id": "4", + "name": "Room 4", + "userEdited": false + } + ], + "objectInfo": [] + } + ], + "timestamp": "2025-07-02T04:35:14.204Z" + } + }, + "samsungce.robotCleanerPatrol": { + "timezone": { + "value": null + }, + "patrolStatus": { + "value": null + }, + "areaIds": { + "value": null + }, + "timeOffset": { + "value": null + }, + "waypoints": { + "value": null + }, + "enabled": { + "value": null + }, + "dayOfWeek": { + "value": null + }, + "blockingStatus": { + "value": null + }, + "mapId": { + "value": null + }, + "startTime": { + "value": null + }, + "interval": { + "value": null + }, + "endTime": { + "value": null + }, + "obsoleted": { + "value": null + } + }, + "samsungce.robotCleanerAudioClip": { + "enabled": { + "value": null + } + }, + "samsungce.musicPlaylist": { + "currentTrack": { + "value": null + }, + "playlist": { + "value": null + } + }, + "audioNotification": {}, + "samsungce.robotCleanerPetMonitorReport": { + "report": { + "value": null + } + }, + "execute": { + "data": { + "value": null + } + }, + "samsungce.energyPlanner": { + "data": { + "value": null + }, + "plan": { + "value": "none", + "timestamp": "2025-07-02T04:35:14.341Z" + } + }, + "samsungce.robotCleanerFeatureVisibility": { + "invisibleFeatures": { + "value": [ + "Start", + "Dock", + "SelectRoom", + "DustEmit", + "SelectSpot", + "CleaningMethod", + "MopWash", + "MopDry" + ], + "timestamp": "2025-07-10T09:52:40.298Z" + }, + "visibleFeatures": { + "value": [ + "Stop", + "Suction", + "Repeat", + "MapMerge", + "MapDivide", + "MySchedule", + "Homecare", + "CleanReport", + "CleanHistory", + "DND", + "Sound", + "NoEntryZone", + "RenameRoom", + "ResetMap", + "Accessory", + "CleaningOption", + "ObjectEdit", + "WaterLevel", + "ClimbZone" + ], + "timestamp": "2025-07-10T09:52:40.298Z" + } + }, + "sec.wifiConfiguration": { + "autoReconnection": { + "value": true, + "timestamp": "2025-07-02T04:35:14.461Z" + }, + "minVersion": { + "value": "1.0", + "timestamp": "2025-07-02T04:35:14.461Z" + }, + "supportedWiFiFreq": { + "value": ["2.4G", "5G"], + "timestamp": "2025-07-02T04:35:14.461Z" + }, + "supportedAuthType": { + "value": ["OPEN", "WEP", "WPA-PSK", "WPA2-PSK", "SAE"], + "timestamp": "2025-07-02T04:35:14.461Z" + }, + "protocolType": { + "value": ["helper_hotspot", "ble_ocf"], + "timestamp": "2025-07-02T04:35:14.461Z" + } + }, + "samsungce.softwareVersion": { + "versions": { + "value": [ + { + "id": "0", + "swType": "Software", + "versionNumber": "25012310" + }, + { + "id": "1", + "swType": "Software", + "versionNumber": "25012310" + }, + { + "id": "2", + "swType": "Firmware", + "versionNumber": "25012100" + }, + { + "id": "3", + "swType": "Firmware", + "versionNumber": "24012200" + }, + { + "id": "4", + "swType": "Bixby", + "versionNumber": "(null)" + }, + { + "id": "5", + "swType": "Firmware", + "versionNumber": "25012200" + } + ], + "timestamp": "2025-07-02T04:35:13.556Z" + } + }, + "samsungce.softwareUpdate": { + "targetModule": { + "value": { + "newVersion": "00000000", + "currentVersion": "00000000", + "moduleType": "mainController" + }, + "timestamp": "2025-07-09T23:00:32.385Z" + }, + "otnDUID": { + "value": "JHCDM7UU7UJWQ", + "timestamp": "2025-07-02T04:35:13.556Z" + }, + "lastUpdatedDate": { + "value": null + }, + "availableModules": { + "value": [], + "timestamp": "2025-07-02T04:35:13.556Z" + }, + "newVersionAvailable": { + "value": false, + "timestamp": "2025-07-02T04:35:13.556Z" + }, + "operatingState": { + "value": "none", + "timestamp": "2025-07-02T04:35:19.823Z" + }, + "progress": { + "value": 0, + "unit": "%", + "timestamp": "2025-07-02T04:35:19.823Z" + } + }, + "samsungce.robotCleanerReservation": { + "reservations": { + "value": [ + { + "id": "2", + "enabled": true, + "dayOfWeek": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + "startTime": "02:32", + "repeatMode": "weekly", + "cleaningMode": "auto" + } + ], + "timestamp": "2025-07-02T04:35:13.844Z" + }, + "maxNumberOfReservations": { + "value": null + } + }, + "audioMute": { + "mute": { + "value": null + } + }, + "mediaTrackControl": { + "supportedTrackControlCommands": { + "value": null + } + }, + "samsungce.robotCleanerMotorFilter": { + "motorFilterResetType": { + "value": ["washable"], + "timestamp": "2025-07-02T04:35:13.496Z" + }, + "motorFilterStatus": { + "value": "normal", + "timestamp": "2025-07-02T04:35:13.496Z" + } + }, + "samsungce.robotCleanerCleaningType": { + "cleaningType": { + "value": "vacuumAndMopTogether", + "timestamp": "2025-07-09T12:44:06.437Z" + }, + "supportedCleaningTypes": { + "value": ["vacuum", "mop", "vacuumAndMopTogether", "mopAfterVacuum"], + "timestamp": "2025-07-02T04:35:13.646Z" + } + }, + "soundDetection": { + "soundDetectionState": { + "value": null + }, + "supportedSoundTypes": { + "value": null + }, + "soundDetected": { + "value": null + } + }, + "samsungce.robotCleanerWelcome": { + "coordinates": { + "value": null + } + }, + "samsungce.robotCleanerPetMonitor": { + "areaIds": { + "value": null + }, + "originator": { + "value": null + }, + "waypoints": { + "value": null + }, + "enabled": { + "value": null + }, + "excludeHolidays": { + "value": null + }, + "dayOfWeek": { + "value": null + }, + "monitoringStatus": { + "value": null + }, + "blockingStatus": { + "value": null + }, + "mapId": { + "value": null + }, + "startTime": { + "value": null + }, + "interval": { + "value": null + }, + "endTime": { + "value": null + }, + "obsoleted": { + "value": null + } + }, + "battery": { + "quantity": { + "value": null + }, + "battery": { + "value": 59, + "unit": "%", + "timestamp": "2025-07-10T11:24:13.441Z" + }, + "type": { + "value": null + } + }, + "samsungce.deviceIdentification": { + "micomAssayCode": { + "value": "50029141", + "timestamp": "2025-07-02T04:35:13.556Z" + }, + "modelName": { + "value": null + }, + "serialNumber": { + "value": null + }, + "serialNumberExtra": { + "value": null + }, + "modelClassificationCode": { + "value": "80010b0002d8411f0100000000000000", + "timestamp": "2025-07-02T04:35:13.556Z" + }, + "description": { + "value": "Jet Bot V/C", + "timestamp": "2025-07-02T04:35:13.556Z" + }, + "releaseYear": { + "value": null + }, + "binaryId": { + "value": "JETBOT_COMBO_9X00_24K", + "timestamp": "2025-07-09T23:00:26.764Z" + } + }, + "samsungce.robotCleanerSystemSoundMode": { + "soundMode": { + "value": "mute", + "timestamp": "2025-07-05T18:17:55.940Z" + }, + "supportedSoundModes": { + "value": ["mute", "beep", "voice"], + "timestamp": "2025-07-02T04:35:13.646Z" + } + }, + "switch": { + "switch": { + "value": "on", + "timestamp": "2025-07-09T23:00:26.829Z" + } + }, + "samsungce.robotCleanerPetCleaningSchedule": { + "excludeHolidays": { + "value": null + }, + "dayOfWeek": { + "value": null + }, + "mapId": { + "value": null + }, + "areaIds": { + "value": null + }, + "startTime": { + "value": null + }, + "originator": { + "value": null + }, + "obsoleted": { + "value": true, + "timestamp": "2025-07-02T04:35:14.317Z" + }, + "enabled": { + "value": null + } + }, + "samsungce.quickControl": { + "version": { + "value": "1.0", + "timestamp": "2025-07-02T04:35:14.234Z" + } + }, + "samsungce.microphoneSettings": { + "mute": { + "value": null + } + }, + "samsungce.robotCleanerMapAreaInfo": { + "areaInfo": { + "value": [ + { + "id": "1", + "name": "Room" + }, + { + "id": "2", + "name": "Room 2" + }, + { + "id": "3", + "name": "Room 3" + }, + { + "id": "4", + "name": "Room 4" + } + ], + "timestamp": "2025-07-03T02:33:15.133Z" + } + }, + "samsungce.audioVolumeLevel": { + "volumeLevel": { + "value": 0, + "timestamp": "2025-07-05T18:17:55.915Z" + }, + "volumeLevelRange": { + "value": { + "minimum": 0, + "maximum": 3, + "step": 1 + }, + "timestamp": "2025-07-02T04:35:13.837Z" + } + }, + "robotCleanerMovement": { + "robotCleanerMovement": { + "value": "cleaning", + "timestamp": "2025-07-10T09:38:52.938Z" + } + }, + "samsungce.robotCleanerSafetyPatrol": { + "personDetection": { + "value": null + } + }, + "sec.calmConnectionCare": { + "role": { + "value": ["things"], + "timestamp": "2025-07-02T04:35:14.461Z" + }, + "protocols": { + "value": null + }, + "version": { + "value": "1.0", + "timestamp": "2025-07-02T04:35:14.461Z" + } + }, + "custom.disabledComponents": { + "disabledComponents": { + "value": ["refill-drainage-kit"], + "timestamp": "2025-06-20T14:12:57.135Z" + } + }, + "videoCapture": { + "stream": { + "value": null + }, + "clip": { + "value": null + } + }, + "samsungce.robotCleanerWaterSprayLevel": { + "availableWaterSprayLevels": { + "value": null + }, + "waterSprayLevel": { + "value": "mediumLow", + "timestamp": "2025-07-10T11:00:35.545Z" + }, + "supportedWaterSprayLevels": { + "value": ["high", "mediumHigh", "medium", "mediumLow", "low"], + "timestamp": "2025-07-02T04:35:13.646Z" + } + }, + "samsungce.robotCleanerMapMetadata": { + "cellSize": { + "value": 20, + "unit": "mm", + "timestamp": "2025-06-20T14:12:57.135Z" + } + }, + "samsungce.robotCleanerGuidedPatrol": { + "mapId": { + "value": null + }, + "waypoints": { + "value": null + } + }, + "audioTrackAddressing": {}, + "refresh": {}, + "samsungce.robotCleanerOperatingState": { + "supportedOperatingState": { + "value": [ + "homing", + "charging", + "charged", + "chargingForRemainingJob", + "moving", + "cleaning", + "paused", + "idle", + "error", + "powerSaving", + "factoryReset", + "relocal", + "exploring", + "processing", + "emitDust", + "washingMop", + "sterilizingMop", + "dryingMop", + "supplyingWater", + "preparingWater", + "spinDrying", + "flexCharged", + "descaling", + "drainingWater", + "waitingForDescaling" + ], + "timestamp": "2025-06-20T14:12:58.012Z" + }, + "operatingState": { + "value": "dryingMop", + "timestamp": "2025-07-10T09:52:40.510Z" + }, + "cleaningStep": { + "value": "none", + "timestamp": "2025-07-10T09:37:07.214Z" + }, + "homingReason": { + "value": "none", + "timestamp": "2025-07-10T09:37:45.152Z" + }, + "isMapBasedOperationAvailable": { + "value": false, + "timestamp": "2025-07-10T09:37:55.690Z" + } + }, + "samsungce.soundDetectionSensitivity": { + "level": { + "value": null + }, + "supportedLevels": { + "value": null + } + }, + "samsungce.robotCleanerMonitoringAutomation": {}, + "mediaPlaybackRepeat": { + "playbackRepeatMode": { + "value": null + } + }, + "imageCapture": { + "image": { + "value": null + }, + "encrypted": { + "value": null + }, + "captureTime": { + "value": null + } + }, + "samsungce.robotCleanerCleaningMode": { + "supportedCleaningMode": { + "value": [ + "auto", + "area", + "spot", + "stop", + "uncleanedObject", + "patternMap" + ], + "timestamp": "2025-06-20T14:12:58.012Z" + }, + "repeatModeEnabled": { + "value": true, + "timestamp": "2025-07-02T04:35:13.646Z" + }, + "supportRepeatMode": { + "value": true, + "timestamp": "2025-07-02T04:35:13.646Z" + }, + "cleaningMode": { + "value": "stop", + "timestamp": "2025-07-10T09:37:07.214Z" + } + }, + "samsungce.robotCleanerAvpRegistration": { + "registrationStatus": { + "value": null + } + }, + "samsungce.robotCleanerDrivingMode": { + "drivingMode": { + "value": "areaThenWalls", + "timestamp": "2025-07-02T04:35:13.646Z" + }, + "supportedDrivingModes": { + "value": ["areaThenWalls", "wallFirst", "quickCleaningZigzagPattern"], + "timestamp": "2025-07-02T04:35:13.646Z" + } + }, + "robotCleanerCleaningMode": { + "robotCleanerCleaningMode": { + "value": "stop", + "timestamp": "2025-07-10T09:37:07.214Z" + } + }, + "custom.doNotDisturbMode": { + "doNotDisturb": { + "value": "off", + "timestamp": "2025-07-02T04:35:13.622Z" + }, + "startTime": { + "value": "0000", + "timestamp": "2025-07-02T04:35:13.622Z" + }, + "endTime": { + "value": "0000", + "timestamp": "2025-07-02T04:35:13.622Z" + } + }, + "samsungce.lamp": { + "brightnessLevel": { + "value": "on", + "timestamp": "2025-07-10T11:20:40.419Z" + }, + "supportedBrightnessLevel": { + "value": ["on", "off"], + "timestamp": "2025-06-20T14:12:57.383Z" + } + } + } + } +} diff --git a/tests/components/smartthings/fixtures/devices/da_rvc_map_01011.json b/tests/components/smartthings/fixtures/devices/da_rvc_map_01011.json new file mode 100644 index 0000000000000..f25797f2dcfbb --- /dev/null +++ b/tests/components/smartthings/fixtures/devices/da_rvc_map_01011.json @@ -0,0 +1,353 @@ +{ + "items": [ + { + "deviceId": "05accb39-2017-c98b-a5ab-04a81f4d3d9a", + "name": "[robot vacuum] Samsung", + "label": "Robot vacuum", + "manufacturerName": "Samsung Electronics", + "presentationId": "DA-RVC-MAP-01011", + "deviceManufacturerCode": "Samsung Electronics", + "locationId": "d31d0982-9bf9-4f0c-afd4-ad3d78842541", + "ownerId": "85532262-6537-54d9-179a-333db98dbcc0", + "roomId": "572f5713-53a9-4fb8-85fd-60515e44f1ed", + "deviceTypeName": "Samsung OCF Robot Vacuum", + "components": [ + { + "id": "main", + "label": "main", + "capabilities": [ + { + "id": "audioMute", + "version": 1 + }, + { + "id": "audioNotification", + "version": 1 + }, + { + "id": "audioTrackAddressing", + "version": 1 + }, + { + "id": "audioVolume", + "version": 1 + }, + { + "id": "battery", + "version": 1 + }, + { + "id": "execute", + "version": 1 + }, + { + "id": "imageCapture", + "version": 1 + }, + { + "id": "logTrigger", + "version": 1 + }, + { + "id": "mediaPlayback", + "version": 1 + }, + { + "id": "mediaPlaybackRepeat", + "version": 1 + }, + { + "id": "mediaTrackControl", + "version": 1 + }, + { + "id": "ocf", + "version": 1 + }, + { + "id": "powerConsumptionReport", + "version": 1 + }, + { + "id": "refresh", + "version": 1 + }, + { + "id": "robotCleanerCleaningMode", + "version": 1 + }, + { + "id": "robotCleanerMovement", + "version": 1 + }, + { + "id": "robotCleanerTurboMode", + "version": 1 + }, + { + "id": "soundDetection", + "version": 1 + }, + { + "id": "switch", + "version": 1 + }, + { + "id": "videoCapture", + "version": 1 + }, + { + "id": "custom.disabledCapabilities", + "version": 1 + }, + { + "id": "custom.disabledComponents", + "version": 1 + }, + { + "id": "custom.doNotDisturbMode", + "version": 1 + }, + { + "id": "custom.hepaFilter", + "version": 1 + }, + { + "id": "samsungce.audioVolumeLevel", + "version": 1 + }, + { + "id": "samsungce.deviceIdentification", + "version": 1 + }, + { + "id": "samsungce.driverVersion", + "version": 1 + }, + { + "id": "samsungce.lamp", + "version": 1 + }, + { + "id": "samsungce.microphoneSettings", + "version": 1 + }, + { + "id": "samsungce.softwareUpdate", + "version": 1 + }, + { + "id": "samsungce.musicPlaylist", + "version": 1 + }, + { + "id": "samsungce.robotCleanerDrivingMode", + "version": 1 + }, + { + "id": "samsungce.robotCleanerCleaningMode", + "version": 1 + }, + { + "id": "samsungce.robotCleanerCleaningType", + "version": 1 + }, + { + "id": "samsungce.robotCleanerOperatingState", + "version": 1 + }, + { + "id": "samsungce.robotCleanerMapAreaInfo", + "version": 1 + }, + { + "id": "samsungce.robotCleanerMapCleaningInfo", + "version": 1 + }, + { + "id": "samsungce.robotCleanerPatrol", + "version": 1 + }, + { + "id": "samsungce.robotCleanerPetCleaningSchedule", + "version": 1 + }, + { + "id": "samsungce.robotCleanerPetMonitor", + "version": 1 + }, + { + "id": "samsungce.robotCleanerPetMonitorReport", + "version": 1 + }, + { + "id": "samsungce.robotCleanerReservation", + "version": 1 + }, + { + "id": "samsungce.robotCleanerMotorFilter", + "version": 1 + }, + { + "id": "samsungce.robotCleanerAvpRegistration", + "version": 1 + }, + { + "id": "samsungce.soundDetectionSensitivity", + "version": 1 + }, + { + "id": "samsungce.robotCleanerWaterSprayLevel", + "version": 1 + }, + { + "id": "samsungce.robotCleanerWelcome", + "version": 1 + }, + { + "id": "samsungce.robotCleanerAudioClip", + "version": 1 + }, + { + "id": "samsungce.robotCleanerMonitoringAutomation", + "version": 1 + }, + { + "id": "samsungce.robotCleanerMapMetadata", + "version": 1 + }, + { + "id": "samsungce.robotCleanerMapList", + "version": 1 + }, + { + "id": "samsungce.robotCleanerSystemSoundMode", + "version": 1 + }, + { + "id": "samsungce.energyPlanner", + "version": 1 + }, + { + "id": "samsungce.quickControl", + "version": 1 + }, + { + "id": "samsungce.robotCleanerFeatureVisibility", + "version": 1 + }, + { + "id": "samsungce.softwareVersion", + "version": 1 + }, + { + "id": "samsungce.robotCleanerGuidedPatrol", + "version": 1 + }, + { + "id": "samsungce.robotCleanerSafetyPatrol", + "version": 1 + }, + { + "id": "sec.calmConnectionCare", + "version": 1 + }, + { + "id": "sec.diagnosticsInformation", + "version": 1 + }, + { + "id": "sec.wifiConfiguration", + "version": 1 + } + ], + "categories": [ + { + "name": "RobotCleaner", + "categoryType": "manufacturer" + } + ], + "optional": false + }, + { + "id": "station", + "label": "station", + "capabilities": [ + { + "id": "custom.hepaFilter", + "version": 1 + }, + { + "id": "samsungce.robotCleanerDustBag", + "version": 1 + } + ], + "categories": [ + { + "name": "Other", + "categoryType": "manufacturer" + } + ], + "optional": false + }, + { + "id": "refill-drainage-kit", + "label": "refill-drainage-kit", + "capabilities": [ + { + "id": "custom.disabledCapabilities", + "version": 1 + }, + { + "id": "samsungce.drainFilter", + "version": 1 + }, + { + "id": "samsungce.connectionState", + "version": 1 + }, + { + "id": "samsungce.activationState", + "version": 1 + } + ], + "categories": [ + { + "name": "Other", + "categoryType": "manufacturer" + } + ], + "optional": false + } + ], + "createTime": "2025-06-20T14:12:56.260Z", + "profile": { + "id": "5d345d41-a497-3fc7-84fe-eaaee50f0509" + }, + "ocf": { + "ocfDeviceType": "oic.d.robotcleaner", + "name": "[robot vacuum] Samsung", + "specVersion": "core.1.1.0", + "verticalDomainSpecVersion": "res.1.1.0,sh.1.1.0", + "manufacturerName": "Samsung Electronics", + "modelNumber": "JETBOT_COMBO_9X00_24K|50029141|80010b0002d8411f0100000000000000", + "platformVersion": "1.0", + "platformOS": "Tizen", + "hwVersion": "", + "firmwareVersion": "20250123.105306", + "vendorId": "DA-RVC-MAP-01011", + "vendorResourceClientServerVersion": "4.0.38", + "lastSignupTime": "2025-06-20T14:12:56.202953160Z", + "transferCandidate": false, + "additionalAuthCodeRequired": false, + "modelCode": "NONE" + }, + "type": "OCF", + "restrictionTier": 0, + "allowed": null, + "executionContext": "CLOUD", + "relationships": [] + } + ], + "_links": {} +} diff --git a/tests/components/smartthings/snapshots/test_init.ambr b/tests/components/smartthings/snapshots/test_init.ambr index 446eca63fb27c..6ce3992d2b49b 100644 --- a/tests/components/smartthings/snapshots/test_init.ambr +++ b/tests/components/smartthings/snapshots/test_init.ambr @@ -728,6 +728,39 @@ 'via_device_id': None, }) # --- +# name: test_devices[da_rvc_map_01011] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': 'https://account.smartthings.com', + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': '', + 'id': , + 'identifiers': set({ + tuple( + 'smartthings', + '05accb39-2017-c98b-a5ab-04a81f4d3d9a', + ), + }), + 'is_new': False, + 'labels': set({ + }), + 'manufacturer': 'Samsung Electronics', + 'model': 'JETBOT_COMBO_9X00_24K', + 'model_id': None, + 'name': 'Robot vacuum', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'suggested_area': None, + 'sw_version': '20250123.105306', + 'via_device_id': None, + }) +# --- # name: test_devices[da_rvc_normal_000001] DeviceRegistryEntrySnapshot({ 'area_id': 'theater', diff --git a/tests/components/smartthings/snapshots/test_select.ambr b/tests/components/smartthings/snapshots/test_select.ambr index 7dd57e89c6a58..8950846ba214b 100644 --- a/tests/components/smartthings/snapshots/test_select.ambr +++ b/tests/components/smartthings/snapshots/test_select.ambr @@ -172,6 +172,63 @@ 'state': 'extra_high', }) # --- +# name: test_all_entities[da_rvc_map_01011][select.robot_vacuum_lamp-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'on', + 'off', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': , + 'entity_id': 'select.robot_vacuum_lamp', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Lamp', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'lamp', + 'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_samsungce.lamp_brightnessLevel_brightnessLevel', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_rvc_map_01011][select.robot_vacuum_lamp-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Robot vacuum Lamp', + 'options': list([ + 'on', + 'off', + ]), + }), + 'context': , + 'entity_id': 'select.robot_vacuum_lamp', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- # name: test_all_entities[da_wm_dw_000001][select.dishwasher-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/smartthings/snapshots/test_sensor.ambr b/tests/components/smartthings/snapshots/test_sensor.ambr index f88524116ee20..169359118dac9 100644 --- a/tests/components/smartthings/snapshots/test_sensor.ambr +++ b/tests/components/smartthings/snapshots/test_sensor.ambr @@ -6066,6 +6066,540 @@ 'state': '97', }) # --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_battery-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.robot_vacuum_battery', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Battery', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_battery_battery_battery', + 'unit_of_measurement': '%', + }) +# --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_battery-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': 'Robot vacuum Battery', + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.robot_vacuum_battery', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '59', + }) +# --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_cleaning_mode-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'auto', + 'part', + 'repeat', + 'manual', + 'stop', + 'map', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.robot_vacuum_cleaning_mode', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Cleaning mode', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'robot_cleaner_cleaning_mode', + 'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_robotCleanerCleaningMode_robotCleanerCleaningMode_robotCleanerCleaningMode', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_cleaning_mode-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Robot vacuum Cleaning mode', + 'options': list([ + 'auto', + 'part', + 'repeat', + 'manual', + 'stop', + 'map', + ]), + }), + 'context': , + 'entity_id': 'sensor.robot_vacuum_cleaning_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'stop', + }) +# --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + '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.robot_vacuum_energy', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Energy', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_powerConsumptionReport_powerConsumption_energy_meter', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Robot vacuum Energy', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.robot_vacuum_energy', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.981', + }) +# --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy_difference-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + '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.robot_vacuum_energy_difference', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Energy difference', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'energy_difference', + 'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy_difference-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Robot vacuum Energy difference', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.robot_vacuum_energy_difference', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.021', + }) +# --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy_saved-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + '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.robot_vacuum_energy_saved', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Energy saved', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'energy_saved', + 'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_powerConsumptionReport_powerConsumption_energySaved_meter', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy_saved-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Robot vacuum Energy saved', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.robot_vacuum_energy_saved', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_movement-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'homing', + 'idle', + 'charging', + 'alarm', + 'off', + 'reserve', + 'point', + 'after', + 'cleaning', + 'pause', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.robot_vacuum_movement', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Movement', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'robot_cleaner_movement', + 'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_robotCleanerMovement_robotCleanerMovement_robotCleanerMovement', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_movement-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Robot vacuum Movement', + 'options': list([ + 'homing', + 'idle', + 'charging', + 'alarm', + 'off', + 'reserve', + 'point', + 'after', + 'cleaning', + 'pause', + ]), + }), + 'context': , + 'entity_id': 'sensor.robot_vacuum_movement', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'cleaning', + }) +# --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + '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.robot_vacuum_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Power', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_powerConsumptionReport_powerConsumption_power_meter', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Robot vacuum Power', + 'power_consumption_end': '2025-07-10T11:20:22Z', + 'power_consumption_start': '2025-07-10T11:11:22Z', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.robot_vacuum_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0', + }) +# --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_power_energy-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + '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.robot_vacuum_power_energy', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Power energy', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'power_energy', + 'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_power_energy-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Robot vacuum Power energy', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.robot_vacuum_power_energy', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_turbo_mode-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'on', + 'off', + 'silence', + 'extra_silence', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.robot_vacuum_turbo_mode', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Turbo mode', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'robot_cleaner_turbo_mode', + 'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_robotCleanerTurboMode_robotCleanerTurboMode_robotCleanerTurboMode', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_turbo_mode-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Robot vacuum Turbo mode', + 'options': list([ + 'on', + 'off', + 'silence', + 'extra_silence', + ]), + }), + 'context': , + 'entity_id': 'sensor.robot_vacuum_turbo_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'extra_silence', + }) +# --- # name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_battery-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/smartthings/snapshots/test_switch.ambr b/tests/components/smartthings/snapshots/test_switch.ambr index d0ea3dbcdad82..1aaeb35205fc8 100644 --- a/tests/components/smartthings/snapshots/test_switch.ambr +++ b/tests/components/smartthings/snapshots/test_switch.ambr @@ -623,6 +623,54 @@ 'state': 'off', }) # --- +# name: test_all_entities[da_rvc_map_01011][switch.robot_vacuum-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': None, + 'entity_id': 'switch.robot_vacuum', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': None, + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_switch_switch_switch', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_rvc_map_01011][switch.robot_vacuum-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Robot vacuum', + }), + 'context': , + 'entity_id': 'switch.robot_vacuum', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- # name: test_all_entities[da_rvc_normal_000001][switch.robot_vacuum-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ From d393d5fdbbc70e84cebdff81bd0a2e081315ccd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Fri, 11 Jul 2025 15:27:06 +0100 Subject: [PATCH 7/7] Use non-autospec mock for Reolink's util and view tests (#148579) --- tests/components/reolink/conftest.py | 2 ++ tests/components/reolink/test_util.py | 12 +++++------- tests/components/reolink/test_views.py | 23 +++++++++++------------ 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/tests/components/reolink/conftest.py b/tests/components/reolink/conftest.py index d34a27045fe5c..1ca6bb4eb55d9 100644 --- a/tests/components/reolink/conftest.py +++ b/tests/components/reolink/conftest.py @@ -84,6 +84,8 @@ def _init_host_mock(host_mock: MagicMock) -> None: host_mock.set_whiteled = AsyncMock() host_mock.set_state_light = AsyncMock() host_mock.renew = AsyncMock() + host_mock.get_vod_source = AsyncMock() + host_mock.expire_session = AsyncMock() host_mock.is_nvr = True host_mock.is_hub = False host_mock.mac_address = TEST_MAC diff --git a/tests/components/reolink/test_util.py b/tests/components/reolink/test_util.py index 181249b8bffcb..8b730bc708b9c 100644 --- a/tests/components/reolink/test_util.py +++ b/tests/components/reolink/test_util.py @@ -103,12 +103,12 @@ async def test_try_function( hass: HomeAssistant, config_entry: MockConfigEntry, - reolink_connect: MagicMock, + reolink_host: MagicMock, side_effect: ReolinkError, expected: HomeAssistantError, ) -> None: """Test try_function error translations using number entity.""" - reolink_connect.volume.return_value = 80 + reolink_host.volume.return_value = 80 with patch("homeassistant.components.reolink.PLATFORMS", [Platform.NUMBER]): assert await hass.config_entries.async_setup(config_entry.entry_id) @@ -117,7 +117,7 @@ async def test_try_function( entity_id = f"{Platform.NUMBER}.{TEST_NVR_NAME}_volume" - reolink_connect.set_volume.side_effect = side_effect + reolink_host.set_volume.side_effect = side_effect with pytest.raises(expected.__class__) as err: await hass.services.async_call( NUMBER_DOMAIN, @@ -128,8 +128,6 @@ async def test_try_function( assert err.value.translation_key == expected.translation_key - reolink_connect.set_volume.reset_mock(side_effect=True) - @pytest.mark.parametrize( ("identifiers"), @@ -141,12 +139,12 @@ async def test_try_function( async def test_get_device_uid_and_ch( hass: HomeAssistant, config_entry: MockConfigEntry, - reolink_connect: MagicMock, + reolink_host: MagicMock, device_registry: dr.DeviceRegistry, identifiers: set[tuple[str, str]], ) -> None: """Test get_device_uid_and_ch with multiple identifiers.""" - reolink_connect.channels = [0] + reolink_host.channels = [0] dev_entry = device_registry.async_get_or_create( identifiers=identifiers, diff --git a/tests/components/reolink/test_views.py b/tests/components/reolink/test_views.py index 992e47f0575b7..6da9fbd29ca52 100644 --- a/tests/components/reolink/test_views.py +++ b/tests/components/reolink/test_views.py @@ -64,14 +64,14 @@ def get_mock_session( ) async def test_playback_proxy( hass: HomeAssistant, - reolink_connect: MagicMock, + reolink_host: MagicMock, config_entry: MockConfigEntry, hass_client: ClientSessionGenerator, caplog: pytest.LogCaptureFixture, content_type: str, ) -> None: """Test successful playback proxy URL.""" - reolink_connect.get_vod_source.return_value = (TEST_MIME_TYPE_MP4, TEST_URL) + reolink_host.get_vod_source.return_value = (TEST_MIME_TYPE_MP4, TEST_URL) mock_session = get_mock_session(content_type=content_type) @@ -100,12 +100,12 @@ async def test_playback_proxy( async def test_proxy_get_source_error( hass: HomeAssistant, - reolink_connect: MagicMock, + reolink_host: MagicMock, config_entry: MockConfigEntry, hass_client: ClientSessionGenerator, ) -> None: """Test error while getting source for playback proxy URL.""" - reolink_connect.get_vod_source.side_effect = ReolinkError(TEST_ERROR) + reolink_host.get_vod_source.side_effect = ReolinkError(TEST_ERROR) assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -123,12 +123,11 @@ async def test_proxy_get_source_error( assert await response.content.read() == bytes(TEST_ERROR, "utf-8") assert response.status == HTTPStatus.BAD_REQUEST - reolink_connect.get_vod_source.side_effect = None async def test_proxy_invalid_config_entry_id( hass: HomeAssistant, - reolink_connect: MagicMock, + reolink_host: MagicMock, config_entry: MockConfigEntry, hass_client: ClientSessionGenerator, ) -> None: @@ -156,12 +155,12 @@ async def test_proxy_invalid_config_entry_id( async def test_playback_proxy_timeout( hass: HomeAssistant, - reolink_connect: MagicMock, + reolink_host: MagicMock, config_entry: MockConfigEntry, hass_client: ClientSessionGenerator, ) -> None: """Test playback proxy URL with a timeout in the second chunk.""" - reolink_connect.get_vod_source.return_value = (TEST_MIME_TYPE_MP4, TEST_URL) + reolink_host.get_vod_source.return_value = (TEST_MIME_TYPE_MP4, TEST_URL) mock_session = get_mock_session([b"test", TimeoutError()], 4) @@ -190,13 +189,13 @@ async def test_playback_proxy_timeout( @pytest.mark.parametrize(("content_type"), [("video/x-flv"), ("text/html")]) async def test_playback_wrong_content( hass: HomeAssistant, - reolink_connect: MagicMock, + reolink_host: MagicMock, config_entry: MockConfigEntry, hass_client: ClientSessionGenerator, content_type: str, ) -> None: """Test playback proxy URL with a wrong content type in the response.""" - reolink_connect.get_vod_source.return_value = (TEST_MIME_TYPE_MP4, TEST_URL) + reolink_host.get_vod_source.return_value = (TEST_MIME_TYPE_MP4, TEST_URL) mock_session = get_mock_session(content_type=content_type) @@ -223,12 +222,12 @@ async def test_playback_wrong_content( async def test_playback_connect_error( hass: HomeAssistant, - reolink_connect: MagicMock, + reolink_host: MagicMock, config_entry: MockConfigEntry, hass_client: ClientSessionGenerator, ) -> None: """Test playback proxy URL with a connection error.""" - reolink_connect.get_vod_source.return_value = (TEST_MIME_TYPE_MP4, TEST_URL) + reolink_host.get_vod_source.return_value = (TEST_MIME_TYPE_MP4, TEST_URL) mock_session = Mock() mock_session.get = AsyncMock(side_effect=ClientConnectionError(TEST_ERROR))