mirror of
https://github.com/home-assistant/core.git
synced 2025-11-22 09:17:02 +00:00
Compare commits
14 Commits
instance-u
...
setpoint_c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
725bd3d671 | ||
|
|
cfc4fa6342 | ||
|
|
b650e71660 | ||
|
|
9ddf15e348 | ||
|
|
15082f9111 | ||
|
|
12f16611ff | ||
|
|
8041be3d08 | ||
|
|
40b021e755 | ||
|
|
aab57eda96 | ||
|
|
f0dd37caa5 | ||
|
|
662b178495 | ||
|
|
cb3d30884a | ||
|
|
49e6f20372 | ||
|
|
75d02661eb |
4
.github/workflows/builder.yml
vendored
4
.github/workflows/builder.yml
vendored
@@ -88,6 +88,10 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: ${{ fromJson(needs.init.outputs.architectures) }}
|
||||
exclude:
|
||||
- arch: armv7
|
||||
- arch: armhf
|
||||
- arch: i386
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
38
.github/workflows/wheels.yml
vendored
38
.github/workflows/wheels.yml
vendored
@@ -77,8 +77,20 @@ jobs:
|
||||
|
||||
# Use C-Extension for SQLAlchemy
|
||||
echo "REQUIRE_SQLALCHEMY_CEXT=1"
|
||||
|
||||
# Add additional pip wheel build constraints
|
||||
echo "PIP_CONSTRAINT=build_constraints.txt"
|
||||
) > .env_file
|
||||
|
||||
- name: Write pip wheel build constraints
|
||||
run: |
|
||||
(
|
||||
# ninja 1.11.1.2 + 1.11.1.3 seem to be broken on at least armhf
|
||||
# this caused the numpy builds to fail
|
||||
# https://github.com/scikit-build/ninja-python-distributions/issues/274
|
||||
echo "ninja==1.11.1.1"
|
||||
) > build_constraints.txt
|
||||
|
||||
- name: Upload env_file
|
||||
uses: &actions-upload-artifact actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
@@ -87,6 +99,13 @@ jobs:
|
||||
include-hidden-files: true
|
||||
overwrite: true
|
||||
|
||||
- name: Upload build_constraints
|
||||
uses: *actions-upload-artifact
|
||||
with:
|
||||
name: build_constraints
|
||||
path: ./build_constraints.txt
|
||||
overwrite: true
|
||||
|
||||
- name: Upload requirements_diff
|
||||
uses: *actions-upload-artifact
|
||||
with:
|
||||
@@ -119,6 +138,13 @@ jobs:
|
||||
- os: ubuntu-latest
|
||||
- arch: aarch64
|
||||
os: ubuntu-24.04-arm
|
||||
exclude:
|
||||
- abi: cp314
|
||||
arch: armv7
|
||||
- abi: cp314
|
||||
arch: armhf
|
||||
- abi: cp314
|
||||
arch: i386
|
||||
steps:
|
||||
- *checkout
|
||||
|
||||
@@ -128,6 +154,12 @@ jobs:
|
||||
with:
|
||||
name: env_file
|
||||
|
||||
- &download-build-constraints
|
||||
name: Download build_constraints
|
||||
uses: *actions-download-artifact
|
||||
with:
|
||||
name: build_constraints
|
||||
|
||||
- &download-requirements-diff
|
||||
name: Download requirements_diff
|
||||
uses: *actions-download-artifact
|
||||
@@ -167,7 +199,7 @@ jobs:
|
||||
- *checkout
|
||||
|
||||
- *download-env-file
|
||||
|
||||
- *download-build-constraints
|
||||
- *download-requirements-diff
|
||||
|
||||
- name: Download requirements_all_wheels
|
||||
@@ -177,6 +209,10 @@ jobs:
|
||||
|
||||
- name: Adjust build env
|
||||
run: |
|
||||
if [ "${{ matrix.arch }}" = "i386" ]; then
|
||||
echo "NPY_DISABLE_SVML=1" >> .env_file
|
||||
fi
|
||||
|
||||
# Do not pin numpy in wheels building
|
||||
sed -i "/numpy/d" homeassistant/package_constraints.txt
|
||||
# Don't build wheels for uv as uv requires a greater version of rust as currently available on alpine
|
||||
|
||||
2
Dockerfile
generated
2
Dockerfile
generated
@@ -21,6 +21,8 @@ ARG BUILD_ARCH
|
||||
RUN \
|
||||
case "${BUILD_ARCH}" in \
|
||||
"aarch64") go2rtc_suffix='arm64' ;; \
|
||||
"armhf") go2rtc_suffix='armv6' ;; \
|
||||
"armv7") go2rtc_suffix='arm' ;; \
|
||||
*) go2rtc_suffix=${BUILD_ARCH} ;; \
|
||||
esac \
|
||||
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v1.9.12/go2rtc_linux_${go2rtc_suffix} --output /bin/go2rtc \
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
image: ghcr.io/home-assistant/{arch}-homeassistant
|
||||
build_from:
|
||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2025.11.0
|
||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2025.11.0
|
||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2025.11.0
|
||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2025.11.0
|
||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2025.11.0
|
||||
cosign:
|
||||
base_identity: https://github.com/home-assistant/docker/.*
|
||||
identity: https://github.com/home-assistant/core/.*
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/awair",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["python_awair"],
|
||||
"requirements": ["python-awair==0.2.5"],
|
||||
"requirements": ["python-awair==0.2.4"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"name": "awair*",
|
||||
|
||||
@@ -7,7 +7,7 @@ from collections.abc import Awaitable, Callable
|
||||
from datetime import datetime, timedelta
|
||||
from enum import Enum
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
from typing import cast
|
||||
|
||||
from hass_nabucasa import Cloud
|
||||
import voluptuous as vol
|
||||
@@ -86,10 +86,6 @@ SIGNAL_CLOUD_CONNECTION_STATE: SignalType[CloudConnectionState] = SignalType(
|
||||
"CLOUD_CONNECTION_STATE"
|
||||
)
|
||||
|
||||
_SIGNAL_CLOUDHOOKS_UPDATED: SignalType[dict[str, Any]] = SignalType(
|
||||
"CLOUDHOOKS_UPDATED"
|
||||
)
|
||||
|
||||
STARTUP_REPAIR_DELAY = 1 # 1 hour
|
||||
|
||||
ALEXA_ENTITY_SCHEMA = vol.Schema(
|
||||
@@ -246,24 +242,6 @@ 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:
|
||||
@@ -311,7 +289,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown)
|
||||
|
||||
_handle_prefs_updated(hass, cloud)
|
||||
_remote_handle_prefs_updated(cloud)
|
||||
_setup_services(hass, prefs)
|
||||
|
||||
async def async_startup_repairs(_: datetime) -> None:
|
||||
@@ -395,32 +373,26 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
|
||||
|
||||
@callback
|
||||
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
|
||||
def _remote_handle_prefs_updated(cloud: Cloud[CloudClient]) -> None:
|
||||
"""Handle remote preferences updated."""
|
||||
cur_pref = cloud.client.prefs.remote_enabled
|
||||
lock = asyncio.Lock()
|
||||
|
||||
async def on_prefs_updated(prefs: CloudPreferences) -> None:
|
||||
"""Handle cloud preferences updates."""
|
||||
nonlocal cur_remote_enabled
|
||||
nonlocal cur_cloudhooks
|
||||
# Sync remote connection with prefs
|
||||
async def remote_prefs_updated(prefs: CloudPreferences) -> None:
|
||||
"""Update remote status."""
|
||||
nonlocal cur_pref
|
||||
|
||||
# Lock protects cur_ state variables from concurrent updates
|
||||
async with lock:
|
||||
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:
|
||||
if prefs.remote_enabled == cur_pref:
|
||||
return
|
||||
|
||||
if cur_remote_enabled := prefs.remote_enabled:
|
||||
if cur_pref := prefs.remote_enabled:
|
||||
await cloud.remote.connect()
|
||||
else:
|
||||
await cloud.remote.disconnect()
|
||||
|
||||
cloud.client.prefs.async_listen_updates(on_prefs_updated)
|
||||
cloud.client.prefs.async_listen_updates(remote_prefs_updated)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
@@ -26,7 +25,6 @@ from homeassistant.core import HomeAssistant
|
||||
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__)
|
||||
|
||||
@@ -169,7 +167,6 @@ class DecoraWifiLight(LightEntity):
|
||||
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:
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20251105.1"]
|
||||
"requirements": ["home-assistant-frontend==20251105.0"]
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
import shutil
|
||||
|
||||
from aiohttp import ClientSession, UnixConnector
|
||||
from aiohttp import ClientSession
|
||||
from aiohttp.client_exceptions import ClientConnectionError, ServerConnectionError
|
||||
from awesomeversion import AwesomeVersion
|
||||
from go2rtc_client import Go2RtcRestClient
|
||||
@@ -53,7 +52,6 @@ from .const import (
|
||||
CONF_DEBUG_UI,
|
||||
DEBUG_UI_URL_MESSAGE,
|
||||
DOMAIN,
|
||||
HA_MANAGED_UNIX_SOCKET,
|
||||
HA_MANAGED_URL,
|
||||
RECOMMENDED_VERSION,
|
||||
)
|
||||
@@ -75,7 +73,7 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
_DATA_GO2RTC: HassKey[Go2RtcConfig] = HassKey(DOMAIN)
|
||||
_DATA_GO2RTC: HassKey[str] = HassKey(DOMAIN)
|
||||
_RETRYABLE_ERRORS = (ClientConnectionError, ServerConnectionError)
|
||||
type Go2RtcConfigEntry = ConfigEntry[WebRTCProvider]
|
||||
|
||||
@@ -102,12 +100,8 @@ 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,
|
||||
session,
|
||||
enable_ui=config.get(DOMAIN, {}).get(CONF_DEBUG_UI, False),
|
||||
hass, binary, enable_ui=config.get(DOMAIN, {}).get(CONF_DEBUG_UI, False)
|
||||
)
|
||||
try:
|
||||
await server.start()
|
||||
@@ -117,15 +111,12 @@ 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] = Go2RtcConfig(url, session)
|
||||
hass.data[_DATA_GO2RTC] = url
|
||||
discovery_flow.async_create_flow(
|
||||
hass, DOMAIN, context={"source": SOURCE_SYSTEM}, data={}
|
||||
)
|
||||
@@ -141,9 +132,8 @@ 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."""
|
||||
|
||||
config = hass.data[_DATA_GO2RTC]
|
||||
url = config.url
|
||||
session = config.session
|
||||
url = hass.data[_DATA_GO2RTC]
|
||||
session = async_get_clientsession(hass)
|
||||
client = Go2RtcRestClient(session, url)
|
||||
# Validate the server URL
|
||||
try:
|
||||
@@ -352,11 +342,3 @@ class WebRTCProvider(CameraWebRTCProvider):
|
||||
for ws_client in self._sessions.values():
|
||||
await ws_client.close()
|
||||
self._sessions.clear()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Go2RtcConfig:
|
||||
"""Go2rtc configuration."""
|
||||
|
||||
url: str
|
||||
session: ClientSession
|
||||
|
||||
@@ -6,5 +6,4 @@ CONF_DEBUG_UI = "debug_ui"
|
||||
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"
|
||||
|
||||
@@ -6,13 +6,13 @@ from contextlib import suppress
|
||||
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_UNIX_SOCKET, HA_MANAGED_URL
|
||||
from .const import HA_MANAGED_API_PORT, HA_MANAGED_URL
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_TERMINATE_TIMEOUT = 5
|
||||
@@ -23,8 +23,7 @@ _LOG_BUFFER_SIZE = 512
|
||||
_RESPAWN_COOLDOWN = 1
|
||||
|
||||
# Default configuration for HA
|
||||
# - Unix socket for secure local communication
|
||||
# - HTTP API only enabled when UI is enabled
|
||||
# - Api is listening only on localhost
|
||||
# - Enable rtsp for localhost only as ffmpeg needs it
|
||||
# - Clear default ice servers
|
||||
_GO2RTC_CONFIG_FORMAT = r"""# This file is managed by Home Assistant
|
||||
@@ -34,8 +33,7 @@ app:
|
||||
modules: {app_modules}
|
||||
|
||||
api:
|
||||
listen: "{listen_config}"
|
||||
unix_listen: "{unix_socket}"
|
||||
listen: "{api_ip}:{api_port}"
|
||||
allow_paths: {api_allow_paths}
|
||||
|
||||
# ffmpeg needs the exec module
|
||||
@@ -122,24 +120,20 @@ 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
|
||||
listen_config = f":{HA_MANAGED_API_PORT}"
|
||||
else:
|
||||
# Disable HTTP listening when UI is not enabled
|
||||
# as HA does not use it.
|
||||
listen_config = ""
|
||||
api_ip = ""
|
||||
|
||||
# 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(
|
||||
listen_config=listen_config,
|
||||
unix_socket=HA_MANAGED_UNIX_SOCKET,
|
||||
api_ip=api_ip,
|
||||
api_port=HA_MANAGED_API_PORT,
|
||||
app_modules=_format_list_for_yaml(app_modules),
|
||||
api_allow_paths=_format_list_for_yaml(api_paths),
|
||||
).encode()
|
||||
@@ -151,17 +145,11 @@ class Server:
|
||||
"""Go2rtc server."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
binary: str,
|
||||
session: ClientSession,
|
||||
*,
|
||||
enable_ui: bool = False,
|
||||
self, hass: HomeAssistant, binary: str, *, 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()
|
||||
@@ -209,7 +197,7 @@ class Server:
|
||||
raise Go2RTCServerStartError from err
|
||||
|
||||
# Check the server version
|
||||
client = Go2RtcRestClient(self._session, HA_MANAGED_URL)
|
||||
client = Go2RtcRestClient(async_get_clientsession(self._hass), HA_MANAGED_URL)
|
||||
await client.validate_server_version()
|
||||
|
||||
async def _log_output(self, process: asyncio.subprocess.Process) -> None:
|
||||
@@ -281,7 +269,7 @@ class Server:
|
||||
|
||||
async def _monitor_api(self) -> None:
|
||||
"""Raise if the go2rtc process terminates."""
|
||||
client = Go2RtcRestClient(self._session, HA_MANAGED_URL)
|
||||
client = Go2RtcRestClient(async_get_clientsession(self._hass), HA_MANAGED_URL)
|
||||
|
||||
_LOGGER.debug("Monitoring go2rtc API")
|
||||
try:
|
||||
|
||||
@@ -145,10 +145,10 @@
|
||||
"loop": "Loop",
|
||||
"off": "[%key:common::state::off%]",
|
||||
"seconds_1": "1 second",
|
||||
"seconds_2": "2 seconds",
|
||||
"seconds_3": "3 seconds",
|
||||
"seconds_4": "4 seconds",
|
||||
"seconds_5": "5 seconds"
|
||||
"seconds_2": "2 second",
|
||||
"seconds_3": "3 second",
|
||||
"seconds_4": "4 second",
|
||||
"seconds_5": "5 second"
|
||||
}
|
||||
},
|
||||
"min_dc_voltage_cells": {
|
||||
|
||||
@@ -15,20 +15,16 @@ from pylamarzocco.const import FirmwareType
|
||||
from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful
|
||||
from pylamarzocco.util import InstallationKey, generate_installation_key
|
||||
|
||||
from homeassistant.components.bluetooth import (
|
||||
async_ble_device_from_address,
|
||||
async_discovered_service_info,
|
||||
)
|
||||
from homeassistant.components.bluetooth import async_discovered_service_info
|
||||
from homeassistant.const import (
|
||||
CONF_MAC,
|
||||
CONF_PASSWORD,
|
||||
CONF_TOKEN,
|
||||
CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
Platform,
|
||||
__version__,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
from homeassistant.helpers.aiohttp_client import async_create_clientsession
|
||||
@@ -103,7 +99,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: LaMarzoccoConfigEntry) -
|
||||
# initialize Bluetooth
|
||||
bluetooth_client: LaMarzoccoBluetoothClient | None = None
|
||||
if entry.options.get(CONF_USE_BLUETOOTH, True) and (
|
||||
token := (entry.data.get(CONF_TOKEN) or settings.ble_auth_token)
|
||||
token := settings.ble_auth_token
|
||||
):
|
||||
if CONF_MAC not in entry.data:
|
||||
for discovery_info in async_discovered_service_info(hass):
|
||||
@@ -112,7 +108,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: LaMarzoccoConfigEntry) -
|
||||
and name.startswith(BT_MODEL_PREFIXES)
|
||||
and name.split("_")[1] == serial
|
||||
):
|
||||
_LOGGER.info("Found lamarzocco Bluetooth device, adding to entry")
|
||||
_LOGGER.debug("Found Bluetooth device, configuring with Bluetooth")
|
||||
# found a device, add MAC address to config entry
|
||||
hass.config_entries.async_update_entry(
|
||||
entry,
|
||||
@@ -122,29 +118,22 @@ async def async_setup_entry(hass: HomeAssistant, entry: LaMarzoccoConfigEntry) -
|
||||
},
|
||||
)
|
||||
|
||||
if not entry.data[CONF_TOKEN]:
|
||||
# update the token in the config entry
|
||||
hass.config_entries.async_update_entry(
|
||||
entry,
|
||||
data={
|
||||
**entry.data,
|
||||
CONF_TOKEN: token,
|
||||
},
|
||||
)
|
||||
|
||||
if CONF_MAC in entry.data:
|
||||
ble_device = async_ble_device_from_address(hass, entry.data[CONF_MAC])
|
||||
if ble_device:
|
||||
_LOGGER.info("Setting up lamarzocco with Bluetooth")
|
||||
bluetooth_client = LaMarzoccoBluetoothClient(
|
||||
ble_device=ble_device,
|
||||
ble_token=token,
|
||||
)
|
||||
|
||||
async def disconnect_bluetooth(_: Event) -> None:
|
||||
"""Stop push updates when hass stops."""
|
||||
await bluetooth_client.disconnect()
|
||||
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_STOP, disconnect_bluetooth
|
||||
)
|
||||
)
|
||||
entry.async_on_unload(bluetooth_client.disconnect)
|
||||
else:
|
||||
_LOGGER.info(
|
||||
"Bluetooth device not found during lamarzocco setup, continuing with cloud only"
|
||||
)
|
||||
_LOGGER.debug("Initializing Bluetooth device")
|
||||
bluetooth_client = LaMarzoccoBluetoothClient(
|
||||
address_or_ble_device=entry.data[CONF_MAC],
|
||||
ble_token=token,
|
||||
)
|
||||
|
||||
device = LaMarzoccoMachine(
|
||||
serial_number=entry.unique_id,
|
||||
|
||||
@@ -37,5 +37,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pylamarzocco"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pylamarzocco==2.2.0"]
|
||||
"requirements": ["pylamarzocco==2.1.3"]
|
||||
}
|
||||
|
||||
@@ -183,6 +183,13 @@ PUMP_CONTROL_MODE_MAP = {
|
||||
clusters.PumpConfigurationAndControl.Enums.ControlModeEnum.kUnknownEnumValue: None,
|
||||
}
|
||||
|
||||
SETPOINT_CHANGE_SOURCE_MAP = {
|
||||
clusters.Thermostat.Enums.SetpointChangeSourceEnum.kManual: "manual",
|
||||
clusters.Thermostat.Enums.SetpointChangeSourceEnum.kSchedule: "schedule",
|
||||
clusters.Thermostat.Enums.SetpointChangeSourceEnum.kExternal: "external",
|
||||
clusters.Thermostat.Enums.SetpointChangeSourceEnum.kUnknownEnumValue: None,
|
||||
}
|
||||
|
||||
HUMIDITY_SCALING_FACTOR = 100
|
||||
TEMPERATURE_SCALING_FACTOR = 100
|
||||
|
||||
@@ -1488,4 +1495,47 @@ DISCOVERY_SCHEMAS = [
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(clusters.ServiceArea.Attributes.EstimatedEndTime,),
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="SetpointChangeSource",
|
||||
translation_key="setpoint_change_source",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
state_class=None,
|
||||
# convert to set first to remove the duplicate unknown value
|
||||
options=[x for x in SETPOINT_CHANGE_SOURCE_MAP.values() if x is not None],
|
||||
device_to_ha=lambda x: SETPOINT_CHANGE_SOURCE_MAP[x],
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(clusters.Thermostat.Attributes.SetpointChangeSource,),
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="SetpointChangeSourceTimestamp",
|
||||
translation_key="setpoint_change_timestamp",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
state_class=None,
|
||||
device_to_ha=(lambda x: dt_util.utc_from_timestamp(x) if x > 0 else None),
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(
|
||||
clusters.Thermostat.Attributes.SetpointChangeSourceTimestamp,
|
||||
),
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="ThermostatSetpointChangeAmount",
|
||||
translation_key="setpoint_change_amount",
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
suggested_display_precision=1,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
device_to_ha=lambda x: x / TEMPERATURE_SCALING_FACTOR,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(clusters.Thermostat.Attributes.SetpointChangeAmount,),
|
||||
device_type=(device_types.Thermostat,),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -223,6 +223,9 @@
|
||||
"pump_setpoint": {
|
||||
"name": "Setpoint"
|
||||
},
|
||||
"setpoint_change_source_timestamp": {
|
||||
"name": "Last change"
|
||||
},
|
||||
"temperature_offset": {
|
||||
"name": "Temperature offset"
|
||||
},
|
||||
@@ -518,6 +521,20 @@
|
||||
"rms_voltage": {
|
||||
"name": "Effective voltage"
|
||||
},
|
||||
"setpoint_change_amount": {
|
||||
"name": "Last change amount"
|
||||
},
|
||||
"setpoint_change_source": {
|
||||
"name": "Last change source",
|
||||
"state": {
|
||||
"external": "External",
|
||||
"manual": "Manual",
|
||||
"schedule": "Schedule"
|
||||
}
|
||||
},
|
||||
"setpoint_change_timestamp": {
|
||||
"name": "Last change"
|
||||
},
|
||||
"switch_current_position": {
|
||||
"name": "Current switch position"
|
||||
},
|
||||
|
||||
@@ -131,41 +131,12 @@ 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 (
|
||||
@@ -176,7 +147,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
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
|
||||
clean_cloudhook()
|
||||
data = dict(entry.data)
|
||||
data.pop(CONF_CLOUDHOOK_URL)
|
||||
hass.config_entries.async_update_entry(entry, data=data)
|
||||
|
||||
entry.async_on_unload(cloud.async_listen_connection_change(hass, manage_cloudhook))
|
||||
|
||||
|
||||
@@ -756,9 +756,10 @@ 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)
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ async def websocket_network_adapters_configure(
|
||||
|
||||
|
||||
@callback
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "network/url",
|
||||
|
||||
@@ -28,13 +28,9 @@ async def test_connection(host: str) -> str | None:
|
||||
controller = NHCController(host, 8000)
|
||||
try:
|
||||
await controller.connect()
|
||||
except TimeoutError:
|
||||
return "timeout_connect"
|
||||
except OSError:
|
||||
return "cannot_connect"
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected exception during connection")
|
||||
return "unknown"
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
return "cannot_connect"
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
},
|
||||
"error": {
|
||||
"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%]"
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"step": {
|
||||
"reconfigure": {
|
||||
|
||||
@@ -129,9 +129,6 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
key=Attribute.REMOTE_CONTROL_ENABLED,
|
||||
translation_key="remote_control",
|
||||
is_on_key="true",
|
||||
component_translation_key={
|
||||
"sub": "sub_remote_control",
|
||||
},
|
||||
)
|
||||
},
|
||||
Capability.SOUND_SENSOR: {
|
||||
|
||||
@@ -30,5 +30,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pysmartthings"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pysmartthings==3.3.4"]
|
||||
"requirements": ["pysmartthings==3.3.3"]
|
||||
}
|
||||
|
||||
@@ -98,7 +98,6 @@ class SmartThingsSelectDescription(SelectEntityDescription):
|
||||
default_options: list[str] | None = None
|
||||
extra_components: list[str] | None = None
|
||||
capability_ignore_list: list[Capability] | None = None
|
||||
value_is_integer: bool = False
|
||||
|
||||
|
||||
CAPABILITIES_TO_SELECT: dict[Capability | str, SmartThingsSelectDescription] = {
|
||||
@@ -186,15 +185,6 @@ CAPABILITIES_TO_SELECT: dict[Capability | str, SmartThingsSelectDescription] = {
|
||||
options_map=WASHER_WATER_TEMPERATURE_TO_HA,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
Capability.SAMSUNG_CE_DUST_FILTER_ALARM: SmartThingsSelectDescription(
|
||||
key=Capability.SAMSUNG_CE_DUST_FILTER_ALARM,
|
||||
translation_key="dust_filter_alarm",
|
||||
options_attribute=Attribute.SUPPORTED_ALARM_THRESHOLDS,
|
||||
status_attribute=Attribute.ALARM_THRESHOLD,
|
||||
command=Command.SET_ALARM_THRESHOLD,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
value_is_integer=True,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -263,8 +253,6 @@ class SmartThingsSelectEntity(SmartThingsEntity, SelectEntity):
|
||||
self.entity_description.options_map.get(option, option)
|
||||
for option in options
|
||||
]
|
||||
if self.entity_description.value_is_integer:
|
||||
options = [str(option) for option in options]
|
||||
return options
|
||||
|
||||
@property
|
||||
@@ -275,8 +263,6 @@ class SmartThingsSelectEntity(SmartThingsEntity, SelectEntity):
|
||||
)
|
||||
if self.entity_description.options_map:
|
||||
option = self.entity_description.options_map.get(option)
|
||||
if self.entity_description.value_is_integer and option is not None:
|
||||
option = str(option)
|
||||
return option
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
@@ -291,20 +277,17 @@ class SmartThingsSelectEntity(SmartThingsEntity, SelectEntity):
|
||||
raise ServiceValidationError(
|
||||
"Can only be updated when remote control is enabled"
|
||||
)
|
||||
new_option: str | int = option
|
||||
if self.entity_description.options_map:
|
||||
new_option = next(
|
||||
option = next(
|
||||
(
|
||||
key
|
||||
for key, value in self.entity_description.options_map.items()
|
||||
if value == option
|
||||
),
|
||||
new_option,
|
||||
option,
|
||||
)
|
||||
if self.entity_description.value_is_integer:
|
||||
new_option = int(option)
|
||||
await self.execute_device_command(
|
||||
self.entity_description.key,
|
||||
self.entity_description.command,
|
||||
new_option,
|
||||
option,
|
||||
)
|
||||
|
||||
@@ -1054,10 +1054,6 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
translation_key="washer_machine_state",
|
||||
options=WASHER_OPTIONS,
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
component_fn=lambda component: component == "sub",
|
||||
component_translation_key={
|
||||
"sub": "washer_sub_machine_state",
|
||||
},
|
||||
)
|
||||
],
|
||||
Attribute.WASHER_JOB_STATE: [
|
||||
@@ -1084,10 +1080,6 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
],
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
value_fn=lambda value: JOB_STATE_MAP.get(value, value),
|
||||
component_fn=lambda component: component == "sub",
|
||||
component_translation_key={
|
||||
"sub": "washer_sub_job_state",
|
||||
},
|
||||
)
|
||||
],
|
||||
Attribute.COMPLETION_TIME: [
|
||||
@@ -1096,10 +1088,6 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
translation_key="completion_time",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=dt_util.parse_datetime,
|
||||
component_fn=lambda component: component == "sub",
|
||||
component_translation_key={
|
||||
"sub": "washer_sub_completion_time",
|
||||
},
|
||||
)
|
||||
],
|
||||
},
|
||||
|
||||
@@ -66,9 +66,6 @@
|
||||
"remote_control": {
|
||||
"name": "Remote control"
|
||||
},
|
||||
"sub_remote_control": {
|
||||
"name": "Upper washer remote control"
|
||||
},
|
||||
"valve": {
|
||||
"name": "Valve"
|
||||
}
|
||||
@@ -164,10 +161,6 @@
|
||||
"standard": "Standard"
|
||||
}
|
||||
},
|
||||
"dust_filter_alarm": {
|
||||
"name": "Dust filter alarm threshold",
|
||||
"unit_of_measurement": "hours"
|
||||
},
|
||||
"flexible_detergent_amount": {
|
||||
"name": "Flexible compartment dispense amount",
|
||||
"state": {
|
||||
@@ -643,38 +636,6 @@
|
||||
"washer_mode": {
|
||||
"name": "Washer mode"
|
||||
},
|
||||
"washer_sub_completion_time": {
|
||||
"name": "Upper washer completion time"
|
||||
},
|
||||
"washer_sub_job_state": {
|
||||
"name": "Upper washer job state",
|
||||
"state": {
|
||||
"ai_rinse": "[%key:component::smartthings::entity::sensor::washer_job_state::state::ai_rinse%]",
|
||||
"ai_spin": "[%key:component::smartthings::entity::sensor::washer_job_state::state::ai_spin%]",
|
||||
"ai_wash": "[%key:component::smartthings::entity::sensor::washer_job_state::state::ai_wash%]",
|
||||
"air_wash": "[%key:component::smartthings::entity::sensor::dishwasher_job_state::state::air_wash%]",
|
||||
"cooling": "[%key:component::smartthings::entity::sensor::dishwasher_job_state::state::cooling%]",
|
||||
"delay_wash": "[%key:component::smartthings::entity::sensor::washer_job_state::state::delay_wash%]",
|
||||
"drying": "[%key:component::smartthings::entity::sensor::dishwasher_job_state::state::drying%]",
|
||||
"finish": "[%key:component::smartthings::entity::sensor::dishwasher_job_state::state::finish%]",
|
||||
"freeze_protection": "[%key:component::smartthings::entity::sensor::washer_job_state::state::freeze_protection%]",
|
||||
"none": "[%key:component::smartthings::entity::sensor::washer_job_state::state::none%]",
|
||||
"pre_wash": "[%key:component::smartthings::entity::sensor::dishwasher_job_state::state::pre_wash%]",
|
||||
"rinse": "[%key:component::smartthings::entity::sensor::dishwasher_job_state::state::rinse%]",
|
||||
"spin": "[%key:component::smartthings::entity::sensor::dishwasher_job_state::state::spin%]",
|
||||
"wash": "[%key:component::smartthings::entity::sensor::dishwasher_job_state::state::wash%]",
|
||||
"weight_sensing": "[%key:component::smartthings::entity::sensor::washer_job_state::state::weight_sensing%]",
|
||||
"wrinkle_prevent": "[%key:component::smartthings::entity::sensor::dishwasher_job_state::state::wrinkle_prevent%]"
|
||||
}
|
||||
},
|
||||
"washer_sub_machine_state": {
|
||||
"name": "Upper washer machine state",
|
||||
"state": {
|
||||
"pause": "[%key:common::state::paused%]",
|
||||
"run": "[%key:component::smartthings::entity::sensor::dishwasher_machine_state::state::run%]",
|
||||
"stop": "[%key:common::state::stopped%]"
|
||||
}
|
||||
},
|
||||
"water_consumption": {
|
||||
"name": "Water consumption"
|
||||
},
|
||||
|
||||
@@ -231,6 +231,10 @@ class AbstractTemplateCover(AbstractTemplateEntity, CoverEntity):
|
||||
|
||||
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.
|
||||
@@ -254,54 +258,82 @@ class AbstractTemplateCover(AbstractTemplateEntity, CoverEntity):
|
||||
@property
|
||||
def is_closed(self) -> bool | None:
|
||||
"""Return if the cover is closed."""
|
||||
if self._attr_current_cover_position is None:
|
||||
if self._position is None:
|
||||
return None
|
||||
|
||||
return self._attr_current_cover_position == 0
|
||||
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
|
||||
|
||||
@callback
|
||||
def _update_position(self, result):
|
||||
if result is None:
|
||||
self._attr_current_cover_position = None
|
||||
self._position = None
|
||||
return
|
||||
|
||||
try:
|
||||
state = float(result)
|
||||
except ValueError as err:
|
||||
_LOGGER.error(err)
|
||||
self._attr_current_cover_position = None
|
||||
self._position = None
|
||||
return
|
||||
|
||||
if state < 0 or state > 100:
|
||||
self._attr_current_cover_position = None
|
||||
self._position = None
|
||||
_LOGGER.error(
|
||||
"Cover position value must be between 0 and 100. Value was: %.2f",
|
||||
state,
|
||||
)
|
||||
else:
|
||||
self._attr_current_cover_position = state
|
||||
self._position = state
|
||||
|
||||
@callback
|
||||
def _update_tilt(self, result):
|
||||
if result is None:
|
||||
self._attr_current_cover_tilt_position = None
|
||||
self._tilt_value = None
|
||||
return
|
||||
|
||||
try:
|
||||
state = float(result)
|
||||
except ValueError as err:
|
||||
_LOGGER.error(err)
|
||||
self._attr_current_cover_tilt_position = None
|
||||
self._tilt_value = None
|
||||
return
|
||||
|
||||
if state < 0 or state > 100:
|
||||
self._attr_current_cover_tilt_position = None
|
||||
self._tilt_value = None
|
||||
_LOGGER.error(
|
||||
"Tilt value must be between 0 and 100. Value was: %.2f",
|
||||
state,
|
||||
)
|
||||
else:
|
||||
self._attr_current_cover_tilt_position = state
|
||||
self._tilt_value = state
|
||||
|
||||
def _update_opening_and_closing(self, result: Any) -> None:
|
||||
state = str(result).lower()
|
||||
@@ -309,12 +341,12 @@ class AbstractTemplateCover(AbstractTemplateEntity, CoverEntity):
|
||||
if state in _VALID_STATES:
|
||||
if not self._position_template:
|
||||
if state in ("true", OPEN_STATE):
|
||||
self._attr_current_cover_position = 100
|
||||
self._position = 100
|
||||
else:
|
||||
self._attr_current_cover_position = 0
|
||||
self._position = 0
|
||||
|
||||
self._attr_is_opening = state == OPENING_STATE
|
||||
self._attr_is_closing = state == CLOSING_STATE
|
||||
self._is_opening = state == OPENING_STATE
|
||||
self._is_closing = state == CLOSING_STATE
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"Received invalid cover is_on state: %s for entity %s. Expected: %s",
|
||||
@@ -323,10 +355,10 @@ class AbstractTemplateCover(AbstractTemplateEntity, CoverEntity):
|
||||
", ".join(_VALID_STATES),
|
||||
)
|
||||
if not self._position_template:
|
||||
self._attr_current_cover_position = None
|
||||
self._position = None
|
||||
|
||||
self._attr_is_opening = False
|
||||
self._attr_is_closing = False
|
||||
self._is_opening = False
|
||||
self._is_closing = False
|
||||
|
||||
async def async_open_cover(self, **kwargs: Any) -> None:
|
||||
"""Move the cover up."""
|
||||
@@ -339,7 +371,7 @@ class AbstractTemplateCover(AbstractTemplateEntity, CoverEntity):
|
||||
context=self._context,
|
||||
)
|
||||
if self._attr_assumed_state:
|
||||
self._attr_current_cover_position = 100
|
||||
self._position = 100
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||
@@ -353,7 +385,7 @@ class AbstractTemplateCover(AbstractTemplateEntity, CoverEntity):
|
||||
context=self._context,
|
||||
)
|
||||
if self._attr_assumed_state:
|
||||
self._attr_current_cover_position = 0
|
||||
self._position = 0
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_stop_cover(self, **kwargs: Any) -> None:
|
||||
@@ -363,10 +395,10 @@ class AbstractTemplateCover(AbstractTemplateEntity, CoverEntity):
|
||||
|
||||
async def async_set_cover_position(self, **kwargs: Any) -> None:
|
||||
"""Set cover position."""
|
||||
self._attr_current_cover_position = kwargs[ATTR_POSITION]
|
||||
self._position = kwargs[ATTR_POSITION]
|
||||
await self.async_run_script(
|
||||
self._action_scripts[POSITION_ACTION],
|
||||
run_variables={"position": self._attr_current_cover_position},
|
||||
run_variables={"position": self._position},
|
||||
context=self._context,
|
||||
)
|
||||
if self._attr_assumed_state:
|
||||
@@ -374,10 +406,10 @@ class AbstractTemplateCover(AbstractTemplateEntity, CoverEntity):
|
||||
|
||||
async def async_open_cover_tilt(self, **kwargs: Any) -> None:
|
||||
"""Tilt the cover open."""
|
||||
self._attr_current_cover_tilt_position = 100
|
||||
self._tilt_value = 100
|
||||
await self.async_run_script(
|
||||
self._action_scripts[TILT_ACTION],
|
||||
run_variables={"tilt": self._attr_current_cover_tilt_position},
|
||||
run_variables={"tilt": self._tilt_value},
|
||||
context=self._context,
|
||||
)
|
||||
if self._tilt_optimistic:
|
||||
@@ -385,10 +417,10 @@ class AbstractTemplateCover(AbstractTemplateEntity, CoverEntity):
|
||||
|
||||
async def async_close_cover_tilt(self, **kwargs: Any) -> None:
|
||||
"""Tilt the cover closed."""
|
||||
self._attr_current_cover_tilt_position = 0
|
||||
self._tilt_value = 0
|
||||
await self.async_run_script(
|
||||
self._action_scripts[TILT_ACTION],
|
||||
run_variables={"tilt": self._attr_current_cover_tilt_position},
|
||||
run_variables={"tilt": self._tilt_value},
|
||||
context=self._context,
|
||||
)
|
||||
if self._tilt_optimistic:
|
||||
@@ -396,10 +428,10 @@ class AbstractTemplateCover(AbstractTemplateEntity, CoverEntity):
|
||||
|
||||
async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
|
||||
"""Move the cover tilt to a specific position."""
|
||||
self._attr_current_cover_tilt_position = kwargs[ATTR_TILT_POSITION]
|
||||
self._tilt_value = kwargs[ATTR_TILT_POSITION]
|
||||
await self.async_run_script(
|
||||
self._action_scripts[TILT_ACTION],
|
||||
run_variables={"tilt": self._attr_current_cover_tilt_position},
|
||||
run_variables={"tilt": self._tilt_value},
|
||||
context=self._context,
|
||||
)
|
||||
if self._tilt_optimistic:
|
||||
@@ -435,11 +467,11 @@ class StateCoverEntity(TemplateEntity, AbstractTemplateCover):
|
||||
"""Set up templates."""
|
||||
if self._template:
|
||||
self.add_template_attribute(
|
||||
"_attr_current_cover_position", self._template, None, self._update_state
|
||||
"_position", self._template, None, self._update_state
|
||||
)
|
||||
if self._position_template:
|
||||
self.add_template_attribute(
|
||||
"_attr_current_cover_position",
|
||||
"_position",
|
||||
self._position_template,
|
||||
None,
|
||||
self._update_position,
|
||||
@@ -447,7 +479,7 @@ class StateCoverEntity(TemplateEntity, AbstractTemplateCover):
|
||||
)
|
||||
if self._tilt_template:
|
||||
self.add_template_attribute(
|
||||
"_attr_current_cover_tilt_position",
|
||||
"_tilt_value",
|
||||
self._tilt_template,
|
||||
None,
|
||||
self._update_tilt,
|
||||
@@ -459,7 +491,7 @@ class StateCoverEntity(TemplateEntity, AbstractTemplateCover):
|
||||
def _update_state(self, result):
|
||||
super()._update_state(result)
|
||||
if isinstance(result, TemplateError):
|
||||
self._attr_current_cover_position = None
|
||||
self._position = None
|
||||
return
|
||||
|
||||
self._update_opening_and_closing(result)
|
||||
|
||||
@@ -144,7 +144,13 @@ async def async_predict_common_control(
|
||||
if not service_data:
|
||||
continue
|
||||
|
||||
entity_ids: str | list[str] | None = service_data.get("entity_id")
|
||||
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")
|
||||
|
||||
# No entity IDs found, skip this event
|
||||
if entity_ids is None:
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
"""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()
|
||||
}
|
||||
@@ -56,6 +56,8 @@ from homeassistant.core import (
|
||||
)
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import (
|
||||
area_registry as ar,
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
issue_registry as ir,
|
||||
location as loc_helper,
|
||||
@@ -76,7 +78,7 @@ from .context import (
|
||||
template_context_manager,
|
||||
template_cv,
|
||||
)
|
||||
from .helpers import raise_no_default
|
||||
from .helpers import raise_no_default, resolve_area_id
|
||||
from .render_info import RenderInfo, render_info_cv
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -1242,6 +1244,103 @@ def issue(hass: HomeAssistant, domain: str, issue_id: str) -> dict[str, Any] | N
|
||||
return None
|
||||
|
||||
|
||||
def areas(hass: HomeAssistant) -> Iterable[str | None]:
|
||||
"""Return all areas."""
|
||||
return list(ar.async_get(hass).areas)
|
||||
|
||||
|
||||
def area_id(hass: HomeAssistant, lookup_value: str) -> str | None:
|
||||
"""Get the area ID from an area name, alias, device id, or entity id."""
|
||||
return resolve_area_id(hass, lookup_value)
|
||||
|
||||
|
||||
def _get_area_name(area_reg: ar.AreaRegistry, valid_area_id: str) -> str:
|
||||
"""Get area name from valid area ID."""
|
||||
area = area_reg.async_get_area(valid_area_id)
|
||||
assert area
|
||||
return area.name
|
||||
|
||||
|
||||
def area_name(hass: HomeAssistant, lookup_value: str) -> str | None:
|
||||
"""Get the area name from an area id, device id, or entity id."""
|
||||
area_reg = ar.async_get(hass)
|
||||
if area := area_reg.async_get_area(lookup_value):
|
||||
return area.name
|
||||
|
||||
dev_reg = dr.async_get(hass)
|
||||
ent_reg = er.async_get(hass)
|
||||
# Import here, not at top-level to avoid circular import
|
||||
from homeassistant.helpers import config_validation as cv # noqa: PLC0415
|
||||
|
||||
try:
|
||||
cv.entity_id(lookup_value)
|
||||
except vol.Invalid:
|
||||
pass
|
||||
else:
|
||||
if entity := ent_reg.async_get(lookup_value):
|
||||
# If entity has an area ID, get the area name for that
|
||||
if entity.area_id:
|
||||
return _get_area_name(area_reg, entity.area_id)
|
||||
# If entity has a device ID and the device exists with an area ID, get the
|
||||
# area name for that
|
||||
if (
|
||||
entity.device_id
|
||||
and (device := dev_reg.async_get(entity.device_id))
|
||||
and device.area_id
|
||||
):
|
||||
return _get_area_name(area_reg, device.area_id)
|
||||
|
||||
if (device := dev_reg.async_get(lookup_value)) and device.area_id:
|
||||
return _get_area_name(area_reg, device.area_id)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def area_entities(hass: HomeAssistant, area_id_or_name: str) -> Iterable[str]:
|
||||
"""Return entities for a given area ID or name."""
|
||||
_area_id: str | None
|
||||
# if area_name returns a value, we know the input was an ID, otherwise we
|
||||
# assume it's a name, and if it's neither, we return early
|
||||
if area_name(hass, area_id_or_name) is None:
|
||||
_area_id = area_id(hass, area_id_or_name)
|
||||
else:
|
||||
_area_id = area_id_or_name
|
||||
if _area_id is None:
|
||||
return []
|
||||
ent_reg = er.async_get(hass)
|
||||
entity_ids = [
|
||||
entry.entity_id for entry in er.async_entries_for_area(ent_reg, _area_id)
|
||||
]
|
||||
dev_reg = dr.async_get(hass)
|
||||
# We also need to add entities tied to a device in the area that don't themselves
|
||||
# have an area specified since they inherit the area from the device.
|
||||
entity_ids.extend(
|
||||
[
|
||||
entity.entity_id
|
||||
for device in dr.async_entries_for_area(dev_reg, _area_id)
|
||||
for entity in er.async_entries_for_device(ent_reg, device.id)
|
||||
if entity.area_id is None
|
||||
]
|
||||
)
|
||||
return entity_ids
|
||||
|
||||
|
||||
def area_devices(hass: HomeAssistant, area_id_or_name: str) -> Iterable[str]:
|
||||
"""Return device IDs for a given area ID or name."""
|
||||
_area_id: str | None
|
||||
# if area_name returns a value, we know the input was an ID, otherwise we
|
||||
# assume it's a name, and if it's neither, we return early
|
||||
if area_name(hass, area_id_or_name) is not None:
|
||||
_area_id = area_id_or_name
|
||||
else:
|
||||
_area_id = area_id(hass, area_id_or_name)
|
||||
if _area_id is None:
|
||||
return []
|
||||
dev_reg = dr.async_get(hass)
|
||||
entries = dr.async_entries_for_area(dev_reg, _area_id)
|
||||
return [entry.id for entry in entries]
|
||||
|
||||
|
||||
def closest(hass: HomeAssistant, *args: Any) -> State | None:
|
||||
"""Find closest entity.
|
||||
|
||||
@@ -2083,7 +2182,6 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment):
|
||||
] = weakref.WeakValueDictionary()
|
||||
self.add_extension("jinja2.ext.loopcontrols")
|
||||
self.add_extension("jinja2.ext.do")
|
||||
self.add_extension("homeassistant.helpers.template.extensions.AreaExtension")
|
||||
self.add_extension("homeassistant.helpers.template.extensions.Base64Extension")
|
||||
self.add_extension(
|
||||
"homeassistant.helpers.template.extensions.CollectionExtension"
|
||||
@@ -2178,6 +2276,22 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment):
|
||||
|
||||
return jinja_context(wrapper)
|
||||
|
||||
# Area extensions
|
||||
|
||||
self.globals["areas"] = hassfunction(areas)
|
||||
|
||||
self.globals["area_id"] = hassfunction(area_id)
|
||||
self.filters["area_id"] = self.globals["area_id"]
|
||||
|
||||
self.globals["area_name"] = hassfunction(area_name)
|
||||
self.filters["area_name"] = self.globals["area_name"]
|
||||
|
||||
self.globals["area_entities"] = hassfunction(area_entities)
|
||||
self.filters["area_entities"] = self.globals["area_entities"]
|
||||
|
||||
self.globals["area_devices"] = hassfunction(area_devices)
|
||||
self.filters["area_devices"] = self.globals["area_devices"]
|
||||
|
||||
# Integration extensions
|
||||
|
||||
self.globals["integration_entities"] = hassfunction(integration_entities)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Home Assistant template extensions."""
|
||||
|
||||
from .areas import AreaExtension
|
||||
from .base64 import Base64Extension
|
||||
from .collection import CollectionExtension
|
||||
from .crypto import CryptoExtension
|
||||
@@ -12,7 +11,6 @@ from .regex import RegexExtension
|
||||
from .string import StringExtension
|
||||
|
||||
__all__ = [
|
||||
"AreaExtension",
|
||||
"Base64Extension",
|
||||
"CollectionExtension",
|
||||
"CryptoExtension",
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
"""Area functions for Home Assistant templates."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.helpers import (
|
||||
area_registry as ar,
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
)
|
||||
from homeassistant.helpers.template.helpers import resolve_area_id
|
||||
|
||||
from .base import BaseTemplateExtension, TemplateFunction
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.helpers.template import TemplateEnvironment
|
||||
|
||||
|
||||
class AreaExtension(BaseTemplateExtension):
|
||||
"""Extension for area-related template functions."""
|
||||
|
||||
def __init__(self, environment: TemplateEnvironment) -> None:
|
||||
"""Initialize the area extension."""
|
||||
super().__init__(
|
||||
environment,
|
||||
functions=[
|
||||
TemplateFunction(
|
||||
"areas",
|
||||
self.areas,
|
||||
as_global=True,
|
||||
requires_hass=True,
|
||||
),
|
||||
TemplateFunction(
|
||||
"area_id",
|
||||
self.area_id,
|
||||
as_global=True,
|
||||
as_filter=True,
|
||||
requires_hass=True,
|
||||
limited_ok=False,
|
||||
),
|
||||
TemplateFunction(
|
||||
"area_name",
|
||||
self.area_name,
|
||||
as_global=True,
|
||||
as_filter=True,
|
||||
requires_hass=True,
|
||||
limited_ok=False,
|
||||
),
|
||||
TemplateFunction(
|
||||
"area_entities",
|
||||
self.area_entities,
|
||||
as_global=True,
|
||||
as_filter=True,
|
||||
requires_hass=True,
|
||||
),
|
||||
TemplateFunction(
|
||||
"area_devices",
|
||||
self.area_devices,
|
||||
as_global=True,
|
||||
as_filter=True,
|
||||
requires_hass=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
def areas(self) -> Iterable[str | None]:
|
||||
"""Return all areas."""
|
||||
return list(ar.async_get(self.hass).areas)
|
||||
|
||||
def area_id(self, lookup_value: str) -> str | None:
|
||||
"""Get the area ID from an area name, alias, device id, or entity id."""
|
||||
return resolve_area_id(self.hass, lookup_value)
|
||||
|
||||
def _get_area_name(self, area_reg: ar.AreaRegistry, valid_area_id: str) -> str:
|
||||
"""Get area name from valid area ID."""
|
||||
area = area_reg.async_get_area(valid_area_id)
|
||||
assert area
|
||||
return area.name
|
||||
|
||||
def area_name(self, lookup_value: str) -> str | None:
|
||||
"""Get the area name from an area id, device id, or entity id."""
|
||||
area_reg = ar.async_get(self.hass)
|
||||
if area := area_reg.async_get_area(lookup_value):
|
||||
return area.name
|
||||
|
||||
dev_reg = dr.async_get(self.hass)
|
||||
ent_reg = er.async_get(self.hass)
|
||||
# Import here, not at top-level to avoid circular import
|
||||
from homeassistant.helpers import config_validation as cv # noqa: PLC0415
|
||||
|
||||
try:
|
||||
cv.entity_id(lookup_value)
|
||||
except vol.Invalid:
|
||||
pass
|
||||
else:
|
||||
if entity := ent_reg.async_get(lookup_value):
|
||||
# If entity has an area ID, get the area name for that
|
||||
if entity.area_id:
|
||||
return self._get_area_name(area_reg, entity.area_id)
|
||||
# If entity has a device ID and the device exists with an area ID, get the
|
||||
# area name for that
|
||||
if (
|
||||
entity.device_id
|
||||
and (device := dev_reg.async_get(entity.device_id))
|
||||
and device.area_id
|
||||
):
|
||||
return self._get_area_name(area_reg, device.area_id)
|
||||
|
||||
if (device := dev_reg.async_get(lookup_value)) and device.area_id:
|
||||
return self._get_area_name(area_reg, device.area_id)
|
||||
|
||||
return None
|
||||
|
||||
def area_entities(self, area_id_or_name: str) -> Iterable[str]:
|
||||
"""Return entities for a given area ID or name."""
|
||||
_area_id: str | None
|
||||
# if area_name returns a value, we know the input was an ID, otherwise we
|
||||
# assume it's a name, and if it's neither, we return early
|
||||
if self.area_name(area_id_or_name) is None:
|
||||
_area_id = self.area_id(area_id_or_name)
|
||||
else:
|
||||
_area_id = area_id_or_name
|
||||
if _area_id is None:
|
||||
return []
|
||||
ent_reg = er.async_get(self.hass)
|
||||
entity_ids = [
|
||||
entry.entity_id for entry in er.async_entries_for_area(ent_reg, _area_id)
|
||||
]
|
||||
dev_reg = dr.async_get(self.hass)
|
||||
# We also need to add entities tied to a device in the area that don't themselves
|
||||
# have an area specified since they inherit the area from the device.
|
||||
entity_ids.extend(
|
||||
[
|
||||
entity.entity_id
|
||||
for device in dr.async_entries_for_area(dev_reg, _area_id)
|
||||
for entity in er.async_entries_for_device(ent_reg, device.id)
|
||||
if entity.area_id is None
|
||||
]
|
||||
)
|
||||
return entity_ids
|
||||
|
||||
def area_devices(self, area_id_or_name: str) -> Iterable[str]:
|
||||
"""Return device IDs for a given area ID or name."""
|
||||
_area_id: str | None
|
||||
# if area_name returns a value, we know the input was an ID, otherwise we
|
||||
# assume it's a name, and if it's neither, we return early
|
||||
if self.area_name(area_id_or_name) is not None:
|
||||
_area_id = area_id_or_name
|
||||
else:
|
||||
_area_id = self.area_id(area_id_or_name)
|
||||
if _area_id is None:
|
||||
return []
|
||||
dev_reg = dr.async_get(self.hass)
|
||||
entries = dr.async_entries_for_area(dev_reg, _area_id)
|
||||
return [entry.id for entry in entries]
|
||||
@@ -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.1
|
||||
home-assistant-frontend==20251105.0
|
||||
home-assistant-intents==2025.11.7
|
||||
httpx==0.28.1
|
||||
ifaddr==0.2.0
|
||||
@@ -210,6 +210,10 @@ aiofiles>=24.1.0
|
||||
# https://github.com/aio-libs/multidict/issues/1131
|
||||
multidict>=6.4.2
|
||||
|
||||
# rpds-py frequently updates cargo causing build failures
|
||||
# No wheels upstream available for armhf & armv7
|
||||
rpds-py==0.26.0
|
||||
|
||||
# Constraint num2words to 0.5.14 as 0.5.15 and 0.5.16 were removed from PyPI
|
||||
num2words==0.5.14
|
||||
|
||||
|
||||
8
requirements_all.txt
generated
8
requirements_all.txt
generated
@@ -1188,7 +1188,7 @@ hole==0.9.0
|
||||
holidays==0.84
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20251105.1
|
||||
home-assistant-frontend==20251105.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2025.11.7
|
||||
@@ -2132,7 +2132,7 @@ pykwb==0.0.8
|
||||
pylacrosse==0.4
|
||||
|
||||
# homeassistant.components.lamarzocco
|
||||
pylamarzocco==2.2.0
|
||||
pylamarzocco==2.1.3
|
||||
|
||||
# homeassistant.components.lastfm
|
||||
pylast==5.1.0
|
||||
@@ -2387,7 +2387,7 @@ pysmappee==0.2.29
|
||||
pysmarlaapi==0.9.2
|
||||
|
||||
# homeassistant.components.smartthings
|
||||
pysmartthings==3.3.4
|
||||
pysmartthings==3.3.3
|
||||
|
||||
# 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.5
|
||||
python-awair==0.2.4
|
||||
|
||||
# homeassistant.components.blockchain
|
||||
python-blockchain-api==0.0.2
|
||||
|
||||
8
requirements_test_all.txt
generated
8
requirements_test_all.txt
generated
@@ -1040,7 +1040,7 @@ hole==0.9.0
|
||||
holidays==0.84
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20251105.1
|
||||
home-assistant-frontend==20251105.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2025.11.7
|
||||
@@ -1779,7 +1779,7 @@ pykrakenapi==0.1.8
|
||||
pykulersky==0.5.8
|
||||
|
||||
# homeassistant.components.lamarzocco
|
||||
pylamarzocco==2.2.0
|
||||
pylamarzocco==2.1.3
|
||||
|
||||
# homeassistant.components.lastfm
|
||||
pylast==5.1.0
|
||||
@@ -1992,7 +1992,7 @@ pysmappee==0.2.29
|
||||
pysmarlaapi==0.9.2
|
||||
|
||||
# homeassistant.components.smartthings
|
||||
pysmartthings==3.3.4
|
||||
pysmartthings==3.3.3
|
||||
|
||||
# homeassistant.components.smarty
|
||||
pysmarty2==0.10.3
|
||||
@@ -2040,7 +2040,7 @@ pytautulli==23.1.1
|
||||
python-MotionMount==2.3.0
|
||||
|
||||
# homeassistant.components.awair
|
||||
python-awair==0.2.5
|
||||
python-awair==0.2.4
|
||||
|
||||
# homeassistant.components.bsblan
|
||||
python-bsblan==3.1.1
|
||||
|
||||
@@ -52,11 +52,30 @@ OVERRIDDEN_REQUIREMENTS_ACTIONS = {
|
||||
"include": INCLUDED_REQUIREMENTS_WHEELS,
|
||||
"markers": {},
|
||||
},
|
||||
# Pandas has issues building on armhf, it is expected they
|
||||
# will drop the platform in the near future (they consider it
|
||||
# "flimsy" on 386). The following packages depend on pandas,
|
||||
# so we comment them out.
|
||||
"wheels_armhf": {
|
||||
"exclude": {"env-canada", "noaa-coops", "pyezviz", "pykrakenapi"},
|
||||
"include": INCLUDED_REQUIREMENTS_WHEELS,
|
||||
"markers": {},
|
||||
},
|
||||
"wheels_armv7": {
|
||||
"exclude": set(),
|
||||
"include": INCLUDED_REQUIREMENTS_WHEELS,
|
||||
"markers": {},
|
||||
},
|
||||
"wheels_amd64": {
|
||||
"exclude": set(),
|
||||
"include": INCLUDED_REQUIREMENTS_WHEELS,
|
||||
"markers": {},
|
||||
},
|
||||
"wheels_i386": {
|
||||
"exclude": set(),
|
||||
"include": INCLUDED_REQUIREMENTS_WHEELS,
|
||||
"markers": {},
|
||||
},
|
||||
}
|
||||
|
||||
URL_PIN = (
|
||||
@@ -202,6 +221,10 @@ aiofiles>=24.1.0
|
||||
# https://github.com/aio-libs/multidict/issues/1131
|
||||
multidict>=6.4.2
|
||||
|
||||
# rpds-py frequently updates cargo causing build failures
|
||||
# No wheels upstream available for armhf & armv7
|
||||
rpds-py==0.26.0
|
||||
|
||||
# Constraint num2words to 0.5.14 as 0.5.15 and 0.5.16 were removed from PyPI
|
||||
num2words==0.5.14
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ ARG BUILD_ARCH
|
||||
RUN \
|
||||
case "${{BUILD_ARCH}}" in \
|
||||
"aarch64") go2rtc_suffix='arm64' ;; \
|
||||
"armhf") go2rtc_suffix='armv6' ;; \
|
||||
"armv7") go2rtc_suffix='arm' ;; \
|
||||
*) go2rtc_suffix=${{BUILD_ARCH}} ;; \
|
||||
esac \
|
||||
&& curl -L https://github.com/AlexxIT/go2rtc/releases/download/v{go2rtc}/go2rtc_linux_${{go2rtc_suffix}} --output /bin/go2rtc \
|
||||
|
||||
@@ -11,7 +11,6 @@ from homeassistant.components.cloud import (
|
||||
CloudNotAvailable,
|
||||
CloudNotConnected,
|
||||
async_get_or_create_cloudhook,
|
||||
async_listen_cloudhook_change,
|
||||
async_listen_connection_change,
|
||||
async_remote_ui_url,
|
||||
)
|
||||
@@ -312,149 +311,3 @@ 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
|
||||
|
||||
@@ -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: ""\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',
|
||||
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',
|
||||
),
|
||||
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 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',
|
||||
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',
|
||||
),
|
||||
dict({
|
||||
}),
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
from collections.abc import Awaitable, Callable
|
||||
import logging
|
||||
from typing import NamedTuple
|
||||
from unittest.mock import ANY, AsyncMock, Mock, patch
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
from aiohttp import UnixConnector
|
||||
from aiohttp.client_exceptions import ClientConnectionError, ServerConnectionError
|
||||
from awesomeversion import AwesomeVersion
|
||||
from go2rtc_client import Stream
|
||||
@@ -39,13 +38,11 @@ from homeassistant.components.go2rtc.const import (
|
||||
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
|
||||
@@ -218,9 +215,7 @@ 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", ANY, enable_ui=ui_enabled
|
||||
)
|
||||
server.assert_called_once_with(hass, "/usr/bin/go2rtc", enable_ui=ui_enabled)
|
||||
server_start.assert_called_once()
|
||||
|
||||
await _test_setup_and_signaling(
|
||||
@@ -910,53 +905,3 @@ 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)
|
||||
|
||||
@@ -23,15 +23,9 @@ def enable_ui() -> bool:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
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:
|
||||
def server(hass: HomeAssistant, enable_ui: bool) -> Server:
|
||||
"""Fixture to initialize the Server."""
|
||||
return Server(hass, binary=TEST_BINARY, session=mock_session, enable_ui=enable_ui)
|
||||
return Server(hass, binary=TEST_BINARY, enable_ui=enable_ui)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -147,7 +147,9 @@ def mock_bluetooth(enable_bluetooth: None) -> None:
|
||||
@pytest.fixture
|
||||
def mock_ble_device() -> BLEDevice:
|
||||
"""Return a mock BLE device."""
|
||||
return BLEDevice("00:00:00:00:00:00", "GS_GS012345", details={"path": "path"})
|
||||
return BLEDevice(
|
||||
"00:00:00:00:00:00", "GS_GS012345", details={"path": "path"}, rssi=50
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from bleak.backends.device import BLEDevice
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from pylamarzocco.const import FirmwareType, ModelName
|
||||
from pylamarzocco.exceptions import AuthFail, RequestNotSuccessful
|
||||
@@ -197,27 +196,11 @@ async def test_config_flow_entry_migration_downgrade(
|
||||
assert not await hass.config_entries.async_setup(entry.entry_id)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("ble_device", "has_client"),
|
||||
[
|
||||
(None, False),
|
||||
(
|
||||
BLEDevice(
|
||||
address="aa:bb:cc:dd:ee:ff",
|
||||
name="name",
|
||||
details={},
|
||||
),
|
||||
True,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_bluetooth_is_set_from_discovery(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_cloud_client: MagicMock,
|
||||
ble_device: BLEDevice | None,
|
||||
has_client: bool,
|
||||
) -> None:
|
||||
"""Check we can fill a device from discovery info."""
|
||||
|
||||
@@ -233,17 +216,13 @@ async def test_bluetooth_is_set_from_discovery(
|
||||
patch(
|
||||
"homeassistant.components.lamarzocco.LaMarzoccoMachine"
|
||||
) as mock_machine_class,
|
||||
patch(
|
||||
"homeassistant.components.lamarzocco.async_ble_device_from_address",
|
||||
return_value=ble_device,
|
||||
),
|
||||
):
|
||||
mock_machine_class.return_value = mock_lamarzocco
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
discovery.assert_called_once()
|
||||
assert mock_machine_class.call_count == 1
|
||||
_, kwargs = mock_machine_class.call_args
|
||||
assert (kwargs["bluetooth_client"] is not None) == has_client
|
||||
assert kwargs["bluetooth_client"] is not None
|
||||
|
||||
assert mock_config_entry.data[CONF_MAC] == service_info.address
|
||||
assert mock_config_entry.data[CONF_TOKEN] == "token"
|
||||
@@ -335,50 +314,6 @@ async def test_device(
|
||||
assert device == snapshot
|
||||
|
||||
|
||||
async def test_disconnect_on_stop(
|
||||
hass: HomeAssistant,
|
||||
mock_lamarzocco: MagicMock,
|
||||
mock_ble_device: BLEDevice,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test we close the connection with the La Marzocco when Home Assistants stops."""
|
||||
mock_config_entry = MockConfigEntry(
|
||||
title="My LaMarzocco",
|
||||
domain=DOMAIN,
|
||||
version=4,
|
||||
data=USER_INPUT
|
||||
| {
|
||||
CONF_MAC: mock_ble_device.address,
|
||||
CONF_TOKEN: "token",
|
||||
CONF_INSTALLATION_KEY: MOCK_INSTALLATION_KEY,
|
||||
},
|
||||
unique_id=mock_lamarzocco.serial_number,
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.lamarzocco.async_ble_device_from_address",
|
||||
return_value=mock_ble_device,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.lamarzocco.LaMarzoccoBluetoothClient",
|
||||
autospec=True,
|
||||
) as mock_bt_client_cls,
|
||||
):
|
||||
mock_bt_client = mock_bt_client_cls.return_value
|
||||
mock_bt_client.disconnect = AsyncMock()
|
||||
|
||||
await async_init_integration(hass, mock_config_entry)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_bt_client.disconnect.assert_awaited_once()
|
||||
|
||||
|
||||
async def test_websocket_reconnects_after_termination(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
|
||||
@@ -113,6 +113,7 @@ async def integration_fixture(
|
||||
"light_sensor",
|
||||
"microwave_oven",
|
||||
"mock_lock",
|
||||
"mock_thermostat",
|
||||
"mounted_dimmable_load_control_fixture",
|
||||
"multi_endpoint_light",
|
||||
"occupancy_sensor",
|
||||
|
||||
526
tests/components/matter/fixtures/nodes/mock_thermostat.json
Normal file
526
tests/components/matter/fixtures/nodes/mock_thermostat.json
Normal file
@@ -0,0 +1,526 @@
|
||||
{
|
||||
"node_id": 150,
|
||||
"date_commissioned": "2025-11-18T06:53:08.679289",
|
||||
"last_interview": "2025-11-18T06:53:08.679325",
|
||||
"interview_version": 6,
|
||||
"available": true,
|
||||
"is_bridge": false,
|
||||
"attributes": {
|
||||
"0/49/0": 1,
|
||||
"0/49/1": [
|
||||
{
|
||||
"0": "ZW5zMzM=",
|
||||
"1": true
|
||||
}
|
||||
],
|
||||
"0/49/4": true,
|
||||
"0/49/5": 0,
|
||||
"0/49/6": "ZW5zMzM=",
|
||||
"0/49/7": null,
|
||||
"0/49/65532": 4,
|
||||
"0/49/65533": 2,
|
||||
"0/49/65528": [],
|
||||
"0/49/65529": [],
|
||||
"0/49/65531": [0, 1, 4, 5, 6, 7, 65532, 65533, 65528, 65529, 65531],
|
||||
"0/65/0": [],
|
||||
"0/65/65532": 0,
|
||||
"0/65/65533": 1,
|
||||
"0/65/65528": [],
|
||||
"0/65/65529": [],
|
||||
"0/65/65531": [0, 65532, 65533, 65528, 65529, 65531],
|
||||
"0/63/0": [],
|
||||
"0/63/1": [],
|
||||
"0/63/2": 4,
|
||||
"0/63/3": 3,
|
||||
"0/63/65532": 0,
|
||||
"0/63/65533": 2,
|
||||
"0/63/65528": [5, 2],
|
||||
"0/63/65529": [0, 1, 3, 4],
|
||||
"0/63/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531],
|
||||
"0/62/0": [
|
||||
{
|
||||
"1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRlhgkBwEkCAEwCUEE2p7AKvoklmZUFHB0JFUiCsv5FCm0dmeH35yXz4UUH4HAWUwpbeU+R7hMGbAITM3T1R/mVWYthssdVcPNsfIVcjcKNQEoARgkAgE2AwQCBAEYMAQUQbZ3toX8hpE/FmJz7M6xHTbh6RMwBRS5+zzv8ZPGnI9mC3wH9vq10JnwlhgwC0DughBITJJHW/pS7o0J6o6FYTe1ufe0vCpaCj3qYeWb/QxLUydUaJQbce5Z3lUcFeHybUa/M9HID+0PRp2Ker3/GA==",
|
||||
"2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEE/DujEcdTsX19xbxX+KuKKWiMaA5D9u99P/pVxIOmscd2BA2PadEMNnjvtPOpf+WE2Zxar4rby1IfAClGUUuQrTcKNQEpARgkAmAwBBS5+zzv8ZPGnI9mC3wH9vq10JnwljAFFPT6p93JKGcb7g+rTWnA6evF2EdGGDALQGkPpvsbkAFEbfPN6H3Kf23R0zzmW/gpAA3kgaL6wKB2Ofm+Tmylw22qM536Kj8mOMwaV0EL1dCCGcuxF98aL6gY",
|
||||
"254": 1
|
||||
}
|
||||
],
|
||||
"0/62/1": [
|
||||
{
|
||||
"1": "BBmX+KwLR5HGlVNbvlC+dO8Jv9fPthHiTfGpUzi2JJADX5az6GxBAFn02QKHwLcZHyh+lh9faf6rf38/nPYF7/M=",
|
||||
"2": 4939,
|
||||
"3": 2,
|
||||
"4": 150,
|
||||
"5": "ha",
|
||||
"254": 1
|
||||
}
|
||||
],
|
||||
"0/62/2": 16,
|
||||
"0/62/3": 1,
|
||||
"0/62/4": [
|
||||
"FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEEGZf4rAtHkcaVU1u+UL507wm/18+2EeJN8alTOLYkkANflrPobEEAWfTZAofAtxkfKH6WH19p/qt/fz+c9gXv8zcKNQEpARgkAmAwBBT0+qfdyShnG+4Pq01pwOnrxdhHRjAFFPT6p93JKGcb7g+rTWnA6evF2EdGGDALQPVrsFnfFplsQGV5m5EUua+rmo9hAr+OP1bvaifdLqiEIn3uXLTLoKmVUkPImRL2Fb+xcMEAqR2p7RM6ZlFCR20Y"
|
||||
],
|
||||
"0/62/5": 1,
|
||||
"0/62/65532": 0,
|
||||
"0/62/65533": 2,
|
||||
"0/62/65528": [1, 3, 5, 8, 14],
|
||||
"0/62/65529": [0, 2, 4, 6, 7, 9, 10, 11, 12, 13],
|
||||
"0/62/65531": [0, 1, 2, 3, 4, 5, 65532, 65533, 65528, 65529, 65531],
|
||||
"0/60/0": 0,
|
||||
"0/60/1": null,
|
||||
"0/60/2": null,
|
||||
"0/60/65532": 0,
|
||||
"0/60/65533": 1,
|
||||
"0/60/65528": [],
|
||||
"0/60/65529": [0, 2],
|
||||
"0/60/65531": [0, 1, 2, 65532, 65533, 65528, 65529, 65531],
|
||||
"0/55/2": 425,
|
||||
"0/55/3": 61,
|
||||
"0/55/4": 0,
|
||||
"0/55/5": 0,
|
||||
"0/55/6": 0,
|
||||
"0/55/7": null,
|
||||
"0/55/1": true,
|
||||
"0/55/0": 2,
|
||||
"0/55/8": 16,
|
||||
"0/55/65532": 3,
|
||||
"0/55/65533": 1,
|
||||
"0/55/65528": [],
|
||||
"0/55/65529": [0],
|
||||
"0/55/65531": [
|
||||
2, 3, 4, 5, 6, 7, 1, 0, 8, 65532, 65533, 65528, 65529, 65531
|
||||
],
|
||||
"0/54/0": null,
|
||||
"0/54/1": null,
|
||||
"0/54/2": 3,
|
||||
"0/54/3": null,
|
||||
"0/54/4": null,
|
||||
"0/54/5": null,
|
||||
"0/54/12": null,
|
||||
"0/54/6": null,
|
||||
"0/54/7": null,
|
||||
"0/54/8": null,
|
||||
"0/54/9": null,
|
||||
"0/54/10": null,
|
||||
"0/54/11": null,
|
||||
"0/54/65532": 3,
|
||||
"0/54/65533": 1,
|
||||
"0/54/65528": [],
|
||||
"0/54/65529": [0],
|
||||
"0/54/65531": [
|
||||
0, 1, 2, 3, 4, 5, 12, 6, 7, 8, 9, 10, 11, 65532, 65533, 65528, 65529,
|
||||
65531
|
||||
],
|
||||
"0/52/0": [
|
||||
{
|
||||
"0": 6163,
|
||||
"1": "6163"
|
||||
},
|
||||
{
|
||||
"0": 6162,
|
||||
"1": "6162"
|
||||
},
|
||||
{
|
||||
"0": 6161,
|
||||
"1": "6161"
|
||||
},
|
||||
{
|
||||
"0": 6160,
|
||||
"1": "6160"
|
||||
},
|
||||
{
|
||||
"0": 6159,
|
||||
"1": "6159"
|
||||
}
|
||||
],
|
||||
"0/52/1": 545392,
|
||||
"0/52/2": 650640,
|
||||
"0/52/3": 650640,
|
||||
"0/52/65532": 1,
|
||||
"0/52/65533": 1,
|
||||
"0/52/65528": [],
|
||||
"0/52/65529": [0],
|
||||
"0/52/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531],
|
||||
"0/51/0": [
|
||||
{
|
||||
"0": "docker0",
|
||||
"1": false,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "8mJ0KirG",
|
||||
"5": ["rBEAAQ=="],
|
||||
"6": [],
|
||||
"7": 0
|
||||
},
|
||||
{
|
||||
"0": "ens33",
|
||||
"1": true,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "AAwpaqXN",
|
||||
"5": ["wKgBxA=="],
|
||||
"6": [
|
||||
"KgEOCgKzOZAcmuLd4EsaUA==",
|
||||
"KgEOCgKzOZA2wMm9YG06Ag==",
|
||||
"/oAAAAAAAACluAo+qvkuxw=="
|
||||
],
|
||||
"7": 2
|
||||
},
|
||||
{
|
||||
"0": "lo",
|
||||
"1": true,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "AAAAAAAA",
|
||||
"5": ["fwAAAQ=="],
|
||||
"6": ["AAAAAAAAAAAAAAAAAAAAAQ=="],
|
||||
"7": 0
|
||||
}
|
||||
],
|
||||
"0/51/1": 1,
|
||||
"0/51/8": false,
|
||||
"0/51/3": 0,
|
||||
"0/51/4": 0,
|
||||
"0/51/2": 16,
|
||||
"0/51/65532": 0,
|
||||
"0/51/65533": 2,
|
||||
"0/51/65528": [2],
|
||||
"0/51/65529": [0, 1],
|
||||
"0/51/65531": [0, 1, 8, 3, 4, 2, 65532, 65533, 65528, 65529, 65531],
|
||||
"0/50/65532": 0,
|
||||
"0/50/65533": 1,
|
||||
"0/50/65528": [1],
|
||||
"0/50/65529": [0],
|
||||
"0/50/65531": [65532, 65533, 65528, 65529, 65531],
|
||||
"0/48/0": 0,
|
||||
"0/48/1": {
|
||||
"0": 60,
|
||||
"1": 900
|
||||
},
|
||||
"0/48/2": 0,
|
||||
"0/48/3": 2,
|
||||
"0/48/4": true,
|
||||
"0/48/65532": 0,
|
||||
"0/48/65533": 2,
|
||||
"0/48/65528": [1, 3, 5],
|
||||
"0/48/65529": [0, 2, 4],
|
||||
"0/48/65531": [0, 1, 2, 3, 4, 65532, 65533, 65528, 65529, 65531],
|
||||
"0/43/0": "en-US",
|
||||
"0/43/1": ["en-US"],
|
||||
"0/43/65532": 0,
|
||||
"0/43/65533": 1,
|
||||
"0/43/65528": [],
|
||||
"0/43/65529": [],
|
||||
"0/43/65531": [0, 1, 65532, 65533, 65528, 65529, 65531],
|
||||
"0/40/0": 19,
|
||||
"0/40/1": "TEST_VENDOR",
|
||||
"0/40/2": 65521,
|
||||
"0/40/3": "Mock Thermostat",
|
||||
"0/40/4": 32769,
|
||||
"0/40/5": "",
|
||||
"0/40/6": "**REDACTED**",
|
||||
"0/40/7": 0,
|
||||
"0/40/8": "TEST_VERSION",
|
||||
"0/40/9": 1,
|
||||
"0/40/10": "1.0",
|
||||
"0/40/19": {
|
||||
"0": 3,
|
||||
"1": 65535
|
||||
},
|
||||
"0/40/21": 17104896,
|
||||
"0/40/22": 1,
|
||||
"0/40/24": 1,
|
||||
"0/40/11": "20200101",
|
||||
"0/40/12": "",
|
||||
"0/40/13": "",
|
||||
"0/40/14": "",
|
||||
"0/40/15": "TEST_SN",
|
||||
"0/40/16": false,
|
||||
"0/40/18": "29DB8B9DB518F05F",
|
||||
"0/40/65532": 0,
|
||||
"0/40/65533": 5,
|
||||
"0/40/65528": [],
|
||||
"0/40/65529": [],
|
||||
"0/40/65531": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 19, 21, 22, 24, 11, 12, 13, 14, 15, 16,
|
||||
18, 65532, 65533, 65528, 65529, 65531
|
||||
],
|
||||
"0/31/0": [
|
||||
{
|
||||
"1": 5,
|
||||
"2": 2,
|
||||
"3": [112233],
|
||||
"4": null,
|
||||
"254": 1
|
||||
}
|
||||
],
|
||||
"0/31/2": 4,
|
||||
"0/31/3": 3,
|
||||
"0/31/4": 4,
|
||||
"0/31/65532": 0,
|
||||
"0/31/65533": 3,
|
||||
"0/31/65528": [],
|
||||
"0/31/65529": [],
|
||||
"0/31/65531": [0, 2, 3, 4, 65532, 65533, 65528, 65529, 65531],
|
||||
"0/30/0": [],
|
||||
"0/30/65532": 0,
|
||||
"0/30/65533": 1,
|
||||
"0/30/65528": [],
|
||||
"0/30/65529": [],
|
||||
"0/30/65531": [0, 65532, 65533, 65528, 65529, 65531],
|
||||
"0/29/0": [
|
||||
{
|
||||
"0": 18,
|
||||
"1": 1
|
||||
},
|
||||
{
|
||||
"0": 22,
|
||||
"1": 3
|
||||
}
|
||||
],
|
||||
"0/29/1": [
|
||||
49, 65, 63, 62, 60, 55, 54, 52, 51, 50, 48, 43, 40, 31, 30, 29, 3, 42, 45,
|
||||
53
|
||||
],
|
||||
"0/29/2": [41],
|
||||
"0/29/3": [1],
|
||||
"0/29/65532": 0,
|
||||
"0/29/65533": 3,
|
||||
"0/29/65528": [],
|
||||
"0/29/65529": [],
|
||||
"0/29/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531],
|
||||
"0/3/0": 0,
|
||||
"0/3/1": 2,
|
||||
"0/3/65532": 0,
|
||||
"0/3/65533": 6,
|
||||
"0/3/65528": [],
|
||||
"0/3/65529": [0, 64],
|
||||
"0/3/65531": [0, 1, 65532, 65533, 65528, 65529, 65531],
|
||||
"0/42/0": [],
|
||||
"0/42/1": true,
|
||||
"0/42/2": 0,
|
||||
"0/42/3": 0,
|
||||
"0/42/65532": 0,
|
||||
"0/42/65533": 1,
|
||||
"0/42/65528": [],
|
||||
"0/42/65529": [0],
|
||||
"0/42/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531],
|
||||
"0/45/0": 1,
|
||||
"0/45/65532": 1,
|
||||
"0/45/65533": 2,
|
||||
"0/45/65528": [],
|
||||
"0/45/65529": [],
|
||||
"0/45/65531": [0, 65532, 65533, 65528, 65529, 65531],
|
||||
"0/53/0": null,
|
||||
"0/53/1": null,
|
||||
"0/53/2": null,
|
||||
"0/53/3": null,
|
||||
"0/53/4": null,
|
||||
"0/53/5": null,
|
||||
"0/53/6": 0,
|
||||
"0/53/7": [],
|
||||
"0/53/8": [],
|
||||
"0/53/9": null,
|
||||
"0/53/10": null,
|
||||
"0/53/11": null,
|
||||
"0/53/12": null,
|
||||
"0/53/13": null,
|
||||
"0/53/14": 0,
|
||||
"0/53/15": 0,
|
||||
"0/53/16": 0,
|
||||
"0/53/17": 0,
|
||||
"0/53/18": 0,
|
||||
"0/53/19": 0,
|
||||
"0/53/20": 0,
|
||||
"0/53/21": 0,
|
||||
"0/53/22": 0,
|
||||
"0/53/23": 0,
|
||||
"0/53/24": 0,
|
||||
"0/53/25": 0,
|
||||
"0/53/26": 0,
|
||||
"0/53/27": 0,
|
||||
"0/53/28": 0,
|
||||
"0/53/29": 0,
|
||||
"0/53/30": 0,
|
||||
"0/53/31": 0,
|
||||
"0/53/32": 0,
|
||||
"0/53/33": 0,
|
||||
"0/53/34": 0,
|
||||
"0/53/35": 0,
|
||||
"0/53/36": 0,
|
||||
"0/53/37": 0,
|
||||
"0/53/38": 0,
|
||||
"0/53/39": 0,
|
||||
"0/53/40": 0,
|
||||
"0/53/41": 0,
|
||||
"0/53/42": 0,
|
||||
"0/53/43": 0,
|
||||
"0/53/44": 0,
|
||||
"0/53/45": 0,
|
||||
"0/53/46": 0,
|
||||
"0/53/47": 0,
|
||||
"0/53/48": 0,
|
||||
"0/53/49": 0,
|
||||
"0/53/50": 0,
|
||||
"0/53/51": 0,
|
||||
"0/53/52": 0,
|
||||
"0/53/53": 0,
|
||||
"0/53/54": 0,
|
||||
"0/53/55": 0,
|
||||
"0/53/56": null,
|
||||
"0/53/57": null,
|
||||
"0/53/58": null,
|
||||
"0/53/59": null,
|
||||
"0/53/60": null,
|
||||
"0/53/61": null,
|
||||
"0/53/62": [],
|
||||
"0/53/65532": 15,
|
||||
"0/53/65533": 3,
|
||||
"0/53/65528": [],
|
||||
"0/53/65529": [0],
|
||||
"0/53/65531": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
|
||||
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
|
||||
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
|
||||
57, 58, 59, 60, 61, 62, 65532, 65533, 65528, 65529, 65531
|
||||
],
|
||||
"1/29/0": [
|
||||
{
|
||||
"0": 769,
|
||||
"1": 4
|
||||
}
|
||||
],
|
||||
"1/29/1": [29, 3, 4, 513, 516],
|
||||
"1/29/2": [3],
|
||||
"1/29/3": [],
|
||||
"1/29/65532": 0,
|
||||
"1/29/65533": 3,
|
||||
"1/29/65528": [],
|
||||
"1/29/65529": [],
|
||||
"1/29/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531],
|
||||
"1/3/0": 0,
|
||||
"1/3/1": 2,
|
||||
"1/3/65532": 0,
|
||||
"1/3/65533": 6,
|
||||
"1/3/65528": [],
|
||||
"1/3/65529": [0, 64],
|
||||
"1/3/65531": [0, 1, 65532, 65533, 65528, 65529, 65531],
|
||||
"1/4/0": 128,
|
||||
"1/4/65532": 1,
|
||||
"1/4/65533": 4,
|
||||
"1/4/65528": [0, 1, 2, 3],
|
||||
"1/4/65529": [0, 1, 2, 3, 4, 5],
|
||||
"1/4/65531": [0, 65532, 65533, 65528, 65529, 65531],
|
||||
|
||||
"1/513/0": 1800,
|
||||
"1/513/1": 500,
|
||||
"1/513/3": 700,
|
||||
"1/513/4": 3000,
|
||||
"1/513/5": 1600,
|
||||
"1/513/6": 3200,
|
||||
"1/513/7": 0,
|
||||
"1/513/8": 25,
|
||||
"1/513/16": 0,
|
||||
"1/513/17": 2600,
|
||||
"1/513/18": 2000,
|
||||
"1/513/21": 700,
|
||||
"1/513/22": 3000,
|
||||
"1/513/23": 1600,
|
||||
"1/513/24": 3200,
|
||||
"1/513/25": 25,
|
||||
"1/513/26": 0,
|
||||
"1/513/27": 4,
|
||||
"1/513/28": 1,
|
||||
"1/513/30": 4,
|
||||
"1/513/35": 0,
|
||||
"1/513/36": 0,
|
||||
"1/513/37": 0,
|
||||
"1/513/41": 1,
|
||||
"1/513/48": 0,
|
||||
"1/513/49": 150,
|
||||
"1/513/50": 1761951600,
|
||||
"1/513/72": [
|
||||
{
|
||||
"0": 1,
|
||||
"1": 1,
|
||||
"2": 1
|
||||
},
|
||||
{
|
||||
"0": 2,
|
||||
"1": 1,
|
||||
"2": 1
|
||||
},
|
||||
{
|
||||
"0": 3,
|
||||
"1": 1,
|
||||
"2": 2
|
||||
},
|
||||
{
|
||||
"0": 4,
|
||||
"1": 1,
|
||||
"2": 2
|
||||
},
|
||||
{
|
||||
"0": 5,
|
||||
"1": 1,
|
||||
"2": 2
|
||||
},
|
||||
{
|
||||
"0": 254,
|
||||
"1": 1,
|
||||
"2": 2
|
||||
}
|
||||
],
|
||||
"1/513/73": [
|
||||
{
|
||||
"0": 4,
|
||||
"1": 1,
|
||||
"2": 2
|
||||
},
|
||||
{
|
||||
"0": 3,
|
||||
"1": 1,
|
||||
"2": 2
|
||||
}
|
||||
],
|
||||
"1/513/74": 5,
|
||||
"1/513/78": null,
|
||||
"1/513/80": [
|
||||
{
|
||||
"0": "AQ==",
|
||||
"1": 1,
|
||||
"3": 2500,
|
||||
"4": 2100,
|
||||
"5": true
|
||||
},
|
||||
{
|
||||
"0": "Ag==",
|
||||
"1": 2,
|
||||
"3": 2600,
|
||||
"4": 2000,
|
||||
"5": true
|
||||
}
|
||||
],
|
||||
"1/513/82": 0,
|
||||
"1/513/83": 5,
|
||||
"1/513/84": [],
|
||||
"1/513/85": null,
|
||||
"1/513/86": null,
|
||||
"1/513/65532": 419,
|
||||
"1/513/65533": 9,
|
||||
"1/513/65528": [2, 253],
|
||||
"1/513/65529": [0, 6, 7, 8, 254],
|
||||
"1/513/65531": [
|
||||
0, 1, 3, 4, 5, 6, 7, 8, 16, 17, 18, 21, 22, 23, 24, 25, 26, 27, 28, 30,
|
||||
35, 36, 37, 41, 48, 49, 50, 72, 73, 74, 78, 80, 82, 83, 84, 85, 86, 65532,
|
||||
65533, 65528, 65529, 65531
|
||||
],
|
||||
"1/516/0": 0,
|
||||
"1/516/1": 0,
|
||||
"1/516/65532": 0,
|
||||
"1/516/65533": 2,
|
||||
"1/516/65528": [],
|
||||
"1/516/65529": [],
|
||||
"1/516/65531": [0, 1, 65532, 65533, 65528, 65529, 65531]
|
||||
},
|
||||
"attribute_subscriptions": []
|
||||
}
|
||||
@@ -2290,6 +2290,104 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[mock_thermostat][button.mock_thermostat_identify_0-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.mock_thermostat_identify_0',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Identify (0)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-0-IdentifyButton-3-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[mock_thermostat][button.mock_thermostat_identify_0-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'identify',
|
||||
'friendly_name': 'Mock Thermostat Identify (0)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.mock_thermostat_identify_0',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[mock_thermostat][button.mock_thermostat_identify_1-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.mock_thermostat_identify_1',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Identify (1)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-IdentifyButton-3-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[mock_thermostat][button.mock_thermostat_identify_1-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'identify',
|
||||
'friendly_name': 'Mock Thermostat Identify (1)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.mock_thermostat_identify_1',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[multi_endpoint_light][button.inovelli_identify_1-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -325,6 +325,77 @@
|
||||
'state': 'cool',
|
||||
})
|
||||
# ---
|
||||
# name: test_climates[mock_thermostat][climate.mock_thermostat-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT_COOL: 'heat_cool'>,
|
||||
]),
|
||||
'max_temp': 32.0,
|
||||
'min_temp': 7.0,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'climate',
|
||||
'entity_category': None,
|
||||
'entity_id': 'climate.mock_thermostat',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <ClimateEntityFeature: 387>,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-MatterThermostat-513-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_climates[mock_thermostat][climate.mock_thermostat-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 18.0,
|
||||
'friendly_name': 'Mock Thermostat',
|
||||
'hvac_action': <HVACAction.HEATING: 'heating'>,
|
||||
'hvac_modes': list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT_COOL: 'heat_cool'>,
|
||||
]),
|
||||
'max_temp': 32.0,
|
||||
'min_temp': 7.0,
|
||||
'supported_features': <ClimateEntityFeature: 387>,
|
||||
'target_temp_high': 26.0,
|
||||
'target_temp_low': 20.0,
|
||||
'temperature': None,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.mock_thermostat',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'heat_cool',
|
||||
})
|
||||
# ---
|
||||
# name: test_climates[room_airconditioner][climate.room_airconditioner-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -2318,6 +2318,63 @@
|
||||
'state': 'silent',
|
||||
})
|
||||
# ---
|
||||
# name: test_selects[mock_thermostat][select.mock_thermostat_temperature_display_mode-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'Celsius',
|
||||
'Fahrenheit',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'select.mock_thermostat_temperature_display_mode',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Temperature display mode',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'temperature_display_mode',
|
||||
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-TrvTemperatureDisplayMode-516-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_selects[mock_thermostat][select.mock_thermostat_temperature_display_mode-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock Thermostat Temperature display mode',
|
||||
'options': list([
|
||||
'Celsius',
|
||||
'Fahrenheit',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'select.mock_thermostat_temperature_display_mode',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'Celsius',
|
||||
})
|
||||
# ---
|
||||
# name: test_selects[mounted_dimmable_load_control_fixture][select.mock_mounted_dimmable_load_control_power_on_behavior_on_startup-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -7259,6 +7259,332 @@
|
||||
'state': 'stopped',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_heating_demand-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.mock_thermostat_heating_demand',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Heating demand',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'pi_heating_demand',
|
||||
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-ThermostatPIHeatingDemand-513-8',
|
||||
'unit_of_measurement': '%',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_heating_demand-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock Thermostat Heating demand',
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.mock_thermostat_heating_demand',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '25',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_last_change-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.mock_thermostat_last_change',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Last change',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'setpoint_change_timestamp',
|
||||
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-SetpointChangeSourceTimestamp-513-50',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_last_change-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'timestamp',
|
||||
'friendly_name': 'Mock Thermostat Last change',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.mock_thermostat_last_change',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '2025-10-31T23:00:00+00:00',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_last_change_amount-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.mock_thermostat_last_change_amount',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 1,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Last change amount',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'setpoint_change_amount',
|
||||
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-ThermostatSetpointChangeAmount-513-49',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_last_change_amount-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'temperature',
|
||||
'friendly_name': 'Mock Thermostat Last change amount',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.mock_thermostat_last_change_amount',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '1.5',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_last_change_source-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'manual',
|
||||
'schedule',
|
||||
'external',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.mock_thermostat_last_change_source',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Last change source',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'setpoint_change_source',
|
||||
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-SetpointChangeSource-513-48',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_last_change_source-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'Mock Thermostat Last change source',
|
||||
'options': list([
|
||||
'manual',
|
||||
'schedule',
|
||||
'external',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.mock_thermostat_last_change_source',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'manual',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_outdoor_temperature-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.mock_thermostat_outdoor_temperature',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 1,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Outdoor temperature',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'outdoor_temperature',
|
||||
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-ThermostatOutdoorTemperature-513-1',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_outdoor_temperature-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'temperature',
|
||||
'friendly_name': 'Mock Thermostat Outdoor temperature',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.mock_thermostat_outdoor_temperature',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '5.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_temperature-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.mock_thermostat_temperature',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 1,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Temperature',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-ThermostatLocalTemperature-513-0',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_temperature-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'temperature',
|
||||
'friendly_name': 'Mock Thermostat Temperature',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.mock_thermostat_temperature',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '18.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[multi_endpoint_light][sensor.inovelli_current_switch_position_config-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -697,3 +697,91 @@ async def test_vacuum_operational_error_sensor(
|
||||
state = hass.states.get("sensor.mock_vacuum_operational_error")
|
||||
assert state
|
||||
assert state.state == "unknown"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("node_fixture", ["mock_thermostat"])
|
||||
async def test_thermostat_setpoint_change_source(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
matter_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test Thermostat SetpointChangeSource sensor."""
|
||||
# Thermostat Cluster / SetpointChangeSource attribute (1/513/48)
|
||||
state = hass.states.get("sensor.mock_thermostat_last_change_source")
|
||||
assert state
|
||||
assert state.state == "manual"
|
||||
assert state.attributes["options"] == ["manual", "schedule", "external"]
|
||||
|
||||
# Test schedule source
|
||||
set_node_attribute(matter_node, 1, 513, 48, 1)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("sensor.mock_thermostat_last_change_source")
|
||||
assert state
|
||||
assert state.state == "schedule"
|
||||
|
||||
# Test external source
|
||||
set_node_attribute(matter_node, 1, 513, 48, 2)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("sensor.mock_thermostat_last_change_source")
|
||||
assert state
|
||||
assert state.state == "external"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("node_fixture", ["mock_thermostat"])
|
||||
async def test_thermostat_setpoint_change_timestamp(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
matter_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test Thermostat SetpointChangeSourceTimestamp sensor."""
|
||||
# Thermostat Cluster / SetpointChangeSourceTimestamp attribute (1/513/50)
|
||||
state = hass.states.get("sensor.mock_thermostat_last_change")
|
||||
assert state
|
||||
assert state.state == "2025-10-31T23:00:00+00:00"
|
||||
|
||||
# Update to a new timestamp (2024-11-15 12:00:00 UTC)
|
||||
set_node_attribute(matter_node, 1, 513, 50, 1731672000)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("sensor.mock_thermostat_last_change")
|
||||
assert state
|
||||
assert state.state == "2024-11-15T12:00:00+00:00"
|
||||
|
||||
# Test zero value (should be None/unknown)
|
||||
set_node_attribute(matter_node, 1, 513, 50, 0)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("sensor.mock_thermostat_last_change")
|
||||
assert state
|
||||
assert state.state == "unknown"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("node_fixture", ["mock_thermostat"])
|
||||
async def test_thermostat_setpoint_change_amount(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
matter_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test Thermostat SetpointChangeAmount sensor."""
|
||||
# Thermostat Cluster / SetpointChangeAmount attribute (1/513/49)
|
||||
state = hass.states.get("sensor.mock_thermostat_last_change_amount")
|
||||
assert state
|
||||
assert state.state == "1.5"
|
||||
|
||||
# Update to 2.0°C (200 in Matter units)
|
||||
set_node_attribute(matter_node, 1, 513, 49, 200)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("sensor.mock_thermostat_last_change_amount")
|
||||
assert state
|
||||
assert state.state == "2.0"
|
||||
|
||||
# Update to -0.5°C (-50 in Matter units)
|
||||
set_node_attribute(matter_node, 1, 513, 49, -50)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("sensor.mock_thermostat_last_change_amount")
|
||||
assert state
|
||||
assert state.state == "-0.5"
|
||||
|
||||
@@ -68,9 +68,7 @@ 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, Callable[[Any], None]], Awaitable[None]
|
||||
],
|
||||
additional_steps: Callable[[ConfigEntry, Mock, str], Awaitable[None]],
|
||||
) -> None:
|
||||
config_entry = MockConfigEntry(
|
||||
data={
|
||||
@@ -86,24 +84,6 @@ 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",
|
||||
@@ -113,24 +93,17 @@ 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",
|
||||
side_effect=mock_get_or_create_cloudhook,
|
||||
autospec=True,
|
||||
) 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,
|
||||
cloudhook_change_callback,
|
||||
config_entry, mock_async_get_or_create_cloudhook, cloud_hook
|
||||
)
|
||||
|
||||
|
||||
@@ -141,10 +114,7 @@ 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,
|
||||
cloudhook_change_callback: Callable[[Any], None],
|
||||
config_entry: ConfigEntry, mock_create_cloudhook: Mock, cloud_hook: str
|
||||
) -> None:
|
||||
assert config_entry.data[CONF_CLOUDHOOK_URL] == cloud_hook
|
||||
mock_create_cloudhook.assert_called_once_with(
|
||||
@@ -164,10 +134,7 @@ 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,
|
||||
cloudhook_change_callback: Callable[[Any], None],
|
||||
config_entry: ConfigEntry, mock_create_cloudhook: Mock, cloud_hook: str
|
||||
) -> None:
|
||||
webhook_id = config_entry.data[CONF_WEBHOOK_ID]
|
||||
assert config_entry.data[CONF_CLOUDHOOK_URL] == cloud_hook
|
||||
@@ -191,10 +158,7 @@ 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,
|
||||
cloudhook_change_callback: Callable[[Any], None],
|
||||
config_entry: ConfigEntry, mock_create_cloudhook: Mock, _: str
|
||||
) -> None:
|
||||
assert config_entry.data[CONF_CLOUDHOOK_URL] == cloud_hook
|
||||
mock_create_cloudhook.assert_not_called()
|
||||
@@ -211,21 +175,13 @@ 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,
|
||||
cloudhook_change_callback: Callable[[Any], None],
|
||||
config_entry: ConfigEntry, mock_create_cloudhook: Mock, cloud_hook: str
|
||||
) -> 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]
|
||||
@@ -304,236 +260,3 @@ 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
|
||||
|
||||
@@ -310,73 +310,6 @@ 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,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
"""Test niko_home_control config flow."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from homeassistant.components.niko_home_control.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_USER
|
||||
@@ -38,22 +36,8 @@ async def test_full_flow(
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
@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."""
|
||||
async def test_cannot_connect(hass: HomeAssistant) -> None:
|
||||
"""Test the cannot connect error."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
@@ -61,22 +45,25 @@ async def test_flow_errors(
|
||||
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.123"},
|
||||
)
|
||||
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"},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {"base": error}
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
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"},
|
||||
)
|
||||
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"},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
@@ -132,7 +119,6 @@ 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)
|
||||
@@ -150,39 +136,28 @@ async def test_reconfigure(
|
||||
assert result["reason"] == "reconfigure_successful"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("exception", "error"),
|
||||
[
|
||||
(TimeoutError, "timeout_connect"),
|
||||
(OSError, "cannot_connect"),
|
||||
(Exception, "unknown"),
|
||||
],
|
||||
)
|
||||
async def test_reconfigure_errors(
|
||||
async def test_reconfigure_cannot_connect(
|
||||
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": error}
|
||||
assert result["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
mock_niko_home_control_connection.connect.side_effect = None
|
||||
|
||||
|
||||
@@ -135,14 +135,12 @@ 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",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,913 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
{
|
||||
"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": {}
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
{
|
||||
"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": {}
|
||||
}
|
||||
@@ -632,102 +632,6 @@
|
||||
'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': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'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': <ANY>,
|
||||
'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': <ANY>,
|
||||
'entity_id': 'binary_sensor.kitchen_oven_child_lock',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'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': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'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': <ANY>,
|
||||
'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': <ANY>,
|
||||
'entity_id': 'binary_sensor.kitchen_oven_remote_control',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ks_range_0101x][binary_sensor.vulcan_child_lock-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -1557,151 +1461,6 @@
|
||||
'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': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'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': <ANY>,
|
||||
'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': <ANY>,
|
||||
'entity_id': 'binary_sensor.dishwasher_child_lock',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'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': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'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': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.POWER: 'power'>,
|
||||
'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': <ANY>,
|
||||
'entity_id': 'binary_sensor.dishwasher_power',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'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': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'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': <ANY>,
|
||||
'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': <ANY>,
|
||||
'entity_id': 'binary_sensor.dishwasher_remote_control',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_sc_000001][binary_sensor.airdresser_child_lock-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -3103,54 +2862,6 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_wm_100002][binary_sensor.washer_upper_washer_remote_control-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'binary_sensor.washer_upper_washer_remote_control',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Upper washer remote control',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sub_remote_control',
|
||||
'unique_id': 'C097276D-C8D4-0000-0000-000000000000_sub_remoteControlStatus_remoteControlEnabled_remoteControlEnabled',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_wm_100002][binary_sensor.washer_upper_washer_remote_control-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Washer Upper washer remote control',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.washer_upper_washer_remote_control',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[ecobee_sensor][binary_sensor.child_bedroom_motion-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -653,37 +653,6 @@
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[da_ks_oven_0107x]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
'config_entries': <ANY>,
|
||||
'config_entries_subentries': <ANY>,
|
||||
'configuration_url': 'https://account.smartthings.com',
|
||||
'connections': set({
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'entry_type': None,
|
||||
'hw_version': 'Realtek',
|
||||
'id': <ANY>,
|
||||
'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': <ANY>,
|
||||
'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,
|
||||
@@ -1025,37 +994,6 @@
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[da_wm_dw_01011]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
'config_entries': <ANY>,
|
||||
'config_entries_subentries': <ANY>,
|
||||
'configuration_url': 'https://account.smartthings.com',
|
||||
'connections': set({
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'entry_type': None,
|
||||
'hw_version': 'Realtek',
|
||||
'id': <ANY>,
|
||||
'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': <ANY>,
|
||||
'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,
|
||||
|
||||
@@ -56,64 +56,6 @@
|
||||
'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': <NumberMode.BOX: 'box'>,
|
||||
'step': 1.0,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.kitchen_oven_rinse_cycles',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': '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': <NumberMode.BOX: 'box'>,
|
||||
'step': 1.0,
|
||||
'unit_of_measurement': 'cycles',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.kitchen_oven_rinse_cycles',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '2',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_000001][number.refrigerator_freezer_temperature-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -1,126 +1,4 @@
|
||||
# serializer version: 1
|
||||
# name: test_all_entities[da_ac_rac_000003][select.clim_salon_dust_filter_alarm_threshold-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'180',
|
||||
'300',
|
||||
'500',
|
||||
'700',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'select.clim_salon_dust_filter_alarm_threshold',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Dust filter alarm threshold',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'dust_filter_alarm',
|
||||
'unique_id': '1e3f7ca2-e005-e1a4-f6d7-bc231e3f7977_main_samsungce.dustFilterAlarm_alarmThreshold_alarmThreshold',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ac_rac_000003][select.clim_salon_dust_filter_alarm_threshold-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Clim Salon Dust filter alarm threshold',
|
||||
'options': list([
|
||||
'180',
|
||||
'300',
|
||||
'500',
|
||||
'700',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'select.clim_salon_dust_filter_alarm_threshold',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '500',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ac_rac_01001][select.aire_dormitorio_principal_dust_filter_alarm_threshold-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'180',
|
||||
'300',
|
||||
'500',
|
||||
'700',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'select.aire_dormitorio_principal_dust_filter_alarm_threshold',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Dust filter alarm threshold',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'dust_filter_alarm',
|
||||
'unique_id': '4ece486b-89db-f06a-d54d-748b676b4d8e_main_samsungce.dustFilterAlarm_alarmThreshold_alarmThreshold',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ac_rac_01001][select.aire_dormitorio_principal_dust_filter_alarm_threshold-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Aire Dormitorio Principal Dust filter alarm threshold',
|
||||
'options': list([
|
||||
'180',
|
||||
'300',
|
||||
'500',
|
||||
'700',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'select.aire_dormitorio_principal_dust_filter_alarm_threshold',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '500',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ks_microwave_0101x][select.microwave_lamp-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -237,199 +115,6 @@
|
||||
'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': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': None,
|
||||
'entity_id': 'select.kitchen_oven',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'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': <ANY>,
|
||||
'entity_id': 'select.kitchen_oven',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'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': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'select.kitchen_oven_spin_level',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': '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': <ANY>,
|
||||
'entity_id': 'select.kitchen_oven_spin_level',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'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': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'select.kitchen_oven_water_temperature',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': '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': <ANY>,
|
||||
'entity_id': 'select.kitchen_oven_water_temperature',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'none',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ks_range_0101x][select.vulcan_lamp-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -603,65 +288,6 @@
|
||||
'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': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': None,
|
||||
'entity_id': 'select.dishwasher',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'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': <ANY>,
|
||||
'entity_id': 'select.dishwasher',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'stop',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_sc_000001][select.airdresser-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -191,102 +191,6 @@
|
||||
'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': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': None,
|
||||
'entity_id': 'switch.kitchen_oven',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'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': <ANY>,
|
||||
'entity_id': 'switch.kitchen_oven',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'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': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.kitchen_oven_bubble_soak',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': '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': <ANY>,
|
||||
'entity_id': 'switch.kitchen_oven_bubble_soak',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_000001][switch.refrigerator_cubed_ice-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -189,34 +189,3 @@ async def test_availability_at_start(
|
||||
"""Test unavailable at boot."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
assert hass.states.get("select.dryer").state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000003"])
|
||||
async def test_select_option_as_integer(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test selecting an option represented as an integer."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get("select.clim_salon_dust_filter_alarm_threshold")
|
||||
assert state.state == "500"
|
||||
assert all(isinstance(option, str) for option in state.attributes[ATTR_OPTIONS])
|
||||
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{
|
||||
ATTR_ENTITY_ID: "select.clim_salon_dust_filter_alarm_threshold",
|
||||
ATTR_OPTION: "300",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
devices.execute_device_command.assert_called_once_with(
|
||||
"1e3f7ca2-e005-e1a4-f6d7-bc231e3f7977",
|
||||
Capability.SAMSUNG_CE_DUST_FILTER_ALARM,
|
||||
Command.SET_ALARM_THRESHOLD,
|
||||
MAIN,
|
||||
argument=300,
|
||||
)
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
{
|
||||
"endpoint": "https://apigw.tuyaus.com",
|
||||
"mqtt_connected": true,
|
||||
"disabled_by": null,
|
||||
"disabled_polling": false,
|
||||
"name": "Mini-Split",
|
||||
"category": "kt",
|
||||
"product_id": "wxqdp6ecfkd78zzz",
|
||||
"product_name": "Air Conditioner",
|
||||
"online": true,
|
||||
"sub": false,
|
||||
"time_zone": "-07:00",
|
||||
"active_time": "2024-07-06T03:42:10+00:00",
|
||||
"create_time": "2024-07-06T03:42:10+00:00",
|
||||
"update_time": "2024-07-06T03:42:10+00:00",
|
||||
"function": {
|
||||
"switch": {
|
||||
"type": "Boolean",
|
||||
"value": {}
|
||||
},
|
||||
"temp_set": {
|
||||
"type": "Integer",
|
||||
"value": {
|
||||
"unit": "\u2103/F",
|
||||
"min": 160,
|
||||
"max": 900,
|
||||
"scale": 1,
|
||||
"step": 10
|
||||
}
|
||||
},
|
||||
"mode": {
|
||||
"type": "Enum",
|
||||
"value": {
|
||||
"range": ["cold", "hot", "wet", "wind", "auto"]
|
||||
}
|
||||
},
|
||||
"mode_eco": {
|
||||
"type": "Boolean",
|
||||
"value": {}
|
||||
},
|
||||
"heat": {
|
||||
"type": "Boolean",
|
||||
"value": {}
|
||||
},
|
||||
"light": {
|
||||
"type": "Boolean",
|
||||
"value": {}
|
||||
},
|
||||
"lock": {
|
||||
"type": "Boolean",
|
||||
"value": {}
|
||||
},
|
||||
"switch_horizontal": {
|
||||
"type": "Boolean",
|
||||
"value": {}
|
||||
},
|
||||
"sleep": {
|
||||
"type": "Boolean",
|
||||
"value": {}
|
||||
},
|
||||
"health": {
|
||||
"type": "Boolean",
|
||||
"value": {}
|
||||
}
|
||||
},
|
||||
"status_range": {
|
||||
"switch": {
|
||||
"type": "Boolean",
|
||||
"value": {}
|
||||
},
|
||||
"temp_set": {
|
||||
"type": "Integer",
|
||||
"value": {
|
||||
"unit": "\u2103/F",
|
||||
"min": 160,
|
||||
"max": 900,
|
||||
"scale": 1,
|
||||
"step": 10
|
||||
}
|
||||
},
|
||||
"temp_current": {
|
||||
"type": "Integer",
|
||||
"value": {
|
||||
"unit": "\u2103/F",
|
||||
"min": -300,
|
||||
"max": 1760,
|
||||
"scale": 1,
|
||||
"step": 10
|
||||
}
|
||||
},
|
||||
"mode": {
|
||||
"type": "Enum",
|
||||
"value": {
|
||||
"range": ["cold", "hot", "wet", "wind", "auto"]
|
||||
}
|
||||
},
|
||||
"mode_eco": {
|
||||
"type": "Boolean",
|
||||
"value": {}
|
||||
},
|
||||
"heat": {
|
||||
"type": "Boolean",
|
||||
"value": {}
|
||||
},
|
||||
"light": {
|
||||
"type": "Boolean",
|
||||
"value": {}
|
||||
},
|
||||
"lock": {
|
||||
"type": "Boolean",
|
||||
"value": {}
|
||||
},
|
||||
"power_consumption": {
|
||||
"type": "Integer",
|
||||
"value": {
|
||||
"unit": "kW\u00b7h",
|
||||
"min": 0,
|
||||
"max": 255,
|
||||
"scale": 0,
|
||||
"step": 1
|
||||
}
|
||||
},
|
||||
"switch_horizontal": {
|
||||
"type": "Boolean",
|
||||
"value": {}
|
||||
},
|
||||
"sleep": {
|
||||
"type": "Boolean",
|
||||
"value": {}
|
||||
},
|
||||
"health": {
|
||||
"type": "Boolean",
|
||||
"value": {}
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"switch": false,
|
||||
"temp_set": 660,
|
||||
"temp_current": 690,
|
||||
"mode": "cold",
|
||||
"mode_eco": false,
|
||||
"heat": false,
|
||||
"light": false,
|
||||
"lock": false,
|
||||
"power_consumption": 0,
|
||||
"switch_horizontal": false,
|
||||
"sleep": false,
|
||||
"health": false
|
||||
},
|
||||
"set_up": true,
|
||||
"support_local": true
|
||||
}
|
||||
@@ -804,89 +804,6 @@
|
||||
'state': 'cool',
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[climate.mini_split-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.DRY: 'dry'>,
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.HEAT_COOL: 'heat_cool'>,
|
||||
]),
|
||||
'max_temp': 90.0,
|
||||
'min_temp': 16.0,
|
||||
'swing_modes': list([
|
||||
'off',
|
||||
'horizontal',
|
||||
]),
|
||||
'target_temp_step': 1.0,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'climate',
|
||||
'entity_category': None,
|
||||
'entity_id': 'climate.mini_split',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'tuya',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <ClimateEntityFeature: 417>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'tuya.zzz87dkfce6pdqxwtk',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[climate.mini_split-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 69.0,
|
||||
'friendly_name': 'Mini-Split',
|
||||
'hvac_modes': list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.DRY: 'dry'>,
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.HEAT_COOL: 'heat_cool'>,
|
||||
]),
|
||||
'max_temp': 90.0,
|
||||
'min_temp': 16.0,
|
||||
'supported_features': <ClimateEntityFeature: 417>,
|
||||
'swing_mode': 'off',
|
||||
'swing_modes': list([
|
||||
'off',
|
||||
'horizontal',
|
||||
]),
|
||||
'target_temp_step': 1.0,
|
||||
'temperature': 66.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.mini_split',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[climate.mr_pure-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -1373,169 +1290,3 @@
|
||||
'state': 'heat_cool',
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.air_conditioner]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': 72,
|
||||
'max_temp': 187,
|
||||
'min_temp': 61,
|
||||
'target_temp_step': 1.0,
|
||||
'temperature': 73,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.anbau]
|
||||
ReadOnlyDict({
|
||||
'max_temp': 95,
|
||||
'min_temp': 41,
|
||||
'target_temp_step': 1.0,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.bathroom_radiator]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': 67,
|
||||
'max_temp': 158,
|
||||
'min_temp': 34,
|
||||
'target_temp_step': 0.5,
|
||||
'temperature': 54,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.boiler_temperature_controller]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': 136,
|
||||
'max_temp': 95,
|
||||
'min_temp': 45,
|
||||
'target_temp_step': 1.0,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.clima_cucina]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': 81,
|
||||
'max_temp': 95,
|
||||
'min_temp': 41,
|
||||
'target_temp_step': 1.0,
|
||||
'temperature': 77,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.el_termostato_de_la_cocina]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': 113,
|
||||
'max_temp': 45,
|
||||
'min_temp': 34,
|
||||
'target_temp_step': 0.5,
|
||||
'temperature': 40,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.empore]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': 66,
|
||||
'max_temp': 95,
|
||||
'min_temp': 41,
|
||||
'target_temp_step': 1.0,
|
||||
'temperature': 95,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.floor_thermostat_kitchen]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': 68,
|
||||
'max_temp': 95,
|
||||
'min_temp': 41,
|
||||
'target_temp_step': 1.0,
|
||||
'temperature': 36,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.geti_solar_pv_water_heater]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': 140,
|
||||
'max_temp': 95,
|
||||
'min_temp': 45,
|
||||
'target_temp_step': 1.0,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.kabinet]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': 67,
|
||||
'max_temp': 203,
|
||||
'min_temp': 41,
|
||||
'target_temp_step': 0.5,
|
||||
'temperature': 71,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.master_bedroom_ac]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': 79,
|
||||
'max_temp': 190,
|
||||
'min_temp': 61,
|
||||
'target_temp_step': 0.5,
|
||||
'temperature': 167,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.mini_split]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': 156,
|
||||
'max_temp': 194,
|
||||
'min_temp': 61,
|
||||
'target_temp_step': 1.0,
|
||||
'temperature': 151,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.mr_pure]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': None,
|
||||
'max_temp': 95,
|
||||
'min_temp': 45,
|
||||
'target_temp_step': 1.0,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.polotentsosushitel]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': 78,
|
||||
'max_temp': 104,
|
||||
'min_temp': 41,
|
||||
'target_temp_step': 0.5,
|
||||
'temperature': 41,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.salon]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': 69,
|
||||
'max_temp': 43,
|
||||
'min_temp': 32,
|
||||
'target_temp_step': 0.5,
|
||||
'temperature': 43,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.smart_thermostats]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': 71,
|
||||
'max_temp': 194,
|
||||
'min_temp': 41,
|
||||
'target_temp_step': 1.0,
|
||||
'temperature': 54,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.sove]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': 75,
|
||||
'max_temp': 187,
|
||||
'min_temp': 61,
|
||||
'target_temp_step': 1.0,
|
||||
'temperature': 61,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.term_prizemi]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': 73,
|
||||
'max_temp': 158,
|
||||
'min_temp': 33,
|
||||
'target_temp_step': 0.1,
|
||||
'temperature': 73,
|
||||
})
|
||||
# ---
|
||||
# name: test_us_customary_system[climate.wifi_smart_gas_boiler_thermostat]
|
||||
ReadOnlyDict({
|
||||
'current_temperature': 77,
|
||||
'max_temp': 95,
|
||||
'min_temp': 41,
|
||||
'target_temp_step': 0.5,
|
||||
'temperature': 72,
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -8462,34 +8462,3 @@
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_device_registry[zzz87dkfce6pdqxwtk]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
'config_entries': <ANY>,
|
||||
'config_entries_subentries': <ANY>,
|
||||
'configuration_url': None,
|
||||
'connections': set({
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'entry_type': None,
|
||||
'hw_version': None,
|
||||
'id': <ANY>,
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'tuya',
|
||||
'zzz87dkfce6pdqxwtk',
|
||||
),
|
||||
}),
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'Tuya',
|
||||
'model': 'Air Conditioner',
|
||||
'model_id': 'wxqdp6ecfkd78zzz',
|
||||
'name': 'Mini-Split',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': None,
|
||||
'sw_version': None,
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -2399,63 +2399,6 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[light.mini_split_backlight-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'supported_color_modes': list([
|
||||
<ColorMode.ONOFF: 'onoff'>,
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'light',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'light.mini_split_backlight',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Backlight',
|
||||
'platform': 'tuya',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'backlight',
|
||||
'unique_id': 'tuya.zzz87dkfce6pdqxwtklight',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[light.mini_split_backlight-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'color_mode': None,
|
||||
'friendly_name': 'Mini-Split Backlight',
|
||||
'supported_color_modes': list([
|
||||
<ColorMode.ONOFF: 'onoff'>,
|
||||
]),
|
||||
'supported_features': <LightEntityFeature: 0>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'light.mini_split_backlight',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[light.parker_ceiling_fan_1-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -6627,54 +6627,6 @@
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[switch.mini_split_child_lock-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.mini_split_child_lock',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Child lock',
|
||||
'platform': 'tuya',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'child_lock',
|
||||
'unique_id': 'tuya.zzz87dkfce6pdqxwtklock',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[switch.mini_split_child_lock-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mini-Split Child lock',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.mini_split_child_lock',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[switch.mirilla_puerta_flip-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -7,18 +7,13 @@ from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
from syrupy.filters import props
|
||||
from tuya_sharing import CustomerDevice, Manager
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_CURRENT_TEMPERATURE,
|
||||
ATTR_FAN_MODE,
|
||||
ATTR_HUMIDITY,
|
||||
ATTR_HVAC_MODE,
|
||||
ATTR_MAX_TEMP,
|
||||
ATTR_MIN_TEMP,
|
||||
ATTR_PRESET_MODE,
|
||||
ATTR_TARGET_TEMP_STEP,
|
||||
ATTR_TEMPERATURE,
|
||||
DOMAIN as CLIMATE_DOMAIN,
|
||||
SERVICE_SET_FAN_MODE,
|
||||
@@ -34,7 +29,6 @@ from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceNotSupported
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM
|
||||
|
||||
from . import initialize_entry
|
||||
|
||||
@@ -56,34 +50,6 @@ async def test_platform_setup_and_discovery(
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
@patch("homeassistant.components.tuya.PLATFORMS", [Platform.CLIMATE])
|
||||
async def test_us_customary_system(
|
||||
hass: HomeAssistant,
|
||||
mock_manager: Manager,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_devices: list[CustomerDevice],
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test platform setup and discovery."""
|
||||
hass.config.units = US_CUSTOMARY_SYSTEM
|
||||
|
||||
await initialize_entry(hass, mock_manager, mock_config_entry, mock_devices)
|
||||
|
||||
for entity in entity_registry.entities.values():
|
||||
state = hass.states.get(entity.entity_id)
|
||||
assert state.attributes == snapshot(
|
||||
name=entity.entity_id,
|
||||
include=props(
|
||||
ATTR_CURRENT_TEMPERATURE,
|
||||
ATTR_MAX_TEMP,
|
||||
ATTR_MIN_TEMP,
|
||||
ATTR_TARGET_TEMP_STEP,
|
||||
ATTR_TEMPERATURE,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("mock_device_code", "entity_id", "service", "service_data", "expected_commands"),
|
||||
[
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# 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,
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
@@ -1,28 +0,0 @@
|
||||
"""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
|
||||
)
|
||||
@@ -3259,7 +3259,7 @@ async def test_extract_from_target(
|
||||
) -> None:
|
||||
"""Test extract_from_target command with mixed target types including entities, devices, areas, and labels."""
|
||||
|
||||
async def call_command(target: dict[str, list[str]]) -> Any:
|
||||
async def call_command(target: dict[str, str]) -> Any:
|
||||
await websocket_client.send_json_auto_id(
|
||||
{"type": "extract_from_target", "target": target}
|
||||
)
|
||||
|
||||
@@ -1,313 +0,0 @@
|
||||
"""Test area template functions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import (
|
||||
area_registry as ar,
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.helpers.template.helpers import assert_result_info, render_to_info
|
||||
|
||||
|
||||
async def test_areas(hass: HomeAssistant, area_registry: ar.AreaRegistry) -> None:
|
||||
"""Test areas function."""
|
||||
# Test no areas
|
||||
info = render_to_info(hass, "{{ areas() }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test one area
|
||||
area1 = area_registry.async_get_or_create("area1")
|
||||
info = render_to_info(hass, "{{ areas() }}")
|
||||
assert_result_info(info, [area1.id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test multiple areas
|
||||
area2 = area_registry.async_get_or_create("area2")
|
||||
info = render_to_info(hass, "{{ areas() }}")
|
||||
assert_result_info(info, [area1.id, area2.id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
|
||||
async def test_area_id(
|
||||
hass: HomeAssistant,
|
||||
area_registry: ar.AreaRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test area_id function."""
|
||||
config_entry = MockConfigEntry(domain="light")
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
# Test non existing entity id
|
||||
info = render_to_info(hass, "{{ area_id('sensor.fake') }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test non existing device id (hex value)
|
||||
info = render_to_info(hass, "{{ area_id('123abc') }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test non existing area name
|
||||
info = render_to_info(hass, "{{ area_id('fake area name') }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test wrong value type
|
||||
info = render_to_info(hass, "{{ area_id(56) }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
area_registry.async_get_or_create("sensor.fake")
|
||||
|
||||
# Test device with single entity, which has no area
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||
)
|
||||
entity_entry = entity_registry.async_get_or_create(
|
||||
"light",
|
||||
"hue",
|
||||
"5678",
|
||||
config_entry=config_entry,
|
||||
device_id=device_entry.id,
|
||||
)
|
||||
info = render_to_info(hass, f"{{{{ area_id('{device_entry.id}') }}}}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_id('{entity_entry.entity_id}') }}}}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test device ID, entity ID and area name as input with area name that looks like
|
||||
# a device ID. Try a filter too
|
||||
area_entry_hex = area_registry.async_get_or_create("123abc")
|
||||
device_entry = device_registry.async_update_device(
|
||||
device_entry.id, area_id=area_entry_hex.id
|
||||
)
|
||||
entity_entry = entity_registry.async_update_entity(
|
||||
entity_entry.entity_id, area_id=area_entry_hex.id
|
||||
)
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{device_entry.id}' | area_id }}}}")
|
||||
assert_result_info(info, area_entry_hex.id)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_id('{entity_entry.entity_id}') }}}}")
|
||||
assert_result_info(info, area_entry_hex.id)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_id('{area_entry_hex.name}') }}}}")
|
||||
assert_result_info(info, area_entry_hex.id)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test device ID, entity ID and area name as input with area name that looks like an
|
||||
# entity ID
|
||||
area_entry_entity_id = area_registry.async_get_or_create("sensor.fake")
|
||||
device_entry = device_registry.async_update_device(
|
||||
device_entry.id, area_id=area_entry_entity_id.id
|
||||
)
|
||||
entity_entry = entity_registry.async_update_entity(
|
||||
entity_entry.entity_id, area_id=area_entry_entity_id.id
|
||||
)
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_id('{device_entry.id}') }}}}")
|
||||
assert_result_info(info, area_entry_entity_id.id)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_id('{entity_entry.entity_id}') }}}}")
|
||||
assert_result_info(info, area_entry_entity_id.id)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_id('{area_entry_entity_id.name}') }}}}")
|
||||
assert_result_info(info, area_entry_entity_id.id)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Make sure that when entity doesn't have an area but its device does, that's what
|
||||
# gets returned
|
||||
entity_entry = entity_registry.async_update_entity(
|
||||
entity_entry.entity_id, area_id=area_entry_entity_id.id
|
||||
)
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_id('{entity_entry.entity_id}') }}}}")
|
||||
assert_result_info(info, area_entry_entity_id.id)
|
||||
assert info.rate_limit is None
|
||||
|
||||
|
||||
async def test_area_name(
|
||||
hass: HomeAssistant,
|
||||
area_registry: ar.AreaRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test area_name function."""
|
||||
config_entry = MockConfigEntry(domain="light")
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
# Test non existing entity id
|
||||
info = render_to_info(hass, "{{ area_name('sensor.fake') }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test non existing device id (hex value)
|
||||
info = render_to_info(hass, "{{ area_name('123abc') }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test non existing area id
|
||||
info = render_to_info(hass, "{{ area_name('1234567890') }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test wrong value type
|
||||
info = render_to_info(hass, "{{ area_name(56) }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test device with single entity, which has no area
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||
)
|
||||
entity_entry = entity_registry.async_get_or_create(
|
||||
"light",
|
||||
"hue",
|
||||
"5678",
|
||||
config_entry=config_entry,
|
||||
device_id=device_entry.id,
|
||||
)
|
||||
info = render_to_info(hass, f"{{{{ area_name('{device_entry.id}') }}}}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_name('{entity_entry.entity_id}') }}}}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test device ID, entity ID and area id as input. Try a filter too
|
||||
area_entry = area_registry.async_get_or_create("123abc")
|
||||
device_entry = device_registry.async_update_device(
|
||||
device_entry.id, area_id=area_entry.id
|
||||
)
|
||||
entity_entry = entity_registry.async_update_entity(
|
||||
entity_entry.entity_id, area_id=area_entry.id
|
||||
)
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{device_entry.id}' | area_name }}}}")
|
||||
assert_result_info(info, area_entry.name)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_name('{entity_entry.entity_id}') }}}}")
|
||||
assert_result_info(info, area_entry.name)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_name('{area_entry.id}') }}}}")
|
||||
assert_result_info(info, area_entry.name)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Make sure that when entity doesn't have an area but its device does, that's what
|
||||
# gets returned
|
||||
entity_entry = entity_registry.async_update_entity(
|
||||
entity_entry.entity_id, area_id=None
|
||||
)
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_name('{entity_entry.entity_id}') }}}}")
|
||||
assert_result_info(info, area_entry.name)
|
||||
assert info.rate_limit is None
|
||||
|
||||
|
||||
async def test_area_entities(
|
||||
hass: HomeAssistant,
|
||||
area_registry: ar.AreaRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test area_entities function."""
|
||||
config_entry = MockConfigEntry(domain="light")
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
# Test non existing device id
|
||||
info = render_to_info(hass, "{{ area_entities('deadbeef') }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test wrong value type
|
||||
info = render_to_info(hass, "{{ area_entities(56) }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
area_entry = area_registry.async_get_or_create("sensor.fake")
|
||||
entity_entry = entity_registry.async_get_or_create(
|
||||
"light",
|
||||
"hue",
|
||||
"5678",
|
||||
config_entry=config_entry,
|
||||
)
|
||||
entity_registry.async_update_entity(entity_entry.entity_id, area_id=area_entry.id)
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_entities('{area_entry.id}') }}}}")
|
||||
assert_result_info(info, ["light.hue_5678"])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{area_entry.name}' | area_entities }}}}")
|
||||
assert_result_info(info, ["light.hue_5678"])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test for entities that inherit area from device
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||
config_entry_id=config_entry.entry_id,
|
||||
suggested_area="sensor.fake",
|
||||
)
|
||||
entity_registry.async_get_or_create(
|
||||
"light",
|
||||
"hue_light",
|
||||
"5678",
|
||||
config_entry=config_entry,
|
||||
device_id=device_entry.id,
|
||||
)
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{area_entry.name}' | area_entities }}}}")
|
||||
assert_result_info(info, ["light.hue_5678", "light.hue_light_5678"])
|
||||
assert info.rate_limit is None
|
||||
|
||||
|
||||
async def test_area_devices(
|
||||
hass: HomeAssistant,
|
||||
area_registry: ar.AreaRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test area_devices function."""
|
||||
config_entry = MockConfigEntry(domain="light")
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
# Test non existing device id
|
||||
info = render_to_info(hass, "{{ area_devices('deadbeef') }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test wrong value type
|
||||
info = render_to_info(hass, "{{ area_devices(56) }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
area_entry = area_registry.async_get_or_create("sensor.fake")
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||
suggested_area=area_entry.name,
|
||||
)
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_devices('{area_entry.id}') }}}}")
|
||||
assert_result_info(info, [device_entry.id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{area_entry.name}' | area_devices }}}}")
|
||||
assert_result_info(info, [device_entry.id])
|
||||
assert info.rate_limit is None
|
||||
@@ -36,6 +36,8 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import (
|
||||
area_registry as ar,
|
||||
device_registry as dr,
|
||||
entity,
|
||||
entity_registry as er,
|
||||
issue_registry as ir,
|
||||
@@ -2634,6 +2636,306 @@ async def test_issue(hass: HomeAssistant, issue_registry: ir.IssueRegistry) -> N
|
||||
assert info.rate_limit is None
|
||||
|
||||
|
||||
async def test_areas(hass: HomeAssistant, area_registry: ar.AreaRegistry) -> None:
|
||||
"""Test areas function."""
|
||||
# Test no areas
|
||||
info = render_to_info(hass, "{{ areas() }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test one area
|
||||
area1 = area_registry.async_get_or_create("area1")
|
||||
info = render_to_info(hass, "{{ areas() }}")
|
||||
assert_result_info(info, [area1.id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test multiple areas
|
||||
area2 = area_registry.async_get_or_create("area2")
|
||||
info = render_to_info(hass, "{{ areas() }}")
|
||||
assert_result_info(info, [area1.id, area2.id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
|
||||
async def test_area_id(
|
||||
hass: HomeAssistant,
|
||||
area_registry: ar.AreaRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test area_id function."""
|
||||
config_entry = MockConfigEntry(domain="light")
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
# Test non existing entity id
|
||||
info = render_to_info(hass, "{{ area_id('sensor.fake') }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test non existing device id (hex value)
|
||||
info = render_to_info(hass, "{{ area_id('123abc') }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test non existing area name
|
||||
info = render_to_info(hass, "{{ area_id('fake area name') }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test wrong value type
|
||||
info = render_to_info(hass, "{{ area_id(56) }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
area_entry_entity_id = area_registry.async_get_or_create("sensor.fake")
|
||||
|
||||
# Test device with single entity, which has no area
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||
)
|
||||
entity_entry = entity_registry.async_get_or_create(
|
||||
"light",
|
||||
"hue",
|
||||
"5678",
|
||||
config_entry=config_entry,
|
||||
device_id=device_entry.id,
|
||||
)
|
||||
info = render_to_info(hass, f"{{{{ area_id('{device_entry.id}') }}}}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_id('{entity_entry.entity_id}') }}}}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test device ID, entity ID and area name as input with area name that looks like
|
||||
# a device ID. Try a filter too
|
||||
area_entry_hex = area_registry.async_get_or_create("123abc")
|
||||
device_entry = device_registry.async_update_device(
|
||||
device_entry.id, area_id=area_entry_hex.id
|
||||
)
|
||||
entity_entry = entity_registry.async_update_entity(
|
||||
entity_entry.entity_id, area_id=area_entry_hex.id
|
||||
)
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{device_entry.id}' | area_id }}}}")
|
||||
assert_result_info(info, area_entry_hex.id)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_id('{entity_entry.entity_id}') }}}}")
|
||||
assert_result_info(info, area_entry_hex.id)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_id('{area_entry_hex.name}') }}}}")
|
||||
assert_result_info(info, area_entry_hex.id)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test device ID, entity ID and area name as input with area name that looks like an
|
||||
# entity ID
|
||||
area_entry_entity_id = area_registry.async_get_or_create("sensor.fake")
|
||||
device_entry = device_registry.async_update_device(
|
||||
device_entry.id, area_id=area_entry_entity_id.id
|
||||
)
|
||||
entity_entry = entity_registry.async_update_entity(
|
||||
entity_entry.entity_id, area_id=area_entry_entity_id.id
|
||||
)
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_id('{device_entry.id}') }}}}")
|
||||
assert_result_info(info, area_entry_entity_id.id)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_id('{entity_entry.entity_id}') }}}}")
|
||||
assert_result_info(info, area_entry_entity_id.id)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_id('{area_entry_entity_id.name}') }}}}")
|
||||
assert_result_info(info, area_entry_entity_id.id)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Make sure that when entity doesn't have an area but its device does, that's what
|
||||
# gets returned
|
||||
entity_entry = entity_registry.async_update_entity(
|
||||
entity_entry.entity_id, area_id=area_entry_entity_id.id
|
||||
)
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_id('{entity_entry.entity_id}') }}}}")
|
||||
assert_result_info(info, area_entry_entity_id.id)
|
||||
assert info.rate_limit is None
|
||||
|
||||
|
||||
async def test_area_name(
|
||||
hass: HomeAssistant,
|
||||
area_registry: ar.AreaRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test area_name function."""
|
||||
config_entry = MockConfigEntry(domain="light")
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
# Test non existing entity id
|
||||
info = render_to_info(hass, "{{ area_name('sensor.fake') }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test non existing device id (hex value)
|
||||
info = render_to_info(hass, "{{ area_name('123abc') }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test non existing area id
|
||||
info = render_to_info(hass, "{{ area_name('1234567890') }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test wrong value type
|
||||
info = render_to_info(hass, "{{ area_name(56) }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test device with single entity, which has no area
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||
)
|
||||
entity_entry = entity_registry.async_get_or_create(
|
||||
"light",
|
||||
"hue",
|
||||
"5678",
|
||||
config_entry=config_entry,
|
||||
device_id=device_entry.id,
|
||||
)
|
||||
info = render_to_info(hass, f"{{{{ area_name('{device_entry.id}') }}}}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_name('{entity_entry.entity_id}') }}}}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test device ID, entity ID and area id as input. Try a filter too
|
||||
area_entry = area_registry.async_get_or_create("123abc")
|
||||
device_entry = device_registry.async_update_device(
|
||||
device_entry.id, area_id=area_entry.id
|
||||
)
|
||||
entity_entry = entity_registry.async_update_entity(
|
||||
entity_entry.entity_id, area_id=area_entry.id
|
||||
)
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{device_entry.id}' | area_name }}}}")
|
||||
assert_result_info(info, area_entry.name)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_name('{entity_entry.entity_id}') }}}}")
|
||||
assert_result_info(info, area_entry.name)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_name('{area_entry.id}') }}}}")
|
||||
assert_result_info(info, area_entry.name)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Make sure that when entity doesn't have an area but its device does, that's what
|
||||
# gets returned
|
||||
entity_entry = entity_registry.async_update_entity(
|
||||
entity_entry.entity_id, area_id=None
|
||||
)
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_name('{entity_entry.entity_id}') }}}}")
|
||||
assert_result_info(info, area_entry.name)
|
||||
assert info.rate_limit is None
|
||||
|
||||
|
||||
async def test_area_entities(
|
||||
hass: HomeAssistant,
|
||||
area_registry: ar.AreaRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test area_entities function."""
|
||||
config_entry = MockConfigEntry(domain="light")
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
# Test non existing device id
|
||||
info = render_to_info(hass, "{{ area_entities('deadbeef') }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test wrong value type
|
||||
info = render_to_info(hass, "{{ area_entities(56) }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
area_entry = area_registry.async_get_or_create("sensor.fake")
|
||||
entity_entry = entity_registry.async_get_or_create(
|
||||
"light",
|
||||
"hue",
|
||||
"5678",
|
||||
config_entry=config_entry,
|
||||
)
|
||||
entity_registry.async_update_entity(entity_entry.entity_id, area_id=area_entry.id)
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_entities('{area_entry.id}') }}}}")
|
||||
assert_result_info(info, ["light.hue_5678"])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{area_entry.name}' | area_entities }}}}")
|
||||
assert_result_info(info, ["light.hue_5678"])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test for entities that inherit area from device
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||
config_entry_id=config_entry.entry_id,
|
||||
suggested_area="sensor.fake",
|
||||
)
|
||||
entity_registry.async_get_or_create(
|
||||
"light",
|
||||
"hue_light",
|
||||
"5678",
|
||||
config_entry=config_entry,
|
||||
device_id=device_entry.id,
|
||||
)
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{area_entry.name}' | area_entities }}}}")
|
||||
assert_result_info(info, ["light.hue_5678", "light.hue_light_5678"])
|
||||
assert info.rate_limit is None
|
||||
|
||||
|
||||
async def test_area_devices(
|
||||
hass: HomeAssistant,
|
||||
area_registry: ar.AreaRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test area_devices function."""
|
||||
config_entry = MockConfigEntry(domain="light")
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
# Test non existing device id
|
||||
info = render_to_info(hass, "{{ area_devices('deadbeef') }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test wrong value type
|
||||
info = render_to_info(hass, "{{ area_devices(56) }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
area_entry = area_registry.async_get_or_create("sensor.fake")
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||
suggested_area=area_entry.name,
|
||||
)
|
||||
|
||||
info = render_to_info(hass, f"{{{{ area_devices('{area_entry.id}') }}}}")
|
||||
assert_result_info(info, [device_entry.id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{area_entry.name}' | area_devices }}}}")
|
||||
assert_result_info(info, [device_entry.id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
|
||||
def test_closest_function_to_coord(hass: HomeAssistant) -> None:
|
||||
"""Test closest function to coord."""
|
||||
hass.states.async_set(
|
||||
|
||||
Reference in New Issue
Block a user