diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index b74a30a40f892b..3e19e03dc4b1a3 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -27,7 +27,7 @@ jobs: publish: ${{ steps.version.outputs.publish }} steps: - name: Checkout the repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 with: fetch-depth: 0 @@ -94,7 +94,7 @@ jobs: - arch: i386 steps: - name: Checkout the repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Download nightly wheels of frontend if: needs.init.outputs.channel == 'dev' @@ -227,7 +227,7 @@ jobs: - green steps: - name: Checkout the repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Set build additional args run: | @@ -265,7 +265,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Initialize git uses: home-assistant/actions/helpers/git-init@master @@ -309,7 +309,7 @@ jobs: registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"] steps: - name: Checkout the repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Install Cosign uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 @@ -418,7 +418,7 @@ jobs: if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true' steps: - name: Checkout the repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 @@ -463,7 +463,7 @@ jobs: HASSFEST_IMAGE_TAG: ghcr.io/home-assistant/hassfest:${{ needs.init.outputs.version }} steps: - name: Checkout repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Login to GitHub Container Registry uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a3651ff56ff756..7d7e971afcfcb8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -99,7 +99,7 @@ jobs: steps: - &checkout name: Check out code from GitHub - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Generate partial Python venv restore key id: generate_python_cache_key run: | diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 360f98032570a9..f50ea3cef0370b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Check out code from GitHub - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Initialize CodeQL uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4 diff --git a/.github/workflows/translations.yml b/.github/workflows/translations.yml index 17f8b3c90f20e3..035a058c96e14d 100644 --- a/.github/workflows/translations.yml +++ b/.github/workflows/translations.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout the repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 6b2a3c95db827a..e19d3203d977ae 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -33,7 +33,7 @@ jobs: steps: - &checkout name: Checkout the repository - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python diff --git a/homeassistant/components/awair/manifest.json b/homeassistant/components/awair/manifest.json index 587aba4b74743d..1ec10bfa8a25ed 100644 --- a/homeassistant/components/awair/manifest.json +++ b/homeassistant/components/awair/manifest.json @@ -6,7 +6,7 @@ "documentation": "https://www.home-assistant.io/integrations/awair", "iot_class": "local_polling", "loggers": ["python_awair"], - "requirements": ["python-awair==0.2.4"], + "requirements": ["python-awair==0.2.5"], "zeroconf": [ { "name": "awair*", diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 05e420cb4143af..5377075fe545e5 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -7,7 +7,7 @@ from datetime import datetime, timedelta from enum import Enum import logging -from typing import cast +from typing import Any, cast from hass_nabucasa import Cloud import voluptuous as vol @@ -86,6 +86,10 @@ "CLOUD_CONNECTION_STATE" ) +_SIGNAL_CLOUDHOOKS_UPDATED: SignalType[dict[str, Any]] = SignalType( + "CLOUDHOOKS_UPDATED" +) + STARTUP_REPAIR_DELAY = 1 # 1 hour ALEXA_ENTITY_SCHEMA = vol.Schema( @@ -242,6 +246,24 @@ async def async_delete_cloudhook(hass: HomeAssistant, webhook_id: str) -> None: await hass.data[DATA_CLOUD].cloudhooks.async_delete(webhook_id) +@callback +def async_listen_cloudhook_change( + hass: HomeAssistant, + webhook_id: str, + on_change: Callable[[dict[str, Any] | None], None], +) -> Callable[[], None]: + """Listen for cloudhook changes for the given webhook and notify when modified or deleted.""" + + @callback + def _handle_cloudhooks_updated(cloudhooks: dict[str, Any]) -> None: + """Handle cloudhooks updated signal.""" + on_change(cloudhooks.get(webhook_id)) + + return async_dispatcher_connect( + hass, _SIGNAL_CLOUDHOOKS_UPDATED, _handle_cloudhooks_updated + ) + + @bind_hass @callback def async_remote_ui_url(hass: HomeAssistant) -> str: @@ -289,7 +311,7 @@ async def _shutdown(event: Event) -> None: hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown) - _remote_handle_prefs_updated(cloud) + _handle_prefs_updated(hass, cloud) _setup_services(hass, prefs) async def async_startup_repairs(_: datetime) -> None: @@ -373,26 +395,32 @@ async def _on_initialized() -> None: @callback -def _remote_handle_prefs_updated(cloud: Cloud[CloudClient]) -> None: - """Handle remote preferences updated.""" - cur_pref = cloud.client.prefs.remote_enabled +def _handle_prefs_updated(hass: HomeAssistant, cloud: Cloud[CloudClient]) -> None: + """Register handler for cloud preferences updates.""" + cur_remote_enabled = cloud.client.prefs.remote_enabled + cur_cloudhooks = cloud.client.prefs.cloudhooks lock = asyncio.Lock() - # Sync remote connection with prefs - async def remote_prefs_updated(prefs: CloudPreferences) -> None: - """Update remote status.""" - nonlocal cur_pref + async def on_prefs_updated(prefs: CloudPreferences) -> None: + """Handle cloud preferences updates.""" + nonlocal cur_remote_enabled + nonlocal cur_cloudhooks + # Lock protects cur_ state variables from concurrent updates async with lock: - if prefs.remote_enabled == cur_pref: + if cur_cloudhooks != prefs.cloudhooks: + cur_cloudhooks = prefs.cloudhooks + async_dispatcher_send(hass, _SIGNAL_CLOUDHOOKS_UPDATED, cur_cloudhooks) + + if prefs.remote_enabled == cur_remote_enabled: return - if cur_pref := prefs.remote_enabled: + if cur_remote_enabled := prefs.remote_enabled: await cloud.remote.connect() else: await cloud.remote.disconnect() - cloud.client.prefs.async_listen_updates(remote_prefs_updated) + cloud.client.prefs.async_listen_updates(on_prefs_updated) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/decora_wifi/light.py b/homeassistant/components/decora_wifi/light.py index 9ad1d9ced04dd4..40a9da49f5d361 100644 --- a/homeassistant/components/decora_wifi/light.py +++ b/homeassistant/components/decora_wifi/light.py @@ -2,6 +2,7 @@ from __future__ import annotations +from datetime import timedelta import logging from typing import Any @@ -25,6 +26,7 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) @@ -167,6 +169,7 @@ def turn_off(self, **kwargs: Any) -> None: except ValueError: _LOGGER.error("Failed to turn off myLeviton switch") + @Throttle(timedelta(seconds=30)) def update(self) -> None: """Fetch new state data for this switch.""" try: diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 9907662c65b7c9..c07739135fbada 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20251105.0"] + "requirements": ["home-assistant-frontend==20251105.1"] } diff --git a/homeassistant/components/go2rtc/__init__.py b/homeassistant/components/go2rtc/__init__.py index 497c03adbd4da4..4895e8dbcd4797 100644 --- a/homeassistant/components/go2rtc/__init__.py +++ b/homeassistant/components/go2rtc/__init__.py @@ -2,10 +2,11 @@ from __future__ import annotations +from dataclasses import dataclass import logging import shutil -from aiohttp import ClientSession +from aiohttp import ClientSession, UnixConnector from aiohttp.client_exceptions import ClientConnectionError, ServerConnectionError from awesomeversion import AwesomeVersion from go2rtc_client import Go2RtcRestClient @@ -52,6 +53,7 @@ CONF_DEBUG_UI, DEBUG_UI_URL_MESSAGE, DOMAIN, + HA_MANAGED_UNIX_SOCKET, HA_MANAGED_URL, RECOMMENDED_VERSION, ) @@ -73,7 +75,7 @@ extra=vol.ALLOW_EXTRA, ) -_DATA_GO2RTC: HassKey[str] = HassKey(DOMAIN) +_DATA_GO2RTC: HassKey[Go2RtcConfig] = HassKey(DOMAIN) _RETRYABLE_ERRORS = (ClientConnectionError, ServerConnectionError) type Go2RtcConfigEntry = ConfigEntry[WebRTCProvider] @@ -100,8 +102,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return False # HA will manage the binary + session = ClientSession(connector=UnixConnector(path=HA_MANAGED_UNIX_SOCKET)) server = Server( - hass, binary, enable_ui=config.get(DOMAIN, {}).get(CONF_DEBUG_UI, False) + hass, + binary, + session, + enable_ui=config.get(DOMAIN, {}).get(CONF_DEBUG_UI, False), ) try: await server.start() @@ -111,12 +117,15 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def on_stop(event: Event) -> None: await server.stop() + await session.close() hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, on_stop) url = HA_MANAGED_URL + else: + session = async_get_clientsession(hass) - hass.data[_DATA_GO2RTC] = url + hass.data[_DATA_GO2RTC] = Go2RtcConfig(url, session) discovery_flow.async_create_flow( hass, DOMAIN, context={"source": SOURCE_SYSTEM}, data={} ) @@ -132,8 +141,9 @@ async def _remove_go2rtc_entries(hass: HomeAssistant) -> None: async def async_setup_entry(hass: HomeAssistant, entry: Go2RtcConfigEntry) -> bool: """Set up go2rtc from a config entry.""" - url = hass.data[_DATA_GO2RTC] - session = async_get_clientsession(hass) + config = hass.data[_DATA_GO2RTC] + url = config.url + session = config.session client = Go2RtcRestClient(session, url) # Validate the server URL try: @@ -342,3 +352,11 @@ async def teardown(self) -> None: for ws_client in self._sessions.values(): await ws_client.close() self._sessions.clear() + + +@dataclass +class Go2RtcConfig: + """Go2rtc configuration.""" + + url: str + session: ClientSession diff --git a/homeassistant/components/go2rtc/const.py b/homeassistant/components/go2rtc/const.py index 0d7d666b284f10..f6d95b855c8c6c 100644 --- a/homeassistant/components/go2rtc/const.py +++ b/homeassistant/components/go2rtc/const.py @@ -6,4 +6,5 @@ DEBUG_UI_URL_MESSAGE = "Url and debug_ui cannot be set at the same time." HA_MANAGED_API_PORT = 11984 HA_MANAGED_URL = f"http://localhost:{HA_MANAGED_API_PORT}/" +HA_MANAGED_UNIX_SOCKET = "/run/go2rtc.sock" RECOMMENDED_VERSION = "1.9.12" diff --git a/homeassistant/components/go2rtc/server.py b/homeassistant/components/go2rtc/server.py index 37040742aea98f..ff9fcd324fba62 100644 --- a/homeassistant/components/go2rtc/server.py +++ b/homeassistant/components/go2rtc/server.py @@ -6,13 +6,13 @@ import logging from tempfile import NamedTemporaryFile +from aiohttp import ClientSession from go2rtc_client import Go2RtcRestClient from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .const import HA_MANAGED_API_PORT, HA_MANAGED_URL +from .const import HA_MANAGED_API_PORT, HA_MANAGED_UNIX_SOCKET, HA_MANAGED_URL _LOGGER = logging.getLogger(__name__) _TERMINATE_TIMEOUT = 5 @@ -23,7 +23,8 @@ _RESPAWN_COOLDOWN = 1 # Default configuration for HA -# - Api is listening only on localhost +# - Unix socket for secure local communication +# - HTTP API only enabled when UI is enabled # - Enable rtsp for localhost only as ffmpeg needs it # - Clear default ice servers _GO2RTC_CONFIG_FORMAT = r"""# This file is managed by Home Assistant @@ -33,7 +34,8 @@ modules: {app_modules} api: - listen: "{api_ip}:{api_port}" + listen: "{listen_config}" + unix_listen: "{unix_socket}" allow_paths: {api_allow_paths} # ffmpeg needs the exec module @@ -120,20 +122,24 @@ def _create_temp_file(enable_ui: bool) -> str: """Create temporary config file.""" app_modules: tuple[str, ...] = _APP_MODULES api_paths: tuple[str, ...] = _API_ALLOW_PATHS - api_ip = _LOCALHOST_IP + if enable_ui: app_modules = _UI_APP_MODULES api_paths = _UI_API_ALLOW_PATHS # Listen on all interfaces for allowing access from all ips - api_ip = "" + listen_config = f":{HA_MANAGED_API_PORT}" + else: + # Disable HTTP listening when UI is not enabled + # as HA does not use it. + listen_config = "" # Set delete=False to prevent the file from being deleted when the file is closed # Linux is clearing tmp folder on reboot, so no need to delete it manually with NamedTemporaryFile(prefix="go2rtc_", suffix=".yaml", delete=False) as file: file.write( _GO2RTC_CONFIG_FORMAT.format( - api_ip=api_ip, - api_port=HA_MANAGED_API_PORT, + listen_config=listen_config, + unix_socket=HA_MANAGED_UNIX_SOCKET, app_modules=_format_list_for_yaml(app_modules), api_allow_paths=_format_list_for_yaml(api_paths), ).encode() @@ -145,11 +151,17 @@ class Server: """Go2rtc server.""" def __init__( - self, hass: HomeAssistant, binary: str, *, enable_ui: bool = False + self, + hass: HomeAssistant, + binary: str, + session: ClientSession, + *, + enable_ui: bool = False, ) -> None: """Initialize the server.""" self._hass = hass self._binary = binary + self._session = session self._log_buffer: deque[str] = deque(maxlen=_LOG_BUFFER_SIZE) self._process: asyncio.subprocess.Process | None = None self._startup_complete = asyncio.Event() @@ -197,7 +209,7 @@ async def _start(self) -> None: raise Go2RTCServerStartError from err # Check the server version - client = Go2RtcRestClient(async_get_clientsession(self._hass), HA_MANAGED_URL) + client = Go2RtcRestClient(self._session, HA_MANAGED_URL) await client.validate_server_version() async def _log_output(self, process: asyncio.subprocess.Process) -> None: @@ -269,7 +281,7 @@ async def _monitor_process(self) -> None: async def _monitor_api(self) -> None: """Raise if the go2rtc process terminates.""" - client = Go2RtcRestClient(async_get_clientsession(self._hass), HA_MANAGED_URL) + client = Go2RtcRestClient(self._session, HA_MANAGED_URL) _LOGGER.debug("Monitoring go2rtc API") try: diff --git a/homeassistant/components/iron_os/strings.json b/homeassistant/components/iron_os/strings.json index 4fbae1a540e25f..4310bd02206728 100644 --- a/homeassistant/components/iron_os/strings.json +++ b/homeassistant/components/iron_os/strings.json @@ -145,10 +145,10 @@ "loop": "Loop", "off": "[%key:common::state::off%]", "seconds_1": "1 second", - "seconds_2": "2 second", - "seconds_3": "3 second", - "seconds_4": "4 second", - "seconds_5": "5 second" + "seconds_2": "2 seconds", + "seconds_3": "3 seconds", + "seconds_4": "4 seconds", + "seconds_5": "5 seconds" } }, "min_dc_voltage_cells": { diff --git a/homeassistant/components/matter/button.py b/homeassistant/components/matter/button.py index a8aa524580db03..11a364622e34ec 100644 --- a/homeassistant/components/matter/button.py +++ b/homeassistant/components/matter/button.py @@ -58,7 +58,7 @@ async def async_press(self) -> None: platform=Platform.BUTTON, entity_description=MatterButtonEntityDescription( key="IdentifyButton", - entity_category=EntityCategory.CONFIG, + entity_category=EntityCategory.DIAGNOSTIC, device_class=ButtonDeviceClass.IDENTIFY, command=lambda: clusters.Identify.Commands.Identify(identifyTime=15), ), diff --git a/homeassistant/components/mobile_app/__init__.py b/homeassistant/components/mobile_app/__init__.py index 64b367112efd95..65c8296264a53d 100644 --- a/homeassistant/components/mobile_app/__init__.py +++ b/homeassistant/components/mobile_app/__init__.py @@ -131,12 +131,41 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: registration_name = f"Mobile App: {registration[ATTR_DEVICE_NAME]}" webhook_register(hass, DOMAIN, registration_name, webhook_id, handle_webhook) + def clean_cloudhook() -> None: + """Clean up cloudhook from config entry.""" + if CONF_CLOUDHOOK_URL in entry.data: + data = dict(entry.data) + data.pop(CONF_CLOUDHOOK_URL) + hass.config_entries.async_update_entry(entry, data=data) + + def on_cloudhook_change(cloudhook: dict[str, Any] | None) -> None: + """Handle cloudhook changes.""" + if cloudhook: + if entry.data.get(CONF_CLOUDHOOK_URL) == cloudhook[CONF_CLOUDHOOK_URL]: + return + + hass.config_entries.async_update_entry( + entry, + data={**entry.data, CONF_CLOUDHOOK_URL: cloudhook[CONF_CLOUDHOOK_URL]}, + ) + else: + clean_cloudhook() + async def manage_cloudhook(state: cloud.CloudConnectionState) -> None: if ( state is cloud.CloudConnectionState.CLOUD_CONNECTED and CONF_CLOUDHOOK_URL not in entry.data ): await async_create_cloud_hook(hass, webhook_id, entry) + elif ( + state is cloud.CloudConnectionState.CLOUD_DISCONNECTED + and not cloud.async_is_logged_in(hass) + ): + clean_cloudhook() + + entry.async_on_unload( + cloud.async_listen_cloudhook_change(hass, webhook_id, on_cloudhook_change) + ) if cloud.async_is_logged_in(hass): if ( @@ -147,9 +176,7 @@ async def manage_cloudhook(state: cloud.CloudConnectionState) -> None: await async_create_cloud_hook(hass, webhook_id, entry) elif CONF_CLOUDHOOK_URL in entry.data: # If we have a cloudhook but no longer logged in to the cloud, remove it from the entry - data = dict(entry.data) - data.pop(CONF_CLOUDHOOK_URL) - hass.config_entries.async_update_entry(entry, data=data) + clean_cloudhook() entry.async_on_unload(cloud.async_listen_connection_change(hass, manage_cloudhook)) diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 102182026683d7..da6c6676b4f358 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -756,10 +756,9 @@ async def webhook_get_config( "theme_color": MANIFEST_JSON["theme_color"], } - if CONF_CLOUDHOOK_URL in config_entry.data: - resp[CONF_CLOUDHOOK_URL] = config_entry.data[CONF_CLOUDHOOK_URL] - if cloud.async_active_subscription(hass): + if CONF_CLOUDHOOK_URL in config_entry.data: + resp[CONF_CLOUDHOOK_URL] = config_entry.data[CONF_CLOUDHOOK_URL] with suppress(cloud.CloudNotAvailable): resp[CONF_REMOTE_UI_URL] = cloud.async_remote_ui_url(hass) diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index b66cee2bce73da..ec452bc95627c5 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -239,6 +239,7 @@ CONF_OSCILLATION_COMMAND_TOPIC, CONF_OSCILLATION_STATE_TOPIC, CONF_OSCILLATION_VALUE_TEMPLATE, + CONF_PATTERN, CONF_PAYLOAD_ARM_AWAY, CONF_PAYLOAD_ARM_CUSTOM_BYPASS, CONF_PAYLOAD_ARM_HOME, @@ -465,6 +466,7 @@ Platform.SENSOR, Platform.SIREN, Platform.SWITCH, + Platform.TEXT, ] _CODE_VALIDATION_MODE = { @@ -819,6 +821,16 @@ mode=SelectSelectorMode.DROPDOWN, ) ) +TEXT_MODE_SELECTOR = SelectSelector( + SelectSelectorConfig( + options=[TextSelectorType.TEXT.value, TextSelectorType.PASSWORD.value], + mode=SelectSelectorMode.DROPDOWN, + translation_key="text_mode", + ) +) +TEXT_SIZE_SELECTOR = NumberSelector( + NumberSelectorConfig(min=0, max=255, step=1, mode=NumberSelectorMode.BOX) +) @callback @@ -1151,6 +1163,22 @@ def validate_sensor_platform_config( return errors +@callback +def validate_text_platform_config( + config: dict[str, Any], +) -> dict[str, str]: + """Validate the text entity options.""" + errors: dict[str, str] = {} + if ( + CONF_MIN in config + and CONF_MAX in config + and config[CONF_MIN] > config[CONF_MAX] + ): + errors["text_advanced_settings"] = "max_below_min" + + return errors + + ENTITY_CONFIG_VALIDATOR: dict[ str, Callable[[dict[str, Any]], dict[str, str]] | None, @@ -1170,6 +1198,7 @@ def validate_sensor_platform_config( Platform.SENSOR: validate_sensor_platform_config, Platform.SIREN: None, Platform.SWITCH: None, + Platform.TEXT: validate_text_platform_config, } @@ -1430,6 +1459,7 @@ class PlatformField: selector=SWITCH_DEVICE_CLASS_SELECTOR, required=False ), }, + Platform.TEXT: {}, } PLATFORM_MQTT_FIELDS: dict[Platform, dict[str, PlatformField]] = { Platform.ALARM_CONTROL_PANEL: { @@ -3298,6 +3328,58 @@ class PlatformField: CONF_RETAIN: PlatformField(selector=BOOLEAN_SELECTOR, required=False), CONF_OPTIMISTIC: PlatformField(selector=BOOLEAN_SELECTOR, required=False), }, + Platform.TEXT: { + CONF_COMMAND_TOPIC: PlatformField( + selector=TEXT_SELECTOR, + required=True, + validator=valid_publish_topic, + error="invalid_publish_topic", + ), + CONF_COMMAND_TEMPLATE: PlatformField( + selector=TEMPLATE_SELECTOR, + required=False, + validator=validate(cv.template), + error="invalid_template", + ), + CONF_STATE_TOPIC: PlatformField( + selector=TEXT_SELECTOR, + required=False, + validator=valid_subscribe_topic, + error="invalid_subscribe_topic", + ), + CONF_VALUE_TEMPLATE: PlatformField( + selector=TEMPLATE_SELECTOR, + required=False, + validator=validate(cv.template), + error="invalid_template", + ), + CONF_RETAIN: PlatformField(selector=BOOLEAN_SELECTOR, required=False), + CONF_MIN: PlatformField( + selector=TEXT_SIZE_SELECTOR, + required=True, + default=0, + section="text_advanced_settings", + ), + CONF_MAX: PlatformField( + selector=TEXT_SIZE_SELECTOR, + required=True, + default=255, + section="text_advanced_settings", + ), + CONF_MODE: PlatformField( + selector=TEXT_MODE_SELECTOR, + required=True, + default=TextSelectorType.TEXT.value, + section="text_advanced_settings", + ), + CONF_PATTERN: PlatformField( + selector=TEXT_SELECTOR, + required=False, + validator=validate(cv.is_regex), + error="invalid_regular_expression", + section="text_advanced_settings", + ), + }, } MQTT_DEVICE_PLATFORM_FIELDS = { ATTR_NAME: PlatformField(selector=TEXT_SELECTOR, required=True), diff --git a/homeassistant/components/mqtt/const.py b/homeassistant/components/mqtt/const.py index 80fbc9059461ff..7dfdac5634fad5 100644 --- a/homeassistant/components/mqtt/const.py +++ b/homeassistant/components/mqtt/const.py @@ -138,6 +138,7 @@ CONF_OSCILLATION_COMMAND_TEMPLATE = "oscillation_command_template" CONF_OSCILLATION_STATE_TOPIC = "oscillation_state_topic" CONF_OSCILLATION_VALUE_TEMPLATE = "oscillation_value_template" +CONF_PATTERN = "pattern" CONF_PAYLOAD_ARM_AWAY = "payload_arm_away" CONF_PAYLOAD_ARM_CUSTOM_BYPASS = "payload_arm_custom_bypass" CONF_PAYLOAD_ARM_HOME = "payload_arm_home" diff --git a/homeassistant/components/mqtt/strings.json b/homeassistant/components/mqtt/strings.json index 1335f58cbd4dfa..f79d1ac54f2696 100644 --- a/homeassistant/components/mqtt/strings.json +++ b/homeassistant/components/mqtt/strings.json @@ -970,6 +970,21 @@ "temperature_state_topic": "The MQTT topic to subscribe for changes of the target temperature. [Learn more.]({url}#temperature_state_topic)" }, "name": "Target temperature settings" + }, + "text_advanced_settings": { + "data": { + "max": "Maximum length", + "min": "Mininum length", + "mode": "Mode", + "pattern": "Pattern" + }, + "data_description": { + "max": "Maximum length of the text input", + "min": "Mininum length of the text input", + "mode": "Mode of the text input", + "pattern": "A valid regex pattern" + }, + "name": "Advanced text settings" } }, "title": "Configure MQTT device \"{mqtt_device}\"" @@ -1387,7 +1402,8 @@ "select": "[%key:component::select::title%]", "sensor": "[%key:component::sensor::title%]", "siren": "[%key:component::siren::title%]", - "switch": "[%key:component::switch::title%]" + "switch": "[%key:component::switch::title%]", + "text": "[%key:component::text::title%]" } }, "set_ca_cert": { @@ -1424,6 +1440,12 @@ "none": "No target temperature", "single": "Single target temperature" } + }, + "text_mode": { + "options": { + "password": "[%key:common::config_flow::data::password%]", + "text": "[%key:component::text::entity_component::_::state_attributes::mode::state::text%]" + } } }, "services": { diff --git a/homeassistant/components/mqtt/text.py b/homeassistant/components/mqtt/text.py index d306fc0819bdfd..c1b6024d9101f8 100644 --- a/homeassistant/components/mqtt/text.py +++ b/homeassistant/components/mqtt/text.py @@ -27,7 +27,14 @@ from . import subscription from .config import MQTT_RW_SCHEMA -from .const import CONF_COMMAND_TEMPLATE, CONF_COMMAND_TOPIC, CONF_STATE_TOPIC +from .const import ( + CONF_COMMAND_TEMPLATE, + CONF_COMMAND_TOPIC, + CONF_MAX, + CONF_MIN, + CONF_PATTERN, + CONF_STATE_TOPIC, +) from .entity import MqttEntity, async_setup_entity_entry_helper from .models import ( MqttCommandTemplate, @@ -42,12 +49,7 @@ PARALLEL_UPDATES = 0 -CONF_MAX = "max" -CONF_MIN = "min" -CONF_PATTERN = "pattern" - DEFAULT_NAME = "MQTT Text" -DEFAULT_PAYLOAD_RESET = "None" MQTT_TEXT_ATTRIBUTES_BLOCKED = frozenset( { diff --git a/homeassistant/components/niko_home_control/config_flow.py b/homeassistant/components/niko_home_control/config_flow.py index f755670814a56c..ce4ae3a9acf98e 100644 --- a/homeassistant/components/niko_home_control/config_flow.py +++ b/homeassistant/components/niko_home_control/config_flow.py @@ -28,9 +28,13 @@ async def test_connection(host: str) -> str | None: controller = NHCController(host, 8000) try: await controller.connect() - except Exception: - _LOGGER.exception("Unexpected exception") + except TimeoutError: + return "timeout_connect" + except OSError: return "cannot_connect" + except Exception: + _LOGGER.exception("Unexpected exception during connection") + return "unknown" return None diff --git a/homeassistant/components/niko_home_control/strings.json b/homeassistant/components/niko_home_control/strings.json index e6dbacccd126b1..5f25e03fe621a3 100644 --- a/homeassistant/components/niko_home_control/strings.json +++ b/homeassistant/components/niko_home_control/strings.json @@ -5,7 +5,9 @@ "reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]" }, "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "timeout_connect": "[%key:common::config_flow::error::timeout_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" }, "step": { "reconfigure": { diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index bcaa1e808dcad8..2b8381a385c09b 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -16,8 +16,8 @@ "integration_type": "device", "iot_class": "local_push", "loggers": ["aioshelly"], - "quality_scale": "silver", - "requirements": ["aioshelly==13.19.0"], + "quality_scale": "platinum", + "requirements": ["aioshelly==13.20.0"], "zeroconf": [ { "name": "shelly*", diff --git a/homeassistant/components/shelly/quality_scale.yaml b/homeassistant/components/shelly/quality_scale.yaml index 170db03411adfe..f97faa88c270a2 100644 --- a/homeassistant/components/shelly/quality_scale.yaml +++ b/homeassistant/components/shelly/quality_scale.yaml @@ -55,7 +55,7 @@ rules: entity-category: done entity-device-class: done entity-disabled-by-default: done - entity-translations: todo + entity-translations: done exception-translations: done icon-translations: done reconfiguration-flow: done diff --git a/homeassistant/components/shelly/utils.py b/homeassistant/components/shelly/utils.py index c74eb44ab86c58..96acc9680b361a 100644 --- a/homeassistant/components/shelly/utils.py +++ b/homeassistant/components/shelly/utils.py @@ -424,16 +424,15 @@ def get_rpc_channel_name(device: RpcDevice, key: str) -> str | None: if BLU_TRV_IDENTIFIER in key: return None - component = key.split(":")[0] - component_id = key.split(":")[-1] + _, component, component_id = get_rpc_key(key) if custom_name := get_rpc_custom_name(device, key): if component in (*VIRTUAL_COMPONENTS, "input", "presencezone", "script"): return custom_name - channels = get_rpc_number_of_channels(device, component) - - return custom_name if channels == 1 else None + return ( + custom_name if get_rpc_number_of_channels(device, component) == 1 else None + ) if component in (*VIRTUAL_COMPONENTS, "input"): return f"{component.title()} {component_id}" @@ -441,6 +440,14 @@ def get_rpc_channel_name(device: RpcDevice, key: str) -> str | None: return None +def get_rpc_key_normalized(key: str) -> str: + """Get normalized key. Workaround for Pro EM50 and Pro 3EM.""" + # workaround for Pro EM50 + key = key.replace("em1data", "em1") + # workaround for Pro 3EM + return key.replace("emdata", "em") + + def get_rpc_sub_device_name( device: RpcDevice, key: str, emeter_phase: str | None = None ) -> str: @@ -455,11 +462,7 @@ def get_rpc_sub_device_name( if entity_name := device.config[key].get("name"): return cast(str, entity_name) - key = key.replace("emdata", "em") - key = key.replace("em1data", "em1") - - component = key.split(":")[0] - component_id = key.split(":")[-1] + _, component, component_id = get_rpc_key(get_rpc_key_normalized(key)) if component in ("cct", "rgb", "rgbw"): return f"{device.name} {component.upper()} light {component_id}" @@ -528,7 +531,7 @@ def get_rpc_key_instances( def get_rpc_key_ids(keys_dict: dict[str, Any], key: str) -> list[int]: """Return list of key ids for RPC device from a dict.""" - return [int(k.split(":")[1]) for k in keys_dict if k.startswith(f"{key}:")] + return [get_rpc_key_id(k) for k in keys_dict if k.startswith(f"{key}:")] def get_rpc_key_by_role(keys_dict: dict[str, Any], role: str) -> str | None: @@ -810,11 +813,10 @@ def is_rpc_exclude_from_relay( settings: dict[str, Any], status: dict[str, Any], channel: str ) -> bool: """Return true if rpc channel should be excludeed from switch platform.""" - ch = int(channel.split(":")[1]) if is_rpc_thermostat_internal_actuator(status): return True - return is_rpc_channel_type_light(settings, ch) + return is_rpc_channel_type_light(settings, get_rpc_key_id(channel)) def get_shelly_air_lamp_life(lamp_seconds: int) -> float: @@ -836,7 +838,7 @@ async def get_rpc_scripts_event_types( if script_name in ignore_scripts: continue - script_id = int(script.split(":")[-1]) + script_id = get_rpc_key_id(script) script_events[script_id] = await get_rpc_script_event_types(device, script_id) return script_events @@ -867,14 +869,8 @@ def get_rpc_device_info( if key is None: return DeviceInfo(connections={(CONNECTION_NETWORK_MAC, mac)}) - # workaround for Pro EM50 - key = key.replace("em1data", "em1") - # workaround for Pro 3EM - key = key.replace("emdata", "em") - - key_parts = key.split(":") - component = key_parts[0] - idx = key_parts[1] if len(key_parts) > 1 else None + key = get_rpc_key_normalized(key) + has_id, component, _ = get_rpc_key(key) if emeter_phase is not None: return DeviceInfo( @@ -893,7 +889,7 @@ def get_rpc_device_info( component not in (*All_LIGHT_TYPES, "cover", "em1", "switch") and get_irrigation_zone_id(device, key) is None ) - or idx is None + or not has_id or get_rpc_number_of_channels(device, component) < 2 ): return DeviceInfo(connections={(CONNECTION_NETWORK_MAC, mac)}) diff --git a/homeassistant/components/smartthings/icons.json b/homeassistant/components/smartthings/icons.json index 8c7f52d01bf5c8..e9b17060b8f3da 100644 --- a/homeassistant/components/smartthings/icons.json +++ b/homeassistant/components/smartthings/icons.json @@ -82,8 +82,14 @@ "stop": "mdi:stop" } }, + "soil_level": { + "default": "mdi:liquid-spot" + }, "spin_level": { "default": "mdi:rotate-right" + }, + "water_temperature": { + "default": "mdi:water-thermometer" } }, "sensor": { diff --git a/homeassistant/components/smartthings/manifest.json b/homeassistant/components/smartthings/manifest.json index a225a28550dcdc..0d202b4b07a48c 100644 --- a/homeassistant/components/smartthings/manifest.json +++ b/homeassistant/components/smartthings/manifest.json @@ -30,5 +30,5 @@ "iot_class": "cloud_push", "loggers": ["pysmartthings"], "quality_scale": "bronze", - "requirements": ["pysmartthings==3.3.3"] + "requirements": ["pysmartthings==3.3.4"] } diff --git a/homeassistant/components/smartthings/select.py b/homeassistant/components/smartthings/select.py index 3106aba5e49b26..2bdd562c8b9e4a 100644 --- a/homeassistant/components/smartthings/select.py +++ b/homeassistant/components/smartthings/select.py @@ -56,6 +56,34 @@ "1600": "1600", } +WASHER_WATER_TEMPERATURE_TO_HA = { + "none": "none", + "20": "20", + "30": "30", + "40": "40", + "50": "50", + "60": "60", + "65": "65", + "70": "70", + "75": "75", + "80": "80", + "90": "90", + "95": "95", + "tapCold": "tap_cold", + "cold": "cold", + "cool": "cool", + "ecoWarm": "eco_warm", + "warm": "warm", + "semiHot": "semi_hot", + "hot": "hot", + "extraHot": "extra_hot", + "extraLow": "extra_low", + "low": "low", + "mediumLow": "medium_low", + "medium": "medium", + "high": "high", +} + @dataclass(frozen=True, kw_only=True) class SmartThingsSelectDescription(SelectEntityDescription): @@ -147,6 +175,16 @@ class SmartThingsSelectDescription(SelectEntityDescription): options_map=WASHER_SOIL_LEVEL_TO_HA, entity_category=EntityCategory.CONFIG, ), + Capability.CUSTOM_WASHER_WATER_TEMPERATURE: SmartThingsSelectDescription( + key=Capability.CUSTOM_WASHER_WATER_TEMPERATURE, + translation_key="water_temperature", + requires_remote_control_status=True, + options_attribute=Attribute.SUPPORTED_WASHER_WATER_TEMPERATURE, + status_attribute=Attribute.WASHER_WATER_TEMPERATURE, + command=Command.SET_WASHER_WATER_TEMPERATURE, + options_map=WASHER_WATER_TEMPERATURE_TO_HA, + entity_category=EntityCategory.CONFIG, + ), } diff --git a/homeassistant/components/smartthings/strings.json b/homeassistant/components/smartthings/strings.json index ab5cae43cd6263..c8888ccfb42be6 100644 --- a/homeassistant/components/smartthings/strings.json +++ b/homeassistant/components/smartthings/strings.json @@ -223,6 +223,36 @@ "none": "None", "rinse_hold": "Rinse hold" } + }, + "water_temperature": { + "name": "Water temperature", + "state": { + "20": "20", + "30": "30", + "40": "40", + "50": "50", + "60": "60", + "65": "65", + "70": "70", + "75": "75", + "80": "80", + "90": "90", + "95": "95", + "cold": "Cold", + "cool": "Cool", + "eco_warm": "Eco Warm", + "extra_hot": "Extra Hot", + "extra_low": "Extra Low", + "high": "[%key:common::state::high%]", + "hot": "Hot", + "low": "[%key:common::state::low%]", + "medium": "[%key:common::state::medium%]", + "medium_low": "Medium Low", + "none": "None", + "semi_hot": "Semi Hot", + "tap_cold": "Tap Cold", + "warm": "Warm" + } } }, "sensor": { diff --git a/homeassistant/components/switchbot/manifest.json b/homeassistant/components/switchbot/manifest.json index 34096227247bea..d5df733c58481b 100644 --- a/homeassistant/components/switchbot/manifest.json +++ b/homeassistant/components/switchbot/manifest.json @@ -41,5 +41,5 @@ "iot_class": "local_push", "loggers": ["switchbot"], "quality_scale": "gold", - "requirements": ["PySwitchbot==0.73.0"] + "requirements": ["PySwitchbot==0.74.0"] } diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index c9ef1c8a26a5ff..a7151db887d298 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -231,10 +231,6 @@ def __init__(self, config: dict[str, Any]) -> None: # pylint: disable=super-ini tilt_optimistic = config.get(CONF_TILT_OPTIMISTIC) self._tilt_optimistic = tilt_optimistic or not self._tilt_template - self._position: int | None = None - self._is_opening = False - self._is_closing = False - self._tilt_value: int | None = None # The config requires (open and close scripts) or a set position script, # therefore the base supported features will always include them. @@ -258,82 +254,54 @@ def _iterate_scripts( @property def is_closed(self) -> bool | None: """Return if the cover is closed.""" - if self._position is None: + if self._attr_current_cover_position is None: return None - return self._position == 0 - - @property - def is_opening(self) -> bool: - """Return if the cover is currently opening.""" - return self._is_opening - - @property - def is_closing(self) -> bool: - """Return if the cover is currently closing.""" - return self._is_closing - - @property - def current_cover_position(self) -> int | None: - """Return current position of cover. - - None is unknown, 0 is closed, 100 is fully open. - """ - if self._position_template or POSITION_ACTION in self._action_scripts: - return self._position - return None - - @property - def current_cover_tilt_position(self) -> int | None: - """Return current position of cover tilt. - - None is unknown, 0 is closed, 100 is fully open. - """ - return self._tilt_value + return self._attr_current_cover_position == 0 @callback def _update_position(self, result): if result is None: - self._position = None + self._attr_current_cover_position = None return try: state = float(result) except ValueError as err: _LOGGER.error(err) - self._position = None + self._attr_current_cover_position = None return if state < 0 or state > 100: - self._position = None + self._attr_current_cover_position = None _LOGGER.error( "Cover position value must be between 0 and 100. Value was: %.2f", state, ) else: - self._position = state + self._attr_current_cover_position = state @callback def _update_tilt(self, result): if result is None: - self._tilt_value = None + self._attr_current_cover_tilt_position = None return try: state = float(result) except ValueError as err: _LOGGER.error(err) - self._tilt_value = None + self._attr_current_cover_tilt_position = None return if state < 0 or state > 100: - self._tilt_value = None + self._attr_current_cover_tilt_position = None _LOGGER.error( "Tilt value must be between 0 and 100. Value was: %.2f", state, ) else: - self._tilt_value = state + self._attr_current_cover_tilt_position = state def _update_opening_and_closing(self, result: Any) -> None: state = str(result).lower() @@ -341,12 +309,12 @@ def _update_opening_and_closing(self, result: Any) -> None: if state in _VALID_STATES: if not self._position_template: if state in ("true", OPEN_STATE): - self._position = 100 + self._attr_current_cover_position = 100 else: - self._position = 0 + self._attr_current_cover_position = 0 - self._is_opening = state == OPENING_STATE - self._is_closing = state == CLOSING_STATE + self._attr_is_opening = state == OPENING_STATE + self._attr_is_closing = state == CLOSING_STATE else: _LOGGER.error( "Received invalid cover is_on state: %s for entity %s. Expected: %s", @@ -355,10 +323,10 @@ def _update_opening_and_closing(self, result: Any) -> None: ", ".join(_VALID_STATES), ) if not self._position_template: - self._position = None + self._attr_current_cover_position = None - self._is_opening = False - self._is_closing = False + self._attr_is_opening = False + self._attr_is_closing = False async def async_open_cover(self, **kwargs: Any) -> None: """Move the cover up.""" @@ -371,7 +339,7 @@ async def async_open_cover(self, **kwargs: Any) -> None: context=self._context, ) if self._attr_assumed_state: - self._position = 100 + self._attr_current_cover_position = 100 self.async_write_ha_state() async def async_close_cover(self, **kwargs: Any) -> None: @@ -385,7 +353,7 @@ async def async_close_cover(self, **kwargs: Any) -> None: context=self._context, ) if self._attr_assumed_state: - self._position = 0 + self._attr_current_cover_position = 0 self.async_write_ha_state() async def async_stop_cover(self, **kwargs: Any) -> None: @@ -395,10 +363,10 @@ async def async_stop_cover(self, **kwargs: Any) -> None: async def async_set_cover_position(self, **kwargs: Any) -> None: """Set cover position.""" - self._position = kwargs[ATTR_POSITION] + self._attr_current_cover_position = kwargs[ATTR_POSITION] await self.async_run_script( self._action_scripts[POSITION_ACTION], - run_variables={"position": self._position}, + run_variables={"position": self._attr_current_cover_position}, context=self._context, ) if self._attr_assumed_state: @@ -406,10 +374,10 @@ async def async_set_cover_position(self, **kwargs: Any) -> None: async def async_open_cover_tilt(self, **kwargs: Any) -> None: """Tilt the cover open.""" - self._tilt_value = 100 + self._attr_current_cover_tilt_position = 100 await self.async_run_script( self._action_scripts[TILT_ACTION], - run_variables={"tilt": self._tilt_value}, + run_variables={"tilt": self._attr_current_cover_tilt_position}, context=self._context, ) if self._tilt_optimistic: @@ -417,10 +385,10 @@ async def async_open_cover_tilt(self, **kwargs: Any) -> None: async def async_close_cover_tilt(self, **kwargs: Any) -> None: """Tilt the cover closed.""" - self._tilt_value = 0 + self._attr_current_cover_tilt_position = 0 await self.async_run_script( self._action_scripts[TILT_ACTION], - run_variables={"tilt": self._tilt_value}, + run_variables={"tilt": self._attr_current_cover_tilt_position}, context=self._context, ) if self._tilt_optimistic: @@ -428,10 +396,10 @@ async def async_close_cover_tilt(self, **kwargs: Any) -> None: async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: """Move the cover tilt to a specific position.""" - self._tilt_value = kwargs[ATTR_TILT_POSITION] + self._attr_current_cover_tilt_position = kwargs[ATTR_TILT_POSITION] await self.async_run_script( self._action_scripts[TILT_ACTION], - run_variables={"tilt": self._tilt_value}, + run_variables={"tilt": self._attr_current_cover_tilt_position}, context=self._context, ) if self._tilt_optimistic: @@ -467,11 +435,11 @@ def _async_setup_templates(self) -> None: """Set up templates.""" if self._template: self.add_template_attribute( - "_position", self._template, None, self._update_state + "_attr_current_cover_position", self._template, None, self._update_state ) if self._position_template: self.add_template_attribute( - "_position", + "_attr_current_cover_position", self._position_template, None, self._update_position, @@ -479,7 +447,7 @@ def _async_setup_templates(self) -> None: ) if self._tilt_template: self.add_template_attribute( - "_tilt_value", + "_attr_current_cover_tilt_position", self._tilt_template, None, self._update_tilt, @@ -491,7 +459,7 @@ def _async_setup_templates(self) -> None: def _update_state(self, result): super()._update_state(result) if isinstance(result, TemplateError): - self._position = None + self._attr_current_cover_position = None return self._update_opening_and_closing(result) diff --git a/homeassistant/components/tesla_fleet/__init__.py b/homeassistant/components/tesla_fleet/__init__.py index b0189ba29d2e2f..3e14e5ad2b2f02 100644 --- a/homeassistant/components/tesla_fleet/__init__.py +++ b/homeassistant/components/tesla_fleet/__init__.py @@ -50,6 +50,7 @@ Platform.SELECT, Platform.SENSOR, Platform.SWITCH, + Platform.UPDATE, ] type TeslaFleetConfigEntry = ConfigEntry[TeslaFleetData] diff --git a/homeassistant/components/tesla_fleet/strings.json b/homeassistant/components/tesla_fleet/strings.json index 53cee2e8142aac..df32da7d4dd2ad 100644 --- a/homeassistant/components/tesla_fleet/strings.json +++ b/homeassistant/components/tesla_fleet/strings.json @@ -576,6 +576,11 @@ "vehicle_state_valet_mode": { "name": "Valet mode" } + }, + "update": { + "vehicle_state_software_update_status": { + "name": "[%key:component::update::title%]" + } } }, "exceptions": { diff --git a/homeassistant/components/tesla_fleet/update.py b/homeassistant/components/tesla_fleet/update.py new file mode 100644 index 00000000000000..4e8f8a936f5259 --- /dev/null +++ b/homeassistant/components/tesla_fleet/update.py @@ -0,0 +1,114 @@ +"""Update platform for Tesla Fleet integration.""" + +from __future__ import annotations + +from typing import Any + +from tesla_fleet_api.const import Scope +from tesla_fleet_api.tesla.vehicle.fleet import VehicleFleet + +from homeassistant.components.update import UpdateEntity, UpdateEntityFeature +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback + +from . import TeslaFleetConfigEntry +from .entity import TeslaFleetVehicleEntity +from .helpers import handle_vehicle_command +from .models import TeslaFleetVehicleData + +AVAILABLE = "available" +DOWNLOADING = "downloading" +INSTALLING = "installing" +WIFI_WAIT = "downloading_wifi_wait" +SCHEDULED = "scheduled" + +PARALLEL_UPDATES = 0 + + +async def async_setup_entry( + hass: HomeAssistant, + entry: TeslaFleetConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set up the Tesla Fleet update platform from a config entry.""" + + async_add_entities( + TeslaFleetUpdateEntity(vehicle, entry.runtime_data.scopes) + for vehicle in entry.runtime_data.vehicles + ) + + +class TeslaFleetUpdateEntity(TeslaFleetVehicleEntity, UpdateEntity): + """Tesla Fleet Update entity.""" + + _attr_supported_features = UpdateEntityFeature.PROGRESS + api: VehicleFleet + + def __init__( + self, + data: TeslaFleetVehicleData, + scopes: list[Scope], + ) -> None: + """Initialize the Update.""" + self.scoped = Scope.VEHICLE_CMDS in scopes + super().__init__( + data, + "vehicle_state_software_update_status", + ) + + async def async_install( + self, version: str | None, backup: bool, **kwargs: Any + ) -> None: + """Install an update.""" + self.raise_for_read_only(Scope.VEHICLE_CMDS) + + await handle_vehicle_command(self.api.schedule_software_update(offset_sec=0)) + self._attr_in_progress = True + self.async_write_ha_state() + + def _async_update_attrs(self) -> None: + """Update the attributes of the entity.""" + + # Supported Features + if self.scoped and self._value in ( + AVAILABLE, + SCHEDULED, + ): + # Only allow install when an update has been fully downloaded + self._attr_supported_features = ( + UpdateEntityFeature.PROGRESS | UpdateEntityFeature.INSTALL + ) + else: + self._attr_supported_features = UpdateEntityFeature.PROGRESS + + # Installed Version + self._attr_installed_version = self.get("vehicle_state_car_version") + if self._attr_installed_version is not None: + # Remove build from version + self._attr_installed_version = self._attr_installed_version.split(" ")[0] + + # Latest Version + if self._value in ( + AVAILABLE, + SCHEDULED, + INSTALLING, + DOWNLOADING, + WIFI_WAIT, + ): + self._attr_latest_version = self.coordinator.data[ + "vehicle_state_software_update_version" + ] + else: + self._attr_latest_version = self._attr_installed_version + + # In Progress + if self._value in ( + SCHEDULED, + INSTALLING, + ): + self._attr_in_progress = True + if install_perc := self.get("vehicle_state_software_update_install_perc"): + self._attr_update_percentage = install_perc + else: + self._attr_in_progress = False + self._attr_update_percentage = None diff --git a/homeassistant/components/tuya/climate.py b/homeassistant/components/tuya/climate.py index a82866c6967952..d6c3193521a79f 100644 --- a/homeassistant/components/tuya/climate.py +++ b/homeassistant/components/tuya/climate.py @@ -3,7 +3,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import TYPE_CHECKING, Any +from typing import Any from tuya_sharing import CustomerDevice, Manager @@ -30,10 +30,8 @@ DPCodeBooleanWrapper, DPCodeEnumWrapper, DPCodeIntegerWrapper, - IntegerTypeData, find_dpcode, ) -from .util import get_dpcode TUYA_HVAC_TO_HA = { "auto": HVACMode.HEAT_COOL, @@ -92,6 +90,82 @@ class TuyaClimateEntityDescription(ClimateEntityDescription): } +def _get_temperature_wrappers( + device: CustomerDevice, system_temperature_unit: UnitOfTemperature +) -> tuple[DPCodeIntegerWrapper | None, DPCodeIntegerWrapper | None, UnitOfTemperature]: + """Get temperature wrappers for current and set temperatures.""" + current_temperature_wrapper: DPCodeIntegerWrapper | None = None + set_temperature_wrapper: DPCodeIntegerWrapper | None = None + + # Default to System Temperature Unit + temperature_unit = system_temperature_unit + + # If both temperature values for celsius and fahrenheit are present, + # use whatever the device is set to, with a fallback to celsius. + preferred_temperature_unit = None + if all( + dpcode in device.status + for dpcode in (DPCode.TEMP_CURRENT, DPCode.TEMP_CURRENT_F) + ) or all( + dpcode in device.status for dpcode in (DPCode.TEMP_SET, DPCode.TEMP_SET_F) + ): + preferred_temperature_unit = UnitOfTemperature.CELSIUS + if any( + "f" in device.status[dpcode].lower() + for dpcode in (DPCode.C_F, DPCode.TEMP_UNIT_CONVERT) + if isinstance(device.status.get(dpcode), str) + ): + preferred_temperature_unit = UnitOfTemperature.FAHRENHEIT + + # Figure out current temperature, use preferred unit or what is available + celsius_type = find_dpcode( + device, (DPCode.TEMP_CURRENT, DPCode.UPPER_TEMP), dptype=DPType.INTEGER + ) + fahrenheit_type = find_dpcode( + device, + (DPCode.TEMP_CURRENT_F, DPCode.UPPER_TEMP_F), + dptype=DPType.INTEGER, + ) + if fahrenheit_type and ( + preferred_temperature_unit == UnitOfTemperature.FAHRENHEIT + or ( + preferred_temperature_unit == UnitOfTemperature.CELSIUS and not celsius_type + ) + ): + temperature_unit = UnitOfTemperature.FAHRENHEIT + current_temperature_wrapper = DPCodeIntegerWrapper( + fahrenheit_type.dpcode, fahrenheit_type + ) + elif celsius_type: + temperature_unit = UnitOfTemperature.CELSIUS + current_temperature_wrapper = DPCodeIntegerWrapper( + celsius_type.dpcode, celsius_type + ) + + # Figure out setting temperature, use preferred unit or what is available + celsius_type = find_dpcode( + device, DPCode.TEMP_SET, dptype=DPType.INTEGER, prefer_function=True + ) + fahrenheit_type = find_dpcode( + device, DPCode.TEMP_SET_F, dptype=DPType.INTEGER, prefer_function=True + ) + if fahrenheit_type and ( + preferred_temperature_unit == UnitOfTemperature.FAHRENHEIT + or ( + preferred_temperature_unit == UnitOfTemperature.CELSIUS and not celsius_type + ) + ): + set_temperature_wrapper = DPCodeIntegerWrapper( + fahrenheit_type.dpcode, fahrenheit_type + ) + elif celsius_type: + set_temperature_wrapper = DPCodeIntegerWrapper( + celsius_type.dpcode, celsius_type + ) + + return current_temperature_wrapper, set_temperature_wrapper, temperature_unit + + async def async_setup_entry( hass: HomeAssistant, entry: TuyaConfigEntry, @@ -107,15 +181,18 @@ def async_discover_device(device_ids: list[str]) -> None: for device_id in device_ids: device = manager.device_map[device_id] if device and device.category in CLIMATE_DESCRIPTIONS: + temperature_wrappers = _get_temperature_wrappers( + device, hass.config.units.temperature_unit + ) entities.append( TuyaClimateEntity( device, manager, CLIMATE_DESCRIPTIONS[device.category], - hass.config.units.temperature_unit, current_humidity_wrapper=_RoundedIntegerWrapper.find_dpcode( device, DPCode.HUMIDITY_CURRENT ), + current_temperature_wrapper=temperature_wrappers[0], fan_mode_wrapper=DPCodeEnumWrapper.find_dpcode( device, (DPCode.FAN_SPEED_ENUM, DPCode.LEVEL, DPCode.WINDSPEED), @@ -124,12 +201,23 @@ def async_discover_device(device_ids: list[str]) -> None: hvac_mode_wrapper=DPCodeEnumWrapper.find_dpcode( device, DPCode.MODE, prefer_function=True ), + set_temperature_wrapper=temperature_wrappers[1], + swing_wrapper=DPCodeBooleanWrapper.find_dpcode( + device, (DPCode.SWING, DPCode.SHAKE), prefer_function=True + ), + swing_h_wrapper=DPCodeBooleanWrapper.find_dpcode( + device, DPCode.SWITCH_HORIZONTAL, prefer_function=True + ), + swing_v_wrapper=DPCodeBooleanWrapper.find_dpcode( + device, DPCode.SWITCH_VERTICAL, prefer_function=True + ), switch_wrapper=DPCodeBooleanWrapper.find_dpcode( device, DPCode.SWITCH, prefer_function=True ), target_humidity_wrapper=_RoundedIntegerWrapper.find_dpcode( device, DPCode.HUMIDITY_SET, prefer_function=True ), + temperature_unit=temperature_wrappers[2], ) ) async_add_entities(entities) @@ -144,9 +232,7 @@ def async_discover_device(device_ids: list[str]) -> None: class TuyaClimateEntity(TuyaEntity, ClimateEntity): """Tuya Climate Device.""" - _current_temperature: IntegerTypeData | None = None _hvac_to_tuya: dict[str, str] - _set_temperature: IntegerTypeData | None = None entity_description: TuyaClimateEntityDescription _attr_name = None @@ -155,13 +241,18 @@ def __init__( device: CustomerDevice, device_manager: Manager, description: TuyaClimateEntityDescription, - system_temperature_unit: UnitOfTemperature, *, current_humidity_wrapper: _RoundedIntegerWrapper | None, + current_temperature_wrapper: DPCodeIntegerWrapper | None, fan_mode_wrapper: DPCodeEnumWrapper | None, hvac_mode_wrapper: DPCodeEnumWrapper | None, + set_temperature_wrapper: DPCodeIntegerWrapper | None, + swing_wrapper: DPCodeBooleanWrapper | None, + swing_h_wrapper: DPCodeBooleanWrapper | None, + swing_v_wrapper: DPCodeBooleanWrapper | None, switch_wrapper: DPCodeBooleanWrapper | None, target_humidity_wrapper: _RoundedIntegerWrapper | None, + temperature_unit: UnitOfTemperature, ) -> None: """Determine which values to use.""" self._attr_target_temperature_step = 1.0 @@ -169,78 +260,26 @@ def __init__( super().__init__(device, device_manager) self._current_humidity_wrapper = current_humidity_wrapper + self._current_temperature = current_temperature_wrapper self._fan_mode_wrapper = fan_mode_wrapper self._hvac_mode_wrapper = hvac_mode_wrapper + self._set_temperature = set_temperature_wrapper + self._swing_wrapper = swing_wrapper + self._swing_h_wrapper = swing_h_wrapper + self._swing_v_wrapper = swing_v_wrapper self._switch_wrapper = switch_wrapper self._target_humidity_wrapper = target_humidity_wrapper - - # If both temperature values for celsius and fahrenheit are present, - # use whatever the device is set to, with a fallback to celsius. - prefered_temperature_unit = None - if all( - dpcode in device.status - for dpcode in (DPCode.TEMP_CURRENT, DPCode.TEMP_CURRENT_F) - ) or all( - dpcode in device.status for dpcode in (DPCode.TEMP_SET, DPCode.TEMP_SET_F) - ): - prefered_temperature_unit = UnitOfTemperature.CELSIUS - if any( - "f" in device.status[dpcode].lower() - for dpcode in (DPCode.C_F, DPCode.TEMP_UNIT_CONVERT) - if isinstance(device.status.get(dpcode), str) - ): - prefered_temperature_unit = UnitOfTemperature.FAHRENHEIT - - # Default to System Temperature Unit - self._attr_temperature_unit = system_temperature_unit - - # Figure out current temperature, use preferred unit or what is available - celsius_type = find_dpcode( - self.device, (DPCode.TEMP_CURRENT, DPCode.UPPER_TEMP), dptype=DPType.INTEGER - ) - fahrenheit_type = find_dpcode( - self.device, - (DPCode.TEMP_CURRENT_F, DPCode.UPPER_TEMP_F), - dptype=DPType.INTEGER, - ) - if fahrenheit_type and ( - prefered_temperature_unit == UnitOfTemperature.FAHRENHEIT - or ( - prefered_temperature_unit == UnitOfTemperature.CELSIUS - and not celsius_type - ) - ): - self._attr_temperature_unit = UnitOfTemperature.FAHRENHEIT - self._current_temperature = fahrenheit_type - elif celsius_type: - self._attr_temperature_unit = UnitOfTemperature.CELSIUS - self._current_temperature = celsius_type - - # Figure out setting temperature, use preferred unit or what is available - celsius_type = find_dpcode( - self.device, DPCode.TEMP_SET, dptype=DPType.INTEGER, prefer_function=True - ) - fahrenheit_type = find_dpcode( - self.device, DPCode.TEMP_SET_F, dptype=DPType.INTEGER, prefer_function=True - ) - if fahrenheit_type and ( - prefered_temperature_unit == UnitOfTemperature.FAHRENHEIT - or ( - prefered_temperature_unit == UnitOfTemperature.CELSIUS - and not celsius_type - ) - ): - self._set_temperature = fahrenheit_type - elif celsius_type: - self._set_temperature = celsius_type + self._attr_temperature_unit = temperature_unit # Get integer type data for the dpcode to set temperature, use # it to define min, max & step temperatures if self._set_temperature: self._attr_supported_features |= ClimateEntityFeature.TARGET_TEMPERATURE - self._attr_max_temp = self._set_temperature.max_scaled - self._attr_min_temp = self._set_temperature.min_scaled - self._attr_target_temperature_step = self._set_temperature.step_scaled + self._attr_max_temp = self._set_temperature.type_information.max_scaled + self._attr_min_temp = self._set_temperature.type_information.min_scaled + self._attr_target_temperature_step = ( + self._set_temperature.type_information.step_scaled + ) # Determine HVAC modes self._attr_hvac_modes: list[HVACMode] = [] @@ -282,24 +321,16 @@ def __init__( self._attr_fan_modes = fan_mode_wrapper.type_information.range # Determine swing modes - if get_dpcode( - self.device, - ( - DPCode.SHAKE, - DPCode.SWING, - DPCode.SWITCH_HORIZONTAL, - DPCode.SWITCH_VERTICAL, - ), - ): + if swing_wrapper or swing_h_wrapper or swing_v_wrapper: self._attr_supported_features |= ClimateEntityFeature.SWING_MODE self._attr_swing_modes = [SWING_OFF] - if get_dpcode(self.device, (DPCode.SHAKE, DPCode.SWING)): + if swing_wrapper: self._attr_swing_modes.append(SWING_ON) - if get_dpcode(self.device, DPCode.SWITCH_HORIZONTAL): + if swing_h_wrapper: self._attr_swing_modes.append(SWING_HORIZONTAL) - if get_dpcode(self.device, DPCode.SWITCH_VERTICAL): + if swing_v_wrapper: self._attr_swing_modes.append(SWING_VERTICAL) if switch_wrapper: @@ -336,66 +367,40 @@ async def async_set_humidity(self, humidity: int) -> None: """Set new target humidity.""" await self._async_send_dpcode_update(self._target_humidity_wrapper, humidity) - def set_swing_mode(self, swing_mode: str) -> None: + async def async_set_swing_mode(self, swing_mode: str) -> None: """Set new target swing operation.""" - # The API accepts these all at once and will ignore the codes - # that don't apply to the device being controlled. - self._send_command( - [ - { - "code": DPCode.SHAKE, - "value": swing_mode == SWING_ON, - }, - { - "code": DPCode.SWING, - "value": swing_mode == SWING_ON, - }, - { - "code": DPCode.SWITCH_VERTICAL, - "value": swing_mode in (SWING_BOTH, SWING_VERTICAL), - }, - { - "code": DPCode.SWITCH_HORIZONTAL, - "value": swing_mode in (SWING_BOTH, SWING_HORIZONTAL), - }, - ] - ) + commands = [] + if self._swing_wrapper: + commands.append( + self._swing_wrapper.get_update_command( + self.device, swing_mode == SWING_ON + ) + ) + if self._swing_v_wrapper: + commands.append( + self._swing_v_wrapper.get_update_command( + self.device, swing_mode in (SWING_BOTH, SWING_VERTICAL) + ) + ) + if self._swing_h_wrapper: + commands.append( + self._swing_h_wrapper.get_update_command( + self.device, swing_mode in (SWING_BOTH, SWING_HORIZONTAL) + ) + ) + if commands: + await self._async_send_commands(commands) - def set_temperature(self, **kwargs: Any) -> None: + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" - if TYPE_CHECKING: - # guarded by ClimateEntityFeature.TARGET_TEMPERATURE - assert self._set_temperature is not None - - self._send_command( - [ - { - "code": self._set_temperature.dpcode, - "value": round( - self._set_temperature.scale_value_back(kwargs[ATTR_TEMPERATURE]) - ), - } - ] + await self._async_send_dpcode_update( + self._set_temperature, kwargs[ATTR_TEMPERATURE] ) @property def current_temperature(self) -> float | None: """Return the current temperature.""" - if self._current_temperature is None: - return None - - temperature = self.device.status.get(self._current_temperature.dpcode) - if temperature is None: - return None - - if self._current_temperature.scale == 0 and self._current_temperature.step != 1: - # The current temperature can have a scale of 0 or 1 and is used for - # rounding, Home Assistant doesn't need to round but we will always - # need to divide the value by 10^1 in case of 0 as scale. - # https://developer.tuya.com/en/docs/iot/shift-temperature-scale-follow-the-setting-of-app-account-center?id=Ka9qo7so58efq#title-7-Round%20values - temperature = temperature / 10 - - return self._current_temperature.scale_value(temperature) + return self._read_wrapper(self._current_temperature) @property def current_humidity(self) -> int | None: @@ -405,14 +410,7 @@ def current_humidity(self) -> int | None: @property def target_temperature(self) -> float | None: """Return the temperature currently set to be reached.""" - if self._set_temperature is None: - return None - - temperature = self.device.status.get(self._set_temperature.dpcode) - if temperature is None: - return None - - return self._set_temperature.scale_value(temperature) + return self._read_wrapper(self._set_temperature) @property def target_humidity(self) -> int | None: @@ -458,13 +456,11 @@ def fan_mode(self) -> str | None: @property def swing_mode(self) -> str: """Return swing mode.""" - if any( - self.device.status.get(dpcode) for dpcode in (DPCode.SHAKE, DPCode.SWING) - ): + if self._read_wrapper(self._swing_wrapper): return SWING_ON - horizontal = self.device.status.get(DPCode.SWITCH_HORIZONTAL) - vertical = self.device.status.get(DPCode.SWITCH_VERTICAL) + horizontal = self._read_wrapper(self._swing_h_wrapper) + vertical = self._read_wrapper(self._swing_v_wrapper) if horizontal and vertical: return SWING_BOTH if horizontal: diff --git a/homeassistant/components/tuya/cover.py b/homeassistant/components/tuya/cover.py index ed488fbbe39c0a..55ca484c441ea9 100644 --- a/homeassistant/components/tuya/cover.py +++ b/homeassistant/components/tuya/cover.py @@ -23,7 +23,6 @@ from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode from .entity import TuyaEntity from .models import DPCodeBooleanWrapper, DPCodeEnumWrapper, DPCodeIntegerWrapper -from .util import get_dpcode class _DPCodePercentageMappingWrapper(DPCodeIntegerWrapper): @@ -127,12 +126,46 @@ class _SpecialInstructionEnumWrapper(_InstructionEnumWrapper): stop_instruction = "STOP" +class _IsClosedWrapper: + """Wrapper for checking if cover is closed.""" + + def is_closed(self, device: CustomerDevice) -> bool | None: + return None + + +class _IsClosedInvertedWrapper(DPCodeBooleanWrapper, _IsClosedWrapper): + """Boolean wrapper for checking if cover is closed (inverted).""" + + def is_closed(self, device: CustomerDevice) -> bool | None: + if (value := self.read_device_status(device)) is None: + return None + return not value + + +class _IsClosedEnumWrapper(DPCodeEnumWrapper, _IsClosedWrapper): + """Enum wrapper for checking if state is closed.""" + + _MAPPINGS = { + "close": True, + "fully_close": True, + "open": False, + "fully_open": False, + } + + def is_closed(self, device: CustomerDevice) -> bool | None: + if (value := self.read_device_status(device)) is None: + return None + return self._MAPPINGS.get(value) + + @dataclass(frozen=True) class TuyaCoverEntityDescription(CoverEntityDescription): """Describe an Tuya cover entity.""" current_state: DPCode | tuple[DPCode, ...] | None = None - current_state_inverse: bool = False + current_state_wrapper: type[_IsClosedInvertedWrapper | _IsClosedEnumWrapper] = ( + _IsClosedEnumWrapper + ) current_position: DPCode | tuple[DPCode, ...] | None = None instruction_wrapper: type[_InstructionEnumWrapper] = _InstructionEnumWrapper position_wrapper: type[_DPCodePercentageMappingWrapper] = ( @@ -148,7 +181,7 @@ class TuyaCoverEntityDescription(CoverEntityDescription): translation_key="indexed_door", translation_placeholders={"index": "1"}, current_state=DPCode.DOORCONTACT_STATE, - current_state_inverse=True, + current_state_wrapper=_IsClosedInvertedWrapper, device_class=CoverDeviceClass.GARAGE, ), TuyaCoverEntityDescription( @@ -156,7 +189,7 @@ class TuyaCoverEntityDescription(CoverEntityDescription): translation_key="indexed_door", translation_placeholders={"index": "2"}, current_state=DPCode.DOORCONTACT_STATE_2, - current_state_inverse=True, + current_state_wrapper=_IsClosedInvertedWrapper, device_class=CoverDeviceClass.GARAGE, ), TuyaCoverEntityDescription( @@ -164,7 +197,7 @@ class TuyaCoverEntityDescription(CoverEntityDescription): translation_key="indexed_door", translation_placeholders={"index": "3"}, current_state=DPCode.DOORCONTACT_STATE_3, - current_state_inverse=True, + current_state_wrapper=_IsClosedInvertedWrapper, device_class=CoverDeviceClass.GARAGE, ), ), @@ -283,6 +316,9 @@ def async_discover_device(device_ids: list[str]) -> None: instruction_wrapper=_get_instruction_wrapper( device, description ), + current_state_wrapper=description.current_state_wrapper.find_dpcode( + device, description.current_state + ), set_position=description.position_wrapper.find_dpcode( device, description.set_position, prefer_function=True ), @@ -311,7 +347,6 @@ def async_discover_device(device_ids: list[str]) -> None: class TuyaCoverEntity(TuyaEntity, CoverEntity): """Tuya Cover Device.""" - _current_state: DPCode | None = None entity_description: TuyaCoverEntityDescription def __init__( @@ -320,10 +355,11 @@ def __init__( device_manager: Manager, description: TuyaCoverEntityDescription, *, - current_position: _DPCodePercentageMappingWrapper | None = None, - instruction_wrapper: _InstructionWrapper | None = None, - set_position: _DPCodePercentageMappingWrapper | None = None, - tilt_position: _DPCodePercentageMappingWrapper | None = None, + current_position: _DPCodePercentageMappingWrapper | None, + current_state_wrapper: _IsClosedWrapper | None, + instruction_wrapper: _InstructionWrapper | None, + set_position: _DPCodePercentageMappingWrapper | None, + tilt_position: _DPCodePercentageMappingWrapper | None, ) -> None: """Init Tuya Cover.""" super().__init__(device, device_manager) @@ -332,6 +368,7 @@ def __init__( self._attr_supported_features = CoverEntityFeature(0) self._current_position = current_position or set_position + self._current_state_wrapper = current_state_wrapper self._instruction_wrapper = instruction_wrapper self._set_position = set_position self._tilt_position = tilt_position @@ -344,8 +381,6 @@ def __init__( if instruction_wrapper.get_stop_command(device) is not None: self._attr_supported_features |= CoverEntityFeature.STOP - self._current_state = get_dpcode(self.device, description.current_state) - if set_position: self._attr_supported_features |= CoverEntityFeature.SET_POSITION if tilt_position: @@ -371,15 +406,8 @@ def is_closed(self) -> bool | None: if (position := self.current_cover_position) is not None: return position == 0 - if ( - self._current_state is not None - and (current_state := self.device.status.get(self._current_state)) - is not None - and current_state != "stop" - ): - return self.entity_description.current_state_inverse is not ( - current_state in (True, "close", "fully_close") - ) + if self._current_state_wrapper: + return self._current_state_wrapper.is_closed(self.device) return None diff --git a/homeassistant/components/tuya/fan.py b/homeassistant/components/tuya/fan.py index e826402a97b97d..3c113bdc9b1b52 100644 --- a/homeassistant/components/tuya/fan.py +++ b/homeassistant/components/tuya/fan.py @@ -21,15 +21,9 @@ ) from . import TuyaConfigEntry -from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode, DPType +from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode from .entity import TuyaEntity -from .models import ( - DPCodeBooleanWrapper, - DPCodeEnumWrapper, - EnumTypeData, - IntegerTypeData, - find_dpcode, -) +from .models import DPCodeBooleanWrapper, DPCodeEnumWrapper, DPCodeIntegerWrapper from .util import get_dpcode _DIRECTION_DPCODES = (DPCode.FAN_DIRECTION,) @@ -53,6 +47,19 @@ } +class _DirectionEnumWrapper(DPCodeEnumWrapper): + """Wrapper for fan direction DP code.""" + + def read_device_status(self, device: CustomerDevice) -> str | None: + """Read the device status and return the direction string.""" + if (value := super().read_device_status(device)) and value in { + DIRECTION_FORWARD, + DIRECTION_REVERSE, + }: + return value + return None + + def _has_a_valid_dpcode(device: CustomerDevice) -> bool: """Check if the device has at least one valid DP code.""" properties_to_check: list[DPCode | tuple[DPCode, ...] | None] = [ @@ -66,6 +73,55 @@ def _has_a_valid_dpcode(device: CustomerDevice) -> bool: return any(get_dpcode(device, code) for code in properties_to_check) +class _FanSpeedEnumWrapper(DPCodeEnumWrapper): + """Wrapper for fan speed DP code (from an enum).""" + + def get_speed_count(self) -> int: + """Get the number of speeds supported by the fan.""" + return len(self.type_information.range) + + def read_device_status(self, device: CustomerDevice) -> int | None: # type: ignore[override] + """Get the current speed as a percentage.""" + if (value := super().read_device_status(device)) is None: + return None + return ordered_list_item_to_percentage(self.type_information.range, value) + + def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any: + """Convert a Home Assistant value back to a raw device value.""" + return percentage_to_ordered_list_item(self.type_information.range, value) + + +class _FanSpeedIntegerWrapper(DPCodeIntegerWrapper): + """Wrapper for fan speed DP code (from an integer).""" + + def get_speed_count(self) -> int: + """Get the number of speeds supported by the fan.""" + return 100 + + def read_device_status(self, device: CustomerDevice) -> int | None: + """Get the current speed as a percentage.""" + if (value := super().read_device_status(device)) is None: + return None + return round(self.type_information.remap_value_to(value, 1, 100)) + + def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any: + """Convert a Home Assistant value back to a raw device value.""" + return round(self.type_information.remap_value_from(value, 1, 100)) + + +def _get_speed_wrapper( + device: CustomerDevice, +) -> _FanSpeedEnumWrapper | _FanSpeedIntegerWrapper | None: + """Get the speed wrapper for the device.""" + if int_wrapper := _FanSpeedIntegerWrapper.find_dpcode( + device, _SPEED_DPCODES, prefer_function=True + ): + return int_wrapper + return _FanSpeedEnumWrapper.find_dpcode( + device, _SPEED_DPCODES, prefer_function=True + ) + + async def async_setup_entry( hass: HomeAssistant, entry: TuyaConfigEntry, @@ -85,9 +141,16 @@ def async_discover_device(device_ids: list[str]) -> None: TuyaFanEntity( device, manager, + direction_wrapper=_DirectionEnumWrapper.find_dpcode( + device, _DIRECTION_DPCODES, prefer_function=True + ), mode_wrapper=DPCodeEnumWrapper.find_dpcode( device, _MODE_DPCODES, prefer_function=True ), + oscillate_wrapper=DPCodeBooleanWrapper.find_dpcode( + device, _OSCILLATE_DPCODES, prefer_function=True + ), + speed_wrapper=_get_speed_wrapper(device), switch_wrapper=DPCodeBooleanWrapper.find_dpcode( device, _SWITCH_DPCODES, prefer_function=True ), @@ -105,10 +168,6 @@ def async_discover_device(device_ids: list[str]) -> None: class TuyaFanEntity(TuyaEntity, FanEntity): """Tuya Fan Device.""" - _direction: EnumTypeData | None = None - _oscillate: DPCode | None = None - _speed: IntegerTypeData | None = None - _speeds: EnumTypeData | None = None _attr_name = None def __init__( @@ -116,37 +175,32 @@ def __init__( device: CustomerDevice, device_manager: Manager, *, + direction_wrapper: _DirectionEnumWrapper | None, mode_wrapper: DPCodeEnumWrapper | None, + oscillate_wrapper: DPCodeBooleanWrapper | None, + speed_wrapper: _FanSpeedEnumWrapper | _FanSpeedIntegerWrapper | None, switch_wrapper: DPCodeBooleanWrapper | None, ) -> None: """Init Tuya Fan Device.""" super().__init__(device, device_manager) + self._direction_wrapper = direction_wrapper self._mode_wrapper = mode_wrapper + self._oscillate_wrapper = oscillate_wrapper + self._speed_wrapper = speed_wrapper self._switch_wrapper = switch_wrapper + if mode_wrapper: self._attr_supported_features |= FanEntityFeature.PRESET_MODE self._attr_preset_modes = mode_wrapper.type_information.range - # Find speed controls, can be either percentage or a set of speeds - if int_type := find_dpcode( - self.device, _SPEED_DPCODES, dptype=DPType.INTEGER, prefer_function=True - ): - self._attr_supported_features |= FanEntityFeature.SET_SPEED - self._speed = int_type - elif enum_type := find_dpcode( - self.device, _SPEED_DPCODES, dptype=DPType.ENUM, prefer_function=True - ): + if speed_wrapper: self._attr_supported_features |= FanEntityFeature.SET_SPEED - self._speeds = enum_type + self._attr_speed_count = speed_wrapper.get_speed_count() - if dpcode := get_dpcode(self.device, _OSCILLATE_DPCODES): - self._oscillate = dpcode + if oscillate_wrapper: self._attr_supported_features |= FanEntityFeature.OSCILLATE - if enum_type := find_dpcode( - self.device, _DIRECTION_DPCODES, dptype=DPType.ENUM, prefer_function=True - ): - self._direction = enum_type + if direction_wrapper: self._attr_supported_features |= FanEntityFeature.DIRECTION if switch_wrapper: self._attr_supported_features |= ( @@ -157,36 +211,13 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: """Set the preset mode of the fan.""" await self._async_send_dpcode_update(self._mode_wrapper, preset_mode) - def set_direction(self, direction: str) -> None: + async def async_set_direction(self, direction: str) -> None: """Set the direction of the fan.""" - if self._direction is None: - return - self._send_command([{"code": self._direction.dpcode, "value": direction}]) + await self._async_send_dpcode_update(self._direction_wrapper, direction) - def set_percentage(self, percentage: int) -> None: + async def async_set_percentage(self, percentage: int) -> None: """Set the speed of the fan, as a percentage.""" - if self._speed is not None: - self._send_command( - [ - { - "code": self._speed.dpcode, - "value": int(self._speed.remap_value_from(percentage, 1, 100)), - } - ] - ) - return - - if self._speeds is not None: - self._send_command( - [ - { - "code": self._speeds.dpcode, - "value": percentage_to_ordered_list_item( - self._speeds.range, percentage - ), - } - ] - ) + await self._async_send_dpcode_update(self._speed_wrapper, percentage) async def async_turn_off(self, **kwargs: Any) -> None: """Turn the fan off.""" @@ -206,22 +237,9 @@ async def async_turn_on( self._switch_wrapper.get_update_command(self.device, True) ] - if percentage is not None and self._speed is not None: + if percentage is not None and self._speed_wrapper is not None: commands.append( - { - "code": self._speed.dpcode, - "value": int(self._speed.remap_value_from(percentage, 1, 100)), - } - ) - - if percentage is not None and self._speeds is not None: - commands.append( - { - "code": self._speeds.dpcode, - "value": percentage_to_ordered_list_item( - self._speeds.range, percentage - ), - } + self._speed_wrapper.get_update_command(self.device, percentage) ) if preset_mode is not None and self._mode_wrapper: @@ -230,11 +248,9 @@ async def async_turn_on( ) await self._async_send_commands(commands) - def oscillate(self, oscillating: bool) -> None: + async def async_oscillate(self, oscillating: bool) -> None: """Oscillate the fan.""" - if self._oscillate is None: - return - self._send_command([{"code": self._oscillate, "value": oscillating}]) + await self._async_send_dpcode_update(self._oscillate_wrapper, oscillating) @property def is_on(self) -> bool | None: @@ -244,26 +260,12 @@ def is_on(self) -> bool | None: @property def current_direction(self) -> str | None: """Return the current direction of the fan.""" - if ( - self._direction is None - or (value := self.device.status.get(self._direction.dpcode)) is None - ): - return None - - if value.lower() == DIRECTION_FORWARD: - return DIRECTION_FORWARD - - if value.lower() == DIRECTION_REVERSE: - return DIRECTION_REVERSE - - return None + return self._read_wrapper(self._direction_wrapper) @property def oscillating(self) -> bool | None: """Return true if the fan is oscillating.""" - if self._oscillate is None: - return None - return self.device.status.get(self._oscillate) + return self._read_wrapper(self._oscillate_wrapper) @property def preset_mode(self) -> str | None: @@ -273,23 +275,4 @@ def preset_mode(self) -> str | None: @property def percentage(self) -> int | None: """Return the current speed.""" - if self._speed is not None: - if (value := self.device.status.get(self._speed.dpcode)) is None: - return None - return int(self._speed.remap_value_to(value, 1, 100)) - - if self._speeds is not None: - if ( - value := self.device.status.get(self._speeds.dpcode) - ) is None or value not in self._speeds.range: - return None - return ordered_list_item_to_percentage(self._speeds.range, value) - - return None - - @property - def speed_count(self) -> int: - """Return the number of speeds the fan supports.""" - if self._speeds is not None: - return len(self._speeds.range) - return 100 + return self._read_wrapper(self._speed_wrapper) diff --git a/homeassistant/components/tuya/light.py b/homeassistant/components/tuya/light.py index 8dfb479140d672..d773ac03a497e1 100644 --- a/homeassistant/components/tuya/light.py +++ b/homeassistant/components/tuya/light.py @@ -2,7 +2,8 @@ from __future__ import annotations -from dataclasses import dataclass, field +from dataclasses import dataclass +from enum import StrEnum import json from typing import Any, cast @@ -27,15 +28,16 @@ from homeassistant.util.json import json_loads_object from . import TuyaConfigEntry -from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode, DPType, WorkMode +from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode, WorkMode from .entity import TuyaEntity from .models import ( DPCodeBooleanWrapper, DPCodeEnumWrapper, DPCodeIntegerWrapper, + DPCodeJsonWrapper, IntegerTypeData, ) -from .util import get_dpcode, get_dptype, remap_value +from .util import remap_value class _BrightnessWrapper(DPCodeIntegerWrapper): @@ -136,43 +138,83 @@ def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any ) -@dataclass -class ColorTypeData: - """Color Type Data.""" - - h_type: IntegerTypeData - s_type: IntegerTypeData - v_type: IntegerTypeData +DEFAULT_H_TYPE = IntegerTypeData( + dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=360, step=1 +) +DEFAULT_S_TYPE = IntegerTypeData( + dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=255, step=1 +) +DEFAULT_V_TYPE = IntegerTypeData( + dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=255, step=1 +) -DEFAULT_COLOR_TYPE_DATA = ColorTypeData( - h_type=IntegerTypeData( - dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=360, step=1 - ), - s_type=IntegerTypeData( - dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=255, step=1 - ), - v_type=IntegerTypeData( - dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=255, step=1 - ), +DEFAULT_H_TYPE_V2 = IntegerTypeData( + dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=360, step=1 ) - -DEFAULT_COLOR_TYPE_DATA_V2 = ColorTypeData( - h_type=IntegerTypeData( - dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=360, step=1 - ), - s_type=IntegerTypeData( - dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=1000, step=1 - ), - v_type=IntegerTypeData( - dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=1000, step=1 - ), +DEFAULT_S_TYPE_V2 = IntegerTypeData( + dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=1000, step=1 +) +DEFAULT_V_TYPE_V2 = IntegerTypeData( + dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=1000, step=1 ) + +class _ColorDataWrapper(DPCodeJsonWrapper): + """Wrapper for color data DP code.""" + + h_type = DEFAULT_H_TYPE + s_type = DEFAULT_S_TYPE + v_type = DEFAULT_V_TYPE + + def read_device_status(self, device: CustomerDevice) -> dict[str, Any] | None: + """Read the color data for the dpcode.""" + if (status_data := self._read_device_status_raw(device)) is None or not ( + status := json_loads_object(status_data) + ): + return None + return status + + def read_hs_color(self, device: CustomerDevice) -> tuple[float, float] | None: + """Get the HS value from this color data.""" + if (status := self.read_device_status(device)) is None: + return None + return ( + self.h_type.remap_value_to(cast(int, status["h"]), 0, 360), + self.s_type.remap_value_to(cast(int, status["s"]), 0, 100), + ) + + def read_brightness(self, device: CustomerDevice) -> int | None: + """Get the brightness value from this color data.""" + if (status := self.read_device_status(device)) is None: + return None + return round(self.v_type.remap_value_to(cast(int, status["v"]), 0, 255)) + + def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any: + """Convert a Home Assistant color/brightness pair back to a raw device value.""" + color, brightness = value + return json.dumps( + { + "h": round(self.h_type.remap_value_from(color[0], 0, 360)), + "s": round(self.s_type.remap_value_from(color[1], 0, 100)), + "v": round(self.v_type.remap_value_from(brightness)), + } + ) + + MAX_MIREDS = 500 # 2000 K MIN_MIREDS = 153 # 6500 K +class FallbackColorDataMode(StrEnum): + """Fallback color data mode.""" + + V1 = "v1" + """hue: 0-360, saturation: 0-255, value: 0-255""" + V2 = "v2" + """hue: 0-360, saturation: 0-1000, value: 0-1000""" + + @dataclass(frozen=True) class TuyaLightEntityDescription(LightEntityDescription): """Describe an Tuya light entity.""" @@ -183,9 +225,7 @@ class TuyaLightEntityDescription(LightEntityDescription): color_data: DPCode | tuple[DPCode, ...] | None = None color_mode: DPCode | None = None color_temp: DPCode | tuple[DPCode, ...] | None = None - default_color_type: ColorTypeData = field( - default_factory=lambda: DEFAULT_COLOR_TYPE_DATA - ) + fallback_color_data_mode: FallbackColorDataMode = FallbackColorDataMode.V1 LIGHTS: dict[DeviceCategory, tuple[TuyaLightEntityDescription, ...]] = { @@ -222,7 +262,7 @@ class TuyaLightEntityDescription(LightEntityDescription): brightness=DPCode.BRIGHT_VALUE, color_temp=DPCode.TEMP_VALUE, color_data=DPCode.COLOUR_DATA, - default_color_type=DEFAULT_COLOR_TYPE_DATA_V2, + fallback_color_data_mode=FallbackColorDataMode.V2, ), ), DeviceCategory.DJ: ( @@ -504,29 +544,6 @@ class TuyaLightEntityDescription(LightEntityDescription): LIGHTS[DeviceCategory.TDQ] = LIGHTS[DeviceCategory.TGQ] -@dataclass -class ColorData: - """Color Data.""" - - type_data: ColorTypeData - h_value: int - s_value: int - v_value: int - - @property - def hs_color(self) -> tuple[float, float]: - """Get the HS value from this color data.""" - return ( - self.type_data.h_type.remap_value_to(self.h_value, 0, 360), - self.type_data.s_type.remap_value_to(self.s_value, 0, 100), - ) - - @property - def brightness(self) -> int: - """Get the brightness value from this color data.""" - return round(self.type_data.v_type.remap_value_to(self.v_value, 0, 255)) - - def _get_brightness_wrapper( device: CustomerDevice, description: TuyaLightEntityDescription ) -> _BrightnessWrapper | None: @@ -545,6 +562,46 @@ def _get_brightness_wrapper( return brightness_wrapper +def _get_color_data_wrapper( + device: CustomerDevice, + description: TuyaLightEntityDescription, + brightness_wrapper: _BrightnessWrapper | None, +) -> _ColorDataWrapper | None: + if ( + color_data_wrapper := _ColorDataWrapper.find_dpcode( + device, description.color_data, prefer_function=True + ) + ) is None: + return None + + # Fetch color data type information + if function_data := json_loads_object( + cast(str, color_data_wrapper.type_information.type_data) + ): + color_data_wrapper.h_type = IntegerTypeData( + dpcode=color_data_wrapper.dpcode, + **cast(dict, function_data["h"]), + ) + color_data_wrapper.s_type = IntegerTypeData( + dpcode=color_data_wrapper.dpcode, + **cast(dict, function_data["s"]), + ) + color_data_wrapper.v_type = IntegerTypeData( + dpcode=color_data_wrapper.dpcode, + **cast(dict, function_data["v"]), + ) + elif ( + description.fallback_color_data_mode == FallbackColorDataMode.V2 + or color_data_wrapper.dpcode == DPCode.COLOUR_DATA_V2 + or (brightness_wrapper and brightness_wrapper.type_information.max > 255) + ): + color_data_wrapper.h_type = DEFAULT_H_TYPE_V2 + color_data_wrapper.s_type = DEFAULT_S_TYPE_V2 + color_data_wrapper.v_type = DEFAULT_V_TYPE_V2 + + return color_data_wrapper + + async def async_setup_entry( hass: HomeAssistant, entry: TuyaConfigEntry, @@ -565,7 +622,14 @@ def async_discover_device(device_ids: list[str]): device, manager, description, - brightness_wrapper=_get_brightness_wrapper(device, description), + brightness_wrapper=( + brightness_wrapper := _get_brightness_wrapper( + device, description + ) + ), + color_data_wrapper=_get_color_data_wrapper( + device, description, brightness_wrapper + ), color_mode_wrapper=DPCodeEnumWrapper.find_dpcode( device, description.color_mode, prefer_function=True ), @@ -596,8 +660,6 @@ class TuyaLightEntity(TuyaEntity, LightEntity): entity_description: TuyaLightEntityDescription - _color_data_dpcode: DPCode | None = None - _color_data_type: ColorTypeData | None = None _white_color_mode = ColorMode.COLOR_TEMP _fixed_color_mode: ColorMode | None = None _attr_min_color_temp_kelvin = 2000 # 500 Mireds @@ -610,6 +672,7 @@ def __init__( description: TuyaLightEntityDescription, *, brightness_wrapper: _BrightnessWrapper | None, + color_data_wrapper: _ColorDataWrapper | None, color_mode_wrapper: DPCodeEnumWrapper | None, color_temp_wrapper: _ColorTempWrapper | None, switch_wrapper: DPCodeBooleanWrapper, @@ -619,6 +682,7 @@ def __init__( self.entity_description = description self._attr_unique_id = f"{super().unique_id}{description.key}" self._brightness_wrapper = brightness_wrapper + self._color_data_wrapper = color_data_wrapper self._color_mode_wrapper = color_mode_wrapper self._color_temp_wrapper = color_temp_wrapper self._switch_wrapper = switch_wrapper @@ -628,37 +692,8 @@ def __init__( if brightness_wrapper: color_modes.add(ColorMode.BRIGHTNESS) - if (dpcode := get_dpcode(self.device, description.color_data)) and ( - get_dptype(self.device, dpcode, prefer_function=True) == DPType.JSON - ): - self._color_data_dpcode = dpcode + if color_data_wrapper: color_modes.add(ColorMode.HS) - if dpcode in self.device.function: - values = cast(str, self.device.function[dpcode].values) - else: - values = self.device.status_range[dpcode].values - - # Fetch color data type information - if function_data := json_loads_object(values): - self._color_data_type = ColorTypeData( - h_type=IntegerTypeData( - dpcode=dpcode, **cast(dict, function_data["h"]) - ), - s_type=IntegerTypeData( - dpcode=dpcode, **cast(dict, function_data["s"]) - ), - v_type=IntegerTypeData( - dpcode=dpcode, **cast(dict, function_data["v"]) - ), - ) - else: - # If no type is found, use a default one - self._color_data_type = self.entity_description.default_color_type - if self._color_data_dpcode == DPCode.COLOUR_DATA_V2 or ( - self._brightness_wrapper - and self._brightness_wrapper.type_information.max > 255 - ): - self._color_data_type = DEFAULT_COLOR_TYPE_DATA_V2 # Check if the light has color temperature if color_temp_wrapper: @@ -705,7 +740,7 @@ def turn_on(self, **kwargs: Any) -> None: ) ] - if self._color_data_type and ( + if self._color_data_wrapper and ( ATTR_HS_COLOR in kwargs or ( ATTR_BRIGHTNESS in kwargs @@ -728,28 +763,9 @@ def turn_on(self, **kwargs: Any) -> None: color = self.hs_color or (0, 0) commands += [ - { - "code": self._color_data_dpcode, - "value": json.dumps( - { - "h": round( - self._color_data_type.h_type.remap_value_from( - color[0], 0, 360 - ) - ), - "s": round( - self._color_data_type.s_type.remap_value_from( - color[1], 0, 100 - ) - ), - "v": round( - self._color_data_type.v_type.remap_value_from( - brightness - ) - ), - } - ), - }, + self._color_data_wrapper.get_update_command( + self.device, (color, brightness) + ), ] elif self._brightness_wrapper and ( @@ -774,8 +790,8 @@ async def async_turn_off(self, **kwargs: Any) -> None: def brightness(self) -> int | None: """Return the brightness of this light between 0..255.""" # If the light is currently in color mode, extract the brightness from the color data - if self.color_mode == ColorMode.HS and (color_data := self._get_color_data()): - return color_data.brightness + if self.color_mode == ColorMode.HS and self._color_data_wrapper: + return self._color_data_wrapper.read_brightness(self.device) return self._read_wrapper(self._brightness_wrapper) @@ -787,11 +803,9 @@ def color_temp_kelvin(self) -> int | None: @property def hs_color(self) -> tuple[float, float] | None: """Return the hs_color of the light.""" - if self._color_data_dpcode is None or not ( - color_data := self._get_color_data() - ): + if self._color_data_wrapper is None: return None - return color_data.hs_color + return self._color_data_wrapper.read_hs_color(self.device) @property def color_mode(self) -> ColorMode: @@ -809,25 +823,3 @@ def color_mode(self) -> ColorMode: ): return ColorMode.HS return self._white_color_mode - - def _get_color_data(self) -> ColorData | None: - """Get current color data from device.""" - if ( - self._color_data_type is None - or self._color_data_dpcode is None - or self._color_data_dpcode not in self.device.status - ): - return None - - if not (status_data := self.device.status[self._color_data_dpcode]): - return None - - if not (status := json_loads_object(status_data)): - return None - - return ColorData( - type_data=self._color_data_type, - h_value=cast(int, status["h"]), - s_value=cast(int, status["s"]), - v_value=cast(int, status["v"]), - ) diff --git a/homeassistant/components/usage_prediction/common_control.py b/homeassistant/components/usage_prediction/common_control.py index 69f2164fc763b3..cfa93e4cb9d857 100644 --- a/homeassistant/components/usage_prediction/common_control.py +++ b/homeassistant/components/usage_prediction/common_control.py @@ -144,13 +144,7 @@ async def async_predict_common_control( if not service_data: continue - entity_ids: str | list[str] | None - if (target := service_data.get("target")) and ( - target_entity_ids := target.get("entity_id") - ): - entity_ids = target_entity_ids - else: - entity_ids = service_data.get("entity_id") + entity_ids: str | list[str] | None = service_data.get("entity_id") # No entity IDs found, skip this event if entity_ids is None: diff --git a/homeassistant/components/waqi/diagnostics.py b/homeassistant/components/waqi/diagnostics.py new file mode 100644 index 00000000000000..636b8980d0a6e9 --- /dev/null +++ b/homeassistant/components/waqi/diagnostics.py @@ -0,0 +1,20 @@ +"""Diagnostics support for WAQI.""" + +from __future__ import annotations + +from dataclasses import asdict +from typing import Any + +from homeassistant.core import HomeAssistant + +from .coordinator import WAQIConfigEntry + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: WAQIConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + return { + subentry_id: asdict(coordinator.data) + for subentry_id, coordinator in entry.runtime_data.items() + } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index 65807a34023507..38e415afb74774 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -39,7 +39,7 @@ habluetooth==5.7.0 hass-nabucasa==1.5.1 hassil==3.4.0 home-assistant-bluetooth==1.13.1 -home-assistant-frontend==20251105.0 +home-assistant-frontend==20251105.1 home-assistant-intents==2025.11.7 httpx==0.28.1 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 62c9fa379a3be7..98b48f600f4c57 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -83,7 +83,7 @@ PyRMVtransport==0.3.3 PySrDaliGateway==0.16.2 # homeassistant.components.switchbot -PySwitchbot==0.73.0 +PySwitchbot==0.74.0 # homeassistant.components.switchmate PySwitchmate==0.5.1 @@ -389,7 +389,7 @@ aioruuvigateway==0.1.0 aiosenz==1.0.0 # homeassistant.components.shelly -aioshelly==13.19.0 +aioshelly==13.20.0 # homeassistant.components.skybell aioskybell==22.7.0 @@ -1188,7 +1188,7 @@ hole==0.9.0 holidays==0.84 # homeassistant.components.frontend -home-assistant-frontend==20251105.0 +home-assistant-frontend==20251105.1 # homeassistant.components.conversation home-assistant-intents==2025.11.7 @@ -2387,7 +2387,7 @@ pysmappee==0.2.29 pysmarlaapi==0.9.2 # homeassistant.components.smartthings -pysmartthings==3.3.3 +pysmartthings==3.3.4 # homeassistant.components.smarty pysmarty2==0.10.3 @@ -2438,7 +2438,7 @@ pythinkingcleaner==0.0.3 python-MotionMount==2.3.0 # homeassistant.components.awair -python-awair==0.2.4 +python-awair==0.2.5 # homeassistant.components.blockchain python-blockchain-api==0.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8e5d65155258b2..bc22a0feb8af1b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -80,7 +80,7 @@ PyRMVtransport==0.3.3 PySrDaliGateway==0.16.2 # homeassistant.components.switchbot -PySwitchbot==0.73.0 +PySwitchbot==0.74.0 # homeassistant.components.syncthru PySyncThru==0.8.0 @@ -371,7 +371,7 @@ aioruuvigateway==0.1.0 aiosenz==1.0.0 # homeassistant.components.shelly -aioshelly==13.19.0 +aioshelly==13.20.0 # homeassistant.components.skybell aioskybell==22.7.0 @@ -649,6 +649,9 @@ colorthief==0.2.1 # homeassistant.components.compit compit-inext-api==0.3.1 +# homeassistant.components.concord232 +concord232==0.15.1 + # homeassistant.components.xiaomi_miio construct==2.10.68 @@ -1037,7 +1040,7 @@ hole==0.9.0 holidays==0.84 # homeassistant.components.frontend -home-assistant-frontend==20251105.0 +home-assistant-frontend==20251105.1 # homeassistant.components.conversation home-assistant-intents==2025.11.7 @@ -1989,7 +1992,7 @@ pysmappee==0.2.29 pysmarlaapi==0.9.2 # homeassistant.components.smartthings -pysmartthings==3.3.3 +pysmartthings==3.3.4 # homeassistant.components.smarty pysmarty2==0.10.3 @@ -2037,7 +2040,7 @@ pytautulli==23.1.1 python-MotionMount==2.3.0 # homeassistant.components.awair -python-awair==0.2.4 +python-awair==0.2.5 # homeassistant.components.bsblan python-bsblan==3.1.1 diff --git a/tests/components/cloud/test_init.py b/tests/components/cloud/test_init.py index 087634c5173eb8..71ff04d9f3a70a 100644 --- a/tests/components/cloud/test_init.py +++ b/tests/components/cloud/test_init.py @@ -11,6 +11,7 @@ CloudNotAvailable, CloudNotConnected, async_get_or_create_cloudhook, + async_listen_cloudhook_change, async_listen_connection_change, async_remote_ui_url, ) @@ -311,3 +312,149 @@ async def test_cloud_logout( await hass.async_block_till_done() assert cloud.is_logged_in is False + + +async def test_async_listen_cloudhook_change( + hass: HomeAssistant, + cloud: MagicMock, + set_cloud_prefs: Callable[[dict[str, Any]], Coroutine[Any, Any, None]], +) -> None: + """Test async_listen_cloudhook_change.""" + assert await async_setup_component(hass, "cloud", {"cloud": {}}) + await hass.async_block_till_done() + await cloud.login("test-user", "test-pass") + + webhook_id = "mock-webhook-id" + cloudhook_url = "https://cloudhook.nabu.casa/abcdefg" + + # Set up initial cloudhooks state + await set_cloud_prefs( + { + PREF_CLOUDHOOKS: { + webhook_id: { + "webhook_id": webhook_id, + "cloudhook_id": "random-id", + "cloudhook_url": cloudhook_url, + "managed": True, + } + } + } + ) + + # Track cloudhook changes + changes = [] + changeInvoked = False + + def on_change(cloudhook: dict[str, Any] | None) -> None: + """Handle cloudhook change.""" + nonlocal changeInvoked + changes.append(cloudhook) + changeInvoked = True + + # Register the change listener + unsubscribe = async_listen_cloudhook_change(hass, webhook_id, on_change) + + # Verify no changes yet + assert len(changes) == 0 + assert changeInvoked is False + + # Delete the cloudhook by updating prefs + await set_cloud_prefs({PREF_CLOUDHOOKS: {}}) + await hass.async_block_till_done() + + # Verify deletion callback was called with None + assert len(changes) == 1 + assert changes[-1] is None + assert changeInvoked is True + + # Reset changeInvoked to detect next change + changeInvoked = False + + # Add cloudhook back + cloudhook_data = { + "webhook_id": webhook_id, + "cloudhook_id": "random-id", + "cloudhook_url": cloudhook_url, + "managed": True, + } + await set_cloud_prefs({PREF_CLOUDHOOKS: {webhook_id: cloudhook_data}}) + await hass.async_block_till_done() + + # Verify callback called with cloudhook data + assert len(changes) == 2 + assert changes[-1] == cloudhook_data + assert changeInvoked is True + + # Reset changeInvoked to detect next change + changeInvoked = False + + # Update cloudhook data with same cloudhook should not trigger callback + await set_cloud_prefs({PREF_CLOUDHOOKS: {webhook_id: cloudhook_data}}) + await hass.async_block_till_done() + + assert changeInvoked is False + + # Unsubscribe from listener + unsubscribe() + + # Delete cloudhook again + await set_cloud_prefs({PREF_CLOUDHOOKS: {}}) + await hass.async_block_till_done() + + # Verify change callback was NOT called after unsubscribe + assert len(changes) == 2 + assert changeInvoked is False + + +async def test_async_listen_cloudhook_change_cloud_setup_later( + hass: HomeAssistant, + cloud: MagicMock, + set_cloud_prefs: Callable[[dict[str, Any]], Coroutine[Any, Any, None]], +) -> None: + """Test async_listen_cloudhook_change works when cloud is set up after listener registration.""" + webhook_id = "mock-webhook-id" + cloudhook_url = "https://cloudhook.nabu.casa/abcdefg" + + # Track cloudhook changes + changes: list[dict[str, Any] | None] = [] + + def on_change(cloudhook: dict[str, Any] | None) -> None: + """Handle cloudhook change.""" + changes.append(cloudhook) + + # Register listener BEFORE cloud is set up + unsubscribe = async_listen_cloudhook_change(hass, webhook_id, on_change) + + # Verify it returns a callable + assert callable(unsubscribe) + + # No changes yet since cloud isn't set up + assert len(changes) == 0 + + # Now set up cloud + assert await async_setup_component(hass, "cloud", {"cloud": {}}) + await hass.async_block_till_done() + await cloud.login("test-user", "test-pass") + + # Add a cloudhook - this should trigger the listener + cloudhook_data = { + "webhook_id": webhook_id, + "cloudhook_id": "random-id", + "cloudhook_url": cloudhook_url, + "managed": True, + } + await set_cloud_prefs({PREF_CLOUDHOOKS: {webhook_id: cloudhook_data}}) + await hass.async_block_till_done() + + # Verify the listener received the update + assert len(changes) == 1 + assert changes[-1] == cloudhook_data + + # Unsubscribe and verify no more updates + unsubscribe() + + await set_cloud_prefs({PREF_CLOUDHOOKS: {}}) + await hass.async_block_till_done() + + # Should not receive update after unsubscribe + assert len(changes) == 1 diff --git a/tests/components/concord232/__init__.py b/tests/components/concord232/__init__.py new file mode 100644 index 00000000000000..d1c77331b7dd02 --- /dev/null +++ b/tests/components/concord232/__init__.py @@ -0,0 +1 @@ +"""Tests for the Concord232 integration.""" diff --git a/tests/components/concord232/conftest.py b/tests/components/concord232/conftest.py new file mode 100644 index 00000000000000..604617bfc1758d --- /dev/null +++ b/tests/components/concord232/conftest.py @@ -0,0 +1,33 @@ +"""Fixtures for the Concord232 integration.""" + +from __future__ import annotations + +from collections.abc import Generator +from unittest.mock import MagicMock, patch + +import pytest + + +@pytest.fixture +def mock_concord232_client() -> Generator[MagicMock]: + """Mock the concord232 Client for easier testing.""" + with ( + patch( + "homeassistant.components.concord232.alarm_control_panel.concord232_client.Client", + autospec=True, + ) as mock_client_class, + patch( + "homeassistant.components.concord232.binary_sensor.concord232_client.Client", + new=mock_client_class, + ), + ): + mock_instance = mock_client_class.return_value + + # Set up default return values + mock_instance.list_partitions.return_value = [{"arming_level": "Off"}] + mock_instance.list_zones.return_value = [ + {"number": 1, "name": "Zone 1", "state": "Normal"}, + {"number": 2, "name": "Zone 2", "state": "Normal"}, + ] + + yield mock_instance diff --git a/tests/components/concord232/test_alarm_control_panel.py b/tests/components/concord232/test_alarm_control_panel.py new file mode 100644 index 00000000000000..7df2f372529d3b --- /dev/null +++ b/tests/components/concord232/test_alarm_control_panel.py @@ -0,0 +1,280 @@ +"""Tests for the Concord232 alarm control panel platform.""" + +from __future__ import annotations + +from unittest.mock import MagicMock + +from freezegun.api import FrozenDateTimeFactory +import pytest +import requests + +from homeassistant.components.alarm_control_panel import ( + DOMAIN as ALARM_DOMAIN, + SERVICE_ALARM_ARM_AWAY, + SERVICE_ALARM_ARM_HOME, + SERVICE_ALARM_DISARM, + AlarmControlPanelState, +) +from homeassistant.const import ( + ATTR_CODE, + ATTR_ENTITY_ID, + CONF_CODE, + CONF_HOST, + CONF_MODE, + CONF_NAME, + CONF_PORT, +) +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from tests.common import async_fire_time_changed + +VALID_CONFIG = { + ALARM_DOMAIN: { + "platform": "concord232", + CONF_HOST: "localhost", + CONF_PORT: 5007, + CONF_NAME: "Test Alarm", + } +} + +VALID_CONFIG_WITH_CODE = { + ALARM_DOMAIN: { + "platform": "concord232", + CONF_HOST: "localhost", + CONF_PORT: 5007, + CONF_NAME: "Test Alarm", + CONF_CODE: "1234", + } +} + +VALID_CONFIG_SILENT_MODE = { + ALARM_DOMAIN: { + "platform": "concord232", + CONF_HOST: "localhost", + CONF_PORT: 5007, + CONF_NAME: "Test Alarm", + CONF_MODE: "silent", + } +} + + +async def test_setup_platform( + hass: HomeAssistant, mock_concord232_client: MagicMock +) -> None: + """Test platform setup.""" + await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() + + state = hass.states.get("alarm_control_panel.test_alarm") + assert state is not None + assert state.state == AlarmControlPanelState.DISARMED + + +async def test_setup_platform_connection_error( + hass: HomeAssistant, mock_concord232_client: MagicMock +) -> None: + """Test platform setup with connection error.""" + mock_concord232_client.list_partitions.side_effect = ( + requests.exceptions.ConnectionError("Connection failed") + ) + + await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() + + assert hass.states.get("alarm_control_panel.test_alarm") is None + + +async def test_alarm_disarm( + hass: HomeAssistant, mock_concord232_client: MagicMock +) -> None: + """Test disarm service.""" + await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() + + await hass.services.async_call( + ALARM_DOMAIN, + SERVICE_ALARM_DISARM, + {ATTR_ENTITY_ID: "alarm_control_panel.test_alarm"}, + blocking=True, + ) + mock_concord232_client.disarm.assert_called_once_with(None) + + +async def test_alarm_disarm_with_code( + hass: HomeAssistant, mock_concord232_client: MagicMock +) -> None: + """Test disarm service with code.""" + await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG_WITH_CODE) + await hass.async_block_till_done() + + await hass.services.async_call( + ALARM_DOMAIN, + SERVICE_ALARM_DISARM, + { + ATTR_ENTITY_ID: "alarm_control_panel.test_alarm", + ATTR_CODE: "1234", + }, + blocking=True, + ) + mock_concord232_client.disarm.assert_called_once_with("1234") + + +async def test_alarm_disarm_invalid_code( + hass: HomeAssistant, + mock_concord232_client: MagicMock, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test disarm service with invalid code.""" + await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG_WITH_CODE) + await hass.async_block_till_done() + + await hass.services.async_call( + ALARM_DOMAIN, + SERVICE_ALARM_DISARM, + { + ATTR_ENTITY_ID: "alarm_control_panel.test_alarm", + ATTR_CODE: "9999", + }, + blocking=True, + ) + mock_concord232_client.disarm.assert_not_called() + assert "Invalid code given" in caplog.text + + +@pytest.mark.parametrize( + ("service", "expected_arm_call"), + [ + (SERVICE_ALARM_ARM_HOME, "stay"), + (SERVICE_ALARM_ARM_AWAY, "away"), + ], +) +async def test_alarm_arm( + hass: HomeAssistant, + mock_concord232_client: MagicMock, + service: str, + expected_arm_call: str, +) -> None: + """Test arm service.""" + await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG_WITH_CODE) + await hass.async_block_till_done() + + await hass.services.async_call( + ALARM_DOMAIN, + service, + { + ATTR_ENTITY_ID: "alarm_control_panel.test_alarm", + ATTR_CODE: "1234", + }, + blocking=True, + ) + mock_concord232_client.arm.assert_called_once_with(expected_arm_call) + + +async def test_alarm_arm_home_silent_mode( + hass: HomeAssistant, mock_concord232_client: MagicMock +) -> None: + """Test arm home service with silent mode.""" + config_with_code = VALID_CONFIG_SILENT_MODE.copy() + config_with_code[ALARM_DOMAIN][CONF_CODE] = "1234" + await async_setup_component(hass, ALARM_DOMAIN, config_with_code) + await hass.async_block_till_done() + + await hass.services.async_call( + ALARM_DOMAIN, + SERVICE_ALARM_ARM_HOME, + { + ATTR_ENTITY_ID: "alarm_control_panel.test_alarm", + ATTR_CODE: "1234", + }, + blocking=True, + ) + mock_concord232_client.arm.assert_called_once_with("stay", "silent") + + +async def test_update_state_disarmed( + hass: HomeAssistant, mock_concord232_client: MagicMock +) -> None: + """Test update when alarm is disarmed.""" + mock_concord232_client.list_partitions.return_value = [{"arming_level": "Off"}] + mock_concord232_client.list_zones.return_value = [ + {"number": 1, "name": "Zone 1", "state": "Normal"}, + ] + + await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() + + state = hass.states.get("alarm_control_panel.test_alarm") + assert state.state == AlarmControlPanelState.DISARMED + + +@pytest.mark.parametrize( + ("arming_level", "expected_state"), + [ + ("Home", AlarmControlPanelState.ARMED_HOME), + ("Away", AlarmControlPanelState.ARMED_AWAY), + ], +) +async def test_update_state_armed( + hass: HomeAssistant, + mock_concord232_client: MagicMock, + freezer: FrozenDateTimeFactory, + arming_level: str, + expected_state: str, +) -> None: + """Test update when alarm is armed.""" + mock_concord232_client.list_partitions.return_value = [ + {"arming_level": arming_level} + ] + mock_concord232_client.partitions = ( + mock_concord232_client.list_partitions.return_value + ) + mock_concord232_client.list_zones.return_value = [ + {"number": 1, "name": "Zone 1", "state": "Normal"}, + ] + + await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() + + # Trigger update + freezer.tick(10) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + state = hass.states.get("alarm_control_panel.test_alarm") + assert state.state == expected_state + + +async def test_update_connection_error( + hass: HomeAssistant, + mock_concord232_client: MagicMock, + freezer: FrozenDateTimeFactory, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test update with connection error.""" + await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() + + mock_concord232_client.list_partitions.side_effect = ( + requests.exceptions.ConnectionError("Connection failed") + ) + + freezer.tick(10) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + assert "Unable to connect to" in caplog.text + + +async def test_update_no_partitions( + hass: HomeAssistant, + mock_concord232_client: MagicMock, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test update when no partitions are available.""" + mock_concord232_client.list_partitions.return_value = [] + + await async_setup_component(hass, ALARM_DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() + + assert "Concord232 reports no partitions" in caplog.text diff --git a/tests/components/concord232/test_binary_sensor.py b/tests/components/concord232/test_binary_sensor.py new file mode 100644 index 00000000000000..b13145ed3179cd --- /dev/null +++ b/tests/components/concord232/test_binary_sensor.py @@ -0,0 +1,201 @@ +"""Tests for the Concord232 binary sensor platform.""" + +from __future__ import annotations + +import datetime +from unittest.mock import MagicMock + +from freezegun.api import FrozenDateTimeFactory +import pytest +import requests + +from homeassistant.components.binary_sensor import ( + DOMAIN as BINARY_SENSOR_DOMAIN, + BinarySensorDeviceClass, +) +from homeassistant.components.concord232.binary_sensor import ( + CONF_EXCLUDE_ZONES, + CONF_ZONE_TYPES, +) +from homeassistant.const import CONF_HOST, CONF_PORT +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component + +from tests.common import async_fire_time_changed + +VALID_CONFIG = { + BINARY_SENSOR_DOMAIN: { + "platform": "concord232", + CONF_HOST: "localhost", + CONF_PORT: 5007, + } +} + +VALID_CONFIG_WITH_EXCLUDE = { + BINARY_SENSOR_DOMAIN: { + "platform": "concord232", + CONF_HOST: "localhost", + CONF_PORT: 5007, + CONF_EXCLUDE_ZONES: [2], + } +} + +VALID_CONFIG_WITH_ZONE_TYPES = { + BINARY_SENSOR_DOMAIN: { + "platform": "concord232", + CONF_HOST: "localhost", + CONF_PORT: 5007, + CONF_ZONE_TYPES: {1: "door", 2: "window"}, + } +} + + +async def test_setup_platform( + hass: HomeAssistant, mock_concord232_client: MagicMock +) -> None: + """Test platform setup.""" + await async_setup_component(hass, BINARY_SENSOR_DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() + + state1 = hass.states.get("binary_sensor.zone_1") + state2 = hass.states.get("binary_sensor.zone_2") + assert state1 is not None + assert state2 is not None + assert state1.state == "off" + assert state2.state == "off" + + +async def test_setup_platform_connection_error( + hass: HomeAssistant, + mock_concord232_client: MagicMock, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test platform setup with connection error.""" + mock_concord232_client.list_zones.side_effect = requests.exceptions.ConnectionError( + "Connection failed" + ) + + await async_setup_component(hass, BINARY_SENSOR_DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() + + assert "Unable to connect to Concord232" in caplog.text + assert hass.states.get("binary_sensor.zone_1") is None + + +async def test_setup_with_exclude_zones( + hass: HomeAssistant, mock_concord232_client: MagicMock +) -> None: + """Test platform setup with excluded zones.""" + await async_setup_component(hass, BINARY_SENSOR_DOMAIN, VALID_CONFIG_WITH_EXCLUDE) + await hass.async_block_till_done() + + state1 = hass.states.get("binary_sensor.zone_1") + state2 = hass.states.get("binary_sensor.zone_2") + assert state1 is not None + assert state2 is None # Zone 2 should be excluded + + +async def test_setup_with_zone_types( + hass: HomeAssistant, mock_concord232_client: MagicMock +) -> None: + """Test platform setup with custom zone types.""" + await async_setup_component( + hass, BINARY_SENSOR_DOMAIN, VALID_CONFIG_WITH_ZONE_TYPES + ) + await hass.async_block_till_done() + + state1 = hass.states.get("binary_sensor.zone_1") + state2 = hass.states.get("binary_sensor.zone_2") + assert state1 is not None + assert state2 is not None + # Check device class is set correctly + assert state1.attributes.get("device_class") == BinarySensorDeviceClass.DOOR + assert state2.attributes.get("device_class") == BinarySensorDeviceClass.WINDOW + + +async def test_zone_state_faulted( + hass: HomeAssistant, mock_concord232_client: MagicMock +) -> None: + """Test zone state when faulted.""" + mock_concord232_client.list_zones.return_value = [ + {"number": 1, "name": "Zone 1", "state": "Faulted"}, + ] + mock_concord232_client.zones = mock_concord232_client.list_zones.return_value + + await async_setup_component(hass, BINARY_SENSOR_DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.zone_1") + assert state.state == "on" # Faulted state means on (faulted) + + +@pytest.mark.freeze_time("2023-10-21") +async def test_zone_update_refresh( + hass: HomeAssistant, + mock_concord232_client: MagicMock, + freezer: FrozenDateTimeFactory, +) -> None: + """Test that zone updates refresh the client data.""" + mock_concord232_client.list_zones.return_value = [ + {"number": 1, "name": "Zone 1", "state": "Normal"}, + ] + mock_concord232_client.zones = mock_concord232_client.list_zones.return_value + + await async_setup_component(hass, BINARY_SENSOR_DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() + + assert hass.states.get("binary_sensor.zone_1").state == "off" + + # Update zone state - need to update both return_value and zones attribute + new_zones = [ + {"number": 1, "name": "Zone 1", "state": "Faulted"}, + ] + mock_concord232_client.list_zones.return_value = new_zones + mock_concord232_client.zones = new_zones + + freezer.tick(datetime.timedelta(seconds=10)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + freezer.tick(datetime.timedelta(seconds=10)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + state = hass.states.get("binary_sensor.zone_1") + assert state.state == "on" + + +@pytest.mark.parametrize( + ("sensor_name", "entity_id", "expected_device_class"), + [ + ( + "MOTION Sensor", + "binary_sensor.motion_sensor", + BinarySensorDeviceClass.MOTION, + ), + ("SMOKE Sensor", "binary_sensor.smoke_sensor", BinarySensorDeviceClass.SMOKE), + ( + "Unknown Sensor", + "binary_sensor.unknown_sensor", + BinarySensorDeviceClass.OPENING, + ), + ], +) +async def test_device_class( + hass: HomeAssistant, + mock_concord232_client: MagicMock, + sensor_name: str, + entity_id: str, + expected_device_class: BinarySensorDeviceClass, +) -> None: + """Test zone type detection for motion sensor.""" + mock_concord232_client.list_zones.return_value = [ + {"number": 1, "name": sensor_name, "state": "Normal"}, + ] + mock_concord232_client.zones = mock_concord232_client.list_zones.return_value + + await async_setup_component(hass, BINARY_SENSOR_DOMAIN, VALID_CONFIG) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state.attributes.get("device_class") == expected_device_class diff --git a/tests/components/go2rtc/snapshots/test_server.ambr b/tests/components/go2rtc/snapshots/test_server.ambr index 9ae2ef9643948f..5100972e5cd682 100644 --- a/tests/components/go2rtc/snapshots/test_server.ambr +++ b/tests/components/go2rtc/snapshots/test_server.ambr @@ -3,7 +3,7 @@ _CallList([ _Call( tuple( - b'# This file is managed by Home Assistant\n# Do not edit it manually\n\napp:\n modules: ["api","exec","ffmpeg","http","mjpeg","onvif","rtmp","rtsp","srtp","webrtc","ws"]\n\napi:\n listen: "127.0.0.1:11984"\n allow_paths: ["/","/api","/api/frame.jpeg","/api/schemes","/api/streams","/api/webrtc","/api/ws"]\n\n# ffmpeg needs the exec module\n# Restrict execution to only ffmpeg binary\nexec:\n allow_paths:\n - ffmpeg\n\nrtsp:\n listen: "127.0.0.1:18554"\n\nwebrtc:\n listen: ":18555/tcp"\n ice_servers: []\n', + b'# This file is managed by Home Assistant\n# Do not edit it manually\n\napp:\n modules: ["api","exec","ffmpeg","http","mjpeg","onvif","rtmp","rtsp","srtp","webrtc","ws"]\n\napi:\n listen: ""\n unix_listen: "/run/go2rtc.sock"\n allow_paths: ["/","/api","/api/frame.jpeg","/api/schemes","/api/streams","/api/webrtc","/api/ws"]\n\n# ffmpeg needs the exec module\n# Restrict execution to only ffmpeg binary\nexec:\n allow_paths:\n - ffmpeg\n\nrtsp:\n listen: "127.0.0.1:18554"\n\nwebrtc:\n listen: ":18555/tcp"\n ice_servers: []\n', ), dict({ }), @@ -14,7 +14,7 @@ _CallList([ _Call( tuple( - b'# This file is managed by Home Assistant\n# Do not edit it manually\n\napp:\n modules: ["api","exec","ffmpeg","http","mjpeg","onvif","rtmp","rtsp","srtp","webrtc","ws","debug"]\n\napi:\n listen: ":11984"\n allow_paths: ["/","/api","/api/frame.jpeg","/api/schemes","/api/streams","/api/webrtc","/api/ws","/api/config","/api/log","/api/streams.dot"]\n\n# ffmpeg needs the exec module\n# Restrict execution to only ffmpeg binary\nexec:\n allow_paths:\n - ffmpeg\n\nrtsp:\n listen: "127.0.0.1:18554"\n\nwebrtc:\n listen: ":18555/tcp"\n ice_servers: []\n', + b'# This file is managed by Home Assistant\n# Do not edit it manually\n\napp:\n modules: ["api","exec","ffmpeg","http","mjpeg","onvif","rtmp","rtsp","srtp","webrtc","ws","debug"]\n\napi:\n listen: ":11984"\n unix_listen: "/run/go2rtc.sock"\n allow_paths: ["/","/api","/api/frame.jpeg","/api/schemes","/api/streams","/api/webrtc","/api/ws","/api/config","/api/log","/api/streams.dot"]\n\n# ffmpeg needs the exec module\n# Restrict execution to only ffmpeg binary\nexec:\n allow_paths:\n - ffmpeg\n\nrtsp:\n listen: "127.0.0.1:18554"\n\nwebrtc:\n listen: ":18555/tcp"\n ice_servers: []\n', ), dict({ }), diff --git a/tests/components/go2rtc/test_init.py b/tests/components/go2rtc/test_init.py index 5b85bfee3dbace..828d1a4cf6915c 100644 --- a/tests/components/go2rtc/test_init.py +++ b/tests/components/go2rtc/test_init.py @@ -3,8 +3,9 @@ from collections.abc import Awaitable, Callable import logging from typing import NamedTuple -from unittest.mock import AsyncMock, Mock, patch +from unittest.mock import ANY, AsyncMock, Mock, patch +from aiohttp import UnixConnector from aiohttp.client_exceptions import ClientConnectionError, ServerConnectionError from awesomeversion import AwesomeVersion from go2rtc_client import Stream @@ -38,11 +39,13 @@ CONF_DEBUG_UI, DEBUG_UI_URL_MESSAGE, DOMAIN, + HA_MANAGED_UNIX_SOCKET, RECOMMENDED_VERSION, ) from homeassistant.components.stream import Orientation from homeassistant.config_entries import ConfigEntryState from homeassistant.const import CONF_URL +from homeassistant.core import EVENT_HOMEASSISTANT_STOP from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import issue_registry as ir from homeassistant.helpers.typing import ConfigType @@ -215,7 +218,9 @@ async def test_setup_go_binary( assert (len(hass.config_entries.async_entries(DOMAIN)) == 1) == has_go2rtc_entry def after_setup() -> None: - server.assert_called_once_with(hass, "/usr/bin/go2rtc", enable_ui=ui_enabled) + server.assert_called_once_with( + hass, "/usr/bin/go2rtc", ANY, enable_ui=ui_enabled + ) server_start.assert_called_once() await _test_setup_and_signaling( @@ -905,3 +910,53 @@ async def test_stream_orientation_with_generic_camera( rest_client, "ffmpeg:https://test.stream/video.m3u8#video=h264#audio=copy#raw=-vf vflip", ) + + +@pytest.mark.usefixtures( + "mock_get_binary", + "mock_is_docker_env", + "mock_go2rtc_entry", + "rest_client", + "server", +) +async def test_unix_socket_connection(hass: HomeAssistant) -> None: + """Test Unix socket is used for HA-managed go2rtc instances.""" + config = {DOMAIN: {}} + + with patch("homeassistant.components.go2rtc.ClientSession") as mock_session_cls: + mock_session = AsyncMock() + mock_session_cls.return_value = mock_session + + assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done(wait_background_tasks=True) + + # Verify ClientSession was created with UnixConnector + mock_session_cls.assert_called_once() + call_kwargs = mock_session_cls.call_args[1] + assert "connector" in call_kwargs + connector = call_kwargs["connector"] + assert isinstance(connector, UnixConnector) + assert connector.path == HA_MANAGED_UNIX_SOCKET + + hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + await hass.async_block_till_done() + + mock_session.close.assert_called_once() + + +@pytest.mark.usefixtures("rest_client", "server") +async def test_unix_socket_not_used_for_custom_server(hass: HomeAssistant) -> None: + """Test Unix socket is not used for custom go2rtc instances.""" + config = {DOMAIN: {CONF_URL: "http://localhost:1984/"}} + + with patch( + "homeassistant.components.go2rtc.async_get_clientsession" + ) as mock_get_session: + mock_session = AsyncMock() + mock_get_session.return_value = mock_session + + assert await async_setup_component(hass, DOMAIN, config) + await hass.async_block_till_done(wait_background_tasks=True) + + # Verify standard clientsession was used, not UnixConnector + mock_get_session.assert_called_once_with(hass) diff --git a/tests/components/go2rtc/test_server.py b/tests/components/go2rtc/test_server.py index 9ec60d30da4f52..843f64c7506a5a 100644 --- a/tests/components/go2rtc/test_server.py +++ b/tests/components/go2rtc/test_server.py @@ -23,9 +23,15 @@ def enable_ui() -> bool: @pytest.fixture -def server(hass: HomeAssistant, enable_ui: bool) -> Server: +def mock_session() -> AsyncMock: + """Fixture to provide a mock ClientSession.""" + return AsyncMock() + + +@pytest.fixture +def server(hass: HomeAssistant, mock_session: AsyncMock, enable_ui: bool) -> Server: """Fixture to initialize the Server.""" - return Server(hass, binary=TEST_BINARY, enable_ui=enable_ui) + return Server(hass, binary=TEST_BINARY, session=mock_session, enable_ui=enable_ui) @pytest.fixture diff --git a/tests/components/matter/snapshots/test_button.ambr b/tests/components/matter/snapshots/test_button.ambr index 52232069d21d1a..d6e24066cff299 100644 --- a/tests/components/matter/snapshots/test_button.ambr +++ b/tests/components/matter/snapshots/test_button.ambr @@ -107,7 +107,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.aqara_door_and_window_sensor_p2_identify', 'has_entity_name': True, 'hidden_by': None, @@ -156,7 +156,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.aqara_motion_and_light_sensor_p2_identify_1', 'has_entity_name': True, 'hidden_by': None, @@ -205,7 +205,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.aqara_motion_and_light_sensor_p2_identify_2', 'has_entity_name': True, 'hidden_by': None, @@ -254,7 +254,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.presence_multi_sensor_fp300_1_identify_1', 'has_entity_name': True, 'hidden_by': None, @@ -303,7 +303,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.presence_multi_sensor_fp300_1_identify_2', 'has_entity_name': True, 'hidden_by': None, @@ -352,7 +352,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.presence_multi_sensor_fp300_1_identify_3', 'has_entity_name': True, 'hidden_by': None, @@ -401,7 +401,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.presence_multi_sensor_fp300_1_identify_4', 'has_entity_name': True, 'hidden_by': None, @@ -450,7 +450,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.climate_sensor_w100_identify_1', 'has_entity_name': True, 'hidden_by': None, @@ -499,7 +499,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.climate_sensor_w100_identify_2', 'has_entity_name': True, 'hidden_by': None, @@ -548,7 +548,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.climate_sensor_w100_identify_3', 'has_entity_name': True, 'hidden_by': None, @@ -597,7 +597,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.climate_sensor_w100_identify_4', 'has_entity_name': True, 'hidden_by': None, @@ -646,7 +646,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.climate_sensor_w100_identify_5', 'has_entity_name': True, 'hidden_by': None, @@ -695,7 +695,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.floor_heating_thermostat_identify_1', 'has_entity_name': True, 'hidden_by': None, @@ -744,7 +744,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.floor_heating_thermostat_identify_2', 'has_entity_name': True, 'hidden_by': None, @@ -793,7 +793,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.aqara_smart_lock_u200_identify', 'has_entity_name': True, 'hidden_by': None, @@ -842,7 +842,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.mock_color_temperature_light_identify', 'has_entity_name': True, 'hidden_by': None, @@ -891,7 +891,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.dimmable_plugin_unit_identify', 'has_entity_name': True, 'hidden_by': None, @@ -940,7 +940,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.connected_thermostat_ute_3000_identify', 'has_entity_name': True, 'hidden_by': None, @@ -989,7 +989,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.ecodeebot_identify', 'has_entity_name': True, 'hidden_by': None, @@ -1038,7 +1038,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.eve_door_identify', 'has_entity_name': True, 'hidden_by': None, @@ -1087,7 +1087,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.eve_energy_20ecn4101_identify_bottom', 'has_entity_name': True, 'hidden_by': None, @@ -1136,7 +1136,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.eve_energy_20ecn4101_identify_top', 'has_entity_name': True, 'hidden_by': None, @@ -1185,7 +1185,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.eve_energy_plug_identify', 'has_entity_name': True, 'hidden_by': None, @@ -1234,7 +1234,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.eve_energy_plug_patched_identify', 'has_entity_name': True, 'hidden_by': None, @@ -1283,7 +1283,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.eve_shutter_switch_20eci1701_identify', 'has_entity_name': True, 'hidden_by': None, @@ -1332,7 +1332,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.eve_thermo_identify', 'has_entity_name': True, 'hidden_by': None, @@ -1381,7 +1381,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.eve_weather_identify_1', 'has_entity_name': True, 'hidden_by': None, @@ -1430,7 +1430,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.eve_weather_identify_2', 'has_entity_name': True, 'hidden_by': None, @@ -1479,7 +1479,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.mock_extended_color_light_identify', 'has_entity_name': True, 'hidden_by': None, @@ -1624,7 +1624,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.mocked_fan_switch_identify_fan', 'has_entity_name': True, 'hidden_by': None, @@ -1673,7 +1673,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.hjmt_6b_identify_1', 'has_entity_name': True, 'hidden_by': None, @@ -1722,7 +1722,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.smart_motion_sensor_identify_1', 'has_entity_name': True, 'hidden_by': None, @@ -1771,7 +1771,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.white_series_onoff_switch_identify_load_control', 'has_entity_name': True, 'hidden_by': None, @@ -2012,7 +2012,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.longan_link_hvac_identify', 'has_entity_name': True, 'hidden_by': None, @@ -2253,7 +2253,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.mock_lock_identify', 'has_entity_name': True, 'hidden_by': None, @@ -2302,7 +2302,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.inovelli_identify_1', 'has_entity_name': True, 'hidden_by': None, @@ -2351,7 +2351,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.inovelli_identify_2', 'has_entity_name': True, 'hidden_by': None, @@ -2400,7 +2400,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.inovelli_identify_6', 'has_entity_name': True, 'hidden_by': None, @@ -2449,7 +2449,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.inovelli_identify_config', 'has_entity_name': True, 'hidden_by': None, @@ -2498,7 +2498,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.inovelli_identify_down', 'has_entity_name': True, 'hidden_by': None, @@ -2547,7 +2547,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.inovelli_identify_up', 'has_entity_name': True, 'hidden_by': None, @@ -2596,7 +2596,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.mock_occupancy_sensor_identify', 'has_entity_name': True, 'hidden_by': None, @@ -2645,7 +2645,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.mock_onoffpluginunit_identify', 'has_entity_name': True, 'hidden_by': None, @@ -2694,7 +2694,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.d215s_identify', 'has_entity_name': True, 'hidden_by': None, @@ -2743,7 +2743,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.dishwasher_identify', 'has_entity_name': True, 'hidden_by': None, @@ -2936,7 +2936,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.laundrywasher_identify', 'has_entity_name': True, 'hidden_by': None, @@ -3177,7 +3177,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.light_switch_example_identify_1', 'has_entity_name': True, 'hidden_by': None, @@ -3226,7 +3226,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.refrigerator_identify', 'has_entity_name': True, 'hidden_by': None, @@ -3323,7 +3323,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.smoke_sensor_identify', 'has_entity_name': True, 'hidden_by': None, @@ -3420,7 +3420,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.mock_switchunit_identify', 'has_entity_name': True, 'hidden_by': None, @@ -3469,7 +3469,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.smart_radiator_thermostat_x_identify', 'has_entity_name': True, 'hidden_by': None, @@ -3518,7 +3518,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.mock_temperature_sensor_identify', 'has_entity_name': True, 'hidden_by': None, @@ -3567,7 +3567,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.longan_link_wncv_da01_identify', 'has_entity_name': True, 'hidden_by': None, @@ -3616,7 +3616,7 @@ 'device_id': , 'disabled_by': None, 'domain': 'button', - 'entity_category': , + 'entity_category': , 'entity_id': 'button.yndx_00540_identify', 'has_entity_name': True, 'hidden_by': None, diff --git a/tests/components/mobile_app/test_init.py b/tests/components/mobile_app/test_init.py index a4edbea6ecf8ba..7b541f3d276807 100644 --- a/tests/components/mobile_app/test_init.py +++ b/tests/components/mobile_app/test_init.py @@ -68,7 +68,9 @@ async def _test_create_cloud_hook( hass_admin_user: MockUser, additional_config: dict[str, Any], async_active_subscription_return_value: bool, - additional_steps: Callable[[ConfigEntry, Mock, str], Awaitable[None]], + additional_steps: Callable[ + [ConfigEntry, Mock, str, Callable[[Any], None]], Awaitable[None] + ], ) -> None: config_entry = MockConfigEntry( data={ @@ -84,6 +86,24 @@ async def _test_create_cloud_hook( ) config_entry.add_to_hass(hass) + cloudhook_change_callback = None + + def mock_listen_cloudhook_change( + _: HomeAssistant, _webhook_id: str, callback: Callable[[Any], None] + ): + """Mock the cloudhook change listener.""" + nonlocal cloudhook_change_callback + cloudhook_change_callback = callback + return lambda: None # Return unsubscribe function + + cloud_hook = "https://hook-url" + + async def mock_get_or_create_cloudhook(_hass: HomeAssistant, _webhook_id: str): + """Mock creating a cloudhook and trigger the change callback.""" + assert cloudhook_change_callback is not None + cloudhook_change_callback({CONF_CLOUDHOOK_URL: cloud_hook}) + return cloud_hook + with ( patch( "homeassistant.components.cloud.async_active_subscription", @@ -93,17 +113,24 @@ async def _test_create_cloud_hook( patch("homeassistant.components.cloud.async_is_connected", return_value=True), patch( "homeassistant.components.cloud.async_get_or_create_cloudhook", - autospec=True, + side_effect=mock_get_or_create_cloudhook, ) as mock_async_get_or_create_cloudhook, + patch( + "homeassistant.components.cloud.async_listen_cloudhook_change", + side_effect=mock_listen_cloudhook_change, + ), ): - cloud_hook = "https://hook-url" - mock_async_get_or_create_cloudhook.return_value = cloud_hook - assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() assert config_entry.state is ConfigEntryState.LOADED + + assert cloudhook_change_callback is not None + await additional_steps( - config_entry, mock_async_get_or_create_cloudhook, cloud_hook + config_entry, + mock_async_get_or_create_cloudhook, + cloud_hook, + cloudhook_change_callback, ) @@ -114,7 +141,10 @@ async def test_create_cloud_hook_on_setup( """Test creating a cloud hook during setup.""" async def additional_steps( - config_entry: ConfigEntry, mock_create_cloudhook: Mock, cloud_hook: str + config_entry: ConfigEntry, + mock_create_cloudhook: Mock, + cloud_hook: str, + cloudhook_change_callback: Callable[[Any], None], ) -> None: assert config_entry.data[CONF_CLOUDHOOK_URL] == cloud_hook mock_create_cloudhook.assert_called_once_with( @@ -134,7 +164,10 @@ async def test_remove_cloudhook( """Test removing a cloud hook when config entry is removed.""" async def additional_steps( - config_entry: ConfigEntry, mock_create_cloudhook: Mock, cloud_hook: str + config_entry: ConfigEntry, + mock_create_cloudhook: Mock, + cloud_hook: str, + cloudhook_change_callback: Callable[[Any], None], ) -> None: webhook_id = config_entry.data[CONF_WEBHOOK_ID] assert config_entry.data[CONF_CLOUDHOOK_URL] == cloud_hook @@ -158,7 +191,10 @@ async def test_create_cloud_hook_aleady_exists( cloud_hook = "https://hook-url-already-exists" async def additional_steps( - config_entry: ConfigEntry, mock_create_cloudhook: Mock, _: str + config_entry: ConfigEntry, + mock_create_cloudhook: Mock, + _: str, + cloudhook_change_callback: Callable[[Any], None], ) -> None: assert config_entry.data[CONF_CLOUDHOOK_URL] == cloud_hook mock_create_cloudhook.assert_not_called() @@ -175,13 +211,21 @@ async def test_create_cloud_hook_after_connection( """Test creating a cloud hook when connected to the cloud.""" async def additional_steps( - config_entry: ConfigEntry, mock_create_cloudhook: Mock, cloud_hook: str + config_entry: ConfigEntry, + mock_create_cloudhook: Mock, + cloud_hook: str, + cloudhook_change_callback: Callable[[Any], None], ) -> None: assert CONF_CLOUDHOOK_URL not in config_entry.data mock_create_cloudhook.assert_not_called() async_mock_cloud_connection_status(hass, True) await hass.async_block_till_done() + + # Simulate cloudhook creation by calling the callback + cloudhook_change_callback({CONF_CLOUDHOOK_URL: cloud_hook}) + await hass.async_block_till_done() + assert config_entry.data[CONF_CLOUDHOOK_URL] == cloud_hook mock_create_cloudhook.assert_called_once_with( hass, config_entry.data[CONF_WEBHOOK_ID] @@ -260,3 +304,236 @@ async def test_remove_entry_on_user_remove( entries = hass.config_entries.async_entries(DOMAIN) assert len(entries) == 0 + + +async def test_cloudhook_cleanup_on_disconnect_and_logout( + hass: HomeAssistant, + hass_admin_user: MockUser, +) -> None: + """Test cloudhook is cleaned up when cloud disconnects and user is logged out.""" + config_entry = MockConfigEntry( + data={ + **REGISTER_CLEARTEXT, + CONF_WEBHOOK_ID: "test-webhook-id", + ATTR_DEVICE_NAME: "Test", + ATTR_DEVICE_ID: "Test", + CONF_USER_ID: hass_admin_user.id, + CONF_CLOUDHOOK_URL: "https://hook-url", + }, + domain=DOMAIN, + title="Test", + ) + config_entry.add_to_hass(hass) + + with ( + patch( + "homeassistant.components.cloud.async_is_logged_in", + return_value=True, + ), + patch( + "homeassistant.components.cloud.async_active_subscription", + return_value=True, + ), + patch( + "homeassistant.components.cloud.async_is_connected", + return_value=True, + ), + ): + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert config_entry.state is ConfigEntryState.LOADED + # Cloudhook should still exist + assert CONF_CLOUDHOOK_URL in config_entry.data + + # Simulate cloud disconnect and logout + with patch( + "homeassistant.components.cloud.async_is_logged_in", + return_value=False, + ): + async_mock_cloud_connection_status(hass, False) + await hass.async_block_till_done() + + # Cloudhook should be removed from config entry + assert CONF_CLOUDHOOK_URL not in config_entry.data + + +async def test_cloudhook_persists_on_disconnect_when_logged_in( + hass: HomeAssistant, + hass_admin_user: MockUser, +) -> None: + """Test cloudhook persists when cloud disconnects but user is still logged in.""" + config_entry = MockConfigEntry( + data={ + **REGISTER_CLEARTEXT, + CONF_WEBHOOK_ID: "test-webhook-id", + ATTR_DEVICE_NAME: "Test", + ATTR_DEVICE_ID: "Test", + CONF_USER_ID: hass_admin_user.id, + CONF_CLOUDHOOK_URL: "https://hook-url", + }, + domain=DOMAIN, + title="Test", + ) + config_entry.add_to_hass(hass) + + with ( + patch( + "homeassistant.components.cloud.async_is_logged_in", + return_value=True, + ), + patch( + "homeassistant.components.cloud.async_active_subscription", + return_value=True, + ), + patch( + "homeassistant.components.cloud.async_is_connected", + return_value=True, + ), + ): + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert config_entry.state is ConfigEntryState.LOADED + # Cloudhook should exist + assert CONF_CLOUDHOOK_URL in config_entry.data + + # Simulate cloud disconnect while still logged in + async_mock_cloud_connection_status(hass, False) + await hass.async_block_till_done() + + # Cloudhook should still exist because user is still logged in + assert CONF_CLOUDHOOK_URL in config_entry.data + + +async def test_cloudhook_change_listener_deletion( + hass: HomeAssistant, + hass_admin_user: MockUser, +) -> None: + """Test cloudhook change listener removes cloudhook from config entry on deletion.""" + webhook_id = "test-webhook-id" + config_entry = MockConfigEntry( + data={ + **REGISTER_CLEARTEXT, + CONF_WEBHOOK_ID: webhook_id, + ATTR_DEVICE_NAME: "Test", + ATTR_DEVICE_ID: "Test", + CONF_USER_ID: hass_admin_user.id, + CONF_CLOUDHOOK_URL: "https://hook-url", + }, + domain=DOMAIN, + title="Test", + ) + config_entry.add_to_hass(hass) + + cloudhook_change_callback = None + + def mock_listen_cloudhook_change( + _: HomeAssistant, _webhook_id: str, callback: Callable[[Any], None] + ): + """Mock the cloudhook change listener.""" + nonlocal cloudhook_change_callback + cloudhook_change_callback = callback + return lambda: None # Return unsubscribe function + + with ( + patch( + "homeassistant.components.cloud.async_is_logged_in", + return_value=True, + ), + patch( + "homeassistant.components.cloud.async_active_subscription", + return_value=True, + ), + patch( + "homeassistant.components.cloud.async_is_connected", + return_value=True, + ), + patch( + "homeassistant.components.cloud.async_listen_cloudhook_change", + side_effect=mock_listen_cloudhook_change, + ), + ): + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert config_entry.state is ConfigEntryState.LOADED + # Cloudhook should exist + assert CONF_CLOUDHOOK_URL in config_entry.data + # Change listener should have been registered + assert cloudhook_change_callback is not None + + # Simulate cloudhook deletion by calling the callback with None + cloudhook_change_callback(None) + await hass.async_block_till_done() + + # Cloudhook should be removed from config entry + assert CONF_CLOUDHOOK_URL not in config_entry.data + + +async def test_cloudhook_change_listener_update( + hass: HomeAssistant, + hass_admin_user: MockUser, +) -> None: + """Test cloudhook change listener updates cloudhook URL in config entry.""" + webhook_id = "test-webhook-id" + original_url = "https://hook-url" + config_entry = MockConfigEntry( + data={ + **REGISTER_CLEARTEXT, + CONF_WEBHOOK_ID: webhook_id, + ATTR_DEVICE_NAME: "Test", + ATTR_DEVICE_ID: "Test", + CONF_USER_ID: hass_admin_user.id, + CONF_CLOUDHOOK_URL: original_url, + }, + domain=DOMAIN, + title="Test", + ) + config_entry.add_to_hass(hass) + + cloudhook_change_callback = None + + def mock_listen_cloudhook_change(hass_instance, wh_id: str, callback): + """Mock the cloudhook change listener.""" + nonlocal cloudhook_change_callback + cloudhook_change_callback = callback + return lambda: None # Return unsubscribe function + + with ( + patch( + "homeassistant.components.cloud.async_is_logged_in", + return_value=True, + ), + patch( + "homeassistant.components.cloud.async_active_subscription", + return_value=True, + ), + patch( + "homeassistant.components.cloud.async_is_connected", + return_value=True, + ), + patch( + "homeassistant.components.cloud.async_listen_cloudhook_change", + side_effect=mock_listen_cloudhook_change, + ), + ): + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + assert config_entry.state is ConfigEntryState.LOADED + # Cloudhook should exist with original URL + assert config_entry.data[CONF_CLOUDHOOK_URL] == original_url + # Change listener should have been registered + assert cloudhook_change_callback is not None + + # Simulate cloudhook URL change + new_url = "https://new-hook-url" + cloudhook_change_callback({CONF_CLOUDHOOK_URL: new_url}) + await hass.async_block_till_done() + + # Cloudhook URL should be updated in config entry + assert config_entry.data[CONF_CLOUDHOOK_URL] == new_url + + # Simulate same URL update (should not trigger update) + cloudhook_change_callback({CONF_CLOUDHOOK_URL: new_url}) + await hass.async_block_till_done() + + # URL should remain the same + assert config_entry.data[CONF_CLOUDHOOK_URL] == new_url diff --git a/tests/components/mobile_app/test_webhook.py b/tests/components/mobile_app/test_webhook.py index b071caebd16e2a..e0c5a1cf77cd43 100644 --- a/tests/components/mobile_app/test_webhook.py +++ b/tests/components/mobile_app/test_webhook.py @@ -310,6 +310,73 @@ async def test_webhook_handle_get_config( assert expected_dict == json +async def test_webhook_handle_get_config_with_cloudhook_and_active_subscription( + hass: HomeAssistant, + create_registrations: tuple[dict[str, Any], dict[str, Any]], + webhook_client: TestClient, +) -> None: + """Test get_config returns cloudhook_url when there's an active subscription.""" + webhook_id = create_registrations[1]["webhook_id"] + webhook_url = f"/api/webhook/{webhook_id}" + + # Get the config entry and add cloudhook_url to it + config_entry = hass.config_entries.async_entries(DOMAIN)[1] + hass.config_entries.async_update_entry( + config_entry, + data={**config_entry.data, "cloudhook_url": "https://hooks.nabu.casa/test"}, + ) + + with ( + patch( + "homeassistant.components.cloud.async_active_subscription", + return_value=True, + ), + patch( + "homeassistant.components.cloud.async_remote_ui_url", + return_value="https://remote.ui.url", + ), + ): + resp = await webhook_client.post(webhook_url, json={"type": "get_config"}) + assert resp.status == HTTPStatus.OK + json_resp = await resp.json() + + # Cloudhook should be in response + assert "cloudhook_url" in json_resp + assert json_resp["cloudhook_url"] == "https://hooks.nabu.casa/test" + # Remote UI should also be in response + assert "remote_ui_url" in json_resp + + +async def test_webhook_handle_get_config_with_cloudhook_no_subscription( + hass: HomeAssistant, + create_registrations: tuple[dict[str, Any], dict[str, Any]], + webhook_client: TestClient, +) -> None: + """Test get_config doesn't return cloudhook_url without active subscription.""" + webhook_id = create_registrations[1]["webhook_id"] + webhook_url = f"/api/webhook/{webhook_id}" + + # Get the config entry and add cloudhook_url to it + config_entry = hass.config_entries.async_entries(DOMAIN)[1] + hass.config_entries.async_update_entry( + config_entry, + data={**config_entry.data, "cloudhook_url": "https://hooks.nabu.casa/test"}, + ) + + with patch( + "homeassistant.components.cloud.async_active_subscription", + return_value=False, + ): + resp = await webhook_client.post(webhook_url, json={"type": "get_config"}) + assert resp.status == HTTPStatus.OK + json_resp = await resp.json() + + # Cloudhook should NOT be in response even though it exists in config entry + assert "cloudhook_url" not in json_resp + # Remote UI should also not be in response + assert "remote_ui_url" not in json_resp + + async def test_webhook_returns_error_incorrect_json( create_registrations: tuple[dict[str, Any], dict[str, Any]], webhook_client: TestClient, diff --git a/tests/components/mqtt/common.py b/tests/components/mqtt/common.py index 5b02453e44e0c9..b69c612585523f 100644 --- a/tests/components/mqtt/common.py +++ b/tests/components/mqtt/common.py @@ -600,6 +600,23 @@ "optimistic": True, }, } +MOCK_SUBENTRY_TEXT_COMPONENT = { + "09261f6feed443e7b7d5f3fbe2a47413": { + "platform": "text", + "name": "MOTD", + "entity_category": None, + "command_topic": "test-topic", + "command_template": "{{ value }}", + "state_topic": "test-topic", + "min": 0.0, + "max": 10.0, + "mode": "password", + "pattern": "^[a-z_]*$", + "value_template": "{{ value_json.value }}", + "retain": False, + "entity_picture": "https://example.com/09261f6feed443e7b7d5f3fbe2a47413", + }, +} MOCK_SUBENTRY_AVAILABILITY_DATA = { "availability": { @@ -725,6 +742,10 @@ "device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 0}}, "components": MOCK_SUBENTRY_SWITCH_COMPONENT, } +MOCK_TEXT_SUBENTRY_DATA = { + "device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 0}}, + "components": MOCK_SUBENTRY_TEXT_COMPONENT, +} MOCK_SUBENTRY_DATA_BAD_COMPONENT_SCHEMA = { "device": MOCK_SUBENTRY_DEVICE_DATA | {"mqtt_settings": {"qos": 0}}, "components": MOCK_SUBENTRY_NOTIFY_BAD_SCHEMA, diff --git a/tests/components/mqtt/test_config_flow.py b/tests/components/mqtt/test_config_flow.py index 4b889e4bda410f..777a2dd3dd658a 100644 --- a/tests/components/mqtt/test_config_flow.py +++ b/tests/components/mqtt/test_config_flow.py @@ -62,6 +62,7 @@ MOCK_SENSOR_SUBENTRY_DATA_STATE_CLASS, MOCK_SIREN_SUBENTRY_DATA, MOCK_SWITCH_SUBENTRY_DATA, + MOCK_TEXT_SUBENTRY_DATA, ) from tests.common import MockConfigEntry, MockMqttReasonCode, get_schema_suggested_value @@ -3720,6 +3721,65 @@ async def test_migrate_of_incompatible_config_entry( "Milk notifier Outlet", id="switch", ), + pytest.param( + MOCK_TEXT_SUBENTRY_DATA, + {"name": "Milk notifier", "mqtt_settings": {"qos": 0}}, + {"name": "MOTD"}, + {}, + (), + { + "command_topic": "test-topic", + "command_template": "{{ value }}", + "state_topic": "test-topic", + "value_template": "{{ value_json.value }}", + "retain": False, + "text_advanced_settings": { + "min": 0, + "max": 10, + "mode": "password", + "pattern": "^[a-z_]*$", + }, + }, + ( + ( + {"command_topic": "test-topic#invalid"}, + {"command_topic": "invalid_publish_topic"}, + ), + ( + { + "command_topic": "test-topic", + "state_topic": "test-topic#invalid", + }, + {"state_topic": "invalid_subscribe_topic"}, + ), + ( + { + "command_topic": "test-topic", + "text_advanced_settings": { + "min": 20, + "max": 10, + "mode": "password", + "pattern": "^[a-z_]*$", + }, + }, + {"text_advanced_settings": "max_below_min"}, + ), + ( + { + "command_topic": "test-topic", + "text_advanced_settings": { + "min": 0, + "max": 10, + "mode": "password", + "pattern": "(", + }, + }, + {"text_advanced_settings": "invalid_regular_expression"}, + ), + ), + "Milk notifier MOTD", + id="text", + ), ], ) async def test_subentry_configflow( diff --git a/tests/components/niko_home_control/test_config_flow.py b/tests/components/niko_home_control/test_config_flow.py index 41f9a3dcf9e0a1..a85cf6d7f49e6f 100644 --- a/tests/components/niko_home_control/test_config_flow.py +++ b/tests/components/niko_home_control/test_config_flow.py @@ -1,6 +1,8 @@ """Test niko_home_control config flow.""" -from unittest.mock import AsyncMock, patch +from unittest.mock import AsyncMock + +import pytest from homeassistant.components.niko_home_control.const import DOMAIN from homeassistant.config_entries import SOURCE_USER @@ -36,8 +38,22 @@ async def test_full_flow( assert len(mock_setup_entry.mock_calls) == 1 -async def test_cannot_connect(hass: HomeAssistant) -> None: - """Test the cannot connect error.""" +@pytest.mark.parametrize( + ("exception", "error"), + [ + (TimeoutError, "timeout_connect"), + (OSError, "cannot_connect"), + (Exception, "unknown"), + ], +) +async def test_flow_errors( + hass: HomeAssistant, + mock_niko_home_control_connection: AsyncMock, + mock_setup_entry: AsyncMock, + exception: Exception, + error: str, +) -> None: + """Test the timeout error.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": SOURCE_USER} @@ -45,25 +61,22 @@ async def test_cannot_connect(hass: HomeAssistant) -> None: assert result["type"] is FlowResultType.FORM assert result["errors"] == {} - with patch( - "homeassistant.components.niko_home_control.config_flow.NHCController.connect", - side_effect=Exception, - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_HOST: "192.168.0.123"}, - ) + mock_niko_home_control_connection.connect.side_effect = exception + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "192.168.0.123"}, + ) assert result["type"] is FlowResultType.FORM - assert result["errors"] == {"base": "cannot_connect"} + assert result["errors"] == {"base": error} - with patch( - "homeassistant.components.niko_home_control.config_flow.NHCController.connect", - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - {CONF_HOST: "192.168.0.123"}, - ) + mock_niko_home_control_connection.connect.side_effect = None + + result = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: "192.168.0.123"}, + ) assert result["type"] is FlowResultType.CREATE_ENTRY @@ -119,6 +132,7 @@ async def test_reconfigure( hass: HomeAssistant, mock_niko_home_control_connection: AsyncMock, mock_config_entry: MockConfigEntry, + mock_setup_entry: AsyncMock, ) -> None: """Test the reconfigure flow.""" mock_config_entry.add_to_hass(hass) @@ -136,28 +150,39 @@ async def test_reconfigure( assert result["reason"] == "reconfigure_successful" -async def test_reconfigure_cannot_connect( +@pytest.mark.parametrize( + ("exception", "error"), + [ + (TimeoutError, "timeout_connect"), + (OSError, "cannot_connect"), + (Exception, "unknown"), + ], +) +async def test_reconfigure_errors( hass: HomeAssistant, mock_niko_home_control_connection: AsyncMock, mock_config_entry: MockConfigEntry, + mock_setup_entry: AsyncMock, + exception: Exception, + error: str, ) -> None: """Test reconfiguration with connection error.""" mock_config_entry.add_to_hass(hass) - mock_niko_home_control_connection.connect.side_effect = Exception("cannot_connect") - result = await mock_config_entry.start_reconfigure_flow(hass) assert result["type"] is FlowResultType.FORM assert result["errors"] == {} + mock_niko_home_control_connection.connect.side_effect = exception + result = await hass.config_entries.flow.async_configure( result["flow_id"], {CONF_HOST: "192.168.0.122"}, ) assert result["type"] is FlowResultType.FORM - assert result["errors"] == {"base": "cannot_connect"} + assert result["errors"] == {"base": error} mock_niko_home_control_connection.connect.side_effect = None diff --git a/tests/components/shelly/test_event.py b/tests/components/shelly/test_event.py index 3591c093f046ab..a8fb08f0421021 100644 --- a/tests/components/shelly/test_event.py +++ b/tests/components/shelly/test_event.py @@ -1,6 +1,6 @@ """Tests for Shelly button platform.""" -import copy +from copy import deepcopy from unittest.mock import Mock from aioshelly.ble.const import BLE_SCRIPT_NAME @@ -264,7 +264,7 @@ async def test_block_event_shix3_1( hass: HomeAssistant, mock_block_device: Mock, monkeypatch: pytest.MonkeyPatch ) -> None: """Test block device event for SHIX3-1.""" - blocks = copy.deepcopy(MOCK_BLOCKS) + blocks = deepcopy(MOCK_BLOCKS) blocks[0] = Mock( sensor_ids={ "inputEvent": "S", diff --git a/tests/components/shelly/test_sensor.py b/tests/components/shelly/test_sensor.py index 8034c71aad215e..16d5fec55b46d8 100644 --- a/tests/components/shelly/test_sensor.py +++ b/tests/components/shelly/test_sensor.py @@ -3,7 +3,7 @@ from copy import deepcopy from unittest.mock import Mock, PropertyMock -from aioshelly.const import MODEL_BLU_GATEWAY_G3 +from aioshelly.const import MODEL_BLU_GATEWAY_G3, MODEL_EM3 from aioshelly.exceptions import NotInitialized from freezegun.api import FrozenDateTimeFactory import pytest @@ -53,7 +53,7 @@ register_device, register_entity, ) -from .conftest import MOCK_CONFIG, MOCK_SHELLY_RPC, MOCK_STATUS_RPC +from .conftest import MOCK_BLOCKS, MOCK_CONFIG, MOCK_SHELLY_RPC, MOCK_STATUS_RPC from tests.common import ( async_fire_time_changed, @@ -97,6 +97,55 @@ async def test_block_sensor( assert entry.unique_id == "123456789ABC-relay_0-power" +async def test_block_sensor_em3( + hass: HomeAssistant, mock_block_device: Mock, monkeypatch: pytest.MonkeyPatch +) -> None: + """Test block sensor of EM3.""" + monkeypatch.setitem(mock_block_device.shelly, "num_emeters", 3) + monkeypatch.setitem(mock_block_device.settings["device"], "type", MODEL_EM3) + monkeypatch.setitem( + mock_block_device.settings, + "emeters", + [ + {"name": "Grid L1", "appliance_type": "General", "max_power": 0}, + {"appliance_type": "General", "max_power": 0}, + {"appliance_type": "General", "max_power": 0}, + ], + ) + blocks = deepcopy(MOCK_BLOCKS) + blocks[5] = Mock( + sensor_ids={"power": 20}, + channel="0", + power=20, + description="emeter_0", + type="emeter", + ) + blocks.append( + Mock( + sensor_ids={"power": 20}, + channel="1", + power=20, + description="emeter_1", + type="emeter", + ) + ) + blocks.append( + Mock( + sensor_ids={"power": 20}, + channel="2", + power=20, + description="emeter_2", + type="emeter", + ) + ) + monkeypatch.setattr(mock_block_device, "blocks", blocks) + await init_integration(hass, 1, model=MODEL_EM3) + + assert hass.states.get(f"{SENSOR_DOMAIN}.grid_l1_power") + assert hass.states.get(f"{SENSOR_DOMAIN}.test_name_phase_b_power") + assert hass.states.get(f"{SENSOR_DOMAIN}.test_name_phase_c_power") + + async def test_energy_sensor( hass: HomeAssistant, mock_block_device: Mock, entity_registry: EntityRegistry ) -> None: diff --git a/tests/components/shelly/test_utils.py b/tests/components/shelly/test_utils.py index 13c306fdb22334..a7d48cace5d434 100644 --- a/tests/components/shelly/test_utils.py +++ b/tests/components/shelly/test_utils.py @@ -147,6 +147,7 @@ async def test_is_block_momentary_input( is False ) + monkeypatch.delitem(mock_block_device.settings, "inputs") monkeypatch.delitem(mock_block_device.settings, "relays") monkeypatch.delitem(mock_block_device.settings, "rollers") assert ( diff --git a/tests/components/smartthings/conftest.py b/tests/components/smartthings/conftest.py index fb6dcedef4030e..ef3aff8be951e2 100644 --- a/tests/components/smartthings/conftest.py +++ b/tests/components/smartthings/conftest.py @@ -135,12 +135,14 @@ def mock_smartthings() -> Generator[AsyncMock]: "da_wm_wm_000001", "da_wm_wm_000001_1", "da_wm_sc_000001", + "da_wm_dw_01011", "da_rvc_normal_000001", "da_rvc_map_01011", "da_ks_microwave_0101x", "da_ks_cooktop_31001", "da_ks_range_0101x", "da_ks_oven_01061", + "da_ks_oven_0107x", "hue_color_temperature_bulb", "hue_rgbw_color_bulb", "c2c_shade", diff --git a/tests/components/smartthings/fixtures/device_status/da_ks_oven_0107x.json b/tests/components/smartthings/fixtures/device_status/da_ks_oven_0107x.json new file mode 100644 index 00000000000000..efc2aad44a0a4b --- /dev/null +++ b/tests/components/smartthings/fixtures/device_status/da_ks_oven_0107x.json @@ -0,0 +1,1576 @@ +{ + "components": { + "hca.main": { + "hca.washerMode": { + "mode": { + "value": "others", + "timestamp": "2025-11-15T12:07:22.458Z" + }, + "supportedModes": { + "value": ["normal", "quickWash", "mix", "eco", "spinOnly"], + "timestamp": "2025-11-15T09:13:38.038Z" + } + } + }, + "main": { + "samsungce.washerWaterLevel": { + "supportedWaterLevel": { + "value": null + }, + "waterLevel": { + "value": null + } + }, + "custom.washerWaterTemperature": { + "supportedWasherWaterTemperature": { + "value": ["none", "cold", "20", "30", "40", "60", "90"], + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "washerWaterTemperature": { + "value": "none", + "timestamp": "2025-11-15T14:38:01.810Z" + } + }, + "samsungce.softenerAutoReplenishment": { + "regularSoftenerType": { + "value": "none", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "regularSoftenerAlarmEnabled": { + "value": false, + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "regularSoftenerInitialAmount": { + "value": 0, + "unit": "cc", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "regularSoftenerRemainingAmount": { + "value": 0, + "unit": "cc", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "regularSoftenerDosage": { + "value": 0, + "unit": "cc", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "regularSoftenerOrderThreshold": { + "value": 0, + "unit": "cc", + "timestamp": "2025-11-15T13:19:14.326Z" + } + }, + "samsungce.autoDispenseSoftener": { + "remainingAmount": { + "value": null + }, + "amount": { + "value": null + }, + "supportedDensity": { + "value": null + }, + "density": { + "value": null + }, + "supportedAmount": { + "value": null + } + }, + "samsungce.autoDispenseDetergent": { + "remainingAmount": { + "value": null + }, + "amount": { + "value": null + }, + "supportedDensity": { + "value": null + }, + "density": { + "value": null + }, + "supportedAmount": { + "value": null + }, + "availableTypes": { + "value": null + }, + "type": { + "value": null + }, + "supportedTypes": { + "value": null + }, + "recommendedAmount": { + "value": null + } + }, + "samsungce.washerWaterValve": { + "waterValve": { + "value": null + }, + "supportedWaterValve": { + "value": null + } + }, + "washerOperatingState": { + "completionTime": { + "value": "2025-11-15T17:16:01Z", + "timestamp": "2025-11-15T14:38:01.985Z" + }, + "machineState": { + "value": "stop", + "timestamp": "2025-11-15T14:38:01.985Z" + }, + "washerJobState": { + "value": "none", + "timestamp": "2025-11-15T14:38:01.985Z" + }, + "supportedMachineStates": { + "value": ["stop", "run", "pause"], + "timestamp": "2025-11-15T09:13:30.146Z" + } + }, + "custom.washerAutoSoftener": { + "washerAutoSoftener": { + "value": null + } + }, + "samsungce.washerCycle": { + "cycleType": { + "value": "washingOnly", + "timestamp": "2025-11-15T12:07:20.852Z" + }, + "supportedCycles": { + "value": [ + { + "cycle": "1C", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "3500", + "default": "off", + "options": [] + }, + "spinLevel": { + "raw": "A67F", + "default": "1400", + "options": [ + "rinseHold", + "noSpin", + "400", + "800", + "1000", + "1200", + "1400" + ] + }, + "rinseCycle": { + "raw": "923F", + "default": "2", + "options": ["0", "1", "2", "3", "4", "5"] + }, + "waterTemperature": { + "raw": "8000", + "default": "none", + "options": [] + } + } + }, + { + "cycle": "1B", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "35F0", + "default": "off", + "options": ["on", "off"] + }, + "spinLevel": { + "raw": "A67F", + "default": "1400", + "options": [ + "rinseHold", + "noSpin", + "400", + "800", + "1000", + "1200", + "1400" + ] + }, + "rinseCycle": { + "raw": "933F", + "default": "3", + "options": ["0", "1", "2", "3", "4", "5"] + }, + "waterTemperature": { + "raw": "857E", + "default": "60", + "options": ["cold", "20", "30", "40", "60", "90"] + } + } + }, + { + "cycle": "1E", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "3500", + "default": "off", + "options": [] + }, + "spinLevel": { + "raw": "A33F", + "default": "800", + "options": [ + "rinseHold", + "noSpin", + "400", + "800", + "1000", + "1200" + ] + }, + "rinseCycle": { + "raw": "933F", + "default": "3", + "options": ["0", "1", "2", "3", "4", "5"] + }, + "waterTemperature": { + "raw": "811E", + "default": "cold", + "options": ["cold", "20", "30", "40"] + } + } + }, + { + "cycle": "1D", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "35F0", + "default": "off", + "options": ["on", "off"] + }, + "spinLevel": { + "raw": "A67F", + "default": "1400", + "options": [ + "rinseHold", + "noSpin", + "400", + "800", + "1000", + "1200", + "1400" + ] + }, + "rinseCycle": { + "raw": "923F", + "default": "2", + "options": ["0", "1", "2", "3", "4", "5"] + }, + "waterTemperature": { + "raw": "841E", + "default": "40", + "options": ["cold", "20", "30", "40"] + } + } + }, + { + "cycle": "96", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "3500", + "default": "off", + "options": [] + }, + "spinLevel": { + "raw": "A37F", + "default": "800", + "options": [ + "rinseHold", + "noSpin", + "400", + "800", + "1000", + "1200", + "1400" + ] + }, + "rinseCycle": { + "raw": "920F", + "default": "2", + "options": ["0", "1", "2", "3"] + }, + "waterTemperature": { + "raw": "841E", + "default": "40", + "options": ["cold", "20", "30", "40"] + } + } + }, + { + "cycle": "1F", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "3500", + "default": "off", + "options": [] + }, + "spinLevel": { + "raw": "A57F", + "default": "1200", + "options": [ + "rinseHold", + "noSpin", + "400", + "800", + "1000", + "1200", + "1400" + ] + }, + "rinseCycle": { + "raw": "923F", + "default": "2", + "options": ["0", "1", "2", "3", "4", "5"] + }, + "waterTemperature": { + "raw": "8102", + "default": "cold", + "options": ["cold"] + } + } + }, + { + "cycle": "25", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "35F0", + "default": "off", + "options": ["on", "off"] + }, + "spinLevel": { + "raw": "A57F", + "default": "1200", + "options": [ + "rinseHold", + "noSpin", + "400", + "800", + "1000", + "1200", + "1400" + ] + }, + "rinseCycle": { + "raw": "923F", + "default": "2", + "options": ["0", "1", "2", "3", "4", "5"] + }, + "waterTemperature": { + "raw": "843E", + "default": "40", + "options": ["cold", "20", "30", "40", "60"] + } + } + }, + { + "cycle": "26", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "3500", + "default": "off", + "options": [] + }, + "spinLevel": { + "raw": "A207", + "default": "400", + "options": ["rinseHold", "noSpin", "400"] + }, + "rinseCycle": { + "raw": "920F", + "default": "2", + "options": ["0", "1", "2", "3"] + }, + "waterTemperature": { + "raw": "831E", + "default": "30", + "options": ["cold", "20", "30", "40"] + } + } + }, + { + "cycle": "33", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "35F0", + "default": "off", + "options": ["on", "off"] + }, + "spinLevel": { + "raw": "A67F", + "default": "1400", + "options": [ + "rinseHold", + "noSpin", + "400", + "800", + "1000", + "1200", + "1400" + ] + }, + "rinseCycle": { + "raw": "933F", + "default": "3", + "options": ["0", "1", "2", "3", "4", "5"] + }, + "waterTemperature": { + "raw": "867E", + "default": "90", + "options": ["cold", "20", "30", "40", "60", "90"] + } + } + }, + { + "cycle": "24", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "35F0", + "default": "off", + "options": ["on", "off"] + }, + "spinLevel": { + "raw": "A30F", + "default": "800", + "options": ["rinseHold", "noSpin", "400", "800"] + }, + "rinseCycle": { + "raw": "930F", + "default": "3", + "options": ["0", "1", "2", "3"] + }, + "waterTemperature": { + "raw": "841E", + "default": "40", + "options": ["cold", "20", "30", "40"] + } + } + }, + { + "cycle": "32", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "35F0", + "default": "off", + "options": ["on", "off"] + }, + "spinLevel": { + "raw": "A37F", + "default": "800", + "options": [ + "rinseHold", + "noSpin", + "400", + "800", + "1000", + "1200", + "1400" + ] + }, + "rinseCycle": { + "raw": "923F", + "default": "2", + "options": ["0", "1", "2", "3", "4", "5"] + }, + "waterTemperature": { + "raw": "833E", + "default": "30", + "options": ["cold", "20", "30", "40", "60"] + } + } + }, + { + "cycle": "20", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "35F0", + "default": "off", + "options": ["on", "off"] + }, + "spinLevel": { + "raw": "A67F", + "default": "1400", + "options": [ + "rinseHold", + "noSpin", + "400", + "800", + "1000", + "1200", + "1400" + ] + }, + "rinseCycle": { + "raw": "943F", + "default": "4", + "options": ["0", "1", "2", "3", "4", "5"] + }, + "waterTemperature": { + "raw": "857E", + "default": "60", + "options": ["cold", "20", "30", "40", "60", "90"] + } + } + }, + { + "cycle": "22", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "3500", + "default": "off", + "options": [] + }, + "spinLevel": { + "raw": "A30F", + "default": "800", + "options": ["rinseHold", "noSpin", "400", "800"] + }, + "rinseCycle": { + "raw": "920F", + "default": "2", + "options": ["0", "1", "2", "3"] + }, + "waterTemperature": { + "raw": "841E", + "default": "40", + "options": ["cold", "20", "30", "40"] + } + } + }, + { + "cycle": "23", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "3500", + "default": "off", + "options": [] + }, + "spinLevel": { + "raw": "A57F", + "default": "1200", + "options": [ + "rinseHold", + "noSpin", + "400", + "800", + "1000", + "1200", + "1400" + ] + }, + "rinseCycle": { + "raw": "930F", + "default": "3", + "options": ["0", "1", "2", "3"] + }, + "waterTemperature": { + "raw": "831E", + "default": "30", + "options": ["cold", "20", "30", "40"] + } + } + }, + { + "cycle": "2F", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "35F0", + "default": "off", + "options": ["on", "off"] + }, + "spinLevel": { + "raw": "A30F", + "default": "800", + "options": ["rinseHold", "noSpin", "400", "800"] + }, + "rinseCycle": { + "raw": "933F", + "default": "3", + "options": ["0", "1", "2", "3", "4", "5"] + }, + "waterTemperature": { + "raw": "831E", + "default": "30", + "options": ["cold", "20", "30", "40"] + } + } + }, + { + "cycle": "21", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "3500", + "default": "off", + "options": [] + }, + "spinLevel": { + "raw": "A57F", + "default": "1200", + "options": [ + "rinseHold", + "noSpin", + "400", + "800", + "1000", + "1200", + "1400" + ] + }, + "rinseCycle": { + "raw": "943F", + "default": "4", + "options": ["0", "1", "2", "3", "4", "5"] + }, + "waterTemperature": { + "raw": "841E", + "default": "40", + "options": ["cold", "20", "30", "40"] + } + } + }, + { + "cycle": "2A", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "35F0", + "default": "off", + "options": ["on", "off"] + }, + "spinLevel": { + "raw": "A30F", + "default": "800", + "options": ["rinseHold", "noSpin", "400", "800"] + }, + "rinseCycle": { + "raw": "933F", + "default": "3", + "options": ["0", "1", "2", "3", "4", "5"] + }, + "waterTemperature": { + "raw": "831E", + "default": "30", + "options": ["cold", "20", "30", "40"] + } + } + }, + { + "cycle": "2E", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "35F0", + "default": "off", + "options": ["on", "off"] + }, + "spinLevel": { + "raw": "A67F", + "default": "1400", + "options": [ + "rinseHold", + "noSpin", + "400", + "800", + "1000", + "1200", + "1400" + ] + }, + "rinseCycle": { + "raw": "943F", + "default": "4", + "options": ["0", "1", "2", "3", "4", "5"] + }, + "waterTemperature": { + "raw": "867E", + "default": "90", + "options": ["cold", "20", "30", "40", "60", "90"] + } + } + }, + { + "cycle": "2D", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "35F0", + "default": "off", + "options": ["on", "off"] + }, + "spinLevel": { + "raw": "A30F", + "default": "800", + "options": ["rinseHold", "noSpin", "400", "800"] + }, + "rinseCycle": { + "raw": "923F", + "default": "2", + "options": ["0", "1", "2", "3", "4", "5"] + }, + "waterTemperature": { + "raw": "841E", + "default": "40", + "options": ["cold", "20", "30", "40"] + } + } + }, + { + "cycle": "30", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "35F0", + "default": "off", + "options": ["on", "off"] + }, + "spinLevel": { + "raw": "A67F", + "default": "1400", + "options": [ + "rinseHold", + "noSpin", + "400", + "800", + "1000", + "1200", + "1400" + ] + }, + "rinseCycle": { + "raw": "923F", + "default": "2", + "options": ["0", "1", "2", "3", "4", "5"] + }, + "waterTemperature": { + "raw": "843E", + "default": "40", + "options": ["cold", "20", "30", "40", "60"] + } + } + }, + { + "cycle": "27", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "3500", + "default": "off", + "options": [] + }, + "spinLevel": { + "raw": "A67F", + "default": "1400", + "options": [ + "rinseHold", + "noSpin", + "400", + "800", + "1000", + "1200", + "1400" + ] + }, + "rinseCycle": { + "raw": "913F", + "default": "1", + "options": ["0", "1", "2", "3", "4", "5"] + }, + "waterTemperature": { + "raw": "8000", + "default": "none", + "options": [] + } + } + }, + { + "cycle": "28", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "3500", + "default": "off", + "options": [] + }, + "spinLevel": { + "raw": "A67E", + "default": "1400", + "options": ["noSpin", "400", "800", "1000", "1200", "1400"] + }, + "rinseCycle": { + "raw": "9000", + "default": "0", + "options": [] + }, + "waterTemperature": { + "raw": "8000", + "default": "none", + "options": [] + } + } + }, + { + "cycle": "29", + "cycleType": "washingOnly", + "supportedOptions": { + "bubbleSoak": { + "raw": "3500", + "default": "off", + "options": [] + }, + "waterTemperature": { + "raw": "8520", + "default": "70", + "options": ["70"] + }, + "spinLevel": { + "raw": "A520", + "default": "1200", + "options": ["1200"] + }, + "rinseCycle": { + "raw": "9204", + "default": "2", + "options": ["2"] + } + } + } + ], + "timestamp": "2025-11-15T09:13:38.038Z" + }, + "washerCycle": { + "value": "Table_02_Course_1C", + "timestamp": "2025-11-15T14:38:01.810Z" + }, + "referenceTable": { + "value": { + "id": "Table_02" + }, + "timestamp": "2024-11-11T21:30:46.105Z" + }, + "specializedFunctionClassification": { + "value": 4, + "timestamp": "2025-11-15T13:19:14.326Z" + } + }, + "samsungce.waterConsumptionReport": { + "waterConsumption": { + "value": null + } + }, + "ocf": { + "st": { + "value": null + }, + "mndt": { + "value": null + }, + "mnfv": { + "value": "DA_WM_TP2_20_COMMON_30250414", + "timestamp": "2025-06-26T15:04:10.842Z" + }, + "mnhw": { + "value": "MediaTek", + "timestamp": "2025-06-26T15:04:10.842Z" + }, + "di": { + "value": "5d53b4f7-9b84-488b-8c12-8a5bb075772c", + "timestamp": "2025-06-26T15:04:10.842Z" + }, + "mnsl": { + "value": "http://www.samsung.com", + "timestamp": "2025-06-26T15:04:10.842Z" + }, + "dmv": { + "value": "1.2.1", + "timestamp": "2025-06-26T15:04:10.842Z" + }, + "n": { + "value": "[washer] Samsung", + "timestamp": "2025-06-26T15:04:10.842Z" + }, + "mnmo": { + "value": "DA_WM_TP2_20_COMMON|20269741|200101020014113442A3020700000000", + "timestamp": "2025-06-26T15:04:10.842Z" + }, + "vid": { + "value": "DA-WM-WM-000001", + "timestamp": "2025-06-26T15:04:10.842Z" + }, + "mnmn": { + "value": "Samsung Electronics", + "timestamp": "2025-06-26T15:04:10.842Z" + }, + "mnml": { + "value": "http://www.samsung.com", + "timestamp": "2025-06-26T15:04:10.842Z" + }, + "mnpv": { + "value": "DAWIT 2.0", + "timestamp": "2025-06-26T15:04:10.842Z" + }, + "mnos": { + "value": "TizenRT 2.0 + IPv6", + "timestamp": "2025-06-26T15:04:10.842Z" + }, + "pi": { + "value": "5d53b4f7-9b84-488b-8c12-8a5bb075772c", + "timestamp": "2025-06-26T15:04:10.842Z" + }, + "icv": { + "value": "core.1.1.0", + "timestamp": "2025-06-26T15:04:10.842Z" + } + }, + "custom.dryerDryLevel": { + "dryerDryLevel": { + "value": null + }, + "supportedDryerDryLevel": { + "value": null + } + }, + "custom.disabledCapabilities": { + "disabledCapabilities": { + "value": [ + "samsungce.autoDispenseDetergent", + "samsungce.autoDispenseSoftener", + "samsungce.waterConsumptionReport", + "samsungce.washerCyclePreset", + "samsungce.welcomeMessage", + "samsungce.dongleSoftwareInstallation", + "sec.wifiConfiguration", + "samsungce.quickControl", + "samsungce.energyPlanner", + "samsungce.audioVolumeLevel", + "samsungce.washerFreezePrevent", + "custom.dryerDryLevel", + "custom.washerSoilLevel", + "samsungce.washerWaterLevel", + "samsungce.washerWaterValve", + "samsungce.washerWashingTime", + "custom.washerAutoDetergent", + "custom.washerAutoSoftener" + ], + "timestamp": "2025-11-07T16:38:23.764Z" + } + }, + "samsungce.driverVersion": { + "versionNumber": { + "value": 25090101, + "timestamp": "2025-11-13T01:54:20.521Z" + } + }, + "sec.diagnosticsInformation": { + "logType": { + "value": ["errCode", "dump"], + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "endpoint": { + "value": "SSM", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "minVersion": { + "value": "1.0", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "signinPermission": { + "value": null + }, + "setupId": { + "value": "210", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "protocolType": { + "value": "wifi_https", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "tsId": { + "value": null + }, + "mnId": { + "value": "0AJT", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "dumpType": { + "value": "file", + "timestamp": "2025-11-15T13:19:14.326Z" + } + }, + "samsungce.washerOperatingState": { + "washerJobState": { + "value": "none", + "timestamp": "2025-11-15T14:38:01.985Z" + }, + "operatingState": { + "value": "ready", + "timestamp": "2025-11-15T14:38:01.985Z" + }, + "supportedOperatingStates": { + "value": ["ready", "running", "paused"], + "timestamp": "2024-11-11T21:30:45.982Z" + }, + "scheduledJobs": { + "value": [ + { + "jobName": "wash", + "timeInMin": 100 + }, + { + "jobName": "rinse", + "timeInMin": 29 + }, + { + "jobName": "spin", + "timeInMin": 15 + } + ], + "timestamp": "2025-11-15T12:07:25.968Z" + }, + "scheduledPhases": { + "value": [ + { + "phaseName": "wash", + "timeInMin": 100 + }, + { + "phaseName": "rinse", + "timeInMin": 29 + }, + { + "phaseName": "spin", + "timeInMin": 15 + } + ], + "timestamp": "2025-11-15T12:07:25.968Z" + }, + "progress": { + "value": 1, + "unit": "%", + "timestamp": "2025-11-15T14:38:01.985Z" + }, + "remainingTimeStr": { + "value": "02:38", + "timestamp": "2025-11-15T14:38:01.985Z" + }, + "washerJobPhase": { + "value": "none", + "timestamp": "2025-11-15T14:38:01.985Z" + }, + "operationTime": { + "value": 158, + "unit": "min", + "timestamp": "2025-11-15T14:38:01.973Z" + }, + "remainingTime": { + "value": 158, + "unit": "min", + "timestamp": "2025-11-15T14:38:01.985Z" + } + }, + "samsungce.kidsLock": { + "lockState": { + "value": "unlocked", + "timestamp": "2025-11-15T09:13:30.192Z" + } + }, + "demandResponseLoadControl": { + "drlcStatus": { + "value": { + "drlcType": 1, + "drlcLevel": 0, + "start": "1970-01-01T00:00:00Z", + "duration": 0, + "override": false + }, + "timestamp": "2025-11-15T13:19:14.326Z" + } + }, + "powerConsumptionReport": { + "powerConsumption": { + "value": { + "energy": 109400, + "deltaEnergy": 0, + "power": 0, + "powerEnergy": 0.0, + "persistedEnergy": 0, + "energySaved": 0, + "persistedSavedEnergy": 0, + "start": "2025-11-15T14:37:32Z", + "end": "2025-11-15T14:38:02Z" + }, + "timestamp": "2025-11-15T14:38:02.360Z" + } + }, + "samsungce.softenerOrder": { + "alarmEnabled": { + "value": false, + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "orderThreshold": { + "value": 0, + "unit": "cc", + "timestamp": "2025-11-15T13:19:14.326Z" + } + }, + "custom.washerSoilLevel": { + "supportedWasherSoilLevel": { + "value": null + }, + "washerSoilLevel": { + "value": null + } + }, + "samsungce.washerBubbleSoak": { + "status": { + "value": "off", + "timestamp": "2025-11-15T13:19:14.326Z" + } + }, + "execute": { + "data": { + "value": null + } + }, + "samsungce.softenerState": { + "remainingAmount": { + "value": 0, + "unit": "cc", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "dosage": { + "value": 0, + "unit": "cc", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "softenerType": { + "value": "none", + "timestamp": "2024-11-11T21:30:46.241Z" + }, + "initialAmount": { + "value": 0, + "unit": "cc", + "timestamp": "2025-11-15T13:19:14.326Z" + } + }, + "samsungce.energyPlanner": { + "data": { + "value": null + }, + "plan": { + "value": null + } + }, + "sec.wifiConfiguration": { + "autoReconnection": { + "value": null + }, + "minVersion": { + "value": null + }, + "supportedWiFiFreq": { + "value": null + }, + "supportedAuthType": { + "value": null + }, + "protocolType": { + "value": null + } + }, + "samsungce.softwareVersion": { + "versions": { + "value": [ + { + "id": "0", + "swType": "Software", + "versionNumber": "02674A250414(F546)", + "description": "DA_WM_TP2_20_COMMON|20269741|200101020014113442A3020700000000" + }, + { + "id": "1", + "swType": "Firmware", + "versionNumber": "23070454,23112384", + "description": "Firmware_1_DB_20269741230704540FFFFF202613412311238404FFFF(01672026974120261341_30000000)(FileDown:0)(Type:0)" + }, + { + "id": "2", + "swType": "Firmware", + "versionNumber": "22042101,FFFFFFFF", + "description": "Firmware_2_DB_2025984622042101042FFFFFFFFFFFFFFFFFFFFFFFFE(016720259846FFFFFFFF_30000000)(FileDown:0)(Type:0)" + } + ], + "timestamp": "2025-11-15T13:19:14.326Z" + } + }, + "remoteControlStatus": { + "remoteControlEnabled": { + "value": "false", + "timestamp": "2025-11-15T13:19:14.326Z" + } + }, + "custom.supportedOptions": { + "course": { + "value": "1C", + "timestamp": "2025-11-15T14:38:01.972Z" + }, + "referenceTable": { + "value": { + "id": "Table_02" + }, + "timestamp": "2025-11-15T12:07:20.922Z" + }, + "supportedCourses": { + "value": [ + "1C", + "1B", + "1E", + "1D", + "96", + "1F", + "25", + "26", + "33", + "24", + "32", + "20", + "22", + "23", + "2F", + "21", + "2A", + "2E", + "2D", + "30", + "27", + "28", + "29" + ], + "timestamp": "2025-11-15T09:13:38.038Z" + } + }, + "samsungce.washerWashingTime": { + "supportedWashingTimes": { + "value": null + }, + "washingTime": { + "value": null + } + }, + "custom.energyType": { + "energyType": { + "value": "2.0", + "timestamp": "2024-11-11T21:30:44.821Z" + }, + "energySavingSupport": { + "value": true, + "timestamp": "2024-11-11T21:30:47.917Z" + }, + "drMaxDuration": { + "value": 99999999, + "unit": "min", + "timestamp": "2024-11-11T21:30:46.191Z" + }, + "energySavingLevel": { + "value": null + }, + "energySavingInfo": { + "value": null + }, + "supportedEnergySavingLevels": { + "value": null + }, + "energySavingOperation": { + "value": false, + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "notificationTemplateID": { + "value": null + }, + "energySavingOperationSupport": { + "value": true, + "timestamp": "2024-11-11T21:30:46.191Z" + } + }, + "samsungce.softwareUpdate": { + "targetModule": { + "value": {}, + "timestamp": "2025-11-13T05:16:31.135Z" + }, + "otnDUID": { + "value": "U7CFUCFWUS4EM", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "lastUpdatedDate": { + "value": null + }, + "availableModules": { + "value": [], + "timestamp": "2025-11-13T05:16:31.135Z" + }, + "newVersionAvailable": { + "value": false, + "timestamp": "2025-11-13T05:16:31.135Z" + }, + "operatingState": { + "value": null + }, + "progress": { + "value": null + } + }, + "custom.washerSpinLevel": { + "washerSpinLevel": { + "value": "1400", + "timestamp": "2025-11-15T14:38:01.974Z" + }, + "supportedWasherSpinLevel": { + "value": [ + "rinseHold", + "noSpin", + "400", + "800", + "1000", + "1200", + "1400" + ], + "timestamp": "2025-11-15T13:19:14.326Z" + } + }, + "samsungce.washerDelayEnd": { + "remainingTime": { + "value": 0, + "unit": "min", + "timestamp": "2025-11-15T09:13:30.146Z" + }, + "minimumReservableTime": { + "value": 158, + "unit": "min", + "timestamp": "2025-11-15T14:38:01.973Z" + } + }, + "samsungce.welcomeMessage": { + "welcomeMessage": { + "value": null + } + }, + "samsungce.dongleSoftwareInstallation": { + "status": { + "value": "completed", + "timestamp": "2024-11-11T21:30:44.821Z" + } + }, + "samsungce.deviceIdentification": { + "micomAssayCode": { + "value": "20269741", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "modelName": { + "value": null + }, + "serialNumber": { + "value": null + }, + "serialNumberExtra": { + "value": null + }, + "modelClassificationCode": { + "value": "200101020014113442A3020700000000", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "description": { + "value": "DA_WM_TP2_20_COMMON_WW90T65/DC92-02613A_5069", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "releaseYear": { + "value": 20, + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "binaryId": { + "value": "DA_WM_TP2_20_COMMON", + "timestamp": "2025-11-15T13:57:03.198Z" + } + }, + "switch": { + "switch": { + "value": "off", + "timestamp": "2025-11-15T14:38:01.759Z" + } + }, + "samsungce.washerFreezePrevent": { + "operatingState": { + "value": null + } + }, + "samsungce.quickControl": { + "version": { + "value": null + } + }, + "samsungce.audioVolumeLevel": { + "volumeLevel": { + "value": null + }, + "volumeLevelRange": { + "value": null + } + }, + "custom.washerRinseCycles": { + "supportedWasherRinseCycles": { + "value": ["0", "1", "2", "3", "4", "5"], + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "washerRinseCycles": { + "value": "2", + "timestamp": "2025-11-15T12:07:22.548Z" + } + }, + "samsungce.detergentOrder": { + "alarmEnabled": { + "value": false, + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "orderThreshold": { + "value": 5, + "unit": "ea", + "timestamp": "2025-11-15T13:19:14.326Z" + } + }, + "samsungce.detergentAutoReplenishment": { + "neutralDetergentType": { + "value": null + }, + "regularDetergentRemainingAmount": { + "value": 0, + "unit": "ea", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "babyDetergentRemainingAmount": { + "value": null + }, + "neutralDetergentRemainingAmount": { + "value": null + }, + "neutralDetergentAlarmEnabled": { + "value": null + }, + "neutralDetergentOrderThreshold": { + "value": null + }, + "babyDetergentInitialAmount": { + "value": null + }, + "babyDetergentType": { + "value": null + }, + "neutralDetergentInitialAmount": { + "value": null + }, + "regularDetergentDosage": { + "value": 1, + "unit": "ea", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "babyDetergentDosage": { + "value": null + }, + "regularDetergentOrderThreshold": { + "value": 5, + "unit": "ea", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "regularDetergentType": { + "value": "capsule", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "regularDetergentInitialAmount": { + "value": 0, + "unit": "ea", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "regularDetergentAlarmEnabled": { + "value": false, + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "neutralDetergentDosage": { + "value": null + }, + "babyDetergentOrderThreshold": { + "value": null + }, + "babyDetergentAlarmEnabled": { + "value": null + } + }, + "samsungce.washerCyclePreset": { + "maxNumberOfPresets": { + "value": 10, + "timestamp": "2025-11-13T05:16:31.297Z" + }, + "presets": { + "value": null + } + }, + "samsungce.detergentState": { + "remainingAmount": { + "value": 0, + "unit": "ea", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "dosage": { + "value": 1, + "unit": "ea", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "initialAmount": { + "value": 0, + "unit": "ea", + "timestamp": "2025-11-15T13:19:14.326Z" + }, + "detergentType": { + "value": "capsule", + "timestamp": "2025-03-18T01:21:06.682Z" + } + }, + "refresh": {}, + "custom.jobBeginningStatus": { + "jobBeginningStatus": { + "value": "None", + "timestamp": "2025-11-15T13:19:14.326Z" + } + }, + "custom.washerAutoDetergent": { + "washerAutoDetergent": { + "value": null + } + } + } + } +} diff --git a/tests/components/smartthings/fixtures/device_status/da_wm_dw_01011.json b/tests/components/smartthings/fixtures/device_status/da_wm_dw_01011.json new file mode 100644 index 00000000000000..3dc1989defaab7 --- /dev/null +++ b/tests/components/smartthings/fixtures/device_status/da_wm_dw_01011.json @@ -0,0 +1,913 @@ +{ + "components": { + "main": { + "samsungce.dishwasherWashingCourse": { + "customCourseCandidates": { + "value": ["plastics", "babycare", "potsAndPans"], + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "washingCourse": { + "value": "eco", + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "supportedCourses": { + "value": [ + "auto", + "eco", + "intensive", + "delicate", + "express_0C", + "preWash", + "extraSilence", + "machineCare", + "plastics", + "babycare", + "potsAndPans" + ], + "timestamp": "2025-11-15T13:19:16.373Z" + } + }, + "dishwasherOperatingState": { + "completionTime": { + "value": "2025-11-15T17:51:16Z", + "timestamp": "2025-11-15T13:56:16.860Z" + }, + "machineState": { + "value": "stop", + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "progress": { + "value": null + }, + "supportedMachineStates": { + "value": ["stop", "run", "pause"], + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "dishwasherJobState": { + "value": "unknown", + "timestamp": "2025-11-15T13:19:16.373Z" + } + }, + "samsungce.dishwasherWashingOptions": { + "dryPlus": { + "value": null + }, + "stormWash": { + "value": null + }, + "multiTab": { + "value": null + }, + "hotAirDry": { + "value": null + }, + "selectedZone": { + "value": { + "value": "all", + "settable": ["lower", "all"] + }, + "timestamp": "2025-11-13T20:13:48.823Z" + }, + "speedBooster": { + "value": { + "value": false, + "settable": [false, true] + }, + "timestamp": "2024-11-11T21:50:17.345Z" + }, + "highTempWash": { + "value": null + }, + "sanitizingWash": { + "value": null + }, + "heatedDry": { + "value": null + }, + "zoneBooster": { + "value": null + }, + "addRinse": { + "value": null + }, + "supportedList": { + "value": ["selectedZone", "speedBooster", "sanitize"], + "timestamp": "2024-11-11T21:50:17.345Z" + }, + "rinsePlus": { + "value": null + }, + "sanitize": { + "value": { + "value": false, + "settable": [false, true] + }, + "timestamp": "2025-09-18T17:29:25.548Z" + }, + "steamSoak": { + "value": null + } + }, + "samsungce.deviceIdentification": { + "micomAssayCode": { + "value": "30008041", + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "modelName": { + "value": null + }, + "serialNumber": { + "value": null + }, + "serialNumberExtra": { + "value": null + }, + "modelClassificationCode": { + "value": "40000200001611004981000000200000", + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "description": { + "value": "DA_DW_TP1_21_COMMON_DW8700B/DD92-0008041_0001", + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "releaseYear": { + "value": 22, + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "binaryId": { + "value": "DA_DW_TP1_21_COMMON", + "timestamp": "2025-11-15T13:57:48.879Z" + } + }, + "custom.dishwasherOperatingProgress": { + "dishwasherOperatingProgress": { + "value": "none", + "timestamp": "2025-11-15T13:19:16.373Z" + } + }, + "switch": { + "switch": { + "value": "off", + "timestamp": "2025-11-15T13:57:48.878Z" + } + }, + "samsungce.quickControl": { + "version": { + "value": "1.0", + "timestamp": "2025-11-15T13:19:16.372Z" + } + }, + "samsungce.waterConsumptionReport": { + "waterConsumption": { + "value": { + "cumulativeAmount": 1336200, + "delta": 0, + "start": "2025-11-15T13:56:40Z", + "end": "2025-11-15T13:57:48Z" + }, + "timestamp": "2025-11-15T13:57:48.880Z" + } + }, + "ocf": { + "st": { + "value": null + }, + "mndt": { + "value": null + }, + "mnfv": { + "value": "DA_DW_TP1_21_COMMON_30250513", + "timestamp": "2025-07-26T13:20:15.202Z" + }, + "mnhw": { + "value": "Realtek", + "timestamp": "2025-07-26T13:20:15.202Z" + }, + "di": { + "value": "7ff318f3-3772-524d-3c9f-72fcd26413ed", + "timestamp": "2025-07-26T13:20:15.202Z" + }, + "mnsl": { + "value": "http://www.samsung.com", + "timestamp": "2025-07-26T13:20:15.202Z" + }, + "dmv": { + "value": "res.1.1.0,sh.1.1.0", + "timestamp": "2025-07-26T13:20:15.202Z" + }, + "n": { + "value": "[dishwasher] Samsung", + "timestamp": "2025-07-26T13:20:15.202Z" + }, + "mnmo": { + "value": "DA_DW_TP1_21_COMMON|30008041|40000200001611004981000000200000", + "timestamp": "2025-07-26T13:20:15.202Z" + }, + "vid": { + "value": "DA-WM-DW-01011", + "timestamp": "2025-07-26T13:20:15.202Z" + }, + "mnmn": { + "value": "Samsung Electronics", + "timestamp": "2025-07-26T13:20:15.202Z" + }, + "mnml": { + "value": "http://www.samsung.com", + "timestamp": "2025-07-26T13:20:15.202Z" + }, + "mnpv": { + "value": "DAWIT 2.0", + "timestamp": "2025-07-26T13:20:15.202Z" + }, + "mnos": { + "value": "TizenRT 3.1", + "timestamp": "2025-07-26T13:20:15.202Z" + }, + "pi": { + "value": "7ff318f3-3772-524d-3c9f-72fcd26413ed", + "timestamp": "2025-07-26T13:20:15.202Z" + }, + "icv": { + "value": "core.1.1.0", + "timestamp": "2025-07-26T13:20:15.202Z" + } + }, + "samsungce.audioVolumeLevel": { + "volumeLevel": { + "value": 1, + "timestamp": "2025-11-15T13:32:41.326Z" + }, + "volumeLevelRange": { + "value": { + "minimum": 0, + "maximum": 1, + "step": 1 + }, + "data": {}, + "timestamp": "2025-11-15T13:18:42.258Z" + } + }, + "custom.disabledCapabilities": { + "disabledCapabilities": { + "value": [ + "samsungce.operationOrigin", + "samsungce.energyPlanner", + "samsungce.autoOpenDoor", + "custom.waterFilter" + ], + "timestamp": "2025-11-14T16:38:15.901Z" + } + }, + "samsungce.driverVersion": { + "versionNumber": { + "value": 25090101, + "timestamp": "2025-09-22T12:24:31.169Z" + } + }, + "sec.diagnosticsInformation": { + "logType": { + "value": ["errCode", "dump"], + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "endpoint": { + "value": "SSM", + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "minVersion": { + "value": "3.0", + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "signinPermission": { + "value": null + }, + "setupId": { + "value": "WD0", + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "protocolType": { + "value": "ble_ocf", + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "tsId": { + "value": "DA01", + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "mnId": { + "value": "0AJT", + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "dumpType": { + "value": "file", + "timestamp": "2025-11-15T13:19:16.373Z" + } + }, + "samsungce.dishwasherOperation": { + "supportedOperatingState": { + "value": ["ready", "running", "paused"], + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "operatingState": { + "value": "ready", + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "reservable": { + "value": false, + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "progressPercentage": { + "value": 1, + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "remainingTimeStr": { + "value": "03:55", + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "operationTime": { + "value": null + }, + "remainingTime": { + "value": 235.0, + "unit": "min", + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "timeLeftToStart": { + "value": 0.0, + "unit": "min", + "timestamp": "2025-11-15T13:19:16.373Z" + } + }, + "samsungce.dishwasherJobState": { + "scheduledJobs": { + "value": [ + { + "jobName": "washing", + "timeInSec": 6660 + }, + { + "jobName": "rinsing", + "timeInSec": 1990 + }, + { + "jobName": "drying", + "timeInSec": 5420 + } + ], + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "dishwasherJobState": { + "value": "none", + "timestamp": "2025-11-15T13:19:16.373Z" + } + }, + "samsungce.kidsLock": { + "lockState": { + "value": "unlocked", + "timestamp": "2025-11-15T13:19:16.373Z" + } + }, + "demandResponseLoadControl": { + "drlcStatus": { + "value": { + "drlcType": 1, + "drlcLevel": 0, + "start": "1970-01-01T00:00:00Z", + "duration": 0, + "override": false + }, + "timestamp": "2025-11-15T13:19:16.373Z" + } + }, + "powerConsumptionReport": { + "powerConsumption": { + "value": { + "energy": 98300, + "deltaEnergy": 0, + "power": 0, + "powerEnergy": 0.0, + "persistedEnergy": 0, + "energySaved": 0, + "persistedSavedEnergy": 0, + "start": "2025-11-15T13:56:40Z", + "end": "2025-11-15T13:57:48Z" + }, + "timestamp": "2025-11-15T13:57:48.880Z" + } + }, + "refresh": {}, + "samsungce.dishwasherWashingCourseDetails": { + "predefinedCourses": { + "value": [ + { + "courseName": "auto", + "energyUsage": 4, + "waterUsage": 4, + "temperature": { + "min": 55, + "max": 62, + "unit": "C" + }, + "expectedTime": { + "time": 155, + "unit": "min" + }, + "options": { + "sanitize": { + "default": false, + "settable": [false, true] + }, + "speedBooster": { + "default": false, + "settable": [false, true] + }, + "selectedZone": { + "default": "all", + "settable": ["none", "lower", "all"] + } + } + }, + { + "courseName": "eco", + "energyUsage": 2, + "waterUsage": 2, + "temperature": { + "min": 49, + "max": 49, + "unit": "C" + }, + "expectedTime": { + "time": 235, + "unit": "min" + }, + "options": { + "sanitize": { + "default": false, + "settable": [false, true] + }, + "speedBooster": { + "default": false, + "settable": [false, true] + }, + "selectedZone": { + "default": "all", + "settable": ["none", "lower", "all"] + } + } + }, + { + "courseName": "intensive", + "energyUsage": 5, + "waterUsage": 5, + "temperature": { + "min": 65, + "max": 65, + "unit": "C" + }, + "expectedTime": { + "time": 180, + "unit": "min" + }, + "options": { + "sanitize": { + "default": false, + "settable": [false, true] + }, + "speedBooster": { + "default": false, + "settable": [false, true] + }, + "selectedZone": { + "default": "all", + "settable": ["none", "lower", "all"] + } + } + }, + { + "courseName": "delicate", + "energyUsage": 2, + "waterUsage": 3, + "temperature": { + "min": 50, + "max": 50, + "unit": "C" + }, + "expectedTime": { + "time": 117, + "unit": "min" + }, + "options": { + "sanitize": { + "default": false, + "settable": [] + }, + "speedBooster": { + "default": false, + "settable": [false, true] + }, + "selectedZone": { + "default": "all", + "settable": ["none", "lower", "all"] + } + } + }, + { + "courseName": "express_0C", + "energyUsage": 3, + "waterUsage": 2, + "temperature": { + "min": 63, + "max": 63, + "unit": "C" + }, + "expectedTime": { + "time": 78, + "unit": "min" + }, + "options": { + "sanitize": { + "default": false, + "settable": [] + }, + "speedBooster": { + "default": false, + "settable": [] + }, + "selectedZone": { + "default": "all", + "settable": ["none", "lower", "all"] + } + } + }, + { + "courseName": "preWash", + "energyUsage": 1, + "waterUsage": 1, + "expectedTime": { + "time": 24, + "unit": "min" + }, + "options": { + "sanitize": { + "default": false, + "settable": [] + }, + "speedBooster": { + "default": false, + "settable": [] + }, + "selectedZone": { + "default": "all", + "settable": ["none", "all"] + } + } + }, + { + "courseName": "extraSilence", + "energyUsage": 3, + "waterUsage": 4, + "temperature": { + "min": 60, + "max": 60, + "unit": "C" + }, + "expectedTime": { + "time": 263, + "unit": "min" + }, + "options": { + "sanitize": { + "default": false, + "settable": [false, true] + }, + "speedBooster": { + "default": false, + "settable": [false, true] + }, + "selectedZone": { + "default": "all", + "settable": ["none", "lower", "all"] + } + } + }, + { + "courseName": "machineCare", + "energyUsage": 5, + "waterUsage": 4, + "temperature": { + "min": 70, + "max": 70, + "unit": "C" + }, + "expectedTime": { + "time": 136, + "unit": "min" + }, + "options": { + "sanitize": { + "default": false, + "settable": [] + }, + "speedBooster": { + "default": false, + "settable": [] + }, + "selectedZone": { + "default": "all", + "settable": ["none", "all"] + } + } + }, + { + "courseName": "plastics", + "energyUsage": 3, + "waterUsage": 3, + "temperature": { + "min": 60, + "max": 60, + "unit": "C" + }, + "expectedTime": { + "time": 97, + "unit": "min" + }, + "options": { + "sanitize": { + "default": false, + "settable": [] + }, + "speedBooster": { + "default": false, + "settable": [] + }, + "selectedZone": { + "default": "all", + "settable": ["none", "lower", "all"] + } + } + }, + { + "courseName": "babycare", + "energyUsage": 3, + "waterUsage": 1, + "temperature": { + "min": 75, + "max": 75, + "unit": "C" + }, + "expectedTime": { + "time": 68, + "unit": "min" + }, + "options": { + "sanitize": { + "default": false, + "settable": [] + }, + "speedBooster": { + "default": false, + "settable": [] + }, + "selectedZone": { + "default": "all", + "settable": ["none", "all"] + } + } + }, + { + "courseName": "potsAndPans", + "energyUsage": 5, + "waterUsage": 5, + "temperature": { + "min": 68, + "max": 68, + "unit": "C" + }, + "expectedTime": { + "time": 151, + "unit": "min" + }, + "options": { + "sanitize": { + "default": false, + "settable": [false, true] + }, + "speedBooster": { + "default": false, + "settable": [false, true] + }, + "selectedZone": { + "default": "all", + "settable": ["none", "lower", "all"] + } + } + } + ], + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "waterUsageMax": { + "value": 5, + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "energyUsageMax": { + "value": 5, + "timestamp": "2025-11-15T13:19:16.373Z" + } + }, + "execute": { + "data": { + "value": null + } + }, + "samsungce.energyPlanner": { + "data": { + "value": null + }, + "plan": { + "value": null + } + }, + "sec.wifiConfiguration": { + "autoReconnection": { + "value": true, + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "minVersion": { + "value": "1.0", + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "supportedWiFiFreq": { + "value": ["2.4G"], + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "supportedAuthType": { + "value": ["OPEN", "WEP", "WPA-PSK", "WPA2-PSK", "SAE"], + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "protocolType": { + "value": ["helper_hotspot", "ble_ocf"], + "timestamp": "2025-11-15T13:19:16.373Z" + } + }, + "custom.dishwasherOperatingPercentage": { + "dishwasherOperatingPercentage": { + "value": 1, + "timestamp": "2025-11-15T13:19:16.373Z" + } + }, + "samsungce.softwareVersion": { + "versions": { + "value": [ + { + "id": "0", + "swType": "Software", + "versionNumber": "00081A250513(A235)", + "description": "DA_DW_TP1_21_COMMON|30008041|40000200001611004981000000200000" + }, + { + "id": "1", + "swType": "Firmware", + "versionNumber": "00080A23120568,FFFFFFFFFFFFFF", + "description": "Firmware_1_DB_30008041231205682FFFFFFFFFFFFFFFFFFFFFFFFFFE(081330008041FFFFFFFF_30000000)(FileDown:0)(Type:0)" + } + ], + "timestamp": "2025-11-15T13:19:16.373Z" + } + }, + "remoteControlStatus": { + "remoteControlEnabled": { + "value": "false", + "timestamp": "2025-11-15T13:19:16.373Z" + } + }, + "custom.supportedOptions": { + "course": { + "value": "8A", + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "referenceTable": { + "value": null + }, + "supportedCourses": { + "value": [ + "82", + "8A", + "A7", + "85", + "0C", + "01", + "8C", + "0D", + "8E", + "8F", + "8D" + ], + "timestamp": "2025-11-15T13:19:16.373Z" + } + }, + "custom.dishwasherDelayStartTime": { + "dishwasherDelayStartTime": { + "value": "00:00:00", + "timestamp": "2025-11-15T13:19:16.373Z" + } + }, + "samsungce.operationOrigin": {}, + "custom.energyType": { + "energyType": { + "value": "2.0", + "timestamp": "2024-11-11T21:50:17.345Z" + }, + "energySavingSupport": { + "value": true, + "timestamp": "2024-11-11T21:50:20.186Z" + }, + "drMaxDuration": { + "value": 99999999, + "unit": "min", + "timestamp": "2024-11-11T21:50:17.345Z" + }, + "energySavingLevel": { + "value": null + }, + "energySavingInfo": { + "value": null + }, + "supportedEnergySavingLevels": { + "value": null + }, + "energySavingOperation": { + "value": false, + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "notificationTemplateID": { + "value": null + }, + "energySavingOperationSupport": { + "value": true, + "timestamp": "2024-11-11T21:50:17.345Z" + } + }, + "samsungce.autoOpenDoor": { + "autoOpenDoor": { + "value": null + }, + "supportedPressureLevels": { + "value": null + }, + "pressureLevel": { + "value": null + } + }, + "samsungce.softwareUpdate": { + "targetModule": { + "value": { + "newVersion": "00000000", + "currentVersion": "00000000", + "moduleType": "mainController" + }, + "timestamp": "2025-11-13T05:16:30.733Z" + }, + "otnDUID": { + "value": "CPCB2ZD47AETC", + "timestamp": "2025-11-15T13:19:16.373Z" + }, + "lastUpdatedDate": { + "value": null + }, + "availableModules": { + "value": [], + "timestamp": "2025-11-13T05:16:30.733Z" + }, + "newVersionAvailable": { + "value": false, + "timestamp": "2025-11-13T05:16:30.733Z" + }, + "operatingState": { + "value": "none", + "timestamp": "2025-11-13T05:16:30.733Z" + }, + "progress": { + "value": null + } + }, + "custom.waterFilter": { + "waterFilterUsageStep": { + "value": null + }, + "waterFilterResetType": { + "value": null + }, + "waterFilterCapacity": { + "value": null + }, + "waterFilterLastResetDate": { + "value": null + }, + "waterFilterUsage": { + "value": null + }, + "waterFilterStatus": { + "value": null + } + } + } + } +} diff --git a/tests/components/smartthings/fixtures/devices/da_ks_oven_0107x.json b/tests/components/smartthings/fixtures/devices/da_ks_oven_0107x.json new file mode 100644 index 00000000000000..fc1cce9854cad5 --- /dev/null +++ b/tests/components/smartthings/fixtures/devices/da_ks_oven_0107x.json @@ -0,0 +1,238 @@ +{ + "items": [ + { + "deviceId": "199d7863-ad04-793d-176d-658f10062575", + "name": "Samsung Oven", + "label": "Kitchen oven", + "manufacturerName": "Samsung Electronics", + "presentationId": "DA-KS-OVEN-0107X", + "deviceManufacturerCode": "Samsung Electronics", + "locationId": "beea82e4-8799-4757-ac70-591d76b2b248", + "ownerId": "6feed944-1d03-0a9f-3262-060ec4f9797d", + "roomId": "f8a4ac44-7f5d-4a73-a6be-189076ec0307", + "deviceTypeName": "Samsung OCF Oven", + "components": [ + { + "id": "main", + "label": "main", + "capabilities": [ + { + "id": "ocf", + "version": 1 + }, + { + "id": "execute", + "version": 1 + }, + { + "id": "refresh", + "version": 1 + }, + { + "id": "remoteControlStatus", + "version": 1 + }, + { + "id": "ovenSetpoint", + "version": 1 + }, + { + "id": "ovenMode", + "version": 1 + }, + { + "id": "ovenOperatingState", + "version": 1 + }, + { + "id": "temperatureMeasurement", + "version": 1 + }, + { + "id": "samsungce.doorState", + "version": 1 + }, + { + "id": "samsungce.definedRecipe", + "version": 1 + }, + { + "id": "samsungce.deviceIdentification", + "version": 1 + }, + { + "id": "samsungce.driverVersion", + "version": 1 + }, + { + "id": "samsungce.kitchenDeviceIdentification", + "version": 1 + }, + { + "id": "samsungce.kitchenDeviceDefaults", + "version": 1 + }, + { + "id": "samsungce.ovenMode", + "version": 1 + }, + { + "id": "samsungce.ovenOperatingState", + "version": 1 + }, + { + "id": "samsungce.meatProbe", + "version": 1 + }, + { + "id": "samsungce.lamp", + "version": 1 + }, + { + "id": "samsungce.kitchenModeSpecification", + "version": 1 + }, + { + "id": "samsungce.kidsLock", + "version": 1 + }, + { + "id": "samsungce.softwareUpdate", + "version": 1 + }, + { + "id": "samsungce.waterReservoir", + "version": 1 + }, + { + "id": "samsungce.ovenDrainageRequirement", + "version": 1 + }, + { + "id": "samsungce.quickControl", + "version": 1 + }, + { + "id": "custom.disabledCapabilities", + "version": 1 + }, + { + "id": "sec.diagnosticsInformation", + "version": 1 + }, + { + "id": "sec.wifiConfiguration", + "version": 1 + }, + { + "id": "sec.calmConnectionCare", + "version": 1 + }, + { + "id": "samsungce.softwareVersion", + "version": 1 + } + ], + "categories": [ + { + "name": "Oven", + "categoryType": "manufacturer" + } + ], + "optional": false + }, + { + "id": "cavity-01", + "label": "cavity-01", + "capabilities": [ + { + "id": "ovenSetpoint", + "version": 1 + }, + { + "id": "ovenMode", + "version": 1 + }, + { + "id": "ovenOperatingState", + "version": 1 + }, + { + "id": "temperatureMeasurement", + "version": 1 + }, + { + "id": "samsungce.ovenMode", + "version": 1 + }, + { + "id": "samsungce.ovenOperatingState", + "version": 1 + }, + { + "id": "samsungce.definedRecipe", + "version": 1 + }, + { + "id": "samsungce.kitchenDeviceDefaults", + "version": 1 + }, + { + "id": "custom.ovenCavityStatus", + "version": 1 + }, + { + "id": "custom.disabledCapabilities", + "version": 1 + } + ], + "categories": [ + { + "name": "Other", + "categoryType": "manufacturer" + } + ], + "optional": false + } + ], + "createTime": "2025-06-25T15:25:14.789Z", + "profile": { + "id": "5136fb87-83ab-3b57-a298-178a371d03c2" + }, + "ocf": { + "ocfDeviceType": "oic.d.oven", + "name": "Samsung Oven", + "specVersion": "core.1.1.0", + "verticalDomainSpecVersion": "1.2.1", + "manufacturerName": "Samsung Electronics", + "modelNumber": "TP1X_DA-KS-OVEN-0107X|40456741|50030018001611400A00000000000000", + "platformVersion": "DAWIT 3.0", + "platformOS": "TizenRT 3.1", + "hwVersion": "Realtek", + "firmwareVersion": "AKS-WW-TP1-22-OVEN-1_40250221", + "vendorId": "DA-KS-OVEN-0107X", + "vendorResourceClientServerVersion": "Realtek Release 3.1.240221", + "lastSignupTime": "2025-06-25T15:25:10.258442968Z", + "transferCandidate": false, + "additionalAuthCodeRequired": false + }, + "type": "OCF", + "restrictionTier": 0, + "allowed": [], + "indoorMap": { + "coordinates": [71.0, 40.0, 178.0], + "rotation": [0.0, 180.0, 0.0], + "visible": true, + "data": { + "expressionType": "both_2d_3d", + "lastHideTime": "0", + "lastUpdateTime": "1763171338982", + "lightingGroupId": "00000000-0000-0000-0000-000000000000" + } + }, + "executionContext": "CLOUD", + "relationships": [] + } + ], + "_links": {} +} diff --git a/tests/components/smartthings/fixtures/devices/da_wm_dw_01011.json b/tests/components/smartthings/fixtures/devices/da_wm_dw_01011.json new file mode 100644 index 00000000000000..bdbe33651c1908 --- /dev/null +++ b/tests/components/smartthings/fixtures/devices/da_wm_dw_01011.json @@ -0,0 +1,202 @@ +{ + "items": [ + { + "deviceId": "7ff318f3-3772-524d-3c9f-72fcd26413ed", + "name": "[dishwasher] Samsung", + "label": "Dishwasher", + "manufacturerName": "Samsung Electronics", + "presentationId": "DA-WM-DW-01011", + "deviceManufacturerCode": "Samsung Electronics", + "locationId": "beea82e4-8799-4757-ac70-591d76b2b248", + "ownerId": "6feed944-1d03-0a9f-3262-060ec4f9797d", + "roomId": "f8a4ac44-7f5d-4a73-a6be-189076ec0307", + "deviceTypeName": "Samsung OCF Dishwasher", + "components": [ + { + "id": "main", + "label": "main", + "capabilities": [ + { + "id": "execute", + "version": 1 + }, + { + "id": "ocf", + "version": 1 + }, + { + "id": "powerConsumptionReport", + "version": 1 + }, + { + "id": "refresh", + "version": 1 + }, + { + "id": "remoteControlStatus", + "version": 1 + }, + { + "id": "switch", + "version": 1 + }, + { + "id": "demandResponseLoadControl", + "version": 1 + }, + { + "id": "dishwasherOperatingState", + "version": 1 + }, + { + "id": "custom.disabledCapabilities", + "version": 1 + }, + { + "id": "custom.dishwasherOperatingProgress", + "version": 1 + }, + { + "id": "custom.dishwasherOperatingPercentage", + "version": 1 + }, + { + "id": "custom.dishwasherDelayStartTime", + "version": 1 + }, + { + "id": "custom.energyType", + "version": 1 + }, + { + "id": "custom.supportedOptions", + "version": 1 + }, + { + "id": "custom.waterFilter", + "version": 1 + }, + { + "id": "samsungce.audioVolumeLevel", + "version": 1 + }, + { + "id": "samsungce.autoOpenDoor", + "version": 1 + }, + { + "id": "samsungce.deviceIdentification", + "version": 1 + }, + { + "id": "samsungce.dishwasherJobState", + "version": 1 + }, + { + "id": "samsungce.dishwasherWashingCourse", + "version": 1 + }, + { + "id": "samsungce.dishwasherWashingCourseDetails", + "version": 1 + }, + { + "id": "samsungce.dishwasherOperation", + "version": 1 + }, + { + "id": "samsungce.dishwasherWashingOptions", + "version": 1 + }, + { + "id": "samsungce.driverVersion", + "version": 1 + }, + { + "id": "samsungce.energyPlanner", + "version": 1 + }, + { + "id": "samsungce.operationOrigin", + "version": 1 + }, + { + "id": "samsungce.softwareUpdate", + "version": 1 + }, + { + "id": "samsungce.kidsLock", + "version": 1 + }, + { + "id": "samsungce.waterConsumptionReport", + "version": 1 + }, + { + "id": "samsungce.quickControl", + "version": 1 + }, + { + "id": "samsungce.softwareVersion", + "version": 1 + }, + { + "id": "sec.diagnosticsInformation", + "version": 1 + }, + { + "id": "sec.wifiConfiguration", + "version": 1 + } + ], + "categories": [ + { + "name": "Dishwasher", + "categoryType": "manufacturer" + } + ], + "optional": false + } + ], + "createTime": "2024-11-11T21:50:12.084Z", + "profile": { + "id": "cdd89bbe-004a-37f9-8100-a211e45688f1" + }, + "ocf": { + "ocfDeviceType": "oic.d.dishwasher", + "name": "[dishwasher] Samsung", + "specVersion": "core.1.1.0", + "verticalDomainSpecVersion": "res.1.1.0,sh.1.1.0", + "manufacturerName": "Samsung Electronics", + "modelNumber": "DA_DW_TP1_21_COMMON|30008041|40000200001611004981000000200000", + "platformVersion": "DAWIT 2.0", + "platformOS": "TizenRT 3.1", + "hwVersion": "Realtek", + "firmwareVersion": "DA_DW_TP1_21_COMMON_30250513", + "vendorId": "DA-WM-DW-01011", + "vendorResourceClientServerVersion": "Realtek Release 3.1.240801", + "lastSignupTime": "2024-11-11T21:50:12.032414997Z", + "transferCandidate": false, + "additionalAuthCodeRequired": false, + "modelCode": "DW60BG850B00ET" + }, + "type": "OCF", + "restrictionTier": 0, + "allowed": [], + "indoorMap": { + "coordinates": [111.0, 0.0, 178.0], + "rotation": [0.0, 180.0, 0.0], + "visible": true, + "data": { + "expressionType": "both_2d_3d", + "lastHideTime": "0", + "lastUpdateTime": "1763171338982", + "lightingGroupId": "00000000-0000-0000-0000-000000000000" + } + }, + "executionContext": "CLOUD", + "relationships": [] + } + ], + "_links": {} +} diff --git a/tests/components/smartthings/snapshots/test_binary_sensor.ambr b/tests/components/smartthings/snapshots/test_binary_sensor.ambr index 392defbb2b32b6..417fd9b9bdd92a 100644 --- a/tests/components/smartthings/snapshots/test_binary_sensor.ambr +++ b/tests/components/smartthings/snapshots/test_binary_sensor.ambr @@ -632,6 +632,102 @@ 'state': 'on', }) # --- +# name: test_all_entities[da_ks_oven_0107x][binary_sensor.kitchen_oven_child_lock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.kitchen_oven_child_lock', + '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': 'Child lock', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'child_lock', + 'unique_id': '199d7863-ad04-793d-176d-658f10062575_main_samsungce.kidsLock_lockState_lockState', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][binary_sensor.kitchen_oven_child_lock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Kitchen oven Child lock', + }), + 'context': , + 'entity_id': 'binary_sensor.kitchen_oven_child_lock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][binary_sensor.kitchen_oven_remote_control-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.kitchen_oven_remote_control', + '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': 'Remote control', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'remote_control', + 'unique_id': '199d7863-ad04-793d-176d-658f10062575_main_remoteControlStatus_remoteControlEnabled_remoteControlEnabled', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][binary_sensor.kitchen_oven_remote_control-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Kitchen oven Remote control', + }), + 'context': , + 'entity_id': 'binary_sensor.kitchen_oven_remote_control', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_all_entities[da_ks_range_0101x][binary_sensor.vulcan_child_lock-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -1461,6 +1557,151 @@ 'state': 'off', }) # --- +# name: test_all_entities[da_wm_dw_01011][binary_sensor.dishwasher_child_lock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.dishwasher_child_lock', + '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': 'Child lock', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'child_lock', + 'unique_id': '7ff318f3-3772-524d-3c9f-72fcd26413ed_main_samsungce.kidsLock_lockState_lockState', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_wm_dw_01011][binary_sensor.dishwasher_child_lock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Dishwasher Child lock', + }), + 'context': , + 'entity_id': 'binary_sensor.dishwasher_child_lock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[da_wm_dw_01011][binary_sensor.dishwasher_power-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.dishwasher_power', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + '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': '7ff318f3-3772-524d-3c9f-72fcd26413ed_main_switch_switch_switch', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_wm_dw_01011][binary_sensor.dishwasher_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Dishwasher Power', + }), + 'context': , + 'entity_id': 'binary_sensor.dishwasher_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[da_wm_dw_01011][binary_sensor.dishwasher_remote_control-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': None, + 'entity_id': 'binary_sensor.dishwasher_remote_control', + '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': 'Remote control', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'remote_control', + 'unique_id': '7ff318f3-3772-524d-3c9f-72fcd26413ed_main_remoteControlStatus_remoteControlEnabled_remoteControlEnabled', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_wm_dw_01011][binary_sensor.dishwasher_remote_control-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Dishwasher Remote control', + }), + 'context': , + 'entity_id': 'binary_sensor.dishwasher_remote_control', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_all_entities[da_wm_sc_000001][binary_sensor.airdresser_child_lock-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/smartthings/snapshots/test_init.ambr b/tests/components/smartthings/snapshots/test_init.ambr index 69af874582dcf1..123918cb3b2f89 100644 --- a/tests/components/smartthings/snapshots/test_init.ambr +++ b/tests/components/smartthings/snapshots/test_init.ambr @@ -653,6 +653,37 @@ 'via_device_id': None, }) # --- +# name: test_devices[da_ks_oven_0107x] + 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': 'Realtek', + 'id': , + 'identifiers': set({ + tuple( + 'smartthings', + '199d7863-ad04-793d-176d-658f10062575', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Samsung Electronics', + 'model': 'TP1X_DA-KS-OVEN-0107X', + 'model_id': None, + 'name': 'Kitchen oven', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': 'AKS-WW-TP1-22-OVEN-1_40250221', + 'via_device_id': None, + }) +# --- # name: test_devices[da_ks_range_0101x] DeviceRegistryEntrySnapshot({ 'area_id': None, @@ -994,6 +1025,37 @@ 'via_device_id': None, }) # --- +# name: test_devices[da_wm_dw_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': 'Realtek', + 'id': , + 'identifiers': set({ + tuple( + 'smartthings', + '7ff318f3-3772-524d-3c9f-72fcd26413ed', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Samsung Electronics', + 'model': 'DA_DW_TP1_21_COMMON', + 'model_id': None, + 'name': 'Dishwasher', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': None, + 'sw_version': 'DA_DW_TP1_21_COMMON_30250513', + 'via_device_id': None, + }) +# --- # name: test_devices[da_wm_sc_000001] DeviceRegistryEntrySnapshot({ 'area_id': None, diff --git a/tests/components/smartthings/snapshots/test_number.ambr b/tests/components/smartthings/snapshots/test_number.ambr index b9af2605f1d8ed..62e5eed640c11a 100644 --- a/tests/components/smartthings/snapshots/test_number.ambr +++ b/tests/components/smartthings/snapshots/test_number.ambr @@ -56,6 +56,64 @@ 'state': '0', }) # --- +# name: test_all_entities[da_ks_oven_0107x][number.kitchen_oven_rinse_cycles-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 5, + 'min': 0, + 'mode': , + 'step': 1.0, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.kitchen_oven_rinse_cycles', + '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': 'Rinse cycles', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'washer_rinse_cycles', + 'unique_id': '199d7863-ad04-793d-176d-658f10062575_main_custom.washerRinseCycles_washerRinseCycles_washerRinseCycles', + 'unit_of_measurement': 'cycles', + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][number.kitchen_oven_rinse_cycles-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Kitchen oven Rinse cycles', + 'max': 5, + 'min': 0, + 'mode': , + 'step': 1.0, + 'unit_of_measurement': 'cycles', + }), + 'context': , + 'entity_id': 'number.kitchen_oven_rinse_cycles', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2', + }) +# --- # name: test_all_entities[da_ref_normal_000001][number.refrigerator_freezer_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/smartthings/snapshots/test_select.ambr b/tests/components/smartthings/snapshots/test_select.ambr index 16956195e2449b..118f81a6843425 100644 --- a/tests/components/smartthings/snapshots/test_select.ambr +++ b/tests/components/smartthings/snapshots/test_select.ambr @@ -115,6 +115,199 @@ 'state': 'high', }) # --- +# name: test_all_entities[da_ks_oven_0107x][select.kitchen_oven-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'stop', + 'run', + 'pause', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': None, + 'entity_id': 'select.kitchen_oven', + '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': 'operating_state', + 'unique_id': '199d7863-ad04-793d-176d-658f10062575_main_washerOperatingState_machineState_machineState', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][select.kitchen_oven-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Kitchen oven', + 'options': list([ + 'stop', + 'run', + 'pause', + ]), + }), + 'context': , + 'entity_id': 'select.kitchen_oven', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'stop', + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][select.kitchen_oven_spin_level-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'rinse_hold', + 'no_spin', + '400', + '800', + '1000', + '1200', + '1400', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': , + 'entity_id': 'select.kitchen_oven_spin_level', + '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': 'Spin level', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'spin_level', + 'unique_id': '199d7863-ad04-793d-176d-658f10062575_main_custom.washerSpinLevel_washerSpinLevel_washerSpinLevel', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][select.kitchen_oven_spin_level-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Kitchen oven Spin level', + 'options': list([ + 'rinse_hold', + 'no_spin', + '400', + '800', + '1000', + '1200', + '1400', + ]), + }), + 'context': , + 'entity_id': 'select.kitchen_oven_spin_level', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1400', + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][select.kitchen_oven_water_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'none', + 'cold', + '20', + '30', + '40', + '60', + '90', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': , + 'entity_id': 'select.kitchen_oven_water_temperature', + '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': 'Water temperature', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'water_temperature', + 'unique_id': '199d7863-ad04-793d-176d-658f10062575_main_custom.washerWaterTemperature_washerWaterTemperature_washerWaterTemperature', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][select.kitchen_oven_water_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Kitchen oven Water temperature', + 'options': list([ + 'none', + 'cold', + '20', + '30', + '40', + '60', + '90', + ]), + }), + 'context': , + 'entity_id': 'select.kitchen_oven_water_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'none', + }) +# --- # name: test_all_entities[da_ks_range_0101x][select.vulcan_lamp-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -288,6 +481,65 @@ 'state': 'stop', }) # --- +# name: test_all_entities[da_wm_dw_01011][select.dishwasher-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'stop', + 'run', + 'pause', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': None, + 'entity_id': 'select.dishwasher', + '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': 'operating_state', + 'unique_id': '7ff318f3-3772-524d-3c9f-72fcd26413ed_main_dishwasherOperatingState_machineState_machineState', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_wm_dw_01011][select.dishwasher-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Dishwasher', + 'options': list([ + 'stop', + 'run', + 'pause', + ]), + }), + 'context': , + 'entity_id': 'select.dishwasher', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'stop', + }) +# --- # name: test_all_entities[da_wm_sc_000001][select.airdresser-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -713,6 +965,71 @@ 'state': 'high', }) # --- +# name: test_all_entities[da_wm_wm_000001][select.washer_water_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'none', + 'tap_cold', + 'cold', + 'warm', + 'hot', + 'extra_hot', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': , + 'entity_id': 'select.washer_water_temperature', + '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': 'Water temperature', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'water_temperature', + 'unique_id': 'f984b91d-f250-9d42-3436-33f09a422a47_main_custom.washerWaterTemperature_washerWaterTemperature_washerWaterTemperature', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_wm_wm_000001][select.washer_water_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Washer Water temperature', + 'options': list([ + 'none', + 'tap_cold', + 'cold', + 'warm', + 'hot', + 'extra_hot', + ]), + }), + 'context': , + 'entity_id': 'select.washer_water_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'warm', + }) +# --- # name: test_all_entities[da_wm_wm_000001_1][select.washing_machine-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -839,6 +1156,73 @@ 'state': '1400', }) # --- +# name: test_all_entities[da_wm_wm_000001_1][select.washing_machine_water_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'none', + 'cold', + '20', + '30', + '40', + '60', + '90', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': , + 'entity_id': 'select.washing_machine_water_temperature', + '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': 'Water temperature', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'water_temperature', + 'unique_id': '63803fae-cbed-f356-a063-2cf148ae3ca7_main_custom.washerWaterTemperature_washerWaterTemperature_washerWaterTemperature', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_wm_wm_000001_1][select.washing_machine_water_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Washing Machine Water temperature', + 'options': list([ + 'none', + 'cold', + '20', + '30', + '40', + '60', + '90', + ]), + }), + 'context': , + 'entity_id': 'select.washing_machine_water_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '40', + }) +# --- # name: test_all_entities[da_wm_wm_01011][select.machine_a_laver-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -1087,6 +1471,73 @@ 'state': '1000', }) # --- +# name: test_all_entities[da_wm_wm_01011][select.machine_a_laver_water_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'none', + 'cold', + '20', + '30', + '40', + '60', + '90', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'select', + 'entity_category': , + 'entity_id': 'select.machine_a_laver_water_temperature', + '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': 'Water temperature', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'water_temperature', + 'unique_id': 'b854ca5f-dc54-140d-6349-758b4d973c41_main_custom.washerWaterTemperature_washerWaterTemperature_washerWaterTemperature', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_wm_wm_01011][select.machine_a_laver_water_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Machine à Laver Water temperature', + 'options': list([ + 'none', + 'cold', + '20', + '30', + '40', + '60', + '90', + ]), + }), + 'context': , + 'entity_id': 'select.machine_a_laver_water_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '40', + }) +# --- # name: test_all_entities[da_wm_wm_100001][select.washer-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/smartthings/snapshots/test_sensor.ambr b/tests/components/smartthings/snapshots/test_sensor.ambr index 2894f1b3193ecb..856515c8f14f2e 100644 --- a/tests/components/smartthings/snapshots/test_sensor.ambr +++ b/tests/components/smartthings/snapshots/test_sensor.ambr @@ -5178,7 +5178,7 @@ 'state': '30', }) # --- -# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_completion_time-entry] +# name: test_all_entities[da_ks_oven_0107x][sensor.kitchen_oven_completion_time-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -5191,7 +5191,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.vulcan_completion_time', + 'entity_id': 'sensor.kitchen_oven_completion_time', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -5209,48 +5209,215 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'completion_time', - 'unique_id': '2c3cbaa0-1899-5ddc-7b58-9d657bd48f18_main_ovenOperatingState_completionTime_completionTime', + 'unique_id': '199d7863-ad04-793d-176d-658f10062575_main_washerOperatingState_completionTime_completionTime', 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_completion_time-state] +# name: test_all_entities[da_ks_oven_0107x][sensor.kitchen_oven_completion_time-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'timestamp', - 'friendly_name': 'Vulcan Completion time', + 'friendly_name': 'Kitchen oven Completion time', }), 'context': , - 'entity_id': 'sensor.vulcan_completion_time', + 'entity_id': 'sensor.kitchen_oven_completion_time', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2025-03-14T03:23:28+00:00', + 'state': '2025-11-15T17:16:01+00:00', }) # --- -# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_job_state-entry] +# name: test_all_entities[da_ks_oven_0107x][sensor.kitchen_oven_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.kitchen_oven_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': '199d7863-ad04-793d-176d-658f10062575_main_powerConsumptionReport_powerConsumption_energy_meter', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][sensor.kitchen_oven_energy-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Kitchen oven Energy', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.kitchen_oven_energy', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '109.4', + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][sensor.kitchen_oven_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.kitchen_oven_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': '199d7863-ad04-793d-176d-658f10062575_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][sensor.kitchen_oven_energy_difference-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Kitchen oven Energy difference', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.kitchen_oven_energy_difference', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][sensor.kitchen_oven_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.kitchen_oven_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': '199d7863-ad04-793d-176d-658f10062575_main_powerConsumptionReport_powerConsumption_energySaved_meter', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][sensor.kitchen_oven_energy_saved-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Kitchen oven Energy saved', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.kitchen_oven_energy_saved', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][sensor.kitchen_oven_job_state-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ 'options': list([ - 'cleaning', - 'cooking', + 'air_wash', + 'ai_rinse', + 'ai_spin', + 'ai_wash', 'cooling', - 'draining', - 'preheat', - 'ready', - 'rinsing', - 'finished', - 'scheduled_start', - 'warming', - 'defrosting', - 'sensing', - 'searing', - 'fast_preheat', - 'scheduled_end', - 'stone_heating', - 'time_hold_preheat', + 'delay_wash', + 'drying', + 'finish', + 'none', + 'pre_wash', + 'rinse', + 'spin', + 'wash', + 'weight_sensing', + 'wrinkle_prevent', + 'freeze_protection', ]), }), 'config_entry_id': , @@ -5260,7 +5427,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.vulcan_job_state', + 'entity_id': 'sensor.kitchen_oven_job_state', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -5277,54 +5444,53 @@ 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'oven_job_state', - 'unique_id': '2c3cbaa0-1899-5ddc-7b58-9d657bd48f18_main_ovenOperatingState_ovenJobState_ovenJobState', + 'translation_key': 'washer_job_state', + 'unique_id': '199d7863-ad04-793d-176d-658f10062575_main_washerOperatingState_washerJobState_washerJobState', 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_job_state-state] +# name: test_all_entities[da_ks_oven_0107x][sensor.kitchen_oven_job_state-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'enum', - 'friendly_name': 'Vulcan Job state', + 'friendly_name': 'Kitchen oven Job state', 'options': list([ - 'cleaning', - 'cooking', + 'air_wash', + 'ai_rinse', + 'ai_spin', + 'ai_wash', 'cooling', - 'draining', - 'preheat', - 'ready', - 'rinsing', - 'finished', - 'scheduled_start', - 'warming', - 'defrosting', - 'sensing', - 'searing', - 'fast_preheat', - 'scheduled_end', - 'stone_heating', - 'time_hold_preheat', + 'delay_wash', + 'drying', + 'finish', + 'none', + 'pre_wash', + 'rinse', + 'spin', + 'wash', + 'weight_sensing', + 'wrinkle_prevent', + 'freeze_protection', ]), }), 'context': , - 'entity_id': 'sensor.vulcan_job_state', + 'entity_id': 'sensor.kitchen_oven_job_state', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'cooking', + 'state': 'none', }) # --- -# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_machine_state-entry] +# name: test_all_entities[da_ks_oven_0107x][sensor.kitchen_oven_machine_state-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ 'options': list([ - 'ready', - 'running', - 'paused', + 'pause', + 'run', + 'stop', ]), }), 'config_entry_id': , @@ -5334,7 +5500,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.vulcan_machine_state', + 'entity_id': 'sensor.kitchen_oven_machine_state', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -5351,40 +5517,843 @@ 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'oven_machine_state', - 'unique_id': '2c3cbaa0-1899-5ddc-7b58-9d657bd48f18_main_ovenOperatingState_machineState_machineState', + 'translation_key': 'washer_machine_state', + 'unique_id': '199d7863-ad04-793d-176d-658f10062575_main_washerOperatingState_machineState_machineState', 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_machine_state-state] +# name: test_all_entities[da_ks_oven_0107x][sensor.kitchen_oven_machine_state-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'enum', - 'friendly_name': 'Vulcan Machine state', + 'friendly_name': 'Kitchen oven Machine state', 'options': list([ - 'ready', - 'running', - 'paused', + 'pause', + 'run', + 'stop', ]), }), 'context': , - 'entity_id': 'sensor.vulcan_machine_state', + 'entity_id': 'sensor.kitchen_oven_machine_state', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'running', + 'state': 'stop', }) # --- -# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_operating_state-entry] +# name: test_all_entities[da_ks_oven_0107x][sensor.kitchen_oven_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'options': list([ - 'run', - 'ready', - ]), + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.kitchen_oven_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': '199d7863-ad04-793d-176d-658f10062575_main_powerConsumptionReport_powerConsumption_power_meter', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][sensor.kitchen_oven_power-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'power', + 'friendly_name': 'Kitchen oven Power', + 'power_consumption_end': '2025-11-15T14:38:02Z', + 'power_consumption_start': '2025-11-15T14:37:32Z', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.kitchen_oven_power', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0', + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][sensor.kitchen_oven_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.kitchen_oven_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': '199d7863-ad04-793d-176d-658f10062575_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][sensor.kitchen_oven_power_energy-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Kitchen oven Power energy', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.kitchen_oven_power_energy', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_completion_time-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': None, + 'entity_id': 'sensor.vulcan_completion_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Completion time', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'completion_time', + 'unique_id': '2c3cbaa0-1899-5ddc-7b58-9d657bd48f18_main_ovenOperatingState_completionTime_completionTime', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_completion_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + 'friendly_name': 'Vulcan Completion time', + }), + 'context': , + 'entity_id': 'sensor.vulcan_completion_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2025-03-14T03:23:28+00:00', + }) +# --- +# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_job_state-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'cleaning', + 'cooking', + 'cooling', + 'draining', + 'preheat', + 'ready', + 'rinsing', + 'finished', + 'scheduled_start', + 'warming', + 'defrosting', + 'sensing', + 'searing', + 'fast_preheat', + 'scheduled_end', + 'stone_heating', + 'time_hold_preheat', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.vulcan_job_state', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Job state', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'oven_job_state', + 'unique_id': '2c3cbaa0-1899-5ddc-7b58-9d657bd48f18_main_ovenOperatingState_ovenJobState_ovenJobState', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_job_state-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Vulcan Job state', + 'options': list([ + 'cleaning', + 'cooking', + 'cooling', + 'draining', + 'preheat', + 'ready', + 'rinsing', + 'finished', + 'scheduled_start', + 'warming', + 'defrosting', + 'sensing', + 'searing', + 'fast_preheat', + 'scheduled_end', + 'stone_heating', + 'time_hold_preheat', + ]), + }), + 'context': , + 'entity_id': 'sensor.vulcan_job_state', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'cooking', + }) +# --- +# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_machine_state-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'ready', + 'running', + 'paused', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.vulcan_machine_state', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Machine state', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'oven_machine_state', + 'unique_id': '2c3cbaa0-1899-5ddc-7b58-9d657bd48f18_main_ovenOperatingState_machineState_machineState', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_machine_state-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Vulcan Machine state', + 'options': list([ + 'ready', + 'running', + 'paused', + ]), + }), + 'context': , + 'entity_id': 'sensor.vulcan_machine_state', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'running', + }) +# --- +# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_operating_state-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'run', + 'ready', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.vulcan_operating_state', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Operating state', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'cooktop_operating_state', + 'unique_id': '2c3cbaa0-1899-5ddc-7b58-9d657bd48f18_main_custom.cooktopOperatingState_cooktopOperatingState_cooktopOperatingState', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_operating_state-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Vulcan Operating state', + 'options': list([ + 'run', + 'ready', + ]), + }), + 'context': , + 'entity_id': 'sensor.vulcan_operating_state', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'ready', + }) +# --- +# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_oven_mode-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'options': list([ + 'conventional', + 'bake', + 'bottom_heat', + 'convection_bake', + 'convection_roast', + 'broil', + 'convection_broil', + 'steam_cook', + 'steam_bake', + 'steam_roast', + 'steam_bottom_heat_plus_convection', + 'microwave', + 'microwave_plus_grill', + 'microwave_plus_convection', + 'microwave_plus_hot_blast', + 'microwave_plus_hot_blast_2', + 'slim_middle', + 'slim_strong', + 'slow_cook', + 'proof', + 'dehydrate', + 'others', + 'strong_steam', + 'descale', + 'rinse', + ]), + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.vulcan_oven_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': 'Oven mode', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'oven_mode', + 'unique_id': '2c3cbaa0-1899-5ddc-7b58-9d657bd48f18_main_ovenMode_ovenMode_ovenMode', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_oven_mode-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'enum', + 'friendly_name': 'Vulcan Oven mode', + 'options': list([ + 'conventional', + 'bake', + 'bottom_heat', + 'convection_bake', + 'convection_roast', + 'broil', + 'convection_broil', + 'steam_cook', + 'steam_bake', + 'steam_roast', + 'steam_bottom_heat_plus_convection', + 'microwave', + 'microwave_plus_grill', + 'microwave_plus_convection', + 'microwave_plus_hot_blast', + 'microwave_plus_hot_blast_2', + 'slim_middle', + 'slim_strong', + 'slow_cook', + 'proof', + 'dehydrate', + 'others', + 'strong_steam', + 'descale', + 'rinse', + ]), + }), + 'context': , + 'entity_id': 'sensor.vulcan_oven_mode', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'bake', + }) +# --- +# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_setpoint-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': None, + 'entity_id': 'sensor.vulcan_setpoint', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Setpoint', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'oven_setpoint', + 'unique_id': '2c3cbaa0-1899-5ddc-7b58-9d657bd48f18_main_ovenSetpoint_ovenSetpoint_ovenSetpoint', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_setpoint-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Vulcan Setpoint', + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.vulcan_setpoint', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '218.333333333333', + }) +# --- +# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_temperature-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.vulcan_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Temperature', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': '2c3cbaa0-1899-5ddc-7b58-9d657bd48f18_main_temperatureMeasurement_temperature_temperature', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Vulcan Temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.vulcan_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '218.333333333333', + }) +# --- +# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_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.refrigerator_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': '7db87911-7dce-1cf2-7119-b953432a2f09_main_powerConsumptionReport_powerConsumption_energy_meter', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_energy-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Refrigerator Energy', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.refrigerator_energy', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1568.087', + }) +# --- +# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_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.refrigerator_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': '7db87911-7dce-1cf2-7119-b953432a2f09_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_energy_difference-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Refrigerator Energy difference', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.refrigerator_energy_difference', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.007', + }) +# --- +# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_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.refrigerator_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': '7db87911-7dce-1cf2-7119-b953432a2f09_main_powerConsumptionReport_powerConsumption_energySaved_meter', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_energy_saved-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Refrigerator Energy saved', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.refrigerator_energy_saved', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_freezer_temperature-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.refrigerator_freezer_temperature', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Freezer temperature', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'freezer_temperature', + 'unique_id': '7db87911-7dce-1cf2-7119-b953432a2f09_freezer_temperatureMeasurement_temperature_temperature', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_freezer_temperature-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'temperature', + 'friendly_name': 'Refrigerator Freezer temperature', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.refrigerator_freezer_temperature', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '-17.7777777777778', + }) +# --- +# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_fridge_temperature-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -5393,7 +6362,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.vulcan_operating_state', + 'entity_id': 'sensor.refrigerator_fridge_temperature', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -5402,70 +6371,45 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Operating state', + 'original_name': 'Fridge temperature', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'cooktop_operating_state', - 'unique_id': '2c3cbaa0-1899-5ddc-7b58-9d657bd48f18_main_custom.cooktopOperatingState_cooktopOperatingState_cooktopOperatingState', - 'unit_of_measurement': None, + 'translation_key': 'cooler_temperature', + 'unique_id': '7db87911-7dce-1cf2-7119-b953432a2f09_cooler_temperatureMeasurement_temperature_temperature', + 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_operating_state-state] +# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_fridge_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'enum', - 'friendly_name': 'Vulcan Operating state', - 'options': list([ - 'run', - 'ready', - ]), + 'device_class': 'temperature', + 'friendly_name': 'Refrigerator Fridge temperature', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.vulcan_operating_state', + 'entity_id': 'sensor.refrigerator_fridge_temperature', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'ready', + 'state': '2.77777777777778', }) # --- -# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_oven_mode-entry] +# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'options': list([ - 'conventional', - 'bake', - 'bottom_heat', - 'convection_bake', - 'convection_roast', - 'broil', - 'convection_broil', - 'steam_cook', - 'steam_bake', - 'steam_roast', - 'steam_bottom_heat_plus_convection', - 'microwave', - 'microwave_plus_grill', - 'microwave_plus_convection', - 'microwave_plus_hot_blast', - 'microwave_plus_hot_blast_2', - 'slim_middle', - 'slim_strong', - 'slow_cook', - 'proof', - 'dehydrate', - 'others', - 'strong_steam', - 'descale', - 'rinse', - ]), + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -5473,8 +6417,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.vulcan_oven_mode', + 'entity_category': None, + 'entity_id': 'sensor.refrigerator_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -5483,66 +6427,48 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Oven mode', + 'original_name': 'Power', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'oven_mode', - 'unique_id': '2c3cbaa0-1899-5ddc-7b58-9d657bd48f18_main_ovenMode_ovenMode_ovenMode', - 'unit_of_measurement': None, + 'translation_key': None, + 'unique_id': '7db87911-7dce-1cf2-7119-b953432a2f09_main_powerConsumptionReport_powerConsumption_power_meter', + 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_oven_mode-state] +# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'enum', - 'friendly_name': 'Vulcan Oven mode', - 'options': list([ - 'conventional', - 'bake', - 'bottom_heat', - 'convection_bake', - 'convection_roast', - 'broil', - 'convection_broil', - 'steam_cook', - 'steam_bake', - 'steam_roast', - 'steam_bottom_heat_plus_convection', - 'microwave', - 'microwave_plus_grill', - 'microwave_plus_convection', - 'microwave_plus_hot_blast', - 'microwave_plus_hot_blast_2', - 'slim_middle', - 'slim_strong', - 'slow_cook', - 'proof', - 'dehydrate', - 'others', - 'strong_steam', - 'descale', - 'rinse', - ]), + 'device_class': 'power', + 'friendly_name': 'Refrigerator Power', + 'power_consumption_end': '2025-02-09T17:49:00Z', + 'power_consumption_start': '2025-02-09T17:38:01Z', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.vulcan_oven_mode', + 'entity_id': 'sensor.refrigerator_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'bake', + 'state': '6', }) # --- -# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_setpoint-entry] +# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_power_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, - 'capabilities': None, + 'capabilities': dict({ + 'state_class': , + }), 'config_entry_id': , 'config_subentry_id': , 'device_class': None, @@ -5550,7 +6476,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.vulcan_setpoint', + 'entity_id': 'sensor.refrigerator_power_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -5560,37 +6486,38 @@ 'name': None, 'options': dict({ 'sensor': dict({ - 'suggested_display_precision': 1, + 'suggested_display_precision': 2, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Setpoint', + 'original_name': 'Power energy', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'oven_setpoint', - 'unique_id': '2c3cbaa0-1899-5ddc-7b58-9d657bd48f18_main_ovenSetpoint_ovenSetpoint_ovenSetpoint', - 'unit_of_measurement': , + 'translation_key': 'power_energy', + 'unique_id': '7db87911-7dce-1cf2-7119-b953432a2f09_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', + 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_setpoint-state] +# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_power_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'Vulcan Setpoint', - 'unit_of_measurement': , + 'device_class': 'energy', + 'friendly_name': 'Refrigerator Power energy', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.vulcan_setpoint', + 'entity_id': 'sensor.refrigerator_power_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '218.333333333333', + 'state': '0.0135559777781698', }) # --- -# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_temperature-entry] +# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_water_filter_usage-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -5605,7 +6532,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.vulcan_temperature', + 'entity_id': 'sensor.refrigerator_water_filter_usage', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -5614,39 +6541,35 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), }), - 'original_device_class': , + 'original_device_class': None, 'original_icon': None, - 'original_name': 'Temperature', + 'original_name': 'Water filter usage', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': None, - 'unique_id': '2c3cbaa0-1899-5ddc-7b58-9d657bd48f18_main_temperatureMeasurement_temperature_temperature', - 'unit_of_measurement': , + 'translation_key': 'water_filter_usage', + 'unique_id': '7db87911-7dce-1cf2-7119-b953432a2f09_main_custom.waterFilter_waterFilterUsage_waterFilterUsage', + 'unit_of_measurement': '%', }) # --- -# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_temperature-state] +# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_water_filter_usage-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'Vulcan Temperature', + 'friendly_name': 'Refrigerator Water filter usage', 'state_class': , - 'unit_of_measurement': , + 'unit_of_measurement': '%', }), 'context': , - 'entity_id': 'sensor.vulcan_temperature', + 'entity_id': 'sensor.refrigerator_water_filter_usage', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '218.333333333333', + 'state': '100', }) # --- -# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_energy-entry] +# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -5682,11 +6605,11 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '7db87911-7dce-1cf2-7119-b953432a2f09_main_powerConsumptionReport_powerConsumption_energy_meter', + 'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_powerConsumptionReport_powerConsumption_energy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_energy-state] +# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -5699,10 +6622,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '1568.087', + 'state': '4381.422', }) # --- -# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_energy_difference-entry] +# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy_difference-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -5738,11 +6661,11 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'energy_difference', - 'unique_id': '7db87911-7dce-1cf2-7119-b953432a2f09_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', + 'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_energy_difference-state] +# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy_difference-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -5755,10 +6678,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.007', + 'state': '0.027', }) # --- -# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_energy_saved-entry] +# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy_saved-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -5794,11 +6717,11 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'energy_saved', - 'unique_id': '7db87911-7dce-1cf2-7119-b953432a2f09_main_powerConsumptionReport_powerConsumption_energySaved_meter', + 'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_powerConsumptionReport_powerConsumption_energySaved_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_energy_saved-state] +# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy_saved-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -5814,7 +6737,7 @@ 'state': '0.0', }) # --- -# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_freezer_temperature-entry] +# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_freezer_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -5850,11 +6773,11 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'freezer_temperature', - 'unique_id': '7db87911-7dce-1cf2-7119-b953432a2f09_freezer_temperatureMeasurement_temperature_temperature', + 'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_freezer_temperatureMeasurement_temperature_temperature', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_freezer_temperature-state] +# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_freezer_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'temperature', @@ -5870,7 +6793,7 @@ 'state': '-17.7777777777778', }) # --- -# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_fridge_temperature-entry] +# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_fridge_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -5906,11 +6829,11 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'cooler_temperature', - 'unique_id': '7db87911-7dce-1cf2-7119-b953432a2f09_cooler_temperatureMeasurement_temperature_temperature', + 'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_cooler_temperatureMeasurement_temperature_temperature', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_fridge_temperature-state] +# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_fridge_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'temperature', @@ -5926,7 +6849,7 @@ 'state': '2.77777777777778', }) # --- -# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_power-entry] +# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -5962,17 +6885,17 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '7db87911-7dce-1cf2-7119-b953432a2f09_main_powerConsumptionReport_powerConsumption_power_meter', + 'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_powerConsumptionReport_powerConsumption_power_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_power-state] +# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', 'friendly_name': 'Refrigerator Power', - 'power_consumption_end': '2025-02-09T17:49:00Z', - 'power_consumption_start': '2025-02-09T17:38:01Z', + 'power_consumption_end': '2025-02-09T00:25:23Z', + 'power_consumption_start': '2025-02-09T00:13:39Z', 'state_class': , 'unit_of_measurement': , }), @@ -5981,10 +6904,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '6', + 'state': '144', }) # --- -# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_power_energy-entry] +# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_power_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -6020,11 +6943,11 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'power_energy', - 'unique_id': '7db87911-7dce-1cf2-7119-b953432a2f09_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', + 'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_power_energy-state] +# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_power_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -6037,10 +6960,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0135559777781698', + 'state': '0.0270189050030708', }) # --- -# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_water_filter_usage-entry] +# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_water_filter_usage-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -6073,11 +6996,11 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'water_filter_usage', - 'unique_id': '7db87911-7dce-1cf2-7119-b953432a2f09_main_custom.waterFilter_waterFilterUsage_waterFilterUsage', + 'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_custom.waterFilter_waterFilterUsage_waterFilterUsage', 'unit_of_measurement': '%', }) # --- -# name: test_all_entities[da_ref_normal_000001][sensor.refrigerator_water_filter_usage-state] +# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_water_filter_usage-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'friendly_name': 'Refrigerator Water filter usage', @@ -6089,10 +7012,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '100', + 'state': '52', }) # --- -# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy-entry] +# name: test_all_entities[da_ref_normal_01011][sensor.frigo_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -6107,7 +7030,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.refrigerator_energy', + 'entity_id': 'sensor.frigo_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -6128,27 +7051,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_powerConsumptionReport_powerConsumption_energy_meter', + 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_main_powerConsumptionReport_powerConsumption_energy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy-state] +# name: test_all_entities[da_ref_normal_01011][sensor.frigo_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Refrigerator Energy', + 'friendly_name': 'Frigo Energy', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.refrigerator_energy', + 'entity_id': 'sensor.frigo_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '4381.422', + 'state': '229.226', }) # --- -# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy_difference-entry] +# name: test_all_entities[da_ref_normal_01011][sensor.frigo_energy_difference-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -6163,7 +7086,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.refrigerator_energy_difference', + 'entity_id': 'sensor.frigo_energy_difference', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -6184,27 +7107,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'energy_difference', - 'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', + 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy_difference-state] +# name: test_all_entities[da_ref_normal_01011][sensor.frigo_energy_difference-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Refrigerator Energy difference', + 'friendly_name': 'Frigo Energy difference', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.refrigerator_energy_difference', + 'entity_id': 'sensor.frigo_energy_difference', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.027', + 'state': '0.01', }) # --- -# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy_saved-entry] +# name: test_all_entities[da_ref_normal_01011][sensor.frigo_energy_saved-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -6219,7 +7142,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.refrigerator_energy_saved', + 'entity_id': 'sensor.frigo_energy_saved', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -6240,27 +7163,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'energy_saved', - 'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_powerConsumptionReport_powerConsumption_energySaved_meter', + 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_main_powerConsumptionReport_powerConsumption_energySaved_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy_saved-state] +# name: test_all_entities[da_ref_normal_01011][sensor.frigo_energy_saved-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Refrigerator Energy saved', + 'friendly_name': 'Frigo Energy saved', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.refrigerator_energy_saved', + 'entity_id': 'sensor.frigo_energy_saved', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '0.0', }) # --- -# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_freezer_temperature-entry] +# name: test_all_entities[da_ref_normal_01011][sensor.frigo_freezer_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -6275,7 +7198,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.refrigerator_freezer_temperature', + 'entity_id': 'sensor.frigo_freezer_temperature', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -6296,27 +7219,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'freezer_temperature', - 'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_freezer_temperatureMeasurement_temperature_temperature', + 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_freezer_temperatureMeasurement_temperature_temperature', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_freezer_temperature-state] +# name: test_all_entities[da_ref_normal_01011][sensor.frigo_freezer_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'temperature', - 'friendly_name': 'Refrigerator Freezer temperature', + 'friendly_name': 'Frigo Freezer temperature', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.refrigerator_freezer_temperature', + 'entity_id': 'sensor.frigo_freezer_temperature', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '-17.7777777777778', + 'state': '-22.2222222222222', }) # --- -# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_fridge_temperature-entry] +# name: test_all_entities[da_ref_normal_01011][sensor.frigo_fridge_temperature-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -6331,7 +7254,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.refrigerator_fridge_temperature', + 'entity_id': 'sensor.frigo_fridge_temperature', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -6352,27 +7275,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'cooler_temperature', - 'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_cooler_temperatureMeasurement_temperature_temperature', + 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_cooler_temperatureMeasurement_temperature_temperature', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_fridge_temperature-state] +# name: test_all_entities[da_ref_normal_01011][sensor.frigo_fridge_temperature-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'temperature', - 'friendly_name': 'Refrigerator Fridge temperature', + 'friendly_name': 'Frigo Fridge temperature', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.refrigerator_fridge_temperature', + 'entity_id': 'sensor.frigo_fridge_temperature', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2.77777777777778', + 'state': '2.22222222222222', }) # --- -# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_power-entry] +# name: test_all_entities[da_ref_normal_01011][sensor.frigo_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -6387,7 +7310,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.refrigerator_power', + 'entity_id': 'sensor.frigo_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -6408,29 +7331,29 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_powerConsumptionReport_powerConsumption_power_meter', + 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_main_powerConsumptionReport_powerConsumption_power_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_power-state] +# name: test_all_entities[da_ref_normal_01011][sensor.frigo_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', - 'friendly_name': 'Refrigerator Power', - 'power_consumption_end': '2025-02-09T00:25:23Z', - 'power_consumption_start': '2025-02-09T00:13:39Z', + 'friendly_name': 'Frigo Power', + 'power_consumption_end': '2025-06-16T16:45:48Z', + 'power_consumption_start': '2025-06-16T16:30:09Z', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.refrigerator_power', + 'entity_id': 'sensor.frigo_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '144', + 'state': '17', }) # --- -# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_power_energy-entry] +# name: test_all_entities[da_ref_normal_01011][sensor.frigo_power_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -6445,7 +7368,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.refrigerator_power_energy', + 'entity_id': 'sensor.frigo_power_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -6466,27 +7389,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'power_energy', - 'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', + 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_power_energy-state] +# name: test_all_entities[da_ref_normal_01011][sensor.frigo_power_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Refrigerator Power energy', + 'friendly_name': 'Frigo Power energy', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.refrigerator_power_energy', + 'entity_id': 'sensor.frigo_power_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0270189050030708', + 'state': '0.0143511805540986', }) # --- -# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_water_filter_usage-entry] +# name: test_all_entities[da_ref_normal_01011][sensor.frigo_water_filter_usage-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -6501,7 +7424,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.refrigerator_water_filter_usage', + 'entity_id': 'sensor.frigo_water_filter_usage', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -6519,26 +7442,26 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'water_filter_usage', - 'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_custom.waterFilter_waterFilterUsage_waterFilterUsage', + 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_main_custom.waterFilter_waterFilterUsage_waterFilterUsage', 'unit_of_measurement': '%', }) # --- -# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_water_filter_usage-state] +# name: test_all_entities[da_ref_normal_01011][sensor.frigo_water_filter_usage-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'friendly_name': 'Refrigerator Water filter usage', + 'friendly_name': 'Frigo Water filter usage', 'state_class': , 'unit_of_measurement': '%', }), 'context': , - 'entity_id': 'sensor.refrigerator_water_filter_usage', + 'entity_id': 'sensor.frigo_water_filter_usage', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '52', + 'state': '97', }) # --- -# name: test_all_entities[da_ref_normal_01011][sensor.frigo_energy-entry] +# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -6553,7 +7476,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.frigo_energy', + 'entity_id': 'sensor.lodowka_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -6574,27 +7497,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_main_powerConsumptionReport_powerConsumption_energy_meter', + 'unique_id': '271d82e0-5b0c-e4b8-058e-cdf23a188610_main_powerConsumptionReport_powerConsumption_energy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_01011][sensor.frigo_energy-state] +# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Frigo Energy', + 'friendly_name': 'Lodówka Energy', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.frigo_energy', + 'entity_id': 'sensor.lodowka_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '229.226', + 'state': '0.861', }) # --- -# name: test_all_entities[da_ref_normal_01011][sensor.frigo_energy_difference-entry] +# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_energy_difference-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -6609,7 +7532,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.frigo_energy_difference', + 'entity_id': 'sensor.lodowka_energy_difference', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -6630,27 +7553,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'energy_difference', - 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', + 'unique_id': '271d82e0-5b0c-e4b8-058e-cdf23a188610_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_01011][sensor.frigo_energy_difference-state] +# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_energy_difference-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Frigo Energy difference', + 'friendly_name': 'Lodówka Energy difference', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.frigo_energy_difference', + 'entity_id': 'sensor.lodowka_energy_difference', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.01', + 'state': '0.0', }) # --- -# name: test_all_entities[da_ref_normal_01011][sensor.frigo_energy_saved-entry] +# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_energy_saved-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -6665,7 +7588,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.frigo_energy_saved', + 'entity_id': 'sensor.lodowka_energy_saved', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -6686,139 +7609,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'energy_saved', - 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_main_powerConsumptionReport_powerConsumption_energySaved_meter', + 'unique_id': '271d82e0-5b0c-e4b8-058e-cdf23a188610_main_powerConsumptionReport_powerConsumption_energySaved_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_01011][sensor.frigo_energy_saved-state] +# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_energy_saved-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Frigo Energy saved', + 'friendly_name': 'Lodówka Energy saved', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.frigo_energy_saved', + 'entity_id': 'sensor.lodowka_energy_saved', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '0.0', }) # --- -# name: test_all_entities[da_ref_normal_01011][sensor.frigo_freezer_temperature-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.frigo_freezer_temperature', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Freezer temperature', - 'platform': 'smartthings', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'freezer_temperature', - 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_freezer_temperatureMeasurement_temperature_temperature', - 'unit_of_measurement': , - }) -# --- -# name: test_all_entities[da_ref_normal_01011][sensor.frigo_freezer_temperature-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'Frigo Freezer temperature', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.frigo_freezer_temperature', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '-22.2222222222222', - }) -# --- -# name: test_all_entities[da_ref_normal_01011][sensor.frigo_fridge_temperature-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.frigo_fridge_temperature', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 1, - }), - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': 'Fridge temperature', - 'platform': 'smartthings', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'cooler_temperature', - 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_cooler_temperatureMeasurement_temperature_temperature', - 'unit_of_measurement': , - }) -# --- -# name: test_all_entities[da_ref_normal_01011][sensor.frigo_fridge_temperature-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'temperature', - 'friendly_name': 'Frigo Fridge temperature', - 'state_class': , - 'unit_of_measurement': , - }), - 'context': , - 'entity_id': 'sensor.frigo_fridge_temperature', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': '2.22222222222222', - }) -# --- -# name: test_all_entities[da_ref_normal_01011][sensor.frigo_power-entry] +# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -6833,7 +7644,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.frigo_power', + 'entity_id': 'sensor.lodowka_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -6854,29 +7665,29 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_main_powerConsumptionReport_powerConsumption_power_meter', + 'unique_id': '271d82e0-5b0c-e4b8-058e-cdf23a188610_main_powerConsumptionReport_powerConsumption_power_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_01011][sensor.frigo_power-state] +# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', - 'friendly_name': 'Frigo Power', - 'power_consumption_end': '2025-06-16T16:45:48Z', - 'power_consumption_start': '2025-06-16T16:30:09Z', + 'friendly_name': 'Lodówka Power', + 'power_consumption_end': '2025-08-14T07:21:35Z', + 'power_consumption_start': '2025-08-14T07:04:50Z', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.frigo_power', + 'entity_id': 'sensor.lodowka_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '17', + 'state': '1', }) # --- -# name: test_all_entities[da_ref_normal_01011][sensor.frigo_power_energy-entry] +# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_power_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -6891,7 +7702,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.frigo_power_energy', + 'entity_id': 'sensor.lodowka_power_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -6912,42 +7723,40 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'power_energy', - 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', + 'unique_id': '271d82e0-5b0c-e4b8-058e-cdf23a188610_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_01011][sensor.frigo_power_energy-state] +# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_power_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Frigo Power energy', + 'friendly_name': 'Lodówka Power energy', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.frigo_power_energy', + 'entity_id': 'sensor.lodowka_power_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0143511805540986', + 'state': '0.00027936416665713', }) # --- -# name: test_all_entities[da_ref_normal_01011][sensor.frigo_water_filter_usage-entry] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_battery-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, 'config_entry_id': , 'config_subentry_id': , 'device_class': None, 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.frigo_water_filter_usage', + 'entity_category': , + 'entity_id': 'sensor.robot_vacuum_battery', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -6957,40 +7766,47 @@ 'name': None, 'options': dict({ }), - 'original_device_class': None, + 'original_device_class': , 'original_icon': None, - 'original_name': 'Water filter usage', + 'original_name': 'Battery', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'water_filter_usage', - 'unique_id': '5758b2ec-563e-f39b-ec39-208e54aabf60_main_custom.waterFilter_waterFilterUsage_waterFilterUsage', + 'translation_key': None, + 'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_battery_battery_battery', 'unit_of_measurement': '%', }) # --- -# name: test_all_entities[da_ref_normal_01011][sensor.frigo_water_filter_usage-state] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_battery-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'friendly_name': 'Frigo Water filter usage', - 'state_class': , + 'device_class': 'battery', + 'friendly_name': 'Robot vacuum Battery', 'unit_of_measurement': '%', }), 'context': , - 'entity_id': 'sensor.frigo_water_filter_usage', + 'entity_id': 'sensor.robot_vacuum_battery', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '97', + 'state': '59', }) # --- -# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_energy-entry] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_cleaning_mode-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'options': list([ + 'auto', + 'part', + 'repeat', + 'manual', + 'stop', + 'map', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -6998,8 +7814,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.lodowka_energy', + 'entity_category': , + 'entity_id': 'sensor.robot_vacuum_cleaning_mode', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7008,45 +7824,48 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy', + 'original_name': 'Cleaning mode', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': None, - 'unique_id': '271d82e0-5b0c-e4b8-058e-cdf23a188610_main_powerConsumptionReport_powerConsumption_energy_meter', - 'unit_of_measurement': , + '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_ref_normal_01011_onedoor][sensor.lodowka_energy-state] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_cleaning_mode-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Lodówka Energy', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'enum', + 'friendly_name': 'Robot vacuum Cleaning mode', + 'options': list([ + 'auto', + 'part', + 'repeat', + 'manual', + 'stop', + 'map', + ]), }), 'context': , - 'entity_id': 'sensor.lodowka_energy', + 'entity_id': 'sensor.robot_vacuum_cleaning_mode', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.861', + 'state': 'stop', }) # --- -# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_energy_difference-entry] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -7055,7 +7874,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.lodowka_energy_difference', + 'entity_id': 'sensor.robot_vacuum_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7070,39 +7889,39 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy difference', + 'original_name': 'Energy', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'energy_difference', - 'unique_id': '271d82e0-5b0c-e4b8-058e-cdf23a188610_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', + 'translation_key': None, + 'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_powerConsumptionReport_powerConsumption_energy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_energy_difference-state] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Lodówka Energy difference', - 'state_class': , + 'friendly_name': 'Robot vacuum Energy', + 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.lodowka_energy_difference', + 'entity_id': 'sensor.robot_vacuum_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': '0.981', }) # --- -# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_energy_saved-entry] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy_difference-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -7111,7 +7930,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.lodowka_energy_saved', + 'entity_id': 'sensor.robot_vacuum_energy_difference', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7126,39 +7945,39 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy saved', + 'original_name': 'Energy difference', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'energy_saved', - 'unique_id': '271d82e0-5b0c-e4b8-058e-cdf23a188610_main_powerConsumptionReport_powerConsumption_energySaved_meter', + 'translation_key': 'energy_difference', + 'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_energy_saved-state] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy_difference-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Lodówka Energy saved', - 'state_class': , + 'friendly_name': 'Robot vacuum Energy difference', + 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.lodowka_energy_saved', + 'entity_id': 'sensor.robot_vacuum_energy_difference', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': '0.021', }) # --- -# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_power-entry] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy_saved-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -7167,7 +7986,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.lodowka_power', + 'entity_id': 'sensor.robot_vacuum_energy_saved', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7180,43 +7999,52 @@ 'suggested_display_precision': 2, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Power', + 'original_name': 'Energy saved', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': None, - 'unique_id': '271d82e0-5b0c-e4b8-058e-cdf23a188610_main_powerConsumptionReport_powerConsumption_power_meter', - 'unit_of_measurement': , + 'translation_key': 'energy_saved', + 'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_powerConsumptionReport_powerConsumption_energySaved_meter', + 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_power-state] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy_saved-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Lodówka Power', - 'power_consumption_end': '2025-08-14T07:21:35Z', - 'power_consumption_start': '2025-08-14T07:04:50Z', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'energy', + 'friendly_name': 'Robot vacuum Energy saved', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.lodowka_power', + 'entity_id': 'sensor.robot_vacuum_energy_saved', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '1', + 'state': '0.0', }) # --- -# name: test_all_entities[da_ref_normal_01011_onedoor][sensor.lodowka_power_energy-entry] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_movement-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'options': list([ + 'homing', + 'idle', + 'charging', + 'alarm', + 'off', + 'reserve', + 'point', + 'after', + 'cleaning', + 'pause', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -7225,7 +8053,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.lodowka_power_energy', + 'entity_id': 'sensor.robot_vacuum_movement', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7234,52 +8062,61 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Power energy', + 'original_name': 'Movement', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'power_energy', - 'unique_id': '271d82e0-5b0c-e4b8-058e-cdf23a188610_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', - 'unit_of_measurement': , + '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_ref_normal_01011_onedoor][sensor.lodowka_power_energy-state] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_movement-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Lodówka Power energy', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'enum', + 'friendly_name': 'Robot vacuum Movement', + 'options': list([ + 'homing', + 'idle', + 'charging', + 'alarm', + 'off', + 'reserve', + 'point', + 'after', + 'cleaning', + 'pause', + ]), }), 'context': , - 'entity_id': 'sensor.lodowka_power_energy', + 'entity_id': 'sensor.robot_vacuum_movement', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.00027936416665713', + 'state': 'cleaning', }) # --- -# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_battery-entry] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, - 'capabilities': 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.robot_vacuum_battery', + 'entity_category': None, + 'entity_id': 'sensor.robot_vacuum_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7288,48 +8125,47 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Battery', + '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_battery_battery_battery', - 'unit_of_measurement': '%', + '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_battery-state] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'battery', - 'friendly_name': 'Robot vacuum Battery', - 'unit_of_measurement': '%', + '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_battery', + 'entity_id': 'sensor.robot_vacuum_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '59', + 'state': '0', }) # --- -# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_cleaning_mode-entry] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_power_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'options': list([ - 'auto', - 'part', - 'repeat', - 'manual', - 'stop', - 'map', - ]), + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -7337,8 +8173,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.robot_vacuum_cleaning_mode', + 'entity_category': None, + 'entity_id': 'sensor.robot_vacuum_power_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7347,48 +8183,50 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Cleaning mode', + 'original_name': 'Power energy', '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, + '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_cleaning_mode-state] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_power_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'enum', - 'friendly_name': 'Robot vacuum Cleaning mode', - 'options': list([ - 'auto', - 'part', - 'repeat', - 'manual', - 'stop', - 'map', - ]), + 'device_class': 'energy', + 'friendly_name': 'Robot vacuum Power energy', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.robot_vacuum_cleaning_mode', + 'entity_id': 'sensor.robot_vacuum_power_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'stop', + 'state': '0.0', }) # --- -# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy-entry] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_turbo_mode-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'options': list([ + 'on', + 'off', + 'silence', + 'extra_silence', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -7396,8 +8234,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.robot_vacuum_energy', + 'entity_category': , + 'entity_id': 'sensor.robot_vacuum_turbo_mode', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7406,54 +8244,53 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy', + 'original_name': 'Turbo mode', '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': , + '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_energy-state] +# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_turbo_mode-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Robot vacuum Energy', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'enum', + 'friendly_name': 'Robot vacuum Turbo mode', + 'options': list([ + 'on', + 'off', + 'silence', + 'extra_silence', + ]), }), 'context': , - 'entity_id': 'sensor.robot_vacuum_energy', + 'entity_id': 'sensor.robot_vacuum_turbo_mode', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.981', + 'state': 'extra_silence', }) # --- -# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy_difference-entry] +# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_battery-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, - 'capabilities': dict({ - 'state_class': , - }), + 'capabilities': None, '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', + 'entity_category': , + 'entity_id': 'sensor.robot_vacuum_battery', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7462,45 +8299,48 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy difference', + 'original_name': 'Battery', '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': , + 'translation_key': None, + 'unique_id': '3442dfc6-17c0-a65f-dae0-4c6e01786f44_main_battery_battery_battery', + 'unit_of_measurement': '%', }) # --- -# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy_difference-state] +# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_battery-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Robot vacuum Energy difference', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'battery', + 'friendly_name': 'Robot vacuum Battery', + 'unit_of_measurement': '%', }), 'context': , - 'entity_id': 'sensor.robot_vacuum_energy_difference', + 'entity_id': 'sensor.robot_vacuum_battery', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.021', + 'state': '100', }) # --- -# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy_saved-entry] +# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_cleaning_mode-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'options': list([ + 'auto', + 'part', + 'repeat', + 'manual', + 'stop', + 'map', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -7508,8 +8348,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.robot_vacuum_energy_saved', + 'entity_category': , + 'entity_id': 'sensor.robot_vacuum_cleaning_mode', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7518,39 +8358,42 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy saved', + 'original_name': 'Cleaning mode', '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': , + 'translation_key': 'robot_cleaner_cleaning_mode', + 'unique_id': '3442dfc6-17c0-a65f-dae0-4c6e01786f44_main_robotCleanerCleaningMode_robotCleanerCleaningMode_robotCleanerCleaningMode', + 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy_saved-state] +# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_cleaning_mode-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Robot vacuum Energy saved', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'enum', + 'friendly_name': 'Robot vacuum Cleaning mode', + 'options': list([ + 'auto', + 'part', + 'repeat', + 'manual', + 'stop', + 'map', + ]), }), 'context': , - 'entity_id': 'sensor.robot_vacuum_energy_saved', + 'entity_id': 'sensor.robot_vacuum_cleaning_mode', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': 'stop', }) # --- -# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_movement-entry] +# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_movement-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -7594,11 +8437,11 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'robot_cleaner_movement', - 'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_robotCleanerMovement_robotCleanerMovement_robotCleanerMovement', + 'unique_id': '3442dfc6-17c0-a65f-dae0-4c6e01786f44_main_robotCleanerMovement_robotCleanerMovement_robotCleanerMovement', 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_movement-state] +# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_movement-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'enum', @@ -7621,16 +8464,21 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'cleaning', + 'state': 'idle', }) # --- -# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_power-entry] +# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_turbo_mode-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'options': list([ + 'on', + 'off', + 'silence', + 'extra_silence', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -7638,8 +8486,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': None, - 'entity_id': 'sensor.robot_vacuum_power', + 'entity_category': , + 'entity_id': 'sensor.robot_vacuum_turbo_mode', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7648,47 +8496,46 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Power', + 'original_name': 'Turbo mode', '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': , + 'translation_key': 'robot_cleaner_turbo_mode', + 'unique_id': '3442dfc6-17c0-a65f-dae0-4c6e01786f44_main_robotCleanerTurboMode_robotCleanerTurboMode_robotCleanerTurboMode', + 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_power-state] +# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_turbo_mode-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': , + 'device_class': 'enum', + 'friendly_name': 'Robot vacuum Turbo mode', + 'options': list([ + 'on', + 'off', + 'silence', + 'extra_silence', + ]), }), 'context': , - 'entity_id': 'sensor.robot_vacuum_power', + 'entity_id': 'sensor.robot_vacuum_turbo_mode', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0', + 'state': 'off', }) # --- -# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_power_energy-entry] +# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -7697,7 +8544,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.robot_vacuum_power_energy', + 'entity_id': 'sensor.eco_heating_system_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7712,44 +8559,39 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Power energy', + 'original_name': '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', + 'translation_key': None, + 'unique_id': '1f98ebd0-ac48-d802-7f62-000001200100_main_powerConsumptionReport_powerConsumption_energy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_power_energy-state] +# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Robot vacuum Power energy', - 'state_class': , + 'friendly_name': 'Eco Heating System Energy', + 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.robot_vacuum_power_energy', + 'entity_id': 'sensor.eco_heating_system_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': '8901.522', }) # --- -# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_turbo_mode-entry] +# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_energy_difference-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'options': list([ - 'on', - 'off', - 'silence', - 'extra_silence', - ]), + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -7757,8 +8599,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.robot_vacuum_turbo_mode', + 'entity_category': None, + 'entity_id': 'sensor.eco_heating_system_energy_difference', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7767,53 +8609,54 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Turbo mode', + 'original_name': 'Energy difference', '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, + 'translation_key': 'energy_difference', + 'unique_id': '1f98ebd0-ac48-d802-7f62-000001200100_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', + 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_turbo_mode-state] +# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_energy_difference-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'enum', - 'friendly_name': 'Robot vacuum Turbo mode', - 'options': list([ - 'on', - 'off', - 'silence', - 'extra_silence', - ]), + 'device_class': 'energy', + 'friendly_name': 'Eco Heating System Energy difference', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.robot_vacuum_turbo_mode', + 'entity_id': 'sensor.eco_heating_system_energy_difference', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'extra_silence', + 'state': '0.0', }) # --- -# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_battery-entry] +# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_energy_saved-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, - 'capabilities': 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.robot_vacuum_battery', + 'entity_category': None, + 'entity_id': 'sensor.eco_heating_system_energy_saved', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7822,48 +8665,45 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Battery', + 'original_name': 'Energy saved', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': None, - 'unique_id': '3442dfc6-17c0-a65f-dae0-4c6e01786f44_main_battery_battery_battery', - 'unit_of_measurement': '%', + 'translation_key': 'energy_saved', + 'unique_id': '1f98ebd0-ac48-d802-7f62-000001200100_main_powerConsumptionReport_powerConsumption_energySaved_meter', + 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_battery-state] +# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_energy_saved-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'battery', - 'friendly_name': 'Robot vacuum Battery', - 'unit_of_measurement': '%', + 'device_class': 'energy', + 'friendly_name': 'Eco Heating System Energy saved', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.robot_vacuum_battery', + 'entity_id': 'sensor.eco_heating_system_energy_saved', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '100', + 'state': '0.0', }) # --- -# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_cleaning_mode-entry] +# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'options': list([ - 'auto', - 'part', - 'repeat', - 'manual', - 'stop', - 'map', - ]), + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -7871,8 +8711,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.robot_vacuum_cleaning_mode', + 'entity_category': None, + 'entity_id': 'sensor.eco_heating_system_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7881,59 +8721,47 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Cleaning mode', + 'original_name': 'Power', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'robot_cleaner_cleaning_mode', - 'unique_id': '3442dfc6-17c0-a65f-dae0-4c6e01786f44_main_robotCleanerCleaningMode_robotCleanerCleaningMode_robotCleanerCleaningMode', - 'unit_of_measurement': None, + 'translation_key': None, + 'unique_id': '1f98ebd0-ac48-d802-7f62-000001200100_main_powerConsumptionReport_powerConsumption_power_meter', + 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_cleaning_mode-state] +# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'enum', - 'friendly_name': 'Robot vacuum Cleaning mode', - 'options': list([ - 'auto', - 'part', - 'repeat', - 'manual', - 'stop', - 'map', - ]), + 'device_class': 'power', + 'friendly_name': 'Eco Heating System Power', + 'power_consumption_end': '2025-05-16T12:01:29Z', + 'power_consumption_start': '2025-05-16T11:18:12Z', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.robot_vacuum_cleaning_mode', + 'entity_id': 'sensor.eco_heating_system_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'stop', + 'state': '0.015', }) # --- -# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_movement-entry] +# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_power_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'options': list([ - 'homing', - 'idle', - 'charging', - 'alarm', - 'off', - 'reserve', - 'point', - 'after', - 'cleaning', - 'pause', - ]), + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -7942,7 +8770,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.robot_vacuum_movement', + 'entity_id': 'sensor.eco_heating_system_power_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -7951,56 +8779,47 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Movement', + 'original_name': 'Power energy', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'robot_cleaner_movement', - 'unique_id': '3442dfc6-17c0-a65f-dae0-4c6e01786f44_main_robotCleanerMovement_robotCleanerMovement_robotCleanerMovement', - 'unit_of_measurement': None, + 'translation_key': 'power_energy', + 'unique_id': '1f98ebd0-ac48-d802-7f62-000001200100_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', + 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_movement-state] +# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_power_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'enum', - 'friendly_name': 'Robot vacuum Movement', - 'options': list([ - 'homing', - 'idle', - 'charging', - 'alarm', - 'off', - 'reserve', - 'point', - 'after', - 'cleaning', - 'pause', - ]), + 'device_class': 'energy', + 'friendly_name': 'Eco Heating System Power energy', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.robot_vacuum_movement', + 'entity_id': 'sensor.eco_heating_system_power_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'idle', + 'state': '1.08249458332857e-05', }) # --- -# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_turbo_mode-entry] +# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_valve_position-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ 'options': list([ - 'on', - 'off', - 'silence', - 'extra_silence', + 'room', + 'tank', ]), }), 'config_entry_id': , @@ -8009,8 +8828,8 @@ 'device_id': , 'disabled_by': None, 'domain': 'sensor', - 'entity_category': , - 'entity_id': 'sensor.robot_vacuum_turbo_mode', + 'entity_category': None, + 'entity_id': 'sensor.eco_heating_system_valve_position', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8022,37 +8841,35 @@ }), 'original_device_class': , 'original_icon': None, - 'original_name': 'Turbo mode', + 'original_name': 'Valve position', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'robot_cleaner_turbo_mode', - 'unique_id': '3442dfc6-17c0-a65f-dae0-4c6e01786f44_main_robotCleanerTurboMode_robotCleanerTurboMode_robotCleanerTurboMode', + 'translation_key': 'diverter_valve_position', + 'unique_id': '1f98ebd0-ac48-d802-7f62-000001200100_main_samsungce.ehsDiverterValve_position_position', 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_turbo_mode-state] +# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_valve_position-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'enum', - 'friendly_name': 'Robot vacuum Turbo mode', + 'friendly_name': 'Eco Heating System Valve position', 'options': list([ - 'on', - 'off', - 'silence', - 'extra_silence', + 'room', + 'tank', ]), }), 'context': , - 'entity_id': 'sensor.robot_vacuum_turbo_mode', + 'entity_id': 'sensor.eco_heating_system_valve_position', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'off', + 'state': 'room', }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_energy-entry] +# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -8067,7 +8884,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.eco_heating_system_energy', + 'entity_id': 'sensor.heat_pump_main_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8088,27 +8905,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '1f98ebd0-ac48-d802-7f62-000001200100_main_powerConsumptionReport_powerConsumption_energy_meter', + 'unique_id': '6a7d5349-0a66-0277-058d-000001200101_main_powerConsumptionReport_powerConsumption_energy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_energy-state] +# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Eco Heating System Energy', + 'friendly_name': 'Heat Pump Main Energy', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.eco_heating_system_energy', + 'entity_id': 'sensor.heat_pump_main_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '8901.522', + 'state': '297.584', }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_energy_difference-entry] +# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_energy_difference-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -8123,7 +8940,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.eco_heating_system_energy_difference', + 'entity_id': 'sensor.heat_pump_main_energy_difference', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8144,27 +8961,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'energy_difference', - 'unique_id': '1f98ebd0-ac48-d802-7f62-000001200100_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', + 'unique_id': '6a7d5349-0a66-0277-058d-000001200101_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_energy_difference-state] +# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_energy_difference-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Eco Heating System Energy difference', + 'friendly_name': 'Heat Pump Main Energy difference', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.eco_heating_system_energy_difference', + 'entity_id': 'sensor.heat_pump_main_energy_difference', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '0.0', }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_energy_saved-entry] +# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_energy_saved-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -8179,7 +8996,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.eco_heating_system_energy_saved', + 'entity_id': 'sensor.heat_pump_main_energy_saved', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8200,27 +9017,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'energy_saved', - 'unique_id': '1f98ebd0-ac48-d802-7f62-000001200100_main_powerConsumptionReport_powerConsumption_energySaved_meter', + 'unique_id': '6a7d5349-0a66-0277-058d-000001200101_main_powerConsumptionReport_powerConsumption_energySaved_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_energy_saved-state] +# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_energy_saved-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Eco Heating System Energy saved', + 'friendly_name': 'Heat Pump Main Energy saved', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.eco_heating_system_energy_saved', + 'entity_id': 'sensor.heat_pump_main_energy_saved', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '0.0', }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_power-entry] +# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -8235,7 +9052,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.eco_heating_system_power', + 'entity_id': 'sensor.heat_pump_main_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8256,29 +9073,29 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '1f98ebd0-ac48-d802-7f62-000001200100_main_powerConsumptionReport_powerConsumption_power_meter', + 'unique_id': '6a7d5349-0a66-0277-058d-000001200101_main_powerConsumptionReport_powerConsumption_power_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_power-state] +# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', - 'friendly_name': 'Eco Heating System Power', - 'power_consumption_end': '2025-05-16T12:01:29Z', - 'power_consumption_start': '2025-05-16T11:18:12Z', + 'friendly_name': 'Heat Pump Main Power', + 'power_consumption_end': '2025-05-15T21:10:02Z', + 'power_consumption_start': '2025-05-15T20:52:02Z', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.eco_heating_system_power', + 'entity_id': 'sensor.heat_pump_main_power', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '0.015', }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_power_energy-entry] +# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_power_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -8293,7 +9110,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.eco_heating_system_power_energy', + 'entity_id': 'sensor.heat_pump_main_power_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8314,27 +9131,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'power_energy', - 'unique_id': '1f98ebd0-ac48-d802-7f62-000001200100_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', + 'unique_id': '6a7d5349-0a66-0277-058d-000001200101_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_power_energy-state] +# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_power_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Eco Heating System Power energy', + 'friendly_name': 'Heat Pump Main Power energy', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.eco_heating_system_power_energy', + 'entity_id': 'sensor.heat_pump_main_power_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '1.08249458332857e-05', + 'state': '4.50185416638851e-06', }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_valve_position-entry] +# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_valve_position-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -8352,7 +9169,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.eco_heating_system_valve_position', + 'entity_id': 'sensor.heat_pump_main_valve_position', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8370,29 +9187,29 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'diverter_valve_position', - 'unique_id': '1f98ebd0-ac48-d802-7f62-000001200100_main_samsungce.ehsDiverterValve_position_position', + 'unique_id': '6a7d5349-0a66-0277-058d-000001200101_main_samsungce.ehsDiverterValve_position_position', 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_valve_position-state] +# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_valve_position-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'enum', - 'friendly_name': 'Eco Heating System Valve position', + 'friendly_name': 'Heat Pump Main Valve position', 'options': list([ 'room', 'tank', ]), }), 'context': , - 'entity_id': 'sensor.eco_heating_system_valve_position', + 'entity_id': 'sensor.heat_pump_main_valve_position', 'last_changed': , 'last_reported': , 'last_updated': , 'state': 'room', }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_energy-entry] +# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -8407,7 +9224,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.heat_pump_main_energy', + 'entity_id': 'sensor.warmepumpe_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8428,27 +9245,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '6a7d5349-0a66-0277-058d-000001200101_main_powerConsumptionReport_powerConsumption_energy_meter', + 'unique_id': '3810e5ad-5351-d9f9-12ff-000001200000_main_powerConsumptionReport_powerConsumption_energy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_energy-state] +# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Heat Pump Main Energy', + 'friendly_name': 'Wärmepumpe Energy', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.heat_pump_main_energy', + 'entity_id': 'sensor.warmepumpe_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '297.584', + 'state': '9575.308', }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_energy_difference-entry] +# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_energy_difference-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -8463,7 +9280,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.heat_pump_main_energy_difference', + 'entity_id': 'sensor.warmepumpe_energy_difference', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8484,27 +9301,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'energy_difference', - 'unique_id': '6a7d5349-0a66-0277-058d-000001200101_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', + 'unique_id': '3810e5ad-5351-d9f9-12ff-000001200000_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_energy_difference-state] +# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_energy_difference-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Heat Pump Main Energy difference', + 'friendly_name': 'Wärmepumpe Energy difference', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.heat_pump_main_energy_difference', + 'entity_id': 'sensor.warmepumpe_energy_difference', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': '0.045', }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_energy_saved-entry] +# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_energy_saved-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -8519,7 +9336,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.heat_pump_main_energy_saved', + 'entity_id': 'sensor.warmepumpe_energy_saved', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8540,27 +9357,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'energy_saved', - 'unique_id': '6a7d5349-0a66-0277-058d-000001200101_main_powerConsumptionReport_powerConsumption_energySaved_meter', + 'unique_id': '3810e5ad-5351-d9f9-12ff-000001200000_main_powerConsumptionReport_powerConsumption_energySaved_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_energy_saved-state] +# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_energy_saved-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Heat Pump Main Energy saved', + 'friendly_name': 'Wärmepumpe Energy saved', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.heat_pump_main_energy_saved', + 'entity_id': 'sensor.warmepumpe_energy_saved', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '0.0', }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_power-entry] +# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -8575,7 +9392,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.heat_pump_main_power', + 'entity_id': 'sensor.warmepumpe_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8596,29 +9413,29 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '6a7d5349-0a66-0277-058d-000001200101_main_powerConsumptionReport_powerConsumption_power_meter', + 'unique_id': '3810e5ad-5351-d9f9-12ff-000001200000_main_powerConsumptionReport_powerConsumption_power_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_power-state] +# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', - 'friendly_name': 'Heat Pump Main Power', - 'power_consumption_end': '2025-05-15T21:10:02Z', - 'power_consumption_start': '2025-05-15T20:52:02Z', + 'friendly_name': 'Wärmepumpe Power', + 'power_consumption_end': '2025-05-09T05:02:01Z', + 'power_consumption_start': '2025-05-09T04:39:01Z', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.heat_pump_main_power', + 'entity_id': 'sensor.warmepumpe_power', 'last_changed': , 'last_reported': , 'last_updated': , 'state': '0.015', }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_power_energy-entry] +# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_power_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -8633,7 +9450,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.heat_pump_main_power_energy', + 'entity_id': 'sensor.warmepumpe_power_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8654,27 +9471,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'power_energy', - 'unique_id': '6a7d5349-0a66-0277-058d-000001200101_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', + 'unique_id': '3810e5ad-5351-d9f9-12ff-000001200000_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_power_energy-state] +# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_power_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Heat Pump Main Power energy', + 'friendly_name': 'Wärmepumpe Power energy', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.heat_pump_main_power_energy', + 'entity_id': 'sensor.warmepumpe_power_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '4.50185416638851e-06', + 'state': '0.000222076093320449', }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_valve_position-entry] +# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_valve_position-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -8692,7 +9509,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.heat_pump_main_valve_position', + 'entity_id': 'sensor.warmepumpe_valve_position', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8710,29 +9527,78 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'diverter_valve_position', - 'unique_id': '6a7d5349-0a66-0277-058d-000001200101_main_samsungce.ehsDiverterValve_position_position', + 'unique_id': '3810e5ad-5351-d9f9-12ff-000001200000_main_samsungce.ehsDiverterValve_position_position', 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_valve_position-state] +# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_valve_position-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'enum', - 'friendly_name': 'Heat Pump Main Valve position', + 'friendly_name': 'Wärmepumpe Valve position', 'options': list([ 'room', 'tank', ]), }), 'context': , - 'entity_id': 'sensor.heat_pump_main_valve_position', + 'entity_id': 'sensor.warmepumpe_valve_position', 'last_changed': , 'last_reported': , 'last_updated': , 'state': 'room', }) # --- -# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_energy-entry] +# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_completion_time-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': None, + 'entity_id': 'sensor.dishwasher_completion_time', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Completion time', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'completion_time', + 'unique_id': 'f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_dishwasherOperatingState_completionTime_completionTime', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_completion_time-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'timestamp', + 'friendly_name': 'Dishwasher Completion time', + }), + 'context': , + 'entity_id': 'sensor.dishwasher_completion_time', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2025-02-08T22:49:26+00:00', + }) +# --- +# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -8747,7 +9613,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.warmepumpe_energy', + 'entity_id': 'sensor.dishwasher_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8768,27 +9634,27 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': '3810e5ad-5351-d9f9-12ff-000001200000_main_powerConsumptionReport_powerConsumption_energy_meter', + 'unique_id': 'f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_powerConsumptionReport_powerConsumption_energy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_energy-state] +# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Wärmepumpe Energy', + 'friendly_name': 'Dishwasher Energy', 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.warmepumpe_energy', + 'entity_id': 'sensor.dishwasher_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '9575.308', + 'state': '101.6', }) # --- -# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_energy_difference-entry] +# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_energy_difference-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -8803,7 +9669,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.warmepumpe_energy_difference', + 'entity_id': 'sensor.dishwasher_energy_difference', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8824,33 +9690,100 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'energy_difference', - 'unique_id': '3810e5ad-5351-d9f9-12ff-000001200000_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', + 'unique_id': 'f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_energy_difference-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'energy', + 'friendly_name': 'Dishwasher Energy difference', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.dishwasher_energy_difference', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- +# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_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.dishwasher_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': 'f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_powerConsumptionReport_powerConsumption_energySaved_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_energy_difference-state] +# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_energy_saved-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', - 'friendly_name': 'Wärmepumpe Energy difference', - 'state_class': , + 'friendly_name': 'Dishwasher Energy saved', + 'state_class': , 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.warmepumpe_energy_difference', + 'entity_id': 'sensor.dishwasher_energy_saved', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.045', + 'state': '0.0', }) # --- -# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_energy_saved-entry] +# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_job_state-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'options': list([ + 'air_wash', + 'cooling', + 'drying', + 'finish', + 'pre_drain', + 'pre_wash', + 'rinse', + 'spin', + 'wash', + 'wrinkle_prevent', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -8859,7 +9792,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.warmepumpe_energy_saved', + 'entity_id': 'sensor.dishwasher_job_state', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8868,45 +9801,56 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Energy saved', + 'original_name': 'Job state', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'energy_saved', - 'unique_id': '3810e5ad-5351-d9f9-12ff-000001200000_main_powerConsumptionReport_powerConsumption_energySaved_meter', - 'unit_of_measurement': , + 'translation_key': 'dishwasher_job_state', + 'unique_id': 'f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_dishwasherOperatingState_dishwasherJobState_dishwasherJobState', + 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_energy_saved-state] +# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_job_state-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Wärmepumpe Energy saved', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'enum', + 'friendly_name': 'Dishwasher Job state', + 'options': list([ + 'air_wash', + 'cooling', + 'drying', + 'finish', + 'pre_drain', + 'pre_wash', + 'rinse', + 'spin', + 'wash', + 'wrinkle_prevent', + ]), }), 'context': , - 'entity_id': 'sensor.warmepumpe_energy_saved', + 'entity_id': 'sensor.dishwasher_job_state', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.0', + 'state': 'unknown', }) # --- -# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_power-entry] +# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_machine_state-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'options': list([ + 'pause', + 'run', + 'stop', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -8915,7 +9859,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.warmepumpe_power', + 'entity_id': 'sensor.dishwasher_machine_state', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8924,47 +9868,45 @@ }), 'name': None, 'options': dict({ - 'sensor': dict({ - 'suggested_display_precision': 2, - }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Power', + 'original_name': 'Machine state', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': None, - 'unique_id': '3810e5ad-5351-d9f9-12ff-000001200000_main_powerConsumptionReport_powerConsumption_power_meter', - 'unit_of_measurement': , + 'translation_key': 'dishwasher_machine_state', + 'unique_id': 'f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_dishwasherOperatingState_machineState_machineState', + 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_power-state] +# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_machine_state-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'power', - 'friendly_name': 'Wärmepumpe Power', - 'power_consumption_end': '2025-05-09T05:02:01Z', - 'power_consumption_start': '2025-05-09T04:39:01Z', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'enum', + 'friendly_name': 'Dishwasher Machine state', + 'options': list([ + 'pause', + 'run', + 'stop', + ]), }), 'context': , - 'entity_id': 'sensor.warmepumpe_power', + 'entity_id': 'sensor.dishwasher_machine_state', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.015', + 'state': 'stop', }) # --- -# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_power_energy-entry] +# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -8973,7 +9915,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.warmepumpe_power_energy', + 'entity_id': 'sensor.dishwasher_power', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -8986,44 +9928,43 @@ 'suggested_display_precision': 2, }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Power energy', + 'original_name': 'Power', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'power_energy', - 'unique_id': '3810e5ad-5351-d9f9-12ff-000001200000_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', - 'unit_of_measurement': , + 'translation_key': None, + 'unique_id': 'f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_powerConsumptionReport_powerConsumption_power_meter', + 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_power_energy-state] +# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'energy', - 'friendly_name': 'Wärmepumpe Power energy', - 'state_class': , - 'unit_of_measurement': , + 'device_class': 'power', + 'friendly_name': 'Dishwasher Power', + 'power_consumption_end': '2025-02-08T20:21:26Z', + 'power_consumption_start': '2025-02-08T20:21:21Z', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.warmepumpe_power_energy', + 'entity_id': 'sensor.dishwasher_power', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '0.000222076093320449', + 'state': '0', }) # --- -# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_valve_position-entry] +# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_power_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), 'area_id': None, 'capabilities': dict({ - 'options': list([ - 'room', - 'tank', - ]), + 'state_class': , }), 'config_entry_id': , 'config_subentry_id': , @@ -9032,7 +9973,7 @@ 'disabled_by': None, 'domain': 'sensor', 'entity_category': None, - 'entity_id': 'sensor.warmepumpe_valve_position', + 'entity_id': 'sensor.dishwasher_power_energy', 'has_entity_name': True, 'hidden_by': None, 'icon': None, @@ -9041,38 +9982,39 @@ }), 'name': None, 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 2, + }), }), - 'original_device_class': , + 'original_device_class': , 'original_icon': None, - 'original_name': 'Valve position', + 'original_name': 'Power energy', 'platform': 'smartthings', 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'diverter_valve_position', - 'unique_id': '3810e5ad-5351-d9f9-12ff-000001200000_main_samsungce.ehsDiverterValve_position_position', - 'unit_of_measurement': None, + 'translation_key': 'power_energy', + 'unique_id': 'f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', + 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_valve_position-state] +# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_power_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'device_class': 'enum', - 'friendly_name': 'Wärmepumpe Valve position', - 'options': list([ - 'room', - 'tank', - ]), + 'device_class': 'energy', + 'friendly_name': 'Dishwasher Power energy', + 'state_class': , + 'unit_of_measurement': , }), 'context': , - 'entity_id': 'sensor.warmepumpe_valve_position', + 'entity_id': 'sensor.dishwasher_power_energy', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': 'room', + 'state': '0.0', }) # --- -# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_completion_time-entry] +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_completion_time-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -9103,11 +10045,11 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'completion_time', - 'unique_id': 'f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_dishwasherOperatingState_completionTime_completionTime', + 'unique_id': '7ff318f3-3772-524d-3c9f-72fcd26413ed_main_dishwasherOperatingState_completionTime_completionTime', 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_completion_time-state] +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_completion_time-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'timestamp', @@ -9118,10 +10060,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2025-02-08T22:49:26+00:00', + 'state': '2025-11-15T17:51:16+00:00', }) # --- -# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_energy-entry] +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -9157,11 +10099,11 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_powerConsumptionReport_powerConsumption_energy_meter', + 'unique_id': '7ff318f3-3772-524d-3c9f-72fcd26413ed_main_powerConsumptionReport_powerConsumption_energy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_energy-state] +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -9174,10 +10116,10 @@ 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '101.6', + 'state': '98.3', }) # --- -# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_energy_difference-entry] +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_energy_difference-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -9213,11 +10155,11 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'energy_difference', - 'unique_id': 'f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', + 'unique_id': '7ff318f3-3772-524d-3c9f-72fcd26413ed_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_energy_difference-state] +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_energy_difference-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -9233,7 +10175,7 @@ 'state': '0.0', }) # --- -# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_energy_saved-entry] +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_energy_saved-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -9269,11 +10211,11 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'energy_saved', - 'unique_id': 'f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_powerConsumptionReport_powerConsumption_energySaved_meter', + 'unique_id': '7ff318f3-3772-524d-3c9f-72fcd26413ed_main_powerConsumptionReport_powerConsumption_energySaved_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_energy_saved-state] +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_energy_saved-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -9289,7 +10231,7 @@ 'state': '0.0', }) # --- -# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_job_state-entry] +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_job_state-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -9333,11 +10275,11 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'dishwasher_job_state', - 'unique_id': 'f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_dishwasherOperatingState_dishwasherJobState_dishwasherJobState', + 'unique_id': '7ff318f3-3772-524d-3c9f-72fcd26413ed_main_dishwasherOperatingState_dishwasherJobState_dishwasherJobState', 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_job_state-state] +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_job_state-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'enum', @@ -9363,7 +10305,7 @@ 'state': 'unknown', }) # --- -# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_machine_state-entry] +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_machine_state-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -9400,11 +10342,11 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'dishwasher_machine_state', - 'unique_id': 'f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_dishwasherOperatingState_machineState_machineState', + 'unique_id': '7ff318f3-3772-524d-3c9f-72fcd26413ed_main_dishwasherOperatingState_machineState_machineState', 'unit_of_measurement': None, }) # --- -# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_machine_state-state] +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_machine_state-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'enum', @@ -9423,7 +10365,7 @@ 'state': 'stop', }) # --- -# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_power-entry] +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_power-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -9459,17 +10401,17 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': None, - 'unique_id': 'f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_powerConsumptionReport_powerConsumption_power_meter', + 'unique_id': '7ff318f3-3772-524d-3c9f-72fcd26413ed_main_powerConsumptionReport_powerConsumption_power_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_power-state] +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_power-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'power', 'friendly_name': 'Dishwasher Power', - 'power_consumption_end': '2025-02-08T20:21:26Z', - 'power_consumption_start': '2025-02-08T20:21:21Z', + 'power_consumption_end': '2025-11-15T13:57:48Z', + 'power_consumption_start': '2025-11-15T13:56:40Z', 'state_class': , 'unit_of_measurement': , }), @@ -9481,7 +10423,7 @@ 'state': '0', }) # --- -# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_power_energy-entry] +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_power_energy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ }), @@ -9517,11 +10459,11 @@ 'suggested_object_id': None, 'supported_features': 0, 'translation_key': 'power_energy', - 'unique_id': 'f36dc7ce-cac0-0667-dc14-a3704eb5e676_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', + 'unique_id': '7ff318f3-3772-524d-3c9f-72fcd26413ed_main_powerConsumptionReport_powerConsumption_powerEnergy_meter', 'unit_of_measurement': , }) # --- -# name: test_all_entities[da_wm_dw_000001][sensor.dishwasher_power_energy-state] +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_power_energy-state] StateSnapshot({ 'attributes': ReadOnlyDict({ 'device_class': 'energy', @@ -9537,6 +10479,62 @@ 'state': '0.0', }) # --- +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_water_consumption-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.dishwasher_water_consumption', + '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': 'Water consumption', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'water_consumption', + 'unique_id': '7ff318f3-3772-524d-3c9f-72fcd26413ed_main_samsungce.waterConsumptionReport_waterConsumption_waterConsumption', + 'unit_of_measurement': , + }) +# --- +# name: test_all_entities[da_wm_dw_01011][sensor.dishwasher_water_consumption-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'water', + 'friendly_name': 'Dishwasher Water consumption', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.dishwasher_water_consumption', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '1336.2', + }) +# --- # name: test_all_entities[da_wm_sc_000001][sensor.airdresser_completion_time-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/smartthings/snapshots/test_switch.ambr b/tests/components/smartthings/snapshots/test_switch.ambr index 33b9ca5e757a56..da804dd320f078 100644 --- a/tests/components/smartthings/snapshots/test_switch.ambr +++ b/tests/components/smartthings/snapshots/test_switch.ambr @@ -191,6 +191,102 @@ 'state': 'off', }) # --- +# name: test_all_entities[da_ks_oven_0107x][switch.kitchen_oven-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.kitchen_oven', + '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': '199d7863-ad04-793d-176d-658f10062575_main_switch_switch_switch', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][switch.kitchen_oven-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Kitchen oven', + }), + 'context': , + 'entity_id': 'switch.kitchen_oven', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][switch.kitchen_oven_bubble_soak-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': , + 'entity_id': 'switch.kitchen_oven_bubble_soak', + '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': 'Bubble Soak', + 'platform': 'smartthings', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'bubble_soak', + 'unique_id': '199d7863-ad04-793d-176d-658f10062575_main_samsungce.washerBubbleSoak_status_status', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[da_ks_oven_0107x][switch.kitchen_oven_bubble_soak-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Kitchen oven Bubble Soak', + }), + 'context': , + 'entity_id': 'switch.kitchen_oven_bubble_soak', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_all_entities[da_ref_normal_000001][switch.refrigerator_cubed_ice-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/tesla_fleet/snapshots/test_update.ambr b/tests/components/tesla_fleet/snapshots/test_update.ambr new file mode 100644 index 00000000000000..445305220d051a --- /dev/null +++ b/tests/components/tesla_fleet/snapshots/test_update.ambr @@ -0,0 +1,121 @@ +# serializer version: 1 +# name: test_update[update.test_update-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'update', + 'entity_category': , + 'entity_id': 'update.test_update', + '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': 'Update', + 'platform': 'tesla_fleet', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': 'vehicle_state_software_update_status', + 'unique_id': 'LRWXF7EK4KC700000-vehicle_state_software_update_status', + 'unit_of_measurement': None, + }) +# --- +# name: test_update[update.test_update-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'auto_update': False, + 'display_precision': 0, + 'entity_picture': 'https://brands.home-assistant.io/_/tesla_fleet/icon.png', + 'friendly_name': 'Test Update', + 'in_progress': False, + 'installed_version': '2023.44.30.8', + 'latest_version': '2024.12.0.0', + 'release_summary': None, + 'release_url': None, + 'skipped_version': None, + 'supported_features': , + 'title': None, + 'update_percentage': None, + }), + 'context': , + 'entity_id': 'update.test_update', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_update_alt[update.test_update-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'update', + 'entity_category': , + 'entity_id': 'update.test_update', + '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': 'Update', + 'platform': 'tesla_fleet', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + 'translation_key': 'vehicle_state_software_update_status', + 'unique_id': 'LRWXF7EK4KC700000-vehicle_state_software_update_status', + 'unit_of_measurement': None, + }) +# --- +# name: test_update_alt[update.test_update-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'auto_update': False, + 'display_precision': 0, + 'entity_picture': 'https://brands.home-assistant.io/_/tesla_fleet/icon.png', + 'friendly_name': 'Test Update', + 'in_progress': False, + 'installed_version': '2023.44.30.8', + 'latest_version': '2023.44.30.8', + 'release_summary': None, + 'release_url': None, + 'skipped_version': None, + 'supported_features': , + 'title': None, + 'update_percentage': None, + }), + 'context': , + 'entity_id': 'update.test_update', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- diff --git a/tests/components/tesla_fleet/test_update.py b/tests/components/tesla_fleet/test_update.py new file mode 100644 index 00000000000000..b741470fb4950c --- /dev/null +++ b/tests/components/tesla_fleet/test_update.py @@ -0,0 +1,83 @@ +"""Test the Tesla Fleet update platform.""" + +import copy +from unittest.mock import AsyncMock, patch + +from freezegun.api import FrozenDateTimeFactory +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.tesla_fleet.coordinator import VEHICLE_INTERVAL +from homeassistant.components.tesla_fleet.update import INSTALLING +from homeassistant.components.update import DOMAIN as UPDATE_DOMAIN, SERVICE_INSTALL +from homeassistant.const import ATTR_ENTITY_ID, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from . import assert_entities, setup_platform +from .const import COMMAND_OK, VEHICLE_DATA, VEHICLE_DATA_ALT + +from tests.common import MockConfigEntry, async_fire_time_changed + + +async def test_update( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + entity_registry: er.EntityRegistry, + normal_config_entry: MockConfigEntry, +) -> None: + """Tests that the update entities are correct.""" + + await setup_platform(hass, normal_config_entry, [Platform.UPDATE]) + assert_entities(hass, normal_config_entry.entry_id, entity_registry, snapshot) + + +async def test_update_alt( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + entity_registry: er.EntityRegistry, + normal_config_entry: MockConfigEntry, + mock_vehicle_data: AsyncMock, +) -> None: + """Tests that the update entities are correct.""" + + mock_vehicle_data.return_value = VEHICLE_DATA_ALT + await setup_platform(hass, normal_config_entry, [Platform.UPDATE]) + assert_entities(hass, normal_config_entry.entry_id, entity_registry, snapshot) + + +async def test_update_services( + hass: HomeAssistant, + normal_config_entry: MockConfigEntry, + mock_vehicle_data: AsyncMock, + freezer: FrozenDateTimeFactory, + snapshot: SnapshotAssertion, +) -> None: + """Tests that the update services work.""" + + await setup_platform(hass, normal_config_entry, [Platform.UPDATE]) + + entity_id = "update.test_update" + + with patch( + "tesla_fleet_api.tesla.VehicleFleet.schedule_software_update", + return_value=COMMAND_OK, + ) as call: + await hass.services.async_call( + UPDATE_DOMAIN, + SERVICE_INSTALL, + {ATTR_ENTITY_ID: entity_id}, + blocking=True, + ) + call.assert_called_once() + + VEHICLE_INSTALLING = copy.deepcopy(VEHICLE_DATA) + VEHICLE_INSTALLING["response"]["vehicle_state"]["software_update"]["status"] = ( # type: ignore[index] + INSTALLING + ) + mock_vehicle_data.return_value = VEHICLE_INSTALLING + freezer.tick(VEHICLE_INTERVAL) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state.attributes["in_progress"] is True # type: ignore[union-attr] diff --git a/tests/components/tuya/snapshots/test_climate.ambr b/tests/components/tuya/snapshots/test_climate.ambr index f7df76f5fc628c..23d6892294f7e7 100644 --- a/tests/components/tuya/snapshots/test_climate.ambr +++ b/tests/components/tuya/snapshots/test_climate.ambr @@ -419,7 +419,7 @@ # name: test_platform_setup_and_discovery[climate.el_termostato_de_la_cocina-state] StateSnapshot({ 'attributes': ReadOnlyDict({ - 'current_temperature': 4.5, + 'current_temperature': 45.0, 'friendly_name': 'El termostato de la cocina', 'hvac_modes': list([ , diff --git a/tests/components/tuya/snapshots/test_fan.ambr b/tests/components/tuya/snapshots/test_fan.ambr index 17925ffca4bcc2..423f93edc3f68c 100644 --- a/tests/components/tuya/snapshots/test_fan.ambr +++ b/tests/components/tuya/snapshots/test_fan.ambr @@ -148,7 +148,7 @@ 'attributes': ReadOnlyDict({ 'direction': 'forward', 'friendly_name': 'ceiling fan/Light v2', - 'percentage': 20, + 'percentage': 21, 'percentage_step': 1.0, 'preset_mode': None, 'preset_modes': None, diff --git a/tests/components/waqi/snapshots/test_diagnostics.ambr b/tests/components/waqi/snapshots/test_diagnostics.ambr new file mode 100644 index 00000000000000..aa40b441f7cfff --- /dev/null +++ b/tests/components/waqi/snapshots/test_diagnostics.ambr @@ -0,0 +1,44 @@ +# serializer version: 1 +# name: test_diagnostics + dict({ + 'ABCDEF': dict({ + 'air_quality_index': 29, + 'attributions': list([ + dict({ + 'logo': 'Netherland-RIVM.png', + 'name': 'RIVM - Rijksinstituut voor Volksgezondheid en Milieum, Landelijk Meetnet Luchtkwaliteit', + 'url': 'http://www.luchtmeetnet.nl/', + }), + dict({ + 'logo': None, + 'name': 'World Air Quality Index Project', + 'url': 'https://waqi.info/', + }), + ]), + 'city': dict({ + 'coordinates': dict({ + 'latitude': 52.105031, + 'longitude': 5.124464, + }), + 'external_url': 'https://aqicn.org/city/netherland/utrecht/de-jongweg', + 'location': None, + 'name': 'de Jongweg, Utrecht', + }), + 'dominant_pollutant': 'o3', + 'extended_air_quality': dict({ + 'carbon_monoxide': 2.3, + 'humidity': 80, + 'nephelometry': 80, + 'nitrogen_dioxide': 2.3, + 'ozone': 29.4, + 'pm10': 12, + 'pm25': 17, + 'pressure': 1008.8, + 'sulfur_dioxide': 2.3, + 'temperature': 16, + }), + 'measured_at': '2023-08-07T17:00:00+02:00', + 'station_id': 4584, + }), + }) +# --- diff --git a/tests/components/waqi/test_diagnostics.py b/tests/components/waqi/test_diagnostics.py new file mode 100644 index 00000000000000..c5b896e9b97959 --- /dev/null +++ b/tests/components/waqi/test_diagnostics.py @@ -0,0 +1,28 @@ +"""Tests for the diagnostics data provided by the WAQI integration.""" + +from unittest.mock import AsyncMock + +from syrupy.assertion import SnapshotAssertion + +from homeassistant.core import HomeAssistant + +from . import setup_integration + +from tests.common import MockConfigEntry +from tests.components.diagnostics import get_diagnostics_for_config_entry +from tests.typing import ClientSessionGenerator + + +async def test_diagnostics( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + mock_waqi: AsyncMock, + mock_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, +) -> None: + """Test diagnostics.""" + await setup_integration(hass, mock_config_entry) + assert ( + await get_diagnostics_for_config_entry(hass, hass_client, mock_config_entry) + == snapshot + )