mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
2025.3.2 (#140392)
* Don't allow creating backups if Home Assistant is not running (#139499) * Don't allow creating backups if hass is not running * Revert "Don't allow creating backups if hass is not running" This reverts commit 1bf545eb25f20fc27fe161691a94531cba7e005c. * Set backup manager to idle only after Home Assistant has started * Update according to discussion, add tests * Add more test * Bump govee_ble to 0.43.1 (#139862) Bump govee_ble to 0.43.0 * Label emergency heat switch (#139872) * Add label to emergency heat switch * Use sentence case names Co-authored-by: Franck Nijhof <frenck@frenck.nl> --------- Co-authored-by: Franck Nijhof <frenck@frenck.nl> * Bump sense-energy lib to 0.13.7 (#140068) * Update jinja to 3.1.6 (#140069) * Update evohome-async to 1.0.3 (#140083) bump client to 1.0.3 * Fix HEOS discovery error when previously ignored (#140091) Abort ignored discovery * Map prewash job state in SmartThings (#140097) * Check support for thermostat operating state in SmartThings (#140103) * Handle None options in SmartThings (#140110) * Handle None options in SmartThings * Handle None options in SmartThings * Fix MQTT JSON light not reporting color temp status if color is not supported (#140113) * Fix HEOS user initiated setup when discovery is waiting confirmation (#140119) * Support null supported Thermostat modes in SmartThings (#140101) * Set device class for Oven Completion time in SmartThings (#140139) * Revert "Check if the unit of measurement is valid before creating the entity" (#140155) Revert "Check if the unit of measurement is valid before creating the entity …" This reverts commit 99e1a7a676b2fc14f9f8a8db64bee2840fae4646. * Fix the order of the group members attribute of the Music Assistant integration (#140204) * Fix events without user in Bring integration (#140213) Fix events without publicUserUuid * Log broad exception in Electricity Maps config flow (#140219) * Bump evohome-async to 1.0.4 to fix #140194 (#140230) bump client, add test for fix #140194 * Refresh Home Connect token during config entry setup (#140233) * Refresh token during config entry setup * Test 500 error --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Add 900 RPM option to washer spin speed options at Home Connect (#140234) Add 900 RPM option to washer spin speed options * Fix todo tool broken with Gemini 2.0 models. (#140246) * Change tool name for addlist item * Change to HasListAddItem * extract to function * Fix version not always available in onewire (#140260) * Fix `client_id` not generated when connecting to the MQTT broker (#140264) Fix client_id not generated when connecting to the MQTT broker * Bump velbusaio to 2025.3.0 (#140267) * Fix dryer operating state in SmartThings (#140277) * FGLair : Upgrade to ayla-iot-unofficial 1.4.7 (#140296) Upgrade to ayla-iot-unofficial 1.4.7 * Bump pyheos to v1.0.3 (#140310) Bump pyheos v1.0.3 * Bump ZHA to 0.0.52 (#140325) * Bump pydrawise to 2025.3.0 (#140330) * Bump teslemetry-stream (#140335) Bump * Fix no temperature unit in SmartThings (#140363) * Fix double space quoting in WebDAV (#140364) * Bump python-roborock to 2.12.2 (#140368) bump python roboorck to 2.12.2 * Handle incomplete power consumption reports in SmartThings (#140370) * Fix browsing Audible Favorites in Sonos (#140378) * initial commit * updates * update test data * Make sure SmartThings light can deal with unknown states (#140190) * Fix * add comment * Make light unknown * Make light unknown * Delete subscription on shutdown of SmartThings (#140135) * Cache subscription url in SmartThings * Cache subscription url in SmartThings * Fix * Fix * Fix * Fix * Fix * Fix * Fix * Fix * Bump pysmartthings to 2.7.1 * 2.7.2 --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Bump version to 2025.3.2 --------- Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Evan Farrell <evan@evanfarrell.com> Co-authored-by: John Hillery <34005807+jrhillery@users.noreply.github.com> Co-authored-by: Keilin Bickar <TrumpetGod@gmail.com> Co-authored-by: David Bonnes <zxdavb@bonnes.me> Co-authored-by: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com> Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com> Co-authored-by: msm595 <msm595@users.noreply.github.com> Co-authored-by: Manu <4445816+tr4nt0r@users.noreply.github.com> Co-authored-by: Jan-Philipp Benecke <jan-philipp@bnck.me> Co-authored-by: J. Diego Rodríguez Royo <jdrr1998@hotmail.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Luke Lashley <conway220@gmail.com> Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> Co-authored-by: Maikel Punie <maikel.punie@gmail.com> Co-authored-by: Antoine Reversat <a.reversat@gmail.com> Co-authored-by: puddly <32534428+puddly@users.noreply.github.com> Co-authored-by: David Knowles <dknowles2@gmail.com> Co-authored-by: Brett Adams <Bre77@users.noreply.github.com> Co-authored-by: Pete Sage <76050312+PeteRager@users.noreply.github.com>
This commit is contained in:
commit
a12915fc14
@ -118,6 +118,7 @@ class BackupManagerState(StrEnum):
|
||||
|
||||
IDLE = "idle"
|
||||
CREATE_BACKUP = "create_backup"
|
||||
BLOCKED = "blocked"
|
||||
RECEIVE_BACKUP = "receive_backup"
|
||||
RESTORE_BACKUP = "restore_backup"
|
||||
|
||||
@ -226,6 +227,13 @@ class RestoreBackupEvent(ManagerStateEvent):
|
||||
state: RestoreBackupState
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True, slots=True)
|
||||
class BlockedEvent(ManagerStateEvent):
|
||||
"""Backup manager blocked, Home Assistant is starting."""
|
||||
|
||||
manager_state: BackupManagerState = BackupManagerState.BLOCKED
|
||||
|
||||
|
||||
class BackupPlatformProtocol(Protocol):
|
||||
"""Define the format that backup platforms can have."""
|
||||
|
||||
@ -340,7 +348,7 @@ class BackupManager:
|
||||
self.remove_next_delete_event: Callable[[], None] | None = None
|
||||
|
||||
# Latest backup event and backup event subscribers
|
||||
self.last_event: ManagerStateEvent = IdleEvent()
|
||||
self.last_event: ManagerStateEvent = BlockedEvent()
|
||||
self.last_non_idle_event: ManagerStateEvent | None = None
|
||||
self._backup_event_subscriptions = hass.data[
|
||||
DATA_BACKUP
|
||||
@ -354,10 +362,19 @@ class BackupManager:
|
||||
self.known_backups.load(stored["backups"])
|
||||
|
||||
await self._reader_writer.async_validate_config(config=self.config)
|
||||
|
||||
await self._reader_writer.async_resume_restore_progress_after_restart(
|
||||
on_progress=self.async_on_backup_event
|
||||
)
|
||||
|
||||
async def set_manager_idle_after_start(hass: HomeAssistant) -> None:
|
||||
"""Set manager to idle after start."""
|
||||
self.async_on_backup_event(IdleEvent())
|
||||
|
||||
if self.state == BackupManagerState.BLOCKED:
|
||||
# If we're not finishing a restore job, set the manager to idle after start
|
||||
start.async_at_started(self.hass, set_manager_idle_after_start)
|
||||
|
||||
await self.load_platforms()
|
||||
|
||||
@property
|
||||
@ -1293,7 +1310,7 @@ class BackupManager:
|
||||
if (current_state := self.state) != (new_state := event.manager_state):
|
||||
LOGGER.debug("Backup state: %s -> %s", current_state, new_state)
|
||||
self.last_event = event
|
||||
if not isinstance(event, IdleEvent):
|
||||
if not isinstance(event, (BlockedEvent, IdleEvent)):
|
||||
self.last_non_idle_event = event
|
||||
for subscription in self._backup_event_subscriptions:
|
||||
subscription(event)
|
||||
|
@ -77,9 +77,12 @@ class BringEventEntity(BringBaseEntity, EventEntity):
|
||||
attributes = asdict(activity.content)
|
||||
|
||||
attributes["last_activity_by"] = next(
|
||||
x.name
|
||||
for x in bring_list.users.users
|
||||
if x.publicUuid == activity.content.publicUserUuid
|
||||
(
|
||||
x.name
|
||||
for x in bring_list.users.users
|
||||
if x.publicUuid == activity.content.publicUserUuid
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
self._trigger_event(
|
||||
|
@ -3,11 +3,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from aioelectricitymaps import (
|
||||
ElectricityMaps,
|
||||
ElectricityMapsError,
|
||||
ElectricityMapsInvalidTokenError,
|
||||
ElectricityMapsNoDataError,
|
||||
)
|
||||
@ -36,6 +36,8 @@ TYPE_USE_HOME = "use_home_location"
|
||||
TYPE_SPECIFY_COORDINATES = "specify_coordinates"
|
||||
TYPE_SPECIFY_COUNTRY = "specify_country_code"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ElectricityMapsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Co2signal."""
|
||||
@ -158,7 +160,8 @@ class ElectricityMapsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors["base"] = "invalid_auth"
|
||||
except ElectricityMapsNoDataError:
|
||||
errors["base"] = "no_data"
|
||||
except ElectricityMapsError:
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected error occurred while checking API key")
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
if self.source == SOURCE_REAUTH:
|
||||
|
@ -6,5 +6,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["sense_energy"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["sense-energy==0.13.6"]
|
||||
"requirements": ["sense-energy==0.13.7"]
|
||||
}
|
||||
|
@ -6,5 +6,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["evohome", "evohomeasync", "evohomeasync2"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["evohome-async==1.0.2"]
|
||||
"requirements": ["evohome-async==1.0.4"]
|
||||
}
|
||||
|
@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/fujitsu_fglair",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["ayla-iot-unofficial==1.4.5"]
|
||||
"requirements": ["ayla-iot-unofficial==1.4.7"]
|
||||
}
|
||||
|
@ -276,6 +276,13 @@ class GoogleGenerativeAIConversationEntity(
|
||||
):
|
||||
return await self._async_handle_message(user_input, chat_log)
|
||||
|
||||
def _fix_tool_name(self, tool_name: str) -> str:
|
||||
"""Fix tool name if needed."""
|
||||
# The Gemini 2.0+ tokenizer seemingly has a issue with the HassListAddItem tool
|
||||
# name. This makes sure when it incorrectly changes the name, that we change it
|
||||
# back for HA to call.
|
||||
return tool_name if tool_name != "HasListAddItem" else "HassListAddItem"
|
||||
|
||||
async def _async_handle_message(
|
||||
self,
|
||||
user_input: conversation.ConversationInput,
|
||||
@ -435,7 +442,10 @@ class GoogleGenerativeAIConversationEntity(
|
||||
tool_name = tool_call.name
|
||||
tool_args = _escape_decode(tool_call.args)
|
||||
tool_calls.append(
|
||||
llm.ToolInput(tool_name=tool_name, tool_args=tool_args)
|
||||
llm.ToolInput(
|
||||
tool_name=self._fix_tool_name(tool_name),
|
||||
tool_args=tool_args,
|
||||
)
|
||||
)
|
||||
|
||||
chat_request = _create_google_tool_response_content(
|
||||
|
@ -135,5 +135,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/govee_ble",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["govee-ble==0.43.0"]
|
||||
"requirements": ["govee-ble==0.43.1"]
|
||||
}
|
||||
|
@ -14,7 +14,12 @@ from pyheos import (
|
||||
)
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_IGNORE,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import selector
|
||||
@ -141,8 +146,10 @@ class HeosFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
hostname = urlparse(discovery_info.ssdp_location).hostname
|
||||
assert hostname is not None
|
||||
|
||||
# Abort early when discovered host is part of the current system
|
||||
if entry and hostname in _get_current_hosts(entry):
|
||||
# Abort early when discovery is ignored or host is part of the current system
|
||||
if entry and (
|
||||
entry.source == SOURCE_IGNORE or hostname in _get_current_hosts(entry)
|
||||
):
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
|
||||
# Connect to discovered host and get system information
|
||||
@ -198,7 +205,7 @@ class HeosFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Obtain host and validate connection."""
|
||||
await self.async_set_unique_id(DOMAIN)
|
||||
await self.async_set_unique_id(DOMAIN, raise_on_progress=False)
|
||||
self._abort_if_unique_id_configured(error="single_instance_allowed")
|
||||
# Try connecting to host if provided
|
||||
errors: dict[str, str] = {}
|
||||
|
@ -159,13 +159,12 @@ class HeosCoordinator(DataUpdateCoordinator[None]):
|
||||
|
||||
async def _async_on_reconnected(self) -> None:
|
||||
"""Handle when reconnected so resources are updated and entities marked available."""
|
||||
await self._async_update_players()
|
||||
await self._async_update_sources()
|
||||
_LOGGER.warning("Successfully reconnected to HEOS host %s", self.host)
|
||||
self.async_update_listeners()
|
||||
|
||||
async def _async_on_controller_event(
|
||||
self, event: str, data: PlayerUpdateResult | None
|
||||
self, event: str, data: PlayerUpdateResult | None = None
|
||||
) -> None:
|
||||
"""Handle a controller event, such as players or groups changed."""
|
||||
if event == const.EVENT_PLAYERS_CHANGED:
|
||||
|
@ -8,7 +8,7 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pyheos"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pyheos==1.0.2"],
|
||||
"requirements": ["pyheos==1.0.3"],
|
||||
"ssdp": [
|
||||
{
|
||||
"st": "urn:schemas-denon-com:device:ACT-Denon:1"
|
||||
|
@ -16,11 +16,17 @@ from aiohomeconnect.model import (
|
||||
SettingKey,
|
||||
)
|
||||
from aiohomeconnect.model.error import HomeConnectError
|
||||
import aiohttp
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import ATTR_DEVICE_ID, Platform
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.exceptions import (
|
||||
ConfigEntryAuthFailed,
|
||||
ConfigEntryNotReady,
|
||||
HomeAssistantError,
|
||||
ServiceValidationError,
|
||||
)
|
||||
from homeassistant.helpers import (
|
||||
config_entry_oauth2_flow,
|
||||
config_validation as cv,
|
||||
@ -611,6 +617,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: HomeConnectConfigEntry)
|
||||
session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation)
|
||||
|
||||
config_entry_auth = AsyncConfigEntryAuth(hass, session)
|
||||
try:
|
||||
await config_entry_auth.async_get_access_token()
|
||||
except aiohttp.ClientResponseError as err:
|
||||
if 400 <= err.status < 500:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
raise ConfigEntryNotReady from err
|
||||
except aiohttp.ClientError as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
home_connect_client = HomeConnectClient(config_entry_auth)
|
||||
|
||||
|
@ -285,6 +285,7 @@ SPIN_SPEED_OPTIONS = {
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM400",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM600",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM800",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM900",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1000",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1200",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1400",
|
||||
|
@ -461,6 +461,7 @@
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m400": "400 rpm",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m600": "600 rpm",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m800": "800 rpm",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m900": "900 rpm",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1000": "1000 rpm",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1200": "1200 rpm",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1400": "1400 rpm",
|
||||
@ -1430,6 +1431,7 @@
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m400": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m400%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m600": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m600%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m800": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m800%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m900": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m900%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1000": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m1000%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1200": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m1200%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1400": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m1400%]",
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/hydrawise",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pydrawise"],
|
||||
"requirements": ["pydrawise==2025.2.0"]
|
||||
"requirements": ["pydrawise==2025.3.0"]
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import socket
|
||||
import ssl
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from uuid import uuid4
|
||||
|
||||
import certifi
|
||||
|
||||
@ -292,7 +293,7 @@ class MqttClientSetup:
|
||||
"""
|
||||
# We don't import on the top because some integrations
|
||||
# should be able to optionally rely on MQTT.
|
||||
import paho.mqtt.client as mqtt # pylint: disable=import-outside-toplevel
|
||||
from paho.mqtt import client as mqtt # pylint: disable=import-outside-toplevel
|
||||
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
from .async_client import AsyncMQTTClient
|
||||
@ -309,9 +310,10 @@ class MqttClientSetup:
|
||||
clean_session = True
|
||||
|
||||
if (client_id := config.get(CONF_CLIENT_ID)) is None:
|
||||
# PAHO MQTT relies on the MQTT server to generate random client IDs.
|
||||
# However, that feature is not mandatory so we generate our own.
|
||||
client_id = None
|
||||
# PAHO MQTT relies on the MQTT server to generate random client ID
|
||||
# for protocol version 3.1, however, that feature is not mandatory
|
||||
# so we generate our own.
|
||||
client_id = mqtt._base62(uuid4().int, padding=22) # noqa: SLF001
|
||||
transport: str = config.get(CONF_TRANSPORT, DEFAULT_TRANSPORT)
|
||||
self._client = AsyncMQTTClient(
|
||||
callback_api_version=mqtt.CallbackAPIVersion.VERSION2,
|
||||
|
@ -31,7 +31,6 @@ from homeassistant.components.light import (
|
||||
LightEntity,
|
||||
LightEntityFeature,
|
||||
brightness_supported,
|
||||
color_supported,
|
||||
valid_supported_color_modes,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
@ -293,7 +292,7 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
|
||||
elif values["state"] is None:
|
||||
self._attr_is_on = None
|
||||
|
||||
if color_supported(self.supported_color_modes) and "color_mode" in values:
|
||||
if "color_mode" in values:
|
||||
self._update_color(values)
|
||||
|
||||
if brightness_supported(self.supported_color_modes):
|
||||
|
@ -11,7 +11,6 @@ import voluptuous as vol
|
||||
from homeassistant.components import sensor
|
||||
from homeassistant.components.sensor import (
|
||||
CONF_STATE_CLASS,
|
||||
DEVICE_CLASS_UNITS,
|
||||
DEVICE_CLASSES_SCHEMA,
|
||||
ENTITY_ID_FORMAT,
|
||||
STATE_CLASSES_SCHEMA,
|
||||
@ -108,20 +107,6 @@ def validate_sensor_state_and_device_class_config(config: ConfigType) -> ConfigT
|
||||
f"got `{CONF_DEVICE_CLASS}` '{device_class}'"
|
||||
)
|
||||
|
||||
if (device_class := config.get(CONF_DEVICE_CLASS)) is None or (
|
||||
unit_of_measurement := config.get(CONF_UNIT_OF_MEASUREMENT)
|
||||
) is None:
|
||||
return config
|
||||
|
||||
if (
|
||||
device_class in DEVICE_CLASS_UNITS
|
||||
and unit_of_measurement not in DEVICE_CLASS_UNITS[device_class]
|
||||
):
|
||||
raise vol.Invalid(
|
||||
f"The unit of measurement `{unit_of_measurement}` is not valid "
|
||||
f"together with device class `{device_class}`"
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
|
@ -276,22 +276,26 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
|
||||
self._attr_state = MediaPlayerState(player.state.value)
|
||||
else:
|
||||
self._attr_state = MediaPlayerState(STATE_OFF)
|
||||
group_members_entity_ids: list[str] = []
|
||||
|
||||
group_members: list[str] = []
|
||||
if player.group_childs:
|
||||
# translate MA group_childs to HA group_members as entity id's
|
||||
entity_registry = er.async_get(self.hass)
|
||||
group_members_entity_ids = [
|
||||
entity_id
|
||||
for child_id in player.group_childs
|
||||
if (
|
||||
entity_id := entity_registry.async_get_entity_id(
|
||||
self.platform.domain, DOMAIN, child_id
|
||||
)
|
||||
group_members = player.group_childs
|
||||
elif player.synced_to and (parent := self.mass.players.get(player.synced_to)):
|
||||
group_members = parent.group_childs
|
||||
|
||||
# translate MA group_childs to HA group_members as entity id's
|
||||
entity_registry = er.async_get(self.hass)
|
||||
group_members_entity_ids: list[str] = [
|
||||
entity_id
|
||||
for child_id in group_members
|
||||
if (
|
||||
entity_id := entity_registry.async_get_entity_id(
|
||||
self.platform.domain, DOMAIN, child_id
|
||||
)
|
||||
]
|
||||
# NOTE: we sort the group_members for now,
|
||||
# until the MA API returns them sorted (group_childs is now a set)
|
||||
self._attr_group_members = sorted(group_members_entity_ids)
|
||||
)
|
||||
]
|
||||
|
||||
self._attr_group_members = group_members_entity_ids
|
||||
self._attr_volume_level = (
|
||||
player.volume_level / 100 if player.volume_level is not None else None
|
||||
)
|
||||
|
@ -58,6 +58,9 @@
|
||||
"switch": {
|
||||
"hold": {
|
||||
"name": "Hold"
|
||||
},
|
||||
"emergency_heat": {
|
||||
"name": "Emergency heat"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
import os
|
||||
@ -58,7 +59,7 @@ class OneWireHub:
|
||||
|
||||
owproxy: protocol._Proxy
|
||||
devices: list[OWDeviceDescription]
|
||||
_version: str
|
||||
_version: str | None = None
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config_entry: OneWireConfigEntry) -> None:
|
||||
"""Initialize."""
|
||||
@ -74,7 +75,9 @@ class OneWireHub:
|
||||
port = self._config_entry.data[CONF_PORT]
|
||||
_LOGGER.debug("Initializing connection to %s:%s", host, port)
|
||||
self.owproxy = protocol.proxy(host, port)
|
||||
self._version = self.owproxy.read(protocol.PTH_VERSION).decode()
|
||||
with contextlib.suppress(protocol.OwnetError):
|
||||
# Version is not available on all servers
|
||||
self._version = self.owproxy.read(protocol.PTH_VERSION).decode()
|
||||
self.devices = _discover_devices(self.owproxy)
|
||||
|
||||
async def initialize(self) -> None:
|
||||
|
@ -7,7 +7,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["roborock"],
|
||||
"requirements": [
|
||||
"python-roborock==2.11.1",
|
||||
"python-roborock==2.12.2",
|
||||
"vacuum-map-parser-roborock==0.1.2"
|
||||
]
|
||||
}
|
||||
|
@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/sense",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["sense_energy"],
|
||||
"requirements": ["sense-energy==0.13.6"]
|
||||
"requirements": ["sense-energy==0.13.7"]
|
||||
}
|
||||
|
@ -16,12 +16,18 @@ from pysmartthings import (
|
||||
Scene,
|
||||
SmartThings,
|
||||
SmartThingsAuthenticationFailedError,
|
||||
SmartThingsSinkError,
|
||||
Status,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.const import (
|
||||
CONF_ACCESS_TOKEN,
|
||||
CONF_TOKEN,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
@ -33,6 +39,7 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
from .const import (
|
||||
CONF_INSTALLED_APP_ID,
|
||||
CONF_LOCATION_ID,
|
||||
CONF_SUBSCRIPTION_ID,
|
||||
DOMAIN,
|
||||
EVENT_BUTTON,
|
||||
MAIN,
|
||||
@ -99,6 +106,54 @@ async def async_setup_entry(hass: HomeAssistant, entry: SmartThingsConfigEntry)
|
||||
|
||||
client.refresh_token_function = _refresh_token
|
||||
|
||||
def _handle_max_connections() -> None:
|
||||
_LOGGER.debug("We hit the limit of max connections")
|
||||
hass.config_entries.async_schedule_reload(entry.entry_id)
|
||||
|
||||
client.max_connections_reached_callback = _handle_max_connections
|
||||
|
||||
def _handle_new_subscription_identifier(identifier: str | None) -> None:
|
||||
"""Handle a new subscription identifier."""
|
||||
hass.config_entries.async_update_entry(
|
||||
entry,
|
||||
data={
|
||||
**entry.data,
|
||||
CONF_SUBSCRIPTION_ID: identifier,
|
||||
},
|
||||
)
|
||||
if identifier is not None:
|
||||
_LOGGER.debug("Updating subscription ID to %s", identifier)
|
||||
else:
|
||||
_LOGGER.debug("Removing subscription ID")
|
||||
|
||||
client.new_subscription_id_callback = _handle_new_subscription_identifier
|
||||
|
||||
if (old_identifier := entry.data.get(CONF_SUBSCRIPTION_ID)) is not None:
|
||||
_LOGGER.debug("Trying to delete old subscription %s", old_identifier)
|
||||
await client.delete_subscription(old_identifier)
|
||||
|
||||
_LOGGER.debug("Trying to create a new subscription")
|
||||
try:
|
||||
subscription = await client.create_subscription(
|
||||
entry.data[CONF_LOCATION_ID],
|
||||
entry.data[CONF_TOKEN][CONF_INSTALLED_APP_ID],
|
||||
)
|
||||
except SmartThingsSinkError as err:
|
||||
_LOGGER.debug("Couldn't create a new subscription: %s", err)
|
||||
raise ConfigEntryNotReady from err
|
||||
subscription_id = subscription.subscription_id
|
||||
_handle_new_subscription_identifier(subscription_id)
|
||||
|
||||
entry.async_create_background_task(
|
||||
hass,
|
||||
client.subscribe(
|
||||
entry.data[CONF_LOCATION_ID],
|
||||
entry.data[CONF_TOKEN][CONF_INSTALLED_APP_ID],
|
||||
subscription,
|
||||
),
|
||||
"smartthings_socket",
|
||||
)
|
||||
|
||||
device_status: dict[str, FullDevice] = {}
|
||||
try:
|
||||
devices = await client.get_devices()
|
||||
@ -145,12 +200,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: SmartThingsConfigEntry)
|
||||
client.add_unspecified_device_event_listener(handle_button_press)
|
||||
)
|
||||
|
||||
entry.async_create_background_task(
|
||||
hass,
|
||||
client.subscribe(
|
||||
entry.data[CONF_LOCATION_ID], entry.data[CONF_TOKEN][CONF_INSTALLED_APP_ID]
|
||||
),
|
||||
"smartthings_webhook",
|
||||
async def _handle_shutdown(_: Event) -> None:
|
||||
"""Handle shutdown."""
|
||||
await client.delete_subscription(subscription_id)
|
||||
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _handle_shutdown)
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
@ -176,6 +231,9 @@ async def async_unload_entry(
|
||||
hass: HomeAssistant, entry: SmartThingsConfigEntry
|
||||
) -> bool:
|
||||
"""Unload a config entry."""
|
||||
client = entry.runtime_data.client
|
||||
if (subscription_id := entry.data.get(CONF_SUBSCRIPTION_ID)) is not None:
|
||||
await client.delete_subscription(subscription_id)
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
@ -194,34 +252,15 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
KEEP_CAPABILITY_QUIRK: dict[
|
||||
Capability | str, Callable[[dict[Attribute | str, Status]], bool]
|
||||
] = {
|
||||
Capability.DRYER_OPERATING_STATE: (
|
||||
lambda status: status[Attribute.SUPPORTED_MACHINE_STATES].value is not None
|
||||
),
|
||||
Capability.WASHER_OPERATING_STATE: (
|
||||
lambda status: status[Attribute.SUPPORTED_MACHINE_STATES].value is not None
|
||||
),
|
||||
Capability.DEMAND_RESPONSE_LOAD_CONTROL: lambda _: True,
|
||||
}
|
||||
|
||||
POWER_CONSUMPTION_FIELDS = {
|
||||
"energy",
|
||||
"power",
|
||||
"deltaEnergy",
|
||||
"powerEnergy",
|
||||
"energySaved",
|
||||
}
|
||||
|
||||
CAPABILITY_VALIDATION: dict[
|
||||
Capability | str, Callable[[dict[Attribute | str, Status]], bool]
|
||||
] = {
|
||||
Capability.POWER_CONSUMPTION_REPORT: (
|
||||
lambda status: (
|
||||
(power_consumption := status[Attribute.POWER_CONSUMPTION].value) is not None
|
||||
and all(
|
||||
field in cast(dict, power_consumption)
|
||||
for field in POWER_CONSUMPTION_FIELDS
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def process_status(
|
||||
status: dict[str, dict[Capability | str, dict[Attribute | str, Status]]],
|
||||
@ -245,8 +284,4 @@ def process_status(
|
||||
or not KEEP_CAPABILITY_QUIRK[capability](main_component[capability])
|
||||
):
|
||||
del main_component[capability]
|
||||
for capability in list(main_component):
|
||||
if capability in CAPABILITY_VALIDATION:
|
||||
if not CAPABILITY_VALIDATION[capability](main_component[capability]):
|
||||
del main_component[capability]
|
||||
return status
|
||||
|
@ -251,6 +251,8 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity):
|
||||
@property
|
||||
def hvac_action(self) -> HVACAction | None:
|
||||
"""Return the current running hvac operation if supported."""
|
||||
if not self.supports_capability(Capability.THERMOSTAT_OPERATING_STATE):
|
||||
return None
|
||||
return OPERATING_STATE_TO_ACTION.get(
|
||||
self.get_attribute_value(
|
||||
Capability.THERMOSTAT_OPERATING_STATE,
|
||||
@ -270,11 +272,15 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity):
|
||||
@property
|
||||
def hvac_modes(self) -> list[HVACMode]:
|
||||
"""Return the list of available operation modes."""
|
||||
return [
|
||||
state
|
||||
for mode in self.get_attribute_value(
|
||||
if (
|
||||
supported_thermostat_modes := self.get_attribute_value(
|
||||
Capability.THERMOSTAT_MODE, Attribute.SUPPORTED_THERMOSTAT_MODES
|
||||
)
|
||||
) is None:
|
||||
return []
|
||||
return [
|
||||
state
|
||||
for mode in supported_thermostat_modes
|
||||
if (state := AC_MODE_TO_STATE.get(mode)) is not None
|
||||
]
|
||||
|
||||
@ -312,10 +318,14 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity):
|
||||
@property
|
||||
def temperature_unit(self) -> str:
|
||||
"""Return the unit of measurement."""
|
||||
unit = self._internal_state[Capability.TEMPERATURE_MEASUREMENT][
|
||||
Attribute.TEMPERATURE
|
||||
].unit
|
||||
assert unit
|
||||
# Offline third party thermostats may not have a unit
|
||||
# Since climate always requires a unit, default to Celsius
|
||||
if (
|
||||
unit := self._internal_state[Capability.TEMPERATURE_MEASUREMENT][
|
||||
Attribute.TEMPERATURE
|
||||
].unit
|
||||
) is None:
|
||||
return UnitOfTemperature.CELSIUS
|
||||
return UNIT_MAP[unit]
|
||||
|
||||
|
||||
|
@ -33,4 +33,5 @@ CONF_REFRESH_TOKEN = "refresh_token"
|
||||
MAIN = "main"
|
||||
OLD_DATA = "old_data"
|
||||
|
||||
CONF_SUBSCRIPTION_ID = "subscription_id"
|
||||
EVENT_BUTTON = "smartthings.button"
|
||||
|
@ -147,14 +147,21 @@ class SmartThingsLight(SmartThingsEntity, LightEntity, RestoreEntity):
|
||||
"""Update entity attributes when the device status has changed."""
|
||||
# Brightness and transition
|
||||
if brightness_supported(self._attr_supported_color_modes):
|
||||
self._attr_brightness = int(
|
||||
convert_scale(
|
||||
self.get_attribute_value(Capability.SWITCH_LEVEL, Attribute.LEVEL),
|
||||
100,
|
||||
255,
|
||||
0,
|
||||
if (
|
||||
brightness := self.get_attribute_value(
|
||||
Capability.SWITCH_LEVEL, Attribute.LEVEL
|
||||
)
|
||||
) is None:
|
||||
self._attr_brightness = None
|
||||
else:
|
||||
self._attr_brightness = int(
|
||||
convert_scale(
|
||||
brightness,
|
||||
100,
|
||||
255,
|
||||
0,
|
||||
)
|
||||
)
|
||||
)
|
||||
# Color Temperature
|
||||
if ColorMode.COLOR_TEMP in self._attr_supported_color_modes:
|
||||
self._attr_color_temp_kelvin = self.get_attribute_value(
|
||||
@ -162,16 +169,21 @@ class SmartThingsLight(SmartThingsEntity, LightEntity, RestoreEntity):
|
||||
)
|
||||
# Color
|
||||
if ColorMode.HS in self._attr_supported_color_modes:
|
||||
self._attr_hs_color = (
|
||||
convert_scale(
|
||||
self.get_attribute_value(Capability.COLOR_CONTROL, Attribute.HUE),
|
||||
100,
|
||||
360,
|
||||
),
|
||||
self.get_attribute_value(
|
||||
Capability.COLOR_CONTROL, Attribute.SATURATION
|
||||
),
|
||||
)
|
||||
if (
|
||||
hue := self.get_attribute_value(Capability.COLOR_CONTROL, Attribute.HUE)
|
||||
) is None:
|
||||
self._attr_hs_color = None
|
||||
else:
|
||||
self._attr_hs_color = (
|
||||
convert_scale(
|
||||
hue,
|
||||
100,
|
||||
360,
|
||||
),
|
||||
self.get_attribute_value(
|
||||
Capability.COLOR_CONTROL, Attribute.SATURATION
|
||||
),
|
||||
)
|
||||
|
||||
async def async_set_color(self, hs_color):
|
||||
"""Set the color of the device."""
|
||||
@ -217,6 +229,10 @@ class SmartThingsLight(SmartThingsEntity, LightEntity, RestoreEntity):
|
||||
super()._update_handler(event)
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return true if light is on."""
|
||||
return self.get_attribute_value(Capability.SWITCH, Attribute.SWITCH) == "on"
|
||||
if (
|
||||
state := self.get_attribute_value(Capability.SWITCH, Attribute.SWITCH)
|
||||
) is None:
|
||||
return None
|
||||
return state == "on"
|
||||
|
@ -29,5 +29,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/smartthings",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pysmartthings"],
|
||||
"requirements": ["pysmartthings==2.7.0"]
|
||||
"requirements": ["pysmartthings==2.7.2"]
|
||||
}
|
||||
|
@ -5,9 +5,9 @@ from __future__ import annotations
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
from pysmartthings import Attribute, Capability, SmartThings
|
||||
from pysmartthings import Attribute, Capability, SmartThings, Status
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
@ -57,6 +57,7 @@ JOB_STATE_MAP = {
|
||||
"freezeProtection": "freeze_protection",
|
||||
"preDrain": "pre_drain",
|
||||
"preWash": "pre_wash",
|
||||
"prewash": "pre_wash",
|
||||
"wrinklePrevent": "wrinkle_prevent",
|
||||
"unknown": None,
|
||||
}
|
||||
@ -130,6 +131,7 @@ class SmartThingsSensorEntityDescription(SensorEntityDescription):
|
||||
unique_id_separator: str = "."
|
||||
capability_ignore_list: list[set[Capability]] | None = None
|
||||
options_attribute: Attribute | None = None
|
||||
exists_fn: Callable[[Status], bool] | None = None
|
||||
|
||||
|
||||
CAPABILITY_TO_SENSORS: dict[
|
||||
@ -560,6 +562,8 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
SmartThingsSensorEntityDescription(
|
||||
key=Attribute.COMPLETION_TIME,
|
||||
translation_key="completion_time",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=dt_util.parse_datetime,
|
||||
)
|
||||
],
|
||||
},
|
||||
@ -580,6 +584,10 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
value_fn=lambda value: value["energy"] / 1000,
|
||||
suggested_display_precision=2,
|
||||
exists_fn=lambda status: (
|
||||
(value := cast(dict | None, status.value)) is not None
|
||||
and "energy" in value
|
||||
),
|
||||
),
|
||||
SmartThingsSensorEntityDescription(
|
||||
key="power_meter",
|
||||
@ -589,6 +597,10 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
value_fn=lambda value: value["power"],
|
||||
extra_state_attributes_fn=power_attributes,
|
||||
suggested_display_precision=2,
|
||||
exists_fn=lambda status: (
|
||||
(value := cast(dict | None, status.value)) is not None
|
||||
and "power" in value
|
||||
),
|
||||
),
|
||||
SmartThingsSensorEntityDescription(
|
||||
key="deltaEnergy_meter",
|
||||
@ -598,6 +610,10 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
value_fn=lambda value: value["deltaEnergy"] / 1000,
|
||||
suggested_display_precision=2,
|
||||
exists_fn=lambda status: (
|
||||
(value := cast(dict | None, status.value)) is not None
|
||||
and "deltaEnergy" in value
|
||||
),
|
||||
),
|
||||
SmartThingsSensorEntityDescription(
|
||||
key="powerEnergy_meter",
|
||||
@ -607,6 +623,10 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
value_fn=lambda value: value["powerEnergy"] / 1000,
|
||||
suggested_display_precision=2,
|
||||
exists_fn=lambda status: (
|
||||
(value := cast(dict | None, status.value)) is not None
|
||||
and "powerEnergy" in value
|
||||
),
|
||||
),
|
||||
SmartThingsSensorEntityDescription(
|
||||
key="energySaved_meter",
|
||||
@ -616,6 +636,10 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
value_fn=lambda value: value["energySaved"] / 1000,
|
||||
suggested_display_precision=2,
|
||||
exists_fn=lambda status: (
|
||||
(value := cast(dict | None, status.value)) is not None
|
||||
and "energySaved" in value
|
||||
),
|
||||
),
|
||||
]
|
||||
},
|
||||
@ -970,6 +994,10 @@ async def async_setup_entry(
|
||||
for capability_list in description.capability_ignore_list
|
||||
)
|
||||
)
|
||||
and (
|
||||
not description.exists_fn
|
||||
or description.exists_fn(device.status[MAIN][capability][attribute])
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -1022,8 +1050,11 @@ class SmartThingsSensor(SmartThingsEntity, SensorEntity):
|
||||
def options(self) -> list[str] | None:
|
||||
"""Return the options for this sensor."""
|
||||
if self.entity_description.options_attribute:
|
||||
options = self.get_attribute_value(
|
||||
self.capability, self.entity_description.options_attribute
|
||||
)
|
||||
if (
|
||||
options := self.get_attribute_value(
|
||||
self.capability, self.entity_description.options_attribute
|
||||
)
|
||||
) is None:
|
||||
return []
|
||||
return [option.lower() for option in options]
|
||||
return super().options
|
||||
|
@ -32,6 +32,7 @@ SONOS_TRACKS = "tracks"
|
||||
SONOS_COMPOSER = "composers"
|
||||
SONOS_RADIO = "radio"
|
||||
SONOS_OTHER_ITEM = "other items"
|
||||
SONOS_AUDIO_BOOK = "audio book"
|
||||
|
||||
SONOS_STATE_PLAYING = "PLAYING"
|
||||
SONOS_STATE_TRANSITIONING = "TRANSITIONING"
|
||||
@ -67,6 +68,7 @@ SONOS_TO_MEDIA_CLASSES = {
|
||||
"object.item": MediaClass.TRACK,
|
||||
"object.item.audioItem.musicTrack": MediaClass.TRACK,
|
||||
"object.item.audioItem.audioBroadcast": MediaClass.GENRE,
|
||||
"object.item.audioItem.audioBook": MediaClass.TRACK,
|
||||
}
|
||||
|
||||
SONOS_TO_MEDIA_TYPES = {
|
||||
@ -84,6 +86,7 @@ SONOS_TO_MEDIA_TYPES = {
|
||||
"object.container.playlistContainer.sameArtist": MediaType.ARTIST,
|
||||
"object.container.playlistContainer": MediaType.PLAYLIST,
|
||||
"object.item.audioItem.musicTrack": MediaType.TRACK,
|
||||
"object.item.audioItem.audioBook": MediaType.TRACK,
|
||||
}
|
||||
|
||||
MEDIA_TYPES_TO_SONOS: dict[MediaType | str, str] = {
|
||||
@ -113,6 +116,7 @@ SONOS_TYPES_MAPPING = {
|
||||
"object.item": SONOS_OTHER_ITEM,
|
||||
"object.item.audioItem.musicTrack": SONOS_TRACKS,
|
||||
"object.item.audioItem.audioBroadcast": SONOS_RADIO,
|
||||
"object.item.audioItem.audioBook": SONOS_AUDIO_BOOK,
|
||||
}
|
||||
|
||||
LIBRARY_TITLES_MAPPING = {
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/teslemetry",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["tesla-fleet-api"],
|
||||
"requirements": ["tesla-fleet-api==0.9.12", "teslemetry-stream==0.6.10"]
|
||||
"requirements": ["tesla-fleet-api==0.9.12", "teslemetry-stream==0.6.12"]
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
"velbus-protocol"
|
||||
],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["velbus-aio==2025.1.1"],
|
||||
"requirements": ["velbus-aio==2025.3.0"],
|
||||
"usb": [
|
||||
{
|
||||
"vid": "10CF",
|
||||
|
@ -13,7 +13,11 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
|
||||
|
||||
from .const import CONF_BACKUP_PATH, DATA_BACKUP_AGENT_LISTENERS, DOMAIN
|
||||
from .helpers import async_create_client, async_ensure_path_exists
|
||||
from .helpers import (
|
||||
async_create_client,
|
||||
async_ensure_path_exists,
|
||||
async_migrate_wrong_folder_path,
|
||||
)
|
||||
|
||||
type WebDavConfigEntry = ConfigEntry[Client]
|
||||
|
||||
@ -46,10 +50,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: WebDavConfigEntry) -> bo
|
||||
translation_key="cannot_connect",
|
||||
)
|
||||
|
||||
path = entry.data.get(CONF_BACKUP_PATH, "/")
|
||||
await async_migrate_wrong_folder_path(client, path)
|
||||
|
||||
# Ensure the backup directory exists
|
||||
if not await async_ensure_path_exists(
|
||||
client, entry.data.get(CONF_BACKUP_PATH, "/")
|
||||
):
|
||||
if not await async_ensure_path_exists(client, path):
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="cannot_access_or_create_backup_path",
|
||||
|
@ -1,10 +1,18 @@
|
||||
"""Helper functions for the WebDAV component."""
|
||||
|
||||
import logging
|
||||
|
||||
from aiowebdav2.client import Client, ClientOptions
|
||||
from aiowebdav2.exceptions import WebDavError
|
||||
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@callback
|
||||
def async_create_client(
|
||||
@ -36,3 +44,24 @@ async def async_ensure_path_exists(client: Client, path: str) -> bool:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_migrate_wrong_folder_path(client: Client, path: str) -> None:
|
||||
"""Migrate the wrong encoded folder path to the correct one."""
|
||||
wrong_path = path.replace(" ", "%20")
|
||||
if await client.check(wrong_path):
|
||||
try:
|
||||
await client.move(wrong_path, path)
|
||||
except WebDavError as err:
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="failed_to_migrate_folder",
|
||||
translation_placeholders={
|
||||
"wrong_path": wrong_path,
|
||||
"correct_path": path,
|
||||
},
|
||||
) from err
|
||||
|
||||
_LOGGER.debug(
|
||||
"Migrated wrong encoded folder path from %s to %s", wrong_path, path
|
||||
)
|
||||
|
@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aiowebdav2"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["aiowebdav2==0.4.1"]
|
||||
"requirements": ["aiowebdav2==0.4.2"]
|
||||
}
|
||||
|
@ -36,6 +36,9 @@
|
||||
},
|
||||
"cannot_access_or_create_backup_path": {
|
||||
"message": "Cannot access or create backup path. Please check the path and permissions."
|
||||
},
|
||||
"failed_to_migrate_folder": {
|
||||
"message": "Failed to migrate wrong encoded folder \"{wrong_path}\" to \"{correct_path}\"."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
||||
"zha",
|
||||
"universal_silabs_flasher"
|
||||
],
|
||||
"requirements": ["zha==0.0.51"],
|
||||
"requirements": ["zha==0.0.52"],
|
||||
"usb": [
|
||||
{
|
||||
"vid": "10C4",
|
||||
|
@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2025
|
||||
MINOR_VERSION: Final = 3
|
||||
PATCH_VERSION: Final = "1"
|
||||
PATCH_VERSION: Final = "2"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 13, 0)
|
||||
|
@ -41,7 +41,7 @@ home-assistant-frontend==20250306.0
|
||||
home-assistant-intents==2025.3.5
|
||||
httpx==0.28.1
|
||||
ifaddr==0.2.0
|
||||
Jinja2==3.1.5
|
||||
Jinja2==3.1.6
|
||||
lru-dict==1.3.0
|
||||
mutagen==1.47.0
|
||||
orjson==3.10.12
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2025.3.1"
|
||||
version = "2025.3.2"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
@ -52,7 +52,7 @@ dependencies = [
|
||||
"httpx==0.28.1",
|
||||
"home-assistant-bluetooth==1.13.1",
|
||||
"ifaddr==0.2.0",
|
||||
"Jinja2==3.1.5",
|
||||
"Jinja2==3.1.6",
|
||||
"lru-dict==1.3.0",
|
||||
"PyJWT==2.10.1",
|
||||
# PyJWT has loose dependency. We want the latest one.
|
||||
|
2
requirements.txt
generated
2
requirements.txt
generated
@ -25,7 +25,7 @@ hass-nabucasa==0.92.0
|
||||
httpx==0.28.1
|
||||
home-assistant-bluetooth==1.13.1
|
||||
ifaddr==0.2.0
|
||||
Jinja2==3.1.5
|
||||
Jinja2==3.1.6
|
||||
lru-dict==1.3.0
|
||||
PyJWT==2.10.1
|
||||
cryptography==44.0.1
|
||||
|
24
requirements_all.txt
generated
24
requirements_all.txt
generated
@ -422,7 +422,7 @@ aiowaqi==3.1.0
|
||||
aiowatttime==0.1.1
|
||||
|
||||
# homeassistant.components.webdav
|
||||
aiowebdav2==0.4.1
|
||||
aiowebdav2==0.4.2
|
||||
|
||||
# homeassistant.components.webostv
|
||||
aiowebostv==0.7.3
|
||||
@ -557,7 +557,7 @@ av==13.1.0
|
||||
axis==64
|
||||
|
||||
# homeassistant.components.fujitsu_fglair
|
||||
ayla-iot-unofficial==1.4.5
|
||||
ayla-iot-unofficial==1.4.7
|
||||
|
||||
# homeassistant.components.azure_event_hub
|
||||
azure-eventhub==5.11.1
|
||||
@ -899,7 +899,7 @@ eufylife-ble-client==0.1.8
|
||||
# evdev==1.6.1
|
||||
|
||||
# homeassistant.components.evohome
|
||||
evohome-async==1.0.2
|
||||
evohome-async==1.0.4
|
||||
|
||||
# homeassistant.components.bryant_evolution
|
||||
evolutionhttp==0.0.18
|
||||
@ -1058,7 +1058,7 @@ goslide-api==0.7.0
|
||||
gotailwind==0.3.0
|
||||
|
||||
# homeassistant.components.govee_ble
|
||||
govee-ble==0.43.0
|
||||
govee-ble==0.43.1
|
||||
|
||||
# homeassistant.components.govee_light_local
|
||||
govee-local-api==2.0.1
|
||||
@ -1906,7 +1906,7 @@ pydiscovergy==3.0.2
|
||||
pydoods==1.0.2
|
||||
|
||||
# homeassistant.components.hydrawise
|
||||
pydrawise==2025.2.0
|
||||
pydrawise==2025.3.0
|
||||
|
||||
# homeassistant.components.android_ip_webcam
|
||||
pydroid-ipcam==2.0.0
|
||||
@ -1996,7 +1996,7 @@ pygti==0.9.4
|
||||
pyhaversion==22.8.0
|
||||
|
||||
# homeassistant.components.heos
|
||||
pyheos==1.0.2
|
||||
pyheos==1.0.3
|
||||
|
||||
# homeassistant.components.hive
|
||||
pyhive-integration==1.0.2
|
||||
@ -2310,7 +2310,7 @@ pysma==0.7.5
|
||||
pysmappee==0.2.29
|
||||
|
||||
# homeassistant.components.smartthings
|
||||
pysmartthings==2.7.0
|
||||
pysmartthings==2.7.2
|
||||
|
||||
# homeassistant.components.smarty
|
||||
pysmarty2==0.10.2
|
||||
@ -2461,7 +2461,7 @@ python-rabbitair==0.0.8
|
||||
python-ripple-api==0.0.3
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==2.11.1
|
||||
python-roborock==2.12.2
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.39
|
||||
@ -2694,7 +2694,7 @@ sendgrid==6.8.2
|
||||
|
||||
# homeassistant.components.emulated_kasa
|
||||
# homeassistant.components.sense
|
||||
sense-energy==0.13.6
|
||||
sense-energy==0.13.7
|
||||
|
||||
# homeassistant.components.sensirion_ble
|
||||
sensirion-ble==0.1.1
|
||||
@ -2881,7 +2881,7 @@ tesla-powerwall==0.5.2
|
||||
tesla-wall-connector==1.0.2
|
||||
|
||||
# homeassistant.components.teslemetry
|
||||
teslemetry-stream==0.6.10
|
||||
teslemetry-stream==0.6.12
|
||||
|
||||
# homeassistant.components.tessie
|
||||
tessie-api==0.1.1
|
||||
@ -3000,7 +3000,7 @@ vallox-websocket-api==5.3.0
|
||||
vehicle==2.2.2
|
||||
|
||||
# homeassistant.components.velbus
|
||||
velbus-aio==2025.1.1
|
||||
velbus-aio==2025.3.0
|
||||
|
||||
# homeassistant.components.venstar
|
||||
venstarcolortouch==0.19
|
||||
@ -3149,7 +3149,7 @@ zeroconf==0.145.1
|
||||
zeversolar==0.3.2
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==0.0.51
|
||||
zha==0.0.52
|
||||
|
||||
# homeassistant.components.zhong_hong
|
||||
zhong-hong-hvac==1.0.13
|
||||
|
24
requirements_test_all.txt
generated
24
requirements_test_all.txt
generated
@ -404,7 +404,7 @@ aiowaqi==3.1.0
|
||||
aiowatttime==0.1.1
|
||||
|
||||
# homeassistant.components.webdav
|
||||
aiowebdav2==0.4.1
|
||||
aiowebdav2==0.4.2
|
||||
|
||||
# homeassistant.components.webostv
|
||||
aiowebostv==0.7.3
|
||||
@ -506,7 +506,7 @@ av==13.1.0
|
||||
axis==64
|
||||
|
||||
# homeassistant.components.fujitsu_fglair
|
||||
ayla-iot-unofficial==1.4.5
|
||||
ayla-iot-unofficial==1.4.7
|
||||
|
||||
# homeassistant.components.azure_event_hub
|
||||
azure-eventhub==5.11.1
|
||||
@ -765,7 +765,7 @@ eternalegypt==0.0.16
|
||||
eufylife-ble-client==0.1.8
|
||||
|
||||
# homeassistant.components.evohome
|
||||
evohome-async==1.0.2
|
||||
evohome-async==1.0.4
|
||||
|
||||
# homeassistant.components.bryant_evolution
|
||||
evolutionhttp==0.0.18
|
||||
@ -908,7 +908,7 @@ goslide-api==0.7.0
|
||||
gotailwind==0.3.0
|
||||
|
||||
# homeassistant.components.govee_ble
|
||||
govee-ble==0.43.0
|
||||
govee-ble==0.43.1
|
||||
|
||||
# homeassistant.components.govee_light_local
|
||||
govee-local-api==2.0.1
|
||||
@ -1556,7 +1556,7 @@ pydexcom==0.2.3
|
||||
pydiscovergy==3.0.2
|
||||
|
||||
# homeassistant.components.hydrawise
|
||||
pydrawise==2025.2.0
|
||||
pydrawise==2025.3.0
|
||||
|
||||
# homeassistant.components.android_ip_webcam
|
||||
pydroid-ipcam==2.0.0
|
||||
@ -1625,7 +1625,7 @@ pygti==0.9.4
|
||||
pyhaversion==22.8.0
|
||||
|
||||
# homeassistant.components.heos
|
||||
pyheos==1.0.2
|
||||
pyheos==1.0.3
|
||||
|
||||
# homeassistant.components.hive
|
||||
pyhive-integration==1.0.2
|
||||
@ -1882,7 +1882,7 @@ pysma==0.7.5
|
||||
pysmappee==0.2.29
|
||||
|
||||
# homeassistant.components.smartthings
|
||||
pysmartthings==2.7.0
|
||||
pysmartthings==2.7.2
|
||||
|
||||
# homeassistant.components.smarty
|
||||
pysmarty2==0.10.2
|
||||
@ -1994,7 +1994,7 @@ python-picnic-api2==1.2.2
|
||||
python-rabbitair==0.0.8
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==2.11.1
|
||||
python-roborock==2.12.2
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.39
|
||||
@ -2173,7 +2173,7 @@ securetar==2025.2.1
|
||||
|
||||
# homeassistant.components.emulated_kasa
|
||||
# homeassistant.components.sense
|
||||
sense-energy==0.13.6
|
||||
sense-energy==0.13.7
|
||||
|
||||
# homeassistant.components.sensirion_ble
|
||||
sensirion-ble==0.1.1
|
||||
@ -2321,7 +2321,7 @@ tesla-powerwall==0.5.2
|
||||
tesla-wall-connector==1.0.2
|
||||
|
||||
# homeassistant.components.teslemetry
|
||||
teslemetry-stream==0.6.10
|
||||
teslemetry-stream==0.6.12
|
||||
|
||||
# homeassistant.components.tessie
|
||||
tessie-api==0.1.1
|
||||
@ -2416,7 +2416,7 @@ vallox-websocket-api==5.3.0
|
||||
vehicle==2.2.2
|
||||
|
||||
# homeassistant.components.velbus
|
||||
velbus-aio==2025.1.1
|
||||
velbus-aio==2025.3.0
|
||||
|
||||
# homeassistant.components.venstar
|
||||
venstarcolortouch==0.19
|
||||
@ -2538,7 +2538,7 @@ zeroconf==0.145.1
|
||||
zeversolar==0.3.2
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==0.0.51
|
||||
zha==0.0.52
|
||||
|
||||
# homeassistant.components.zwave_js
|
||||
zwave-js-server-python==0.60.1
|
||||
|
@ -47,7 +47,8 @@ from homeassistant.components.backup.manager import (
|
||||
WrittenBackup,
|
||||
)
|
||||
from homeassistant.components.backup.util import password_to_key
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STARTED
|
||||
from homeassistant.core import CoreState, HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
@ -3469,3 +3470,66 @@ async def test_restore_progress_after_restart_fail_to_remove(
|
||||
"Unexpected error deleting backup restore result file: <class 'OSError'> Boom!"
|
||||
in caplog.text
|
||||
)
|
||||
|
||||
|
||||
async def test_manager_blocked_until_home_assistant_started(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test backup manager's state is blocked until Home Assistant has started."""
|
||||
|
||||
hass.set_state(CoreState.not_running)
|
||||
|
||||
await setup_backup_integration(hass)
|
||||
manager = hass.data[DATA_MANAGER]
|
||||
|
||||
assert manager.state == BackupManagerState.BLOCKED
|
||||
assert manager.last_non_idle_event is None
|
||||
|
||||
# Fired when Home Assistant changes to starting state
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
assert manager.state == BackupManagerState.BLOCKED
|
||||
assert manager.last_non_idle_event is None
|
||||
|
||||
# Fired when Home Assistant changes to running state
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
assert manager.state == BackupManagerState.IDLE
|
||||
assert manager.last_non_idle_event is None
|
||||
|
||||
|
||||
async def test_manager_not_blocked_after_restore(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test restore backup progress after restart."""
|
||||
restore_result = {"error": None, "error_type": None, "success": True}
|
||||
|
||||
hass.set_state(CoreState.not_running)
|
||||
with patch(
|
||||
"pathlib.Path.read_bytes", return_value=json.dumps(restore_result).encode()
|
||||
):
|
||||
await setup_backup_integration(hass)
|
||||
|
||||
ws_client = await hass_ws_client(hass)
|
||||
await ws_client.send_json_auto_id({"type": "backup/info"})
|
||||
result = await ws_client.receive_json()
|
||||
assert result["success"] is True
|
||||
assert result["result"] == {
|
||||
"agent_errors": {},
|
||||
"backups": [],
|
||||
"last_attempted_automatic_backup": None,
|
||||
"last_completed_automatic_backup": None,
|
||||
"last_non_idle_event": {
|
||||
"manager_state": "restore_backup",
|
||||
"reason": None,
|
||||
"stage": None,
|
||||
"state": "completed",
|
||||
},
|
||||
"next_automatic_backup": None,
|
||||
"next_automatic_backup_additional": False,
|
||||
"state": "idle",
|
||||
}
|
||||
|
@ -8,14 +8,14 @@
|
||||
"country": "UnitedKingdom",
|
||||
"postcode": "E1 1AA",
|
||||
"locationType": "Residential",
|
||||
"useDaylightSaveSwitching": true,
|
||||
"timeZone": {
|
||||
"timeZoneId": "GMTStandardTime",
|
||||
"displayName": "(UTC+00:00) Dublin, Edinburgh, Lisbon, London",
|
||||
"offsetMinutes": 0,
|
||||
"currentOffsetMinutes": 60,
|
||||
"timeZoneId": "PacificSAStandardTime",
|
||||
"displayName": "(UTC-04:00) Santiago",
|
||||
"offsetMinutes": -240,
|
||||
"currentOffsetMinutes": -180,
|
||||
"supportsDaylightSaving": true
|
||||
},
|
||||
"useDaylightSaveSwitching": true,
|
||||
"locationOwner": {
|
||||
"userId": "2263181",
|
||||
"username": "user_2263181@gmail.com",
|
||||
|
@ -168,10 +168,10 @@
|
||||
'target_heat_temperature': 16.0,
|
||||
}),
|
||||
'setpoints': dict({
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'next_sp_temp': 18.6,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'this_sp_temp': 16.0,
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'next_sp_temp': 16.0,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'this_sp_temp': 18.1,
|
||||
}),
|
||||
'temperature_status': dict({
|
||||
'is_available': True,
|
||||
@ -215,10 +215,10 @@
|
||||
'target_heat_temperature': 17.0,
|
||||
}),
|
||||
'setpoints': dict({
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'next_sp_temp': 18.6,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'this_sp_temp': 16.0,
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'next_sp_temp': 16.0,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'this_sp_temp': 18.1,
|
||||
}),
|
||||
'temperature_status': dict({
|
||||
'is_available': False,
|
||||
@ -257,19 +257,19 @@
|
||||
'activeFaults': tuple(
|
||||
dict({
|
||||
'fault_type': 'TempZoneActuatorLowBattery',
|
||||
'since': '2022-03-02T04:50:20+00:00',
|
||||
'since': '2022-03-02T04:50:20-03:00',
|
||||
}),
|
||||
),
|
||||
'setpoint_status': dict({
|
||||
'setpoint_mode': 'TemporaryOverride',
|
||||
'target_heat_temperature': 21.0,
|
||||
'until': '2022-03-07T19:00:00+00:00',
|
||||
'until': '2022-03-07T16:00:00-03:00',
|
||||
}),
|
||||
'setpoints': dict({
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'next_sp_temp': 18.6,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'this_sp_temp': 16.0,
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'next_sp_temp': 16.0,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'this_sp_temp': 18.1,
|
||||
}),
|
||||
'temperature_status': dict({
|
||||
'is_available': True,
|
||||
@ -313,10 +313,10 @@
|
||||
'target_heat_temperature': 17.0,
|
||||
}),
|
||||
'setpoints': dict({
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'next_sp_temp': 18.6,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'this_sp_temp': 16.0,
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'next_sp_temp': 16.0,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'this_sp_temp': 18.1,
|
||||
}),
|
||||
'temperature_status': dict({
|
||||
'is_available': True,
|
||||
@ -360,10 +360,10 @@
|
||||
'target_heat_temperature': 17.0,
|
||||
}),
|
||||
'setpoints': dict({
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'next_sp_temp': 18.6,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'this_sp_temp': 16.0,
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'next_sp_temp': 16.0,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'this_sp_temp': 18.1,
|
||||
}),
|
||||
'temperature_status': dict({
|
||||
'is_available': True,
|
||||
@ -407,10 +407,10 @@
|
||||
'target_heat_temperature': 16.0,
|
||||
}),
|
||||
'setpoints': dict({
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'next_sp_temp': 18.6,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'this_sp_temp': 16.0,
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'next_sp_temp': 16.0,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'this_sp_temp': 18.1,
|
||||
}),
|
||||
'temperature_status': dict({
|
||||
'is_available': True,
|
||||
@ -450,7 +450,7 @@
|
||||
'activeFaults': tuple(
|
||||
dict({
|
||||
'fault_type': 'TempZoneActuatorCommunicationLost',
|
||||
'since': '2022-03-02T15:56:01+00:00',
|
||||
'since': '2022-03-02T15:56:01-03:00',
|
||||
}),
|
||||
),
|
||||
'setpoint_status': dict({
|
||||
@ -458,10 +458,10 @@
|
||||
'target_heat_temperature': 17.0,
|
||||
}),
|
||||
'setpoints': dict({
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'next_sp_temp': 18.6,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'this_sp_temp': 16.0,
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'next_sp_temp': 16.0,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'this_sp_temp': 18.1,
|
||||
}),
|
||||
'temperature_status': dict({
|
||||
'is_available': True,
|
||||
|
@ -2,10 +2,10 @@
|
||||
# name: test_set_operation_mode[botched]
|
||||
list([
|
||||
dict({
|
||||
'until': HAFakeDatetime(2024, 7, 10, 12, 0, tzinfo=datetime.timezone.utc),
|
||||
'until': HAFakeDatetime(2024, 7, 10, 12, 30, tzinfo=datetime.timezone.utc),
|
||||
}),
|
||||
dict({
|
||||
'until': HAFakeDatetime(2024, 7, 10, 12, 0, tzinfo=datetime.timezone.utc),
|
||||
'until': HAFakeDatetime(2024, 7, 10, 12, 30, tzinfo=datetime.timezone.utc),
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
@ -39,9 +39,9 @@
|
||||
),
|
||||
'dhw_id': '3933910',
|
||||
'setpoints': dict({
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 13, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 30, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'next_sp_state': 'Off',
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 6, 30, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'this_sp_state': 'On',
|
||||
}),
|
||||
'state_status': dict({
|
||||
|
@ -11,7 +11,7 @@ import pytest
|
||||
|
||||
from homeassistant.auth.models import RefreshToken
|
||||
from homeassistant.components.hassio.handler import HassIO, HassioAPIError
|
||||
from homeassistant.core import CoreState, HomeAssistant
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
@ -75,7 +75,6 @@ def hassio_stubs(
|
||||
"homeassistant.components.hassio.issues.SupervisorIssues.setup",
|
||||
),
|
||||
):
|
||||
hass.set_state(CoreState.starting)
|
||||
hass.loop.run_until_complete(async_setup_component(hass, "hassio", {}))
|
||||
|
||||
return hass_api.call_args[0][1]
|
||||
|
@ -106,6 +106,7 @@
|
||||
'model': 'HEOS Drive HS2',
|
||||
'name': 'Test Player',
|
||||
'network': 'wired',
|
||||
'preferred_host': True,
|
||||
'serial': '**REDACTED**',
|
||||
'supported_version': True,
|
||||
'version': '1.0.0',
|
||||
@ -116,6 +117,7 @@
|
||||
'model': 'HEOS Drive HS2',
|
||||
'name': 'Test Player',
|
||||
'network': 'wired',
|
||||
'preferred_host': True,
|
||||
'serial': '**REDACTED**',
|
||||
'supported_version': True,
|
||||
'version': '1.0.0',
|
||||
@ -125,6 +127,7 @@
|
||||
'model': 'Speaker',
|
||||
'name': 'Test Player 2',
|
||||
'network': 'wifi',
|
||||
'preferred_host': False,
|
||||
'serial': '**REDACTED**',
|
||||
'supported_version': True,
|
||||
'version': '1.0.0',
|
||||
@ -137,6 +140,7 @@
|
||||
'model': 'HEOS Drive HS2',
|
||||
'name': 'Test Player',
|
||||
'network': 'wired',
|
||||
'preferred_host': True,
|
||||
'serial': '**REDACTED**',
|
||||
'supported_version': True,
|
||||
'version': '1.0.0',
|
||||
|
@ -14,7 +14,12 @@ from pyheos import (
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.heos.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_SSDP, SOURCE_USER, ConfigEntryState
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_IGNORE,
|
||||
SOURCE_SSDP,
|
||||
SOURCE_USER,
|
||||
ConfigEntryState,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
@ -83,6 +88,35 @@ async def test_create_entry_when_host_valid(
|
||||
assert controller.disconnect.call_count == 1
|
||||
|
||||
|
||||
async def test_manual_setup_with_discovery_in_progress(
|
||||
hass: HomeAssistant,
|
||||
discovery_data: SsdpServiceInfo,
|
||||
controller: MockHeos,
|
||||
system: HeosSystem,
|
||||
) -> None:
|
||||
"""Test user can manually set up when discovery is in progress."""
|
||||
# Single discovered, selects preferred host, shows confirm
|
||||
controller.get_system_info.return_value = system
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_SSDP}, data=discovery_data
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "confirm_discovery"
|
||||
|
||||
# Setup manually
|
||||
user_result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
assert user_result["type"] is FlowResultType.FORM
|
||||
user_result = await hass.config_entries.flow.async_configure(
|
||||
user_result["flow_id"], user_input={CONF_HOST: "127.0.0.1"}
|
||||
)
|
||||
assert user_result["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
# Discovery flow is removed
|
||||
assert not hass.config_entries.flow.async_progress_by_handler(DOMAIN)
|
||||
|
||||
|
||||
async def test_discovery(
|
||||
hass: HomeAssistant,
|
||||
discovery_data: SsdpServiceInfo,
|
||||
@ -160,6 +194,22 @@ async def test_discovery_aborts_same_system(
|
||||
assert config_entry.data[CONF_HOST] == "127.0.0.1"
|
||||
|
||||
|
||||
async def test_discovery_ignored_aborts(
|
||||
hass: HomeAssistant,
|
||||
discovery_data: SsdpServiceInfo,
|
||||
) -> None:
|
||||
"""Test discovery aborts when ignored."""
|
||||
MockConfigEntry(domain=DOMAIN, unique_id=DOMAIN, source=SOURCE_IGNORE).add_to_hass(
|
||||
hass
|
||||
)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_SSDP}, data=discovery_data
|
||||
)
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "single_instance_allowed"
|
||||
|
||||
|
||||
async def test_discovery_fails_to_connect_aborts(
|
||||
hass: HomeAssistant, discovery_data: SsdpServiceInfo, controller: MockHeos
|
||||
) -> None:
|
||||
|
@ -285,11 +285,11 @@ async def test_reconnected_new_entities_created(
|
||||
players = controller.players.copy()
|
||||
players[3] = player_factory(3, "Test Player 3", "HEOS Link")
|
||||
controller.mock_set_players(players)
|
||||
controller.load_players.return_value = PlayerUpdateResult([3], [], {})
|
||||
update = PlayerUpdateResult([3], [], {})
|
||||
|
||||
# Simulate reconnection
|
||||
await controller.dispatcher.wait_send(
|
||||
SignalType.HEOS_EVENT, SignalHeosEvent.CONNECTED
|
||||
SignalType.CONTROLLER_EVENT, const.EVENT_PLAYERS_CHANGED, update
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
@ -158,7 +158,6 @@ async def test_updates_from_connection_event(
|
||||
state = hass.states.get("media_player.test_player")
|
||||
assert state is not None
|
||||
assert state.state == STATE_IDLE
|
||||
assert controller.load_players.call_count == 1
|
||||
|
||||
# Disconnected
|
||||
controller.load_players.reset_mock()
|
||||
@ -170,11 +169,8 @@ async def test_updates_from_connection_event(
|
||||
state = hass.states.get("media_player.test_player")
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
assert controller.load_players.call_count == 0
|
||||
|
||||
# Connected handles refresh failure
|
||||
controller.load_players.reset_mock()
|
||||
controller.load_players.side_effect = CommandFailedError("", "Failure", 1)
|
||||
# Reconnect and state updates
|
||||
player.available = True
|
||||
await controller.dispatcher.wait_send(
|
||||
SignalType.HEOS_EVENT, SignalHeosEvent.CONNECTED
|
||||
@ -183,38 +179,6 @@ async def test_updates_from_connection_event(
|
||||
state = hass.states.get("media_player.test_player")
|
||||
assert state is not None
|
||||
assert state.state == STATE_IDLE
|
||||
assert controller.load_players.call_count == 1
|
||||
assert "Unable to refresh players" in caplog.text
|
||||
|
||||
|
||||
async def test_updates_from_connection_event_new_player_ids(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
config_entry: MockConfigEntry,
|
||||
controller: MockHeos,
|
||||
change_data_mapped_ids: PlayerUpdateResult,
|
||||
) -> None:
|
||||
"""Test player ids changed after reconnection updates ids."""
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
# Assert current IDs
|
||||
assert device_registry.async_get_device(identifiers={(DOMAIN, "1")})
|
||||
assert entity_registry.async_get_entity_id(MEDIA_PLAYER_DOMAIN, DOMAIN, "1")
|
||||
|
||||
# Send event which will result in updated IDs.
|
||||
controller.load_players.return_value = change_data_mapped_ids
|
||||
await controller.dispatcher.wait_send(
|
||||
SignalType.HEOS_EVENT, SignalHeosEvent.CONNECTED
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Assert updated IDs and previous don't exist
|
||||
assert not device_registry.async_get_device(identifiers={(DOMAIN, "1")})
|
||||
assert device_registry.async_get_device(identifiers={(DOMAIN, "101")})
|
||||
assert not entity_registry.async_get_entity_id(MEDIA_PLAYER_DOMAIN, DOMAIN, "1")
|
||||
assert entity_registry.async_get_entity_id(MEDIA_PLAYER_DOMAIN, DOMAIN, "101")
|
||||
|
||||
|
||||
async def test_updates_from_sources_updated(
|
||||
|
@ -8,9 +8,8 @@ from unittest.mock import MagicMock, patch
|
||||
from aiohomeconnect.const import OAUTH2_TOKEN
|
||||
from aiohomeconnect.model import OptionKey, ProgramKey, SettingKey, StatusKey
|
||||
from aiohomeconnect.model.error import HomeConnectError, UnauthorizedError
|
||||
import aiohttp
|
||||
import pytest
|
||||
import requests_mock
|
||||
import respx
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
@ -221,14 +220,12 @@ async def test_exception_handling(
|
||||
|
||||
|
||||
@pytest.mark.parametrize("token_expiration_time", [12345])
|
||||
@respx.mock
|
||||
async def test_token_refresh_success(
|
||||
hass: HomeAssistant,
|
||||
platforms: list[Platform],
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
config_entry: MockConfigEntry,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
requests_mock: requests_mock.Mocker,
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
) -> None:
|
||||
@ -236,7 +233,6 @@ async def test_token_refresh_success(
|
||||
|
||||
assert config_entry.data["token"]["access_token"] == FAKE_ACCESS_TOKEN
|
||||
|
||||
requests_mock.post(OAUTH2_TOKEN, json=SERVER_ACCESS_TOKEN)
|
||||
aioclient_mock.post(
|
||||
OAUTH2_TOKEN,
|
||||
json=SERVER_ACCESS_TOKEN,
|
||||
@ -280,6 +276,61 @@ async def test_token_refresh_success(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("token_expiration_time", [12345])
|
||||
@pytest.mark.parametrize(
|
||||
("aioclient_mock_args", "expected_config_entry_state"),
|
||||
[
|
||||
(
|
||||
{
|
||||
"status": 400,
|
||||
"json": {"error": "invalid_grant"},
|
||||
},
|
||||
ConfigEntryState.SETUP_ERROR,
|
||||
),
|
||||
(
|
||||
{
|
||||
"status": 500,
|
||||
},
|
||||
ConfigEntryState.SETUP_RETRY,
|
||||
),
|
||||
(
|
||||
{
|
||||
"exc": aiohttp.ClientError,
|
||||
},
|
||||
ConfigEntryState.SETUP_RETRY,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_token_refresh_error(
|
||||
aioclient_mock_args: dict[str, Any],
|
||||
expected_config_entry_state: ConfigEntryState,
|
||||
hass: HomeAssistant,
|
||||
platforms: list[Platform],
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
config_entry: MockConfigEntry,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
) -> None:
|
||||
"""Test where token is expired and the refresh attempt fails."""
|
||||
|
||||
config_entry.data["token"]["access_token"] = FAKE_ACCESS_TOKEN
|
||||
|
||||
aioclient_mock.post(
|
||||
OAUTH2_TOKEN,
|
||||
**aioclient_mock_args,
|
||||
)
|
||||
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
with patch(
|
||||
"homeassistant.components.home_connect.HomeConnectClient", return_value=client
|
||||
):
|
||||
assert not await integration_setup(client)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state == expected_config_entry_state
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("exception", "expected_state"),
|
||||
[
|
||||
|
@ -1556,6 +1556,42 @@ async def test_setup_uses_certificate_on_certificate_set_to_auto_and_insecure(
|
||||
assert insecure_check["insecure"] == insecure_param
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("mqtt_config_entry_data", "client_id"),
|
||||
[
|
||||
(
|
||||
{
|
||||
mqtt.CONF_BROKER: "mock-broker",
|
||||
"client_id": "random01234random0124",
|
||||
},
|
||||
"random01234random0124",
|
||||
),
|
||||
(
|
||||
{
|
||||
mqtt.CONF_BROKER: "mock-broker",
|
||||
},
|
||||
None,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_client_id_is_set(
|
||||
hass: HomeAssistant,
|
||||
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||
client_id: str | None,
|
||||
) -> None:
|
||||
"""Test setup defaults for tls."""
|
||||
with patch(
|
||||
"homeassistant.components.mqtt.async_client.AsyncMQTTClient"
|
||||
) as async_client_mock:
|
||||
await mqtt_mock_entry()
|
||||
await hass.async_block_till_done()
|
||||
assert async_client_mock.call_count == 1
|
||||
call_params: dict[str, Any] = async_client_mock.call_args[1]
|
||||
assert "client_id" in call_params
|
||||
assert client_id is None or client_id == call_params["client_id"]
|
||||
assert call_params["client_id"] is not None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mqtt_config_entry_data",
|
||||
[
|
||||
|
@ -432,6 +432,65 @@ async def test_brightness_only(
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"hass_config",
|
||||
[
|
||||
{
|
||||
mqtt.DOMAIN: {
|
||||
light.DOMAIN: {
|
||||
"schema": "json",
|
||||
"name": "test",
|
||||
"state_topic": "test_light_rgb",
|
||||
"command_topic": "test_light_rgb/set",
|
||||
"supported_color_modes": ["color_temp"],
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
)
|
||||
async def test_color_temp_only(
|
||||
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
|
||||
) -> None:
|
||||
"""Test a light that only support color_temp as supported color mode."""
|
||||
await mqtt_mock_entry()
|
||||
|
||||
state = hass.states.get("light.test")
|
||||
assert state.state == STATE_UNKNOWN
|
||||
assert state.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES) == [
|
||||
light.ColorMode.COLOR_TEMP
|
||||
]
|
||||
expected_features = (
|
||||
light.LightEntityFeature.FLASH | light.LightEntityFeature.TRANSITION
|
||||
)
|
||||
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
|
||||
assert state.attributes.get("rgb_color") is None
|
||||
assert state.attributes.get("brightness") is None
|
||||
assert state.attributes.get("color_temp_kelvin") is None
|
||||
assert state.attributes.get("effect") is None
|
||||
assert state.attributes.get("xy_color") is None
|
||||
assert state.attributes.get("hs_color") is None
|
||||
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
"test_light_rgb",
|
||||
'{"state":"ON", "color_mode": "color_temp", "color_temp": 250, "brightness": 50}',
|
||||
)
|
||||
|
||||
state = hass.states.get("light.test")
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes.get("rgb_color") == (255, 206, 166)
|
||||
assert state.attributes.get("brightness") == 50
|
||||
assert state.attributes.get("color_temp_kelvin") == 4000
|
||||
assert state.attributes.get("effect") is None
|
||||
assert state.attributes.get("xy_color") == (0.42, 0.365)
|
||||
assert state.attributes.get("hs_color") == (26.812, 34.87)
|
||||
|
||||
async_fire_mqtt_message(hass, "test_light_rgb", '{"state":"OFF"}')
|
||||
|
||||
state = hass.states.get("light.test")
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"hass_config",
|
||||
[
|
||||
|
@ -870,32 +870,6 @@ async def test_invalid_device_class(
|
||||
assert "expected SensorDeviceClass or one of" in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"hass_config",
|
||||
[
|
||||
{
|
||||
mqtt.DOMAIN: {
|
||||
sensor.DOMAIN: {
|
||||
"name": "test",
|
||||
"state_topic": "test-topic",
|
||||
"device_class": "energy",
|
||||
"unit_of_measurement": "ppm",
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
)
|
||||
async def test_invalid_unit_of_measurement(
|
||||
mqtt_mock_entry: MqttMockHAClientGenerator, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test device_class with invalid unit of measurement."""
|
||||
assert await mqtt_mock_entry()
|
||||
assert (
|
||||
"The unit of measurement `ppm` is not valid together with device class `energy`"
|
||||
in caplog.text
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"hass_config",
|
||||
[
|
||||
|
@ -109,8 +109,8 @@
|
||||
'entity_picture_local': None,
|
||||
'friendly_name': 'Test Group Player 1',
|
||||
'group_members': list([
|
||||
'media_player.my_super_test_player_2',
|
||||
'media_player.test_player_1',
|
||||
'media_player.my_super_test_player_2',
|
||||
]),
|
||||
'icon': 'mdi:speaker-multiple',
|
||||
'is_volume_muted': False,
|
||||
|
@ -9,6 +9,7 @@ from pysmartthings.models import (
|
||||
DeviceStatus,
|
||||
LocationResponse,
|
||||
SceneResponse,
|
||||
Subscription,
|
||||
)
|
||||
import pytest
|
||||
|
||||
@ -78,6 +79,9 @@ def mock_smartthings() -> Generator[AsyncMock]:
|
||||
client.get_locations.return_value = LocationResponse.from_json(
|
||||
load_fixture("locations.json", DOMAIN)
|
||||
).items
|
||||
client.create_subscription.return_value = Subscription.from_json(
|
||||
load_fixture("subscription.json", DOMAIN)
|
||||
)
|
||||
yield client
|
||||
|
||||
|
||||
@ -100,6 +104,7 @@ def mock_smartthings() -> Generator[AsyncMock]:
|
||||
"iphone",
|
||||
"da_wm_dw_000001",
|
||||
"da_wm_wd_000001",
|
||||
"da_wm_wd_000001_1",
|
||||
"da_wm_wm_000001",
|
||||
"da_wm_wm_000001_1",
|
||||
"da_rvc_normal_000001",
|
||||
@ -115,9 +120,15 @@ def mock_smartthings() -> Generator[AsyncMock]:
|
||||
"sensibo_airconditioner_1",
|
||||
"ecobee_sensor",
|
||||
"ecobee_thermostat",
|
||||
"ecobee_thermostat_offline",
|
||||
"fake_fan",
|
||||
"generic_fan_3_speed",
|
||||
"heatit_ztrm3_thermostat",
|
||||
"generic_ef00_v1",
|
||||
"bosch_radiator_thermostat_ii",
|
||||
"im_speaker_ai_0001",
|
||||
"abl_light_b_001",
|
||||
"tplink_p110",
|
||||
]
|
||||
)
|
||||
def device_fixture(
|
||||
|
@ -0,0 +1,27 @@
|
||||
{
|
||||
"components": {
|
||||
"main": {
|
||||
"switchLevel": {
|
||||
"levelRange": {
|
||||
"value": null
|
||||
},
|
||||
"level": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"colorTemperature": {
|
||||
"colorTemperatureRange": {
|
||||
"value": null
|
||||
},
|
||||
"colorTemperature": {
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
{
|
||||
"components": {
|
||||
"main": {
|
||||
"temperatureMeasurement": {
|
||||
"temperatureRange": {
|
||||
"value": null
|
||||
},
|
||||
"temperature": {
|
||||
"value": 23.9,
|
||||
"unit": "C",
|
||||
"timestamp": "2025-03-07T19:55:13.328Z"
|
||||
}
|
||||
},
|
||||
"thermostatHeatingSetpoint": {
|
||||
"heatingSetpoint": {
|
||||
"value": 22.0,
|
||||
"unit": "C",
|
||||
"timestamp": "2025-03-05T03:05:26.510Z"
|
||||
},
|
||||
"heatingSetpointRange": {
|
||||
"value": {
|
||||
"minimum": 5.0,
|
||||
"maximum": 40.0,
|
||||
"step": 0.1
|
||||
},
|
||||
"unit": "C",
|
||||
"timestamp": "2025-03-05T03:05:26.510Z"
|
||||
}
|
||||
},
|
||||
"refresh": {},
|
||||
"thermostatMode": {
|
||||
"thermostatMode": {
|
||||
"value": "heat",
|
||||
"data": {
|
||||
"supportedThermostatModes": ["off", "heat"]
|
||||
},
|
||||
"timestamp": "2025-03-05T03:05:26.489Z"
|
||||
},
|
||||
"supportedThermostatModes": {
|
||||
"value": ["off", "heat"],
|
||||
"timestamp": "2025-03-05T03:05:26.509Z"
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"quantity": {
|
||||
"value": null
|
||||
},
|
||||
"battery": {
|
||||
"value": 94,
|
||||
"unit": "%",
|
||||
"timestamp": "2025-03-07T20:47:27.362Z"
|
||||
},
|
||||
"type": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"firmwareUpdate": {
|
||||
"lastUpdateStatusReason": {
|
||||
"value": null
|
||||
},
|
||||
"availableVersion": {
|
||||
"value": "2.00.09 (20009)",
|
||||
"timestamp": "2024-11-29T19:55:02.005Z"
|
||||
},
|
||||
"lastUpdateStatus": {
|
||||
"value": null
|
||||
},
|
||||
"supportedCommands": {
|
||||
"value": null
|
||||
},
|
||||
"state": {
|
||||
"value": "normalOperation",
|
||||
"timestamp": "2024-11-29T19:55:02.009Z"
|
||||
},
|
||||
"updateAvailable": {
|
||||
"value": false,
|
||||
"timestamp": "2024-11-29T19:55:02.004Z"
|
||||
},
|
||||
"currentVersion": {
|
||||
"value": "2.00.09 (20009)",
|
||||
"timestamp": "2024-11-29T19:55:02.037Z"
|
||||
},
|
||||
"lastUpdateTime": {
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,692 @@
|
||||
{
|
||||
"components": {
|
||||
"hca.main": {
|
||||
"hca.dryerMode": {
|
||||
"mode": {
|
||||
"value": "normal",
|
||||
"timestamp": "2025-03-09T16:31:41.247Z"
|
||||
},
|
||||
"supportedModes": {
|
||||
"value": ["normal", "quickDry", "mix", "timeDry"],
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"main": {
|
||||
"custom.dryerWrinklePrevent": {
|
||||
"operatingState": {
|
||||
"value": "ready",
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
},
|
||||
"dryerWrinklePrevent": {
|
||||
"value": "off",
|
||||
"timestamp": "2025-03-09T16:31:41.077Z"
|
||||
}
|
||||
},
|
||||
"samsungce.dryerDryingTemperature": {
|
||||
"dryingTemperature": {
|
||||
"value": null,
|
||||
"timestamp": "2021-04-02T18:31:36.756Z"
|
||||
},
|
||||
"supportedDryingTemperature": {
|
||||
"value": null,
|
||||
"timestamp": "2021-04-02T18:29:52.258Z"
|
||||
}
|
||||
},
|
||||
"samsungce.welcomeMessage": {
|
||||
"welcomeMessage": {
|
||||
"value": null,
|
||||
"timestamp": "2021-04-02T18:32:37.913Z"
|
||||
}
|
||||
},
|
||||
"samsungce.dongleSoftwareInstallation": {
|
||||
"status": {
|
||||
"value": "completed",
|
||||
"timestamp": "2022-06-17T17:07:35.734Z"
|
||||
}
|
||||
},
|
||||
"samsungce.dryerCyclePreset": {
|
||||
"maxNumberOfPresets": {
|
||||
"value": 10,
|
||||
"timestamp": "2025-03-09T16:31:41.229Z"
|
||||
},
|
||||
"presets": {
|
||||
"value": null,
|
||||
"timestamp": "2021-04-02T18:30:36.772Z"
|
||||
}
|
||||
},
|
||||
"samsungce.deviceIdentification": {
|
||||
"micomAssayCode": {
|
||||
"value": "20221341",
|
||||
"timestamp": "2025-03-09T16:31:40.834Z"
|
||||
},
|
||||
"modelName": {
|
||||
"value": null,
|
||||
"timestamp": "2021-04-02T18:29:53.622Z"
|
||||
},
|
||||
"serialNumber": {
|
||||
"value": null,
|
||||
"timestamp": "2021-04-02T18:29:52.641Z"
|
||||
},
|
||||
"serialNumberExtra": {
|
||||
"value": null,
|
||||
"timestamp": "2021-04-02T18:29:51.653Z"
|
||||
},
|
||||
"modelClassificationCode": {
|
||||
"value": "30010102001211000103000000000000",
|
||||
"timestamp": "2025-03-09T16:31:40.834Z"
|
||||
},
|
||||
"description": {
|
||||
"value": "DA_WM_A51_20_COMMON_DV6800N/DC92-01967B_0404",
|
||||
"timestamp": "2025-03-09T16:31:40.834Z"
|
||||
},
|
||||
"releaseYear": {
|
||||
"value": null
|
||||
},
|
||||
"binaryId": {
|
||||
"value": "DA_WM_A51_20_COMMON",
|
||||
"timestamp": "2025-03-09T19:07:40.295Z"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"value": "off",
|
||||
"timestamp": "2025-03-09T19:47:36.549Z"
|
||||
}
|
||||
},
|
||||
"samsungce.quickControl": {
|
||||
"version": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungce.dryerFreezePrevent": {
|
||||
"operatingState": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"ocf": {
|
||||
"st": {
|
||||
"value": null,
|
||||
"timestamp": "2020-06-20T10:01:02.741Z"
|
||||
},
|
||||
"mndt": {
|
||||
"value": null,
|
||||
"timestamp": "2020-06-25T01:53:25.278Z"
|
||||
},
|
||||
"mnfv": {
|
||||
"value": "DA_WM_A51_20_COMMON_30230708",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"mnhw": {
|
||||
"value": "ARTIK051",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"di": {
|
||||
"value": "3a6c4e05-811d-5041-e956-3d04c424cbcd",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"mnsl": {
|
||||
"value": "http://www.samsung.com",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"dmv": {
|
||||
"value": "res.1.1.0,sh.1.1.0",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"n": {
|
||||
"value": "[dryer] Samsung",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"mnmo": {
|
||||
"value": "DA_WM_A51_20_COMMON|20221341|30010102001211000103000000000000",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"vid": {
|
||||
"value": "DA-WM-WD-000001",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"mnmn": {
|
||||
"value": "Samsung Electronics",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"mnml": {
|
||||
"value": "http://www.samsung.com",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"mnpv": {
|
||||
"value": "DAWIT 2.0",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"mnos": {
|
||||
"value": "TizenRT 1.0 + IPv6",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"pi": {
|
||||
"value": "3a6c4e05-811d-5041-e956-3d04c424cbcd",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"icv": {
|
||||
"value": "core.1.1.0",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
}
|
||||
},
|
||||
"custom.dryerDryLevel": {
|
||||
"dryerDryLevel": {
|
||||
"value": "2",
|
||||
"timestamp": "2025-03-09T19:47:36.806Z"
|
||||
},
|
||||
"supportedDryerDryLevel": {
|
||||
"value": ["none", "1", "2", "3"],
|
||||
"timestamp": "2020-11-18T20:16:43.428Z"
|
||||
}
|
||||
},
|
||||
"samsungce.dryerAutoCycleLink": {
|
||||
"dryerAutoCycleLink": {
|
||||
"value": null,
|
||||
"timestamp": "2020-08-11T12:41:38.646Z"
|
||||
}
|
||||
},
|
||||
"samsungce.dryerCycle": {
|
||||
"dryerCycle": {
|
||||
"value": "Table_00_Course_9A",
|
||||
"timestamp": "2025-03-09T16:31:41.247Z"
|
||||
},
|
||||
"supportedCycles": {
|
||||
"value": [
|
||||
{
|
||||
"cycle": "9A",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D20E",
|
||||
"default": "2",
|
||||
"options": ["1", "2", "3"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "CA",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D10E",
|
||||
"default": "1",
|
||||
"options": ["1", "2", "3"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "DB",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D204",
|
||||
"default": "2",
|
||||
"options": ["2"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "99",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D20E",
|
||||
"default": "2",
|
||||
"options": ["1", "2", "3"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "93",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D102",
|
||||
"default": "1",
|
||||
"options": ["1"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "B5",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D102",
|
||||
"default": "1",
|
||||
"options": ["1"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "D7",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D204",
|
||||
"default": "2",
|
||||
"options": ["2"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "A5",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D204",
|
||||
"default": "2",
|
||||
"options": ["2"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "96",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D000",
|
||||
"default": "none",
|
||||
"options": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "97",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D000",
|
||||
"default": "none",
|
||||
"options": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "7F",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D000",
|
||||
"default": "none",
|
||||
"options": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "98",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D000",
|
||||
"default": "none",
|
||||
"options": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "EB",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D204",
|
||||
"default": "2",
|
||||
"options": ["2"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "B6",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D20E",
|
||||
"default": "2",
|
||||
"options": ["1", "2", "3"]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"timestamp": "2025-02-10T02:24:03.524Z"
|
||||
},
|
||||
"referenceTable": {
|
||||
"value": {
|
||||
"id": "Table_00"
|
||||
},
|
||||
"timestamp": "2025-03-09T16:31:41.247Z"
|
||||
},
|
||||
"specializedFunctionClassification": {
|
||||
"value": 4,
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
}
|
||||
},
|
||||
"custom.disabledCapabilities": {
|
||||
"disabledCapabilities": {
|
||||
"value": [
|
||||
"samsungce.dryerDelayEnd",
|
||||
"dryerOperatingState",
|
||||
"samsungce.dryerCyclePreset",
|
||||
"samsungce.welcomeMessage",
|
||||
"samsungce.dongleSoftwareInstallation",
|
||||
"sec.wifiConfiguration",
|
||||
"samsungce.quickControl",
|
||||
"samsungce.deviceInfoPrivate",
|
||||
"demandResponseLoadControl",
|
||||
"samsungce.dryerFreezePrevent",
|
||||
"samsungce.dryerDryingTemperature",
|
||||
"sec.diagnosticsInformation"
|
||||
],
|
||||
"timestamp": "2024-07-02T14:42:38.334Z"
|
||||
}
|
||||
},
|
||||
"samsungce.driverVersion": {
|
||||
"versionNumber": {
|
||||
"value": 24110101,
|
||||
"timestamp": "2024-12-02T07:43:41.263Z"
|
||||
}
|
||||
},
|
||||
"sec.diagnosticsInformation": {
|
||||
"logType": {
|
||||
"value": null
|
||||
},
|
||||
"endpoint": {
|
||||
"value": null
|
||||
},
|
||||
"minVersion": {
|
||||
"value": null
|
||||
},
|
||||
"signinPermission": {
|
||||
"value": null
|
||||
},
|
||||
"setupId": {
|
||||
"value": null
|
||||
},
|
||||
"protocolType": {
|
||||
"value": null
|
||||
},
|
||||
"tsId": {
|
||||
"value": null
|
||||
},
|
||||
"mnId": {
|
||||
"value": null
|
||||
},
|
||||
"dumpType": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungce.kidsLock": {
|
||||
"lockState": {
|
||||
"value": "unlocked",
|
||||
"timestamp": "2025-03-09T16:31:40.882Z"
|
||||
}
|
||||
},
|
||||
"demandResponseLoadControl": {
|
||||
"drlcStatus": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungce.detergentOrder": {
|
||||
"alarmEnabled": {
|
||||
"value": false,
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
},
|
||||
"orderThreshold": {
|
||||
"value": 0,
|
||||
"unit": "cc",
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
}
|
||||
},
|
||||
"powerConsumptionReport": {
|
||||
"powerConsumption": {
|
||||
"value": {
|
||||
"energy": 796400,
|
||||
"deltaEnergy": 0,
|
||||
"power": 0,
|
||||
"powerEnergy": 0.0,
|
||||
"persistedEnergy": 0,
|
||||
"energySaved": 0,
|
||||
"start": "2025-03-09T19:47:26Z",
|
||||
"end": "2025-03-09T19:47:37Z"
|
||||
},
|
||||
"timestamp": "2025-03-09T19:47:37.283Z"
|
||||
}
|
||||
},
|
||||
"dryerOperatingState": {
|
||||
"completionTime": {
|
||||
"value": "2025-03-09T22:55:37Z",
|
||||
"timestamp": "2025-03-09T19:47:37.015Z"
|
||||
},
|
||||
"machineState": {
|
||||
"value": "stop",
|
||||
"timestamp": "2025-03-09T19:47:37.015Z"
|
||||
},
|
||||
"supportedMachineStates": {
|
||||
"value": ["stop", "run", "pause"],
|
||||
"timestamp": "2025-03-09T16:31:41.172Z"
|
||||
},
|
||||
"dryerJobState": {
|
||||
"value": "none",
|
||||
"timestamp": "2025-03-09T19:47:37.015Z"
|
||||
}
|
||||
},
|
||||
"samsungce.detergentState": {
|
||||
"remainingAmount": {
|
||||
"value": 0,
|
||||
"unit": "cc",
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
},
|
||||
"dosage": {
|
||||
"value": 0,
|
||||
"unit": "cc",
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
},
|
||||
"initialAmount": {
|
||||
"value": 0,
|
||||
"unit": "cc",
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
},
|
||||
"detergentType": {
|
||||
"value": "none",
|
||||
"timestamp": "2021-04-02T18:29:51.428Z"
|
||||
}
|
||||
},
|
||||
"samsungce.dryerDelayEnd": {
|
||||
"remainingTime": {
|
||||
"value": 0,
|
||||
"unit": "min",
|
||||
"timestamp": "2025-03-09T16:31:41.172Z"
|
||||
}
|
||||
},
|
||||
"refresh": {},
|
||||
"custom.jobBeginningStatus": {
|
||||
"jobBeginningStatus": {
|
||||
"value": null,
|
||||
"timestamp": "2020-06-25T01:53:34.974Z"
|
||||
}
|
||||
},
|
||||
"execute": {
|
||||
"data": {
|
||||
"value": {
|
||||
"payload": {
|
||||
"rt": ["x.com.samsung.da.information"],
|
||||
"if": ["oic.if.baseline", "oic.if.a"],
|
||||
"x.com.samsung.da.modelNum": "DA_WM_A51_20_COMMON|20221341|30010102001211000103000000000000",
|
||||
"x.com.samsung.da.description": "DA_WM_A51_20_COMMON_DV6800N/DC92-01967B_0404",
|
||||
"x.com.samsung.da.serialNum": "0T625AEN100200N",
|
||||
"x.com.samsung.da.otnDUID": "SHCDM6YAPCCXC",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "DA_WM_A51_20_COMMON|20221341|30010102001211000103000000000000",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "02198A220728(E256)",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "DA_WM_A51_20_COMMON",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.number": "17111305,19060420",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"href": "/information/vs/0"
|
||||
},
|
||||
"timestamp": "2023-08-07T00:06:05.984Z"
|
||||
}
|
||||
},
|
||||
"sec.wifiConfiguration": {
|
||||
"autoReconnection": {
|
||||
"value": null
|
||||
},
|
||||
"minVersion": {
|
||||
"value": null
|
||||
},
|
||||
"supportedWiFiFreq": {
|
||||
"value": null
|
||||
},
|
||||
"supportedAuthType": {
|
||||
"value": null
|
||||
},
|
||||
"protocolType": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"remoteControlStatus": {
|
||||
"remoteControlEnabled": {
|
||||
"value": "false",
|
||||
"timestamp": "2025-03-09T16:31:41.180Z"
|
||||
}
|
||||
},
|
||||
"custom.supportedOptions": {
|
||||
"course": {
|
||||
"value": null
|
||||
},
|
||||
"referenceTable": {
|
||||
"value": {
|
||||
"id": "Table_00"
|
||||
},
|
||||
"timestamp": "2025-03-09T16:31:41.247Z"
|
||||
},
|
||||
"supportedCourses": {
|
||||
"value": [
|
||||
"9A",
|
||||
"CA",
|
||||
"DB",
|
||||
"99",
|
||||
"93",
|
||||
"B5",
|
||||
"D7",
|
||||
"A5",
|
||||
"96",
|
||||
"97",
|
||||
"7F",
|
||||
"98",
|
||||
"EB",
|
||||
"B6"
|
||||
],
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
}
|
||||
},
|
||||
"custom.energyType": {
|
||||
"energyType": {
|
||||
"value": "2.0",
|
||||
"timestamp": "2022-06-17T17:07:35.734Z"
|
||||
},
|
||||
"energySavingSupport": {
|
||||
"value": false,
|
||||
"timestamp": "2022-06-17T17:07:35.734Z"
|
||||
},
|
||||
"drMaxDuration": {
|
||||
"value": null
|
||||
},
|
||||
"energySavingLevel": {
|
||||
"value": null
|
||||
},
|
||||
"energySavingInfo": {
|
||||
"value": null
|
||||
},
|
||||
"supportedEnergySavingLevels": {
|
||||
"value": null
|
||||
},
|
||||
"energySavingOperation": {
|
||||
"value": null
|
||||
},
|
||||
"notificationTemplateID": {
|
||||
"value": null
|
||||
},
|
||||
"energySavingOperationSupport": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungce.dryerOperatingState": {
|
||||
"operatingState": {
|
||||
"value": "ready",
|
||||
"timestamp": "2025-03-09T19:47:37.015Z"
|
||||
},
|
||||
"supportedOperatingStates": {
|
||||
"value": ["ready", "running", "paused"],
|
||||
"timestamp": "2022-11-01T12:48:22.390Z"
|
||||
},
|
||||
"scheduledJobs": {
|
||||
"value": [
|
||||
{
|
||||
"jobName": "drying",
|
||||
"timeInMin": 192
|
||||
},
|
||||
{
|
||||
"jobName": "cooling",
|
||||
"timeInMin": 1
|
||||
}
|
||||
],
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
},
|
||||
"progress": {
|
||||
"value": 1,
|
||||
"unit": "%",
|
||||
"timestamp": "2025-03-09T19:47:37.015Z"
|
||||
},
|
||||
"remainingTimeStr": {
|
||||
"value": "03:08",
|
||||
"timestamp": "2025-03-09T19:47:37.015Z"
|
||||
},
|
||||
"dryerJobState": {
|
||||
"value": "none",
|
||||
"timestamp": "2025-03-09T19:47:37.015Z"
|
||||
},
|
||||
"remainingTime": {
|
||||
"value": 188,
|
||||
"unit": "min",
|
||||
"timestamp": "2025-03-09T19:47:37.015Z"
|
||||
}
|
||||
},
|
||||
"samsungce.softwareUpdate": {
|
||||
"targetModule": {
|
||||
"value": null
|
||||
},
|
||||
"otnDUID": {
|
||||
"value": "SHCDM6YAPCCXC",
|
||||
"timestamp": "2025-03-09T16:31:40.834Z"
|
||||
},
|
||||
"lastUpdatedDate": {
|
||||
"value": null
|
||||
},
|
||||
"availableModules": {
|
||||
"value": [],
|
||||
"timestamp": "2024-12-01T21:16:50.598Z"
|
||||
},
|
||||
"newVersionAvailable": {
|
||||
"value": false,
|
||||
"timestamp": "2024-12-01T21:16:50.598Z"
|
||||
},
|
||||
"operatingState": {
|
||||
"value": null
|
||||
},
|
||||
"progress": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungce.dryerDryingTime": {
|
||||
"supportedDryingTime": {
|
||||
"value": ["0", "30", "60", "90", "120", "150"],
|
||||
"timestamp": "2021-04-02T18:29:51.428Z"
|
||||
},
|
||||
"dryingTime": {
|
||||
"value": "0",
|
||||
"unit": "min",
|
||||
"timestamp": "2025-03-09T16:31:41.077Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
{
|
||||
"components": {
|
||||
"main": {
|
||||
"relativeHumidityMeasurement": {
|
||||
"humidity": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"thermostatOperatingState": {
|
||||
"thermostatOperatingState": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"healthCheck": {
|
||||
"checkInterval": {
|
||||
"value": 60,
|
||||
"unit": "s",
|
||||
"data": {
|
||||
"deviceScheme": "UNTRACKED",
|
||||
"protocol": "cloud"
|
||||
},
|
||||
"timestamp": "2025-03-10T00:57:26.866Z"
|
||||
},
|
||||
"healthStatus": {
|
||||
"value": null
|
||||
},
|
||||
"DeviceWatch-Enroll": {
|
||||
"value": null
|
||||
},
|
||||
"DeviceWatch-DeviceStatus": {
|
||||
"value": "offline",
|
||||
"data": {
|
||||
"reason": "DEVICE-OFFLINE"
|
||||
},
|
||||
"timestamp": "2025-03-11T10:22:17.013Z"
|
||||
}
|
||||
},
|
||||
"temperatureMeasurement": {
|
||||
"temperatureRange": {
|
||||
"value": null
|
||||
},
|
||||
"temperature": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"thermostatHeatingSetpoint": {
|
||||
"heatingSetpoint": {
|
||||
"value": null
|
||||
},
|
||||
"heatingSetpointRange": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"thermostatFanMode": {
|
||||
"thermostatFanMode": {
|
||||
"value": null
|
||||
},
|
||||
"supportedThermostatFanModes": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"refresh": {},
|
||||
"thermostatMode": {
|
||||
"thermostatMode": {
|
||||
"value": null
|
||||
},
|
||||
"supportedThermostatModes": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"thermostatCoolingSetpoint": {
|
||||
"coolingSetpointRange": {
|
||||
"value": null
|
||||
},
|
||||
"coolingSetpoint": {
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
{
|
||||
"components": {
|
||||
"main02": {
|
||||
"temperatureMeasurement": {
|
||||
"temperatureRange": {
|
||||
"value": null
|
||||
},
|
||||
"temperature": {
|
||||
"value": 200.0,
|
||||
"unit": "C",
|
||||
"timestamp": "2024-12-02T20:18:52.095Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"main": {
|
||||
"thermostatOperatingState": {
|
||||
"thermostatOperatingState": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"signalStrength": {
|
||||
"rssi": {
|
||||
"value": -84,
|
||||
"unit": "dBm",
|
||||
"timestamp": "2025-03-07T20:53:55.346Z"
|
||||
},
|
||||
"lqi": {
|
||||
"value": 255,
|
||||
"timestamp": "2025-03-07T20:53:55.387Z"
|
||||
}
|
||||
},
|
||||
"temperatureMeasurement": {
|
||||
"temperatureRange": {
|
||||
"value": null
|
||||
},
|
||||
"temperature": {
|
||||
"value": 21.0,
|
||||
"unit": "C",
|
||||
"timestamp": "2025-03-07T16:58:23.773Z"
|
||||
}
|
||||
},
|
||||
"thermostatHeatingSetpoint": {
|
||||
"heatingSetpoint": {
|
||||
"value": 23.0,
|
||||
"unit": "C",
|
||||
"timestamp": "2025-02-10T17:48:38.299Z"
|
||||
},
|
||||
"heatingSetpointRange": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"refresh": {},
|
||||
"valleyboard16460.debug": {
|
||||
"value": {
|
||||
"value": "<table style=\"font-size:0.6em;min-width:100%\"><tbody>\n <tr><th align=\"left\" style=\"width:35%\">Actual</th><td style=\"width:65%\">_TZE200_rxntag7i</td></tr>\n <tr><th align=\"left\">Expected</th><td>_TZE200_4hbx5cvx</td></tr>\n <tr><th align=\"left\">Profile</th><td>normal-thermostat-v3</td></tr>\n <tr><th align=\"left\">Mode</th><td>Similarity</td></tr>\n <tr><th align=\"left\">Preferences</th><td>Modified</td></tr>\n <tr><th align=\"left\">Exposes EF00</th><td>Yes</td></tr>\n <tr><th align=\"left\">Default DP</th><td>No</td></tr>\n </tbody></table>",
|
||||
"timestamp": "2025-03-05T03:04:54.025Z"
|
||||
}
|
||||
},
|
||||
"thermostatMode": {
|
||||
"thermostatMode": {
|
||||
"value": "heat",
|
||||
"data": {},
|
||||
"timestamp": "2024-12-30T08:22:19.273Z"
|
||||
},
|
||||
"supportedThermostatModes": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,222 @@
|
||||
{
|
||||
"components": {
|
||||
"main": {
|
||||
"mediaPlayback": {
|
||||
"supportedPlaybackCommands": {
|
||||
"value": ["play", "pause", "stop"],
|
||||
"timestamp": "2025-03-08T12:06:24.496Z"
|
||||
},
|
||||
"playbackStatus": {
|
||||
"value": "stopped",
|
||||
"timestamp": "2025-03-08T12:06:24.496Z"
|
||||
}
|
||||
},
|
||||
"audioVolume": {
|
||||
"volume": {
|
||||
"value": 52,
|
||||
"unit": "%",
|
||||
"timestamp": "2025-03-08T12:08:00.153Z"
|
||||
}
|
||||
},
|
||||
"mediaInputSource": {
|
||||
"supportedInputSources": {
|
||||
"value": null
|
||||
},
|
||||
"inputSource": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"audioTrackAddressing": {},
|
||||
"samsungim.networkAudioGroupInfo": {
|
||||
"groupName": {
|
||||
"value": "",
|
||||
"timestamp": "2025-03-08T12:06:24.628Z"
|
||||
},
|
||||
"role": {
|
||||
"value": "",
|
||||
"timestamp": "2025-03-08T12:06:24.628Z"
|
||||
},
|
||||
"channel": {
|
||||
"value": "",
|
||||
"timestamp": "2025-03-08T12:06:24.628Z"
|
||||
},
|
||||
"stereoType": {
|
||||
"value": "A",
|
||||
"timestamp": "2025-03-08T12:06:24.628Z"
|
||||
},
|
||||
"masterDi": {
|
||||
"value": "",
|
||||
"timestamp": "2025-03-08T12:06:24.628Z"
|
||||
},
|
||||
"acmMode": {
|
||||
"value": "",
|
||||
"timestamp": "2025-03-08T12:06:24.628Z"
|
||||
},
|
||||
"status": {
|
||||
"value": "single",
|
||||
"timestamp": "2025-03-08T12:06:24.628Z"
|
||||
},
|
||||
"masterName": {
|
||||
"value": "",
|
||||
"timestamp": "2025-03-08T12:06:24.628Z"
|
||||
}
|
||||
},
|
||||
"refresh": {},
|
||||
"audioNotification": {},
|
||||
"execute": {
|
||||
"data": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungim.networkAudioMode": {
|
||||
"mode": {
|
||||
"value": "wifi",
|
||||
"timestamp": "2025-03-08T12:06:24.573Z"
|
||||
}
|
||||
},
|
||||
"mediaPlaybackRepeat": {
|
||||
"playbackRepeatMode": {
|
||||
"value": "off",
|
||||
"timestamp": "2025-03-08T12:06:24.519Z"
|
||||
}
|
||||
},
|
||||
"musicPlayer": {
|
||||
"trackDescription": {
|
||||
"value": null
|
||||
},
|
||||
"level": {
|
||||
"value": null
|
||||
},
|
||||
"mute": {
|
||||
"value": null
|
||||
},
|
||||
"trackData": {
|
||||
"value": null
|
||||
},
|
||||
"status": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"ocf": {
|
||||
"st": {
|
||||
"value": null
|
||||
},
|
||||
"mndt": {
|
||||
"value": null
|
||||
},
|
||||
"mnfv": {
|
||||
"value": "V310XXU1AWK1",
|
||||
"timestamp": "2025-03-08T12:06:18.931Z"
|
||||
},
|
||||
"mnhw": {
|
||||
"value": "1.0",
|
||||
"timestamp": "2025-03-08T12:06:18.931Z"
|
||||
},
|
||||
"di": {
|
||||
"value": "c9276e43-fe3c-88c3-1dcc-2eb79e292b8c",
|
||||
"timestamp": "2025-03-08T12:06:18.931Z"
|
||||
},
|
||||
"mnsl": {
|
||||
"value": null
|
||||
},
|
||||
"dmv": {
|
||||
"value": "IoTivity1.2.1",
|
||||
"timestamp": "2025-03-08T12:06:18.942Z"
|
||||
},
|
||||
"n": {
|
||||
"value": "Galaxy Home Mini (MQVL)",
|
||||
"timestamp": "2025-03-08T12:06:18.931Z"
|
||||
},
|
||||
"mnmo": {
|
||||
"value": "SM-V310",
|
||||
"timestamp": "2025-03-08T12:06:18.931Z"
|
||||
},
|
||||
"vid": {
|
||||
"value": "IM-SPEAKER-AI-0001",
|
||||
"timestamp": "2025-03-08T12:06:18.931Z"
|
||||
},
|
||||
"mnmn": {
|
||||
"value": "Samsung Electronics",
|
||||
"timestamp": "2025-03-08T12:06:18.931Z"
|
||||
},
|
||||
"mnml": {
|
||||
"value": null
|
||||
},
|
||||
"mnpv": {
|
||||
"value": "4.0.0.1",
|
||||
"timestamp": "2025-03-08T12:06:18.931Z"
|
||||
},
|
||||
"mnos": {
|
||||
"value": "Tizen",
|
||||
"timestamp": "2025-03-08T12:06:18.931Z"
|
||||
},
|
||||
"pi": {
|
||||
"value": "c9276e43-fe3c-88c3-1dcc-2eb79e292b8c",
|
||||
"timestamp": "2025-03-08T12:06:18.931Z"
|
||||
},
|
||||
"icv": {
|
||||
"value": "core0.0.1",
|
||||
"timestamp": "2025-03-08T12:06:18.942Z"
|
||||
}
|
||||
},
|
||||
"samsungim.announcement": {
|
||||
"enableState": {
|
||||
"value": null
|
||||
},
|
||||
"supportedCategories": {
|
||||
"value": null
|
||||
},
|
||||
"supportedTypes": {
|
||||
"value": null
|
||||
},
|
||||
"supportedMimes": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungim.bixbyContent": {
|
||||
"supportedModes": {
|
||||
"value": ["news", "weather", "music", "search_all"],
|
||||
"timestamp": "2025-03-08T12:06:24.817Z"
|
||||
}
|
||||
},
|
||||
"mediaPlaybackShuffle": {
|
||||
"playbackShuffle": {
|
||||
"value": "disabled",
|
||||
"timestamp": "2025-03-08T12:06:24.592Z"
|
||||
}
|
||||
},
|
||||
"audioMute": {
|
||||
"mute": {
|
||||
"value": "unmuted",
|
||||
"timestamp": "2025-03-08T12:06:24.478Z"
|
||||
}
|
||||
},
|
||||
"mediaTrackControl": {
|
||||
"supportedTrackControlCommands": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"speechSynthesis": {},
|
||||
"samsungim.networkAudioTrackData": {
|
||||
"appName": {
|
||||
"value": null
|
||||
},
|
||||
"source": {
|
||||
"value": "wifi",
|
||||
"timestamp": "2025-03-08T12:06:24.540Z"
|
||||
}
|
||||
},
|
||||
"audioTrackData": {
|
||||
"totalTime": {
|
||||
"value": null
|
||||
},
|
||||
"audioTrackData": {
|
||||
"value": null
|
||||
},
|
||||
"elapsedTime": {
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
{
|
||||
"components": {
|
||||
"main": {
|
||||
"powerConsumptionReport": {
|
||||
"powerConsumption": {
|
||||
"value": {
|
||||
"start": "2025-03-10T14:43:42.500Z",
|
||||
"end": "2025-03-10T14:59:42.500Z",
|
||||
"energy": 15720,
|
||||
"deltaEnergy": 0
|
||||
},
|
||||
"timestamp": "2025-03-10T14:59:50.010Z"
|
||||
}
|
||||
},
|
||||
"healthCheck": {
|
||||
"checkInterval": {
|
||||
"value": 60,
|
||||
"unit": "s",
|
||||
"data": {
|
||||
"deviceScheme": "UNTRACKED",
|
||||
"protocol": "cloud"
|
||||
},
|
||||
"timestamp": "2024-03-07T21:14:59.839Z"
|
||||
},
|
||||
"healthStatus": {
|
||||
"value": null
|
||||
},
|
||||
"DeviceWatch-Enroll": {
|
||||
"value": null
|
||||
},
|
||||
"DeviceWatch-DeviceStatus": {
|
||||
"value": "online",
|
||||
"data": {},
|
||||
"timestamp": "2025-03-10T14:14:37.232Z"
|
||||
}
|
||||
},
|
||||
"refresh": {},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"value": "on",
|
||||
"timestamp": "2025-03-10T14:14:37.232Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"deviceId": "7c16163e-c94e-482f-95f6-139ae0cd9d5e",
|
||||
"name": "ABL Wafer Down Light(BLE)",
|
||||
"label": "Kitchen Light 5",
|
||||
"manufacturerName": "Samsung Electronics",
|
||||
"presentationId": "ABL-LIGHT-B-001",
|
||||
"deviceManufacturerCode": "Samsung Electronics",
|
||||
"locationId": "6c314222-8baf-48a0-9442-5b1102a8757f",
|
||||
"ownerId": "f24ff388-700c-7d1e-91f2-1c37ae68ce2b",
|
||||
"components": [
|
||||
{
|
||||
"id": "main",
|
||||
"label": "main",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "switch",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "colorTemperature",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "switchLevel",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Light",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"createTime": "2025-03-08T22:40:25.073Z",
|
||||
"profile": {
|
||||
"id": "65f5db53-9a78-4b19-8e40-d32187cd59ab"
|
||||
},
|
||||
"bleD2D": {
|
||||
"encryptionKey": "f593369dcea915f6352a4a42cd4b2ea6",
|
||||
"cipher": "AES_128-CBC-PKCS7Padding",
|
||||
"advertisingId": "b13d7192",
|
||||
"identifier": "88-57-1d-7c-cb-cf",
|
||||
"configurationUrl": "https://apis.samsungiotcloud.com/v1/miniature/profile/65f5db53-9a78-4b19-8e40-d32187cd59ab",
|
||||
"bleDeviceType": "BLE",
|
||||
"metadata": null
|
||||
},
|
||||
"type": "BLE_D2D",
|
||||
"restrictionTier": 0,
|
||||
"allowed": null,
|
||||
"executionContext": "CLOUD",
|
||||
"relationships": []
|
||||
}
|
||||
],
|
||||
"_links": {}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"deviceId": "286ba274-4093-4bcb-849c-a1a3efe7b1e5",
|
||||
"name": "thermostat",
|
||||
"label": "Radiator Thermostat II [+M] Wohnzimmer",
|
||||
"manufacturerName": "SmartThingsCommunity",
|
||||
"presentationId": "2a1c9915-f61b-3f3a-a02b-703b8cccf3d6",
|
||||
"deviceManufacturerCode": "BOSCH",
|
||||
"locationId": "0b6618a6-c3ab-4b6e-968d-59cc8c2761bc",
|
||||
"ownerId": "8a20b799-9d87-ecdc-39de-c93c6e4d3ea1",
|
||||
"roomId": "11374ab5-9b4e-416b-91d1-745bbf9b6db4",
|
||||
"components": [
|
||||
{
|
||||
"id": "main",
|
||||
"label": "main",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "temperatureMeasurement",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "thermostatMode",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "thermostatHeatingSetpoint",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "battery",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "firmwareUpdate",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "refresh",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Thermostat",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"createTime": "2024-11-29T19:55:00.910Z",
|
||||
"parentDeviceId": "61bd280e-71c4-44fb-9b6e-53fdf14718a2",
|
||||
"profile": {
|
||||
"id": "4da5d086-111e-3084-a039-616974326833"
|
||||
},
|
||||
"matter": {
|
||||
"driverId": "5f3c42eb-5704-4c95-9705-c51c1a6764bf",
|
||||
"hubId": "61bd280e-71c4-44fb-9b6e-53fdf14718a2",
|
||||
"provisioningState": "PROVISIONED",
|
||||
"networkId": "8EF2CF7A212285B2-46C6B9F266A4521A",
|
||||
"executingLocally": true,
|
||||
"uniqueId": "8475B3FEFF6748D4",
|
||||
"vendorId": 4617,
|
||||
"productId": 12306,
|
||||
"serialNumber": "D44867FFFEB37584",
|
||||
"listeningType": "SLEEPY",
|
||||
"supportedNetworkInterfaces": ["THREAD"],
|
||||
"version": {
|
||||
"hardware": 18,
|
||||
"hardwareLabel": "1.2.0",
|
||||
"software": 20009,
|
||||
"softwareLabel": "2.00.09"
|
||||
},
|
||||
"endpoints": [
|
||||
{
|
||||
"endpointId": 0,
|
||||
"deviceTypes": [
|
||||
{
|
||||
"deviceTypeId": 22
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"endpointId": 1,
|
||||
"deviceTypes": [
|
||||
{
|
||||
"deviceTypeId": 769
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"syncDrivers": true
|
||||
},
|
||||
"type": "MATTER",
|
||||
"restrictionTier": 0,
|
||||
"allowed": null,
|
||||
"executionContext": "LOCAL",
|
||||
"relationships": []
|
||||
}
|
||||
],
|
||||
"_links": {}
|
||||
}
|
@ -0,0 +1,205 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"deviceId": "3a6c4e05-811d-5041-e956-3d04c424cbcd",
|
||||
"name": "[dryer] Samsung",
|
||||
"label": "Seca-Roupa",
|
||||
"manufacturerName": "Samsung Electronics",
|
||||
"presentationId": "DA-WM-WD-000001",
|
||||
"deviceManufacturerCode": "Samsung Electronics",
|
||||
"locationId": "06efa178-ad2f-4d22-838c-d63e05e5a58a",
|
||||
"ownerId": "1a5f5619-e9ec-4302-beb9-633bb1657897",
|
||||
"roomId": "dde24053-9707-49a5-ba0e-f19681514f37",
|
||||
"deviceTypeName": "Samsung OCF Dryer",
|
||||
"components": [
|
||||
{
|
||||
"id": "main",
|
||||
"label": "Seca-Roupa",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "ocf",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "execute",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "refresh",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "switch",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "remoteControlStatus",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "dryerOperatingState",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "powerConsumptionReport",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "demandResponseLoadControl",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.disabledCapabilities",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.dryerDryLevel",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.dryerWrinklePrevent",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.energyType",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.jobBeginningStatus",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.supportedOptions",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.softwareUpdate",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.detergentOrder",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.detergentState",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.deviceIdentification",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.dongleSoftwareInstallation",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.driverVersion",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.dryerAutoCycleLink",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.dryerCycle",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.dryerCyclePreset",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.dryerDelayEnd",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.dryerDryingTemperature",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.dryerDryingTime",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.dryerFreezePrevent",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.dryerOperatingState",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.kidsLock",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.welcomeMessage",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.quickControl",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "sec.diagnosticsInformation",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "sec.wifiConfiguration",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Dryer",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "hca.main",
|
||||
"label": "hca.main",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "hca.dryerMode",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Other",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"createTime": "2020-06-20T10:00:42Z",
|
||||
"profile": {
|
||||
"id": "53a1d049-eeda-396c-8324-e33438ef57be"
|
||||
},
|
||||
"ocf": {
|
||||
"ocfDeviceType": "oic.d.dryer",
|
||||
"name": "[dryer] Samsung",
|
||||
"specVersion": "core.1.1.0",
|
||||
"verticalDomainSpecVersion": "res.1.1.0,sh.1.1.0",
|
||||
"manufacturerName": "Samsung Electronics",
|
||||
"modelNumber": "DA_WM_A51_20_COMMON|20221341|30010102001211000103000000000000",
|
||||
"platformVersion": "DAWIT 2.0",
|
||||
"platformOS": "TizenRT 1.0 + IPv6",
|
||||
"hwVersion": "ARTIK051",
|
||||
"firmwareVersion": "DA_WM_A51_20_COMMON_30230708",
|
||||
"vendorId": "DA-WM-WD-000001",
|
||||
"vendorResourceClientServerVersion": "ARTIK051 Release 2.210224.1",
|
||||
"lastSignupTime": "2020-11-19T04:43:50.736Z",
|
||||
"transferCandidate": false,
|
||||
"additionalAuthCodeRequired": false
|
||||
},
|
||||
"type": "OCF",
|
||||
"restrictionTier": 0,
|
||||
"allowed": null,
|
||||
"executionContext": "CLOUD",
|
||||
"relationships": []
|
||||
}
|
||||
],
|
||||
"_links": {}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"deviceId": "1888b38f-6246-4f1e-911b-bfcfb66999db",
|
||||
"name": "v4 - ecobee Thermostat - Heat and Cool (F)",
|
||||
"label": "Downstairs",
|
||||
"manufacturerName": "0A0b",
|
||||
"presentationId": "ST_5334da38-8076-4b40-9f6c-ac3fccaa5d24",
|
||||
"deviceManufacturerCode": "ecobee",
|
||||
"locationId": "1030449a-22c1-4a80-9781-0bd4ab7f0f2f",
|
||||
"ownerId": "e7dbb793-4351-4cdc-b037-e6e0b4f9df67",
|
||||
"roomId": "d22e6f98-78fe-4a76-b904-6cad8628da59",
|
||||
"components": [
|
||||
{
|
||||
"id": "main",
|
||||
"label": "main",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "temperatureMeasurement",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "relativeHumidityMeasurement",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "thermostatHeatingSetpoint",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "thermostatCoolingSetpoint",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "thermostatOperatingState",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "thermostatMode",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "thermostatFanMode",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "refresh",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "healthCheck",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Thermostat",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"createTime": "2025-03-10T00:57:26.760Z",
|
||||
"profile": {
|
||||
"id": "234d537d-d388-497f-b0f4-2e25025119ba"
|
||||
},
|
||||
"viper": {
|
||||
"manufacturerName": "ecobee",
|
||||
"modelName": "nikeSmart-thermostat",
|
||||
"swVersion": "250308073247",
|
||||
"hwVersion": "250308073247",
|
||||
"endpointAppId": "viper_92ccdcc0-4184-11eb-b9c5-036180216747"
|
||||
},
|
||||
"type": "VIPER",
|
||||
"restrictionTier": 0,
|
||||
"allowed": null,
|
||||
"executionContext": "CLOUD",
|
||||
"relationships": []
|
||||
}
|
||||
],
|
||||
"_links": {}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"deviceId": "656569c2-7976-4232-a789-34b4d1176c3a",
|
||||
"name": "generic-ef00-v1",
|
||||
"label": "Thermostat K\u00fcche",
|
||||
"manufacturerName": "SmartThingsCommunity",
|
||||
"presentationId": "be641577-f796-315b-af6f-b3ad14dd7a58",
|
||||
"deviceManufacturerCode": "_TZE200_rxntag7i",
|
||||
"locationId": "0b6618a6-c3ab-4b6e-968d-59cc8c2761bc",
|
||||
"ownerId": "8a20b799-9d87-ecdc-39de-c93c6e4d3ea1",
|
||||
"roomId": "eeb2f9d2-19cc-4dad-9f23-28ec807de97e",
|
||||
"components": [
|
||||
{
|
||||
"id": "main",
|
||||
"label": "Thermostat",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "switch",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "thermostatMode",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "temperatureMeasurement",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "thermostatHeatingSetpoint",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "thermostatOperatingState",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "signalStrength",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "refresh",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "valleyboard16460.debug",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Thermostat",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "main02",
|
||||
"label": "Floor",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "temperatureMeasurement",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Thermostat",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"createTime": "2024-12-02T15:58:01.598Z",
|
||||
"profile": {
|
||||
"id": "3ad2e1e3-8867-332c-85b5-b291602c324f"
|
||||
},
|
||||
"zigbee": {
|
||||
"eui": "A4C1388B31017B5F",
|
||||
"networkId": "162F",
|
||||
"driverId": "585328e6-ac85-4ac5-bce4-286efd0ab980",
|
||||
"executingLocally": true,
|
||||
"hubId": "61bd280e-71c4-44fb-9b6e-53fdf14718a2",
|
||||
"provisioningState": "DRIVER_SWITCH"
|
||||
},
|
||||
"type": "ZIGBEE",
|
||||
"restrictionTier": 0,
|
||||
"allowed": null,
|
||||
"executionContext": "LOCAL",
|
||||
"relationships": []
|
||||
}
|
||||
],
|
||||
"_links": {}
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"deviceId": "c9276e43-fe3c-88c3-1dcc-2eb79e292b8c",
|
||||
"name": "Galaxy Home Mini (MQVL)",
|
||||
"label": "Galaxy Home Mini",
|
||||
"manufacturerName": "Samsung Electronics",
|
||||
"presentationId": "IM-SPEAKER-AI-0001",
|
||||
"deviceManufacturerCode": "Samsung Electronics",
|
||||
"locationId": "33db9e71-abe9-43a0-acd3-3f0927bbe5b7",
|
||||
"ownerId": "9a1ee192-04ba-46ca-9c3d-196d8dbcf807",
|
||||
"roomId": "445c006d-1796-4dd6-8308-1c3cd045e8ff",
|
||||
"deviceTypeName": "Samsung OCF Network Audio Player",
|
||||
"components": [
|
||||
{
|
||||
"id": "main",
|
||||
"label": "main",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "execute",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "ocf",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "refresh",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "audioMute",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "audioVolume",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "mediaInputSource",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "mediaPlaybackRepeat",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "mediaPlaybackShuffle",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "mediaPlayback",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "mediaTrackControl",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "audioTrackAddressing",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "audioTrackData",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "musicPlayer",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "audioNotification",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "speechSynthesis",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungim.bixbyContent",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungim.announcement",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungim.networkAudioMode",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungim.networkAudioGroupInfo",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungim.networkAudioTrackData",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "NetworkAudio",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"createTime": "2025-03-08T12:06:18.865Z",
|
||||
"profile": {
|
||||
"id": "09df8e36-e94f-339c-9086-9639505e1fb2"
|
||||
},
|
||||
"ocf": {
|
||||
"ocfDeviceType": "oic.d.networkaudio",
|
||||
"name": "Galaxy Home Mini (MQVL)",
|
||||
"specVersion": "core0.0.1",
|
||||
"verticalDomainSpecVersion": "IoTivity1.2.1",
|
||||
"manufacturerName": "Samsung Electronics",
|
||||
"modelNumber": "SM-V310",
|
||||
"platformVersion": "4.0.0.1",
|
||||
"platformOS": "Tizen",
|
||||
"hwVersion": "1.0",
|
||||
"firmwareVersion": "V310XXU1AWK1",
|
||||
"vendorId": "IM-SPEAKER-AI-0001",
|
||||
"lastSignupTime": "2025-03-08T12:06:16.386696652Z",
|
||||
"transferCandidate": false,
|
||||
"additionalAuthCodeRequired": false
|
||||
},
|
||||
"type": "OCF",
|
||||
"restrictionTier": 0,
|
||||
"allowed": null,
|
||||
"executionContext": "CLOUD",
|
||||
"relationships": []
|
||||
}
|
||||
],
|
||||
"_links": {}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"deviceId": "6602696a-1e48-49e4-919f-69406f5b5da1",
|
||||
"name": "plug-energy-usage-report",
|
||||
"label": "Sp\u00fclmaschine",
|
||||
"manufacturerName": "0AI2",
|
||||
"presentationId": "ST_8f2be0ec-1113-46e0-ad56-3e92eb27410f",
|
||||
"deviceManufacturerCode": "TP-Link",
|
||||
"locationId": "70da36b0-bd25-410c-beed-7f0dbf658448",
|
||||
"ownerId": "be5d4173-dd49-1eee-56f5-f98306ee872c",
|
||||
"roomId": "bd13616d-b7e2-44ff-914c-eb38ea18c4b4",
|
||||
"components": [
|
||||
{
|
||||
"id": "main",
|
||||
"label": "main",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "healthCheck",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "refresh",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "switch",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "powerConsumptionReport",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "SmartPlug",
|
||||
"categoryType": "manufacturer"
|
||||
},
|
||||
{
|
||||
"name": "SmartPlug",
|
||||
"categoryType": "user"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"createTime": "2024-03-07T21:14:59.762Z",
|
||||
"profile": {
|
||||
"id": "a25b207e-cbb9-40ae-8a88-906637c22ab6"
|
||||
},
|
||||
"viper": {
|
||||
"uniqueIdentifier": "8022F7F6FE0A6EACA52B5D89C0D667352136D8C6",
|
||||
"manufacturerName": "TP-Link",
|
||||
"modelName": "P110",
|
||||
"swVersion": "1.3.1 Build 240621 Rel.162048",
|
||||
"hwVersion": "1.0",
|
||||
"endpointAppId": "viper_7ea6bb80-b876-11eb-be42-952f31ab3f7b"
|
||||
},
|
||||
"type": "VIPER",
|
||||
"restrictionTier": 0,
|
||||
"allowed": null,
|
||||
"indoorMap": {
|
||||
"coordinates": [0.0, 0.0, 0.0],
|
||||
"rotation": [0.0, 180.0, 0.0],
|
||||
"visible": false,
|
||||
"data": null
|
||||
},
|
||||
"executionContext": "CLOUD",
|
||||
"relationships": []
|
||||
}
|
||||
],
|
||||
"_links": {}
|
||||
}
|
16
tests/components/smartthings/fixtures/subscription.json
Normal file
16
tests/components/smartthings/fixtures/subscription.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"subscriptionId": "f5768ce8-c9e5-4507-9020-912c0c60e0ab",
|
||||
"registrationUrl": "https://spigot-regional.api.smartthings.com/filters/f5768ce8-c9e5-4507-9020-912c0c60e0ab/activate?filterRegion=eu-west-1",
|
||||
"name": "My Home Assistant sub",
|
||||
"version": 20250122,
|
||||
"subscriptionFilters": [
|
||||
{
|
||||
"type": "LOCATIONIDS",
|
||||
"value": ["88a3a314-f0c8-40b4-bb44-44ba06c9c42e"],
|
||||
"eventType": ["DEVICE_EVENT"],
|
||||
"attribute": null,
|
||||
"capability": null,
|
||||
"component": null
|
||||
}
|
||||
]
|
||||
}
|
@ -1,4 +1,67 @@
|
||||
# serializer version: 1
|
||||
# name: test_all_entities[bosch_radiator_thermostat_ii][climate.radiator_thermostat_ii_m_wohnzimmer-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
}),
|
||||
'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.radiator_thermostat_ii_m_wohnzimmer',
|
||||
'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,
|
||||
'supported_features': <ClimateEntityFeature: 387>,
|
||||
'translation_key': None,
|
||||
'unique_id': '286ba274-4093-4bcb-849c-a1a3efe7b1e5',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[bosch_radiator_thermostat_ii][climate.radiator_thermostat_ii_m_wohnzimmer-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 23.9,
|
||||
'friendly_name': 'Radiator Thermostat II [+M] Wohnzimmer',
|
||||
'hvac_modes': list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
'supported_features': <ClimateEntityFeature: 387>,
|
||||
'target_temp_high': None,
|
||||
'target_temp_low': None,
|
||||
'temperature': 22.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.radiator_thermostat_ii_m_wohnzimmer',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'heat',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ac_rac_000001][climate.ac_office_granit-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@ -369,6 +432,131 @@
|
||||
'state': 'heat',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[ecobee_thermostat_offline][climate.downstairs-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'fan_modes': None,
|
||||
'hvac_modes': list([
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
}),
|
||||
'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.downstairs',
|
||||
'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,
|
||||
'supported_features': <ClimateEntityFeature: 395>,
|
||||
'translation_key': None,
|
||||
'unique_id': '1888b38f-6246-4f1e-911b-bfcfb66999db',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[ecobee_thermostat_offline][climate.downstairs-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': None,
|
||||
'fan_mode': None,
|
||||
'fan_modes': None,
|
||||
'friendly_name': 'Downstairs',
|
||||
'hvac_modes': list([
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
'supported_features': <ClimateEntityFeature: 395>,
|
||||
'target_temp_high': None,
|
||||
'target_temp_low': None,
|
||||
'temperature': None,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.downstairs',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[generic_ef00_v1][climate.thermostat_kuche-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
}),
|
||||
'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.thermostat_kuche',
|
||||
'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,
|
||||
'supported_features': <ClimateEntityFeature: 387>,
|
||||
'translation_key': None,
|
||||
'unique_id': '656569c2-7976-4232-a789-34b4d1176c3a',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[generic_ef00_v1][climate.thermostat_kuche-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 21.0,
|
||||
'friendly_name': 'Thermostat Küche',
|
||||
'hvac_modes': list([
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
'supported_features': <ClimateEntityFeature: 387>,
|
||||
'target_temp_high': None,
|
||||
'target_temp_low': None,
|
||||
'temperature': 23.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.thermostat_kuche',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'heat',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[heatit_ztrm3_thermostat][climate.hall_thermostat-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
@ -2,6 +2,39 @@
|
||||
# name: test_button_event[button]
|
||||
<Event smartthings.button[L]: component_id=main, device_id=c4bdd19f-85d1-4d58-8f9c-e75ac3cf113b, location_id=abc, value=pushed, name=button, data=None>
|
||||
# ---
|
||||
# name: test_devices[abl_light_b_001]
|
||||
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': None,
|
||||
'id': <ANY>,
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'smartthings',
|
||||
'7c16163e-c94e-482f-95f6-139ae0cd9d5e',
|
||||
),
|
||||
}),
|
||||
'is_new': False,
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': None,
|
||||
'model': None,
|
||||
'model_id': None,
|
||||
'name': 'Kitchen Light 5',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': None,
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[aeotec_home_energy_meter_gen5]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
@ -68,6 +101,39 @@
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[bosch_radiator_thermostat_ii]
|
||||
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': None,
|
||||
'id': <ANY>,
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'smartthings',
|
||||
'286ba274-4093-4bcb-849c-a1a3efe7b1e5',
|
||||
),
|
||||
}),
|
||||
'is_new': False,
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': None,
|
||||
'model': None,
|
||||
'model_id': None,
|
||||
'name': 'Radiator Thermostat II [+M] Wohnzimmer',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': None,
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[c2c_arlo_pro_3_switch]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
@ -464,6 +530,39 @@
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[da_wm_wd_000001_1]
|
||||
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': 'ARTIK051',
|
||||
'id': <ANY>,
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'smartthings',
|
||||
'3a6c4e05-811d-5041-e956-3d04c424cbcd',
|
||||
),
|
||||
}),
|
||||
'is_new': False,
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'Samsung Electronics',
|
||||
'model': 'DA_WM_A51_20_COMMON',
|
||||
'model_id': None,
|
||||
'name': 'Seca-Roupa',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': 'DA_WM_A51_20_COMMON_30230708',
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[da_wm_wm_000001]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
@ -596,6 +695,39 @@
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[ecobee_thermostat_offline]
|
||||
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': '250308073247',
|
||||
'id': <ANY>,
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'smartthings',
|
||||
'1888b38f-6246-4f1e-911b-bfcfb66999db',
|
||||
),
|
||||
}),
|
||||
'is_new': False,
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'ecobee',
|
||||
'model': 'nikeSmart-thermostat',
|
||||
'model_id': None,
|
||||
'name': 'Downstairs',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': '250308073247',
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[fake_fan]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
@ -662,6 +794,39 @@
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[generic_ef00_v1]
|
||||
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': None,
|
||||
'id': <ANY>,
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'smartthings',
|
||||
'656569c2-7976-4232-a789-34b4d1176c3a',
|
||||
),
|
||||
}),
|
||||
'is_new': False,
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': None,
|
||||
'model': None,
|
||||
'model_id': None,
|
||||
'name': 'Thermostat Küche',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': None,
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[generic_fan_3_speed]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
@ -794,6 +959,39 @@
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[im_speaker_ai_0001]
|
||||
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': '1.0',
|
||||
'id': <ANY>,
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'smartthings',
|
||||
'c9276e43-fe3c-88c3-1dcc-2eb79e292b8c',
|
||||
),
|
||||
}),
|
||||
'is_new': False,
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'Samsung Electronics',
|
||||
'model': 'SM-V310',
|
||||
'model_id': None,
|
||||
'name': 'Galaxy Home Mini',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': 'V310XXU1AWK1',
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[iphone]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
@ -959,6 +1157,39 @@
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[tplink_p110]
|
||||
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': '1.0',
|
||||
'id': <ANY>,
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'smartthings',
|
||||
'6602696a-1e48-49e4-919f-69406f5b5da1',
|
||||
),
|
||||
}),
|
||||
'is_new': False,
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'TP-Link',
|
||||
'model': 'P110',
|
||||
'model_id': None,
|
||||
'name': 'Spülmaschine',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': '1.3.1 Build 240621 Rel.162048',
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[vd_network_audio_002s]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
|
@ -1,4 +1,74 @@
|
||||
# serializer version: 1
|
||||
# name: test_all_entities[abl_light_b_001][light.kitchen_light_5-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max_color_temp_kelvin': 9000,
|
||||
'max_mireds': 500,
|
||||
'min_color_temp_kelvin': 2000,
|
||||
'min_mireds': 111,
|
||||
'supported_color_modes': list([
|
||||
<ColorMode.COLOR_TEMP: 'color_temp'>,
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'light',
|
||||
'entity_category': None,
|
||||
'entity_id': 'light.kitchen_light_5',
|
||||
'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,
|
||||
'supported_features': <LightEntityFeature: 32>,
|
||||
'translation_key': None,
|
||||
'unique_id': '7c16163e-c94e-482f-95f6-139ae0cd9d5e',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[abl_light_b_001][light.kitchen_light_5-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'brightness': None,
|
||||
'color_mode': None,
|
||||
'color_temp': None,
|
||||
'color_temp_kelvin': None,
|
||||
'friendly_name': 'Kitchen Light 5',
|
||||
'hs_color': None,
|
||||
'max_color_temp_kelvin': 9000,
|
||||
'max_mireds': 500,
|
||||
'min_color_temp_kelvin': 2000,
|
||||
'min_mireds': 111,
|
||||
'rgb_color': None,
|
||||
'supported_color_modes': list([
|
||||
<ColorMode.COLOR_TEMP: 'color_temp'>,
|
||||
]),
|
||||
'supported_features': <LightEntityFeature: 32>,
|
||||
'xy_color': None,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'light.kitchen_light_5',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[centralite][light.dimmer_debian-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -234,6 +234,53 @@
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_wd_000001_1][switch.seca_roupa-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.seca_roupa',
|
||||
'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,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '3a6c4e05-811d-5041-e956-3d04c424cbcd',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_wd_000001_1][switch.seca_roupa-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Seca-Roupa',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.seca_roupa',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_wm_000001][switch.washer-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@ -328,6 +375,53 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[generic_ef00_v1][switch.thermostat_kuche-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.thermostat_kuche',
|
||||
'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,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '656569c2-7976-4232-a789-34b4d1176c3a',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[generic_ef00_v1][switch.thermostat_kuche-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Thermostat Küche',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.thermostat_kuche',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensibo_airconditioner_1][switch.office-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@ -422,6 +516,53 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[tplink_p110][switch.spulmaschine-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.spulmaschine',
|
||||
'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,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '6602696a-1e48-49e4-919f-69406f5b5da1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[tplink_p110][switch.spulmaschine-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Spülmaschine',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.spulmaschine',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[vd_network_audio_002s][switch.soundbar_living-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
@ -10,6 +10,7 @@ from homeassistant.components.smartthings.const import (
|
||||
CONF_INSTALLED_APP_ID,
|
||||
CONF_LOCATION_ID,
|
||||
CONF_REFRESH_TOKEN,
|
||||
CONF_SUBSCRIPTION_ID,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_USER, ConfigEntryState
|
||||
@ -508,6 +509,7 @@ async def test_migration(
|
||||
"installed_app_id": "123123123-2be1-4e40-b257-e4ef59083324",
|
||||
},
|
||||
CONF_LOCATION_ID: "397678e5-9995-4a39-9d9f-ae6ba310236c",
|
||||
CONF_SUBSCRIPTION_ID: "f5768ce8-c9e5-4507-9020-912c0c60e0ab",
|
||||
}
|
||||
assert mock_old_config_entry.unique_id == "397678e5-9995-4a39-9d9f-ae6ba310236c"
|
||||
assert mock_old_config_entry.version == 3
|
||||
|
@ -2,18 +2,21 @@
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from pysmartthings import Attribute, Capability
|
||||
from pysmartthings import Attribute, Capability, SmartThingsSinkError
|
||||
from pysmartthings.models import Subscription
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.smartthings import EVENT_BUTTON
|
||||
from homeassistant.components.smartthings.const import DOMAIN
|
||||
from homeassistant.components.smartthings.const import CONF_SUBSCRIPTION_ID, DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from . import setup_integration, trigger_update
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, load_fixture
|
||||
|
||||
|
||||
async def test_devices(
|
||||
@ -63,6 +66,178 @@ async def test_button_event(
|
||||
assert events[0] == snapshot
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
|
||||
async def test_create_subscription(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test creating a subscription."""
|
||||
assert CONF_SUBSCRIPTION_ID not in mock_config_entry.data
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
devices.create_subscription.assert_called_once()
|
||||
|
||||
assert (
|
||||
mock_config_entry.data[CONF_SUBSCRIPTION_ID]
|
||||
== "f5768ce8-c9e5-4507-9020-912c0c60e0ab"
|
||||
)
|
||||
|
||||
devices.subscribe.assert_called_once_with(
|
||||
"397678e5-9995-4a39-9d9f-ae6ba310236c",
|
||||
"5aaaa925-2be1-4e40-b257-e4ef59083324",
|
||||
Subscription.from_json(load_fixture("subscription.json", DOMAIN)),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
|
||||
async def test_create_subscription_sink_error(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test handling an error when creating a subscription."""
|
||||
assert CONF_SUBSCRIPTION_ID not in mock_config_entry.data
|
||||
|
||||
devices.create_subscription.side_effect = SmartThingsSinkError("Sink error")
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
devices.subscribe.assert_not_called()
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert CONF_SUBSCRIPTION_ID not in mock_config_entry.data
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
|
||||
async def test_update_subscription_identifier(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test updating the subscription identifier."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert (
|
||||
mock_config_entry.data[CONF_SUBSCRIPTION_ID]
|
||||
== "f5768ce8-c9e5-4507-9020-912c0c60e0ab"
|
||||
)
|
||||
|
||||
devices.new_subscription_id_callback("abc")
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry.data[CONF_SUBSCRIPTION_ID] == "abc"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
|
||||
async def test_stale_subscription_id(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test updating the subscription identifier."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
hass.config_entries.async_update_entry(
|
||||
mock_config_entry,
|
||||
data={**mock_config_entry.data, CONF_SUBSCRIPTION_ID: "test"},
|
||||
)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (
|
||||
mock_config_entry.data[CONF_SUBSCRIPTION_ID]
|
||||
== "f5768ce8-c9e5-4507-9020-912c0c60e0ab"
|
||||
)
|
||||
devices.delete_subscription.assert_called_once_with("test")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
|
||||
async def test_remove_subscription_identifier(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test removing the subscription identifier."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert (
|
||||
mock_config_entry.data[CONF_SUBSCRIPTION_ID]
|
||||
== "f5768ce8-c9e5-4507-9020-912c0c60e0ab"
|
||||
)
|
||||
|
||||
devices.new_subscription_id_callback(None)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry.data[CONF_SUBSCRIPTION_ID] is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
|
||||
async def test_max_connections_handling(
|
||||
hass: HomeAssistant, devices: AsyncMock, mock_config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test handling reaching max connections."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert (
|
||||
mock_config_entry.data[CONF_SUBSCRIPTION_ID]
|
||||
== "f5768ce8-c9e5-4507-9020-912c0c60e0ab"
|
||||
)
|
||||
|
||||
devices.create_subscription.side_effect = SmartThingsSinkError("Sink error")
|
||||
|
||||
devices.max_connections_reached_callback()
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
|
||||
async def test_unloading(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test unloading the integration."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await hass.config_entries.async_unload(mock_config_entry.entry_id)
|
||||
devices.delete_subscription.assert_called_once_with(
|
||||
"f5768ce8-c9e5-4507-9020-912c0c60e0ab"
|
||||
)
|
||||
# Deleting the subscription automatically deletes the subscription ID
|
||||
devices.new_subscription_id_callback(None)
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
assert mock_config_entry.data[CONF_SUBSCRIPTION_ID] is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
|
||||
async def test_shutdown(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test shutting down Home Assistant."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
||||
devices.delete_subscription.assert_called_once_with(
|
||||
"f5768ce8-c9e5-4507-9020-912c0c60e0ab"
|
||||
)
|
||||
# Deleting the subscription automatically deletes the subscription ID
|
||||
devices.new_subscription_id_callback(None)
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||
assert mock_config_entry.data[CONF_SUBSCRIPTION_ID] is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
|
||||
async def test_removing_stale_devices(
|
||||
hass: HomeAssistant,
|
||||
|
@ -34,5 +34,23 @@
|
||||
"protocol_info": "a:b:c:d"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "American Tall Tales",
|
||||
"parent_id": "FV:2",
|
||||
"item_id": "FV:2/66",
|
||||
"restricted": false,
|
||||
"resource_meta_data": "<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns:r=\"urn:schemas-rinconnetworks-com:metadata-1-0/\" xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\"><item id=\"101340c8reftitleC9F27_com\" parentID=\"101340c8reftitleC9F27_com\" restricted=\"true\"><dc:title>American Tall Tales</dc:title><upnp:class>object.item.audioItem.audioBook</upnp:class><desc id=\"cdudn\" nameSpace=\"urn:schemas-rinconnetworks-com:metadata-1-0/\">SA_RINCON61191_X_#Svc6-0-Token</desc></item></DIDL-Lite>",
|
||||
"resources": [
|
||||
{
|
||||
"uri": "x-rincon-cpcontainer:101340c8reftitle%C9F27_com?sid=239&flags=16584&sn=5",
|
||||
"protocol_info": "x-rincon-cpcontainer:*:*:*"
|
||||
}
|
||||
],
|
||||
"desc": null,
|
||||
"album_art_uri": "https://m.media-amazon.com/images/I/810lqLo5m0L._SL600_.jpg",
|
||||
"type": "instantPlay",
|
||||
"description": "Audible",
|
||||
"favorite_nr": "0"
|
||||
}
|
||||
]
|
||||
|
@ -1,4 +1,74 @@
|
||||
# serializer version: 1
|
||||
# name: test_browse_media_favorites[-favorites]
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'album',
|
||||
'media_content_id': 'object.container.album.musicAlbum',
|
||||
'media_content_type': 'favorites_folder',
|
||||
'thumbnail': None,
|
||||
'title': 'Albums',
|
||||
}),
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'track',
|
||||
'media_content_id': 'object.item.audioItem.audioBook',
|
||||
'media_content_type': 'favorites_folder',
|
||||
'thumbnail': None,
|
||||
'title': 'Audio Book',
|
||||
}),
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children_media_class': None,
|
||||
'media_class': 'genre',
|
||||
'media_content_id': 'object.item.audioItem.audioBroadcast',
|
||||
'media_content_type': 'favorites_folder',
|
||||
'thumbnail': None,
|
||||
'title': 'Radio',
|
||||
}),
|
||||
]),
|
||||
'children_media_class': 'directory',
|
||||
'media_class': 'directory',
|
||||
'media_content_id': '',
|
||||
'media_content_type': 'favorites',
|
||||
'not_shown': 0,
|
||||
'thumbnail': None,
|
||||
'title': 'Favorites',
|
||||
})
|
||||
# ---
|
||||
# name: test_browse_media_favorites[object.item.audioItem.audioBook-favorites_folder]
|
||||
dict({
|
||||
'can_expand': True,
|
||||
'can_play': False,
|
||||
'children': list([
|
||||
dict({
|
||||
'can_expand': False,
|
||||
'can_play': True,
|
||||
'children_media_class': None,
|
||||
'media_class': 'track',
|
||||
'media_content_id': 'FV:2/66',
|
||||
'media_content_type': 'favorite_item_id',
|
||||
'thumbnail': 'https://m.media-amazon.com/images/I/810lqLo5m0L._SL600_.jpg',
|
||||
'title': 'American Tall Tales',
|
||||
}),
|
||||
]),
|
||||
'children_media_class': 'track',
|
||||
'media_class': 'directory',
|
||||
'media_content_id': '',
|
||||
'media_content_type': 'favorites',
|
||||
'not_shown': 0,
|
||||
'thumbnail': None,
|
||||
'title': 'Audio Book',
|
||||
})
|
||||
# ---
|
||||
# name: test_browse_media_library
|
||||
list([
|
||||
dict({
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
from functools import partial
|
||||
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.media_player import BrowseMedia, MediaClass, MediaType
|
||||
@ -176,3 +177,39 @@ async def test_browse_media_library_albums(
|
||||
assert response["success"]
|
||||
assert response["result"]["children"] == snapshot
|
||||
assert soco_mock.music_library.browse_by_idstring.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("media_content_id", "media_content_type"),
|
||||
[
|
||||
(
|
||||
"",
|
||||
"favorites",
|
||||
),
|
||||
(
|
||||
"object.item.audioItem.audioBook",
|
||||
"favorites_folder",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_browse_media_favorites(
|
||||
async_autosetup_sonos,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
snapshot: SnapshotAssertion,
|
||||
media_content_id,
|
||||
media_content_type,
|
||||
) -> None:
|
||||
"""Test the async_browse_media method."""
|
||||
client = await hass_ws_client()
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 1,
|
||||
"type": "media_player/browse_media",
|
||||
"entity_id": "media_player.zone_a",
|
||||
"media_content_id": media_content_id,
|
||||
"media_content_type": media_content_type,
|
||||
}
|
||||
)
|
||||
response = await client.receive_json()
|
||||
assert response["success"]
|
||||
assert response["result"] == snapshot
|
||||
|
@ -1 +1,14 @@
|
||||
"""Tests for the WebDAV integration."""
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def setup_integration(
|
||||
hass: HomeAssistant, mock_config_entry: MockConfigEntry
|
||||
) -> None:
|
||||
"""Set up the WebDAV integration for testing."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
@ -62,4 +62,5 @@ def mock_webdav_client() -> Generator[AsyncMock]:
|
||||
mock.download_iter.side_effect = _download_mock
|
||||
mock.upload_iter.return_value = None
|
||||
mock.clean.return_value = None
|
||||
mock.move.return_value = None
|
||||
yield mock
|
||||
|
96
tests/components/webdav/test_init.py
Normal file
96
tests/components/webdav/test_init.py
Normal file
@ -0,0 +1,96 @@
|
||||
"""Test WebDAV component setup."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from aiowebdav2.exceptions import WebDavError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.webdav.const import CONF_BACKUP_PATH, DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_migrate_wrong_path(
|
||||
hass: HomeAssistant, webdav_client: AsyncMock
|
||||
) -> None:
|
||||
"""Test migration of wrong encoded folder path."""
|
||||
webdav_client.list_with_properties.return_value = [
|
||||
{"/wrong%20path": []},
|
||||
]
|
||||
|
||||
config_entry = MockConfigEntry(
|
||||
title="user@webdav.demo",
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_URL: "https://webdav.demo",
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "supersecretpassword",
|
||||
CONF_BACKUP_PATH: "/wrong path",
|
||||
},
|
||||
entry_id="01JKXV07ASC62D620DGYNG2R8H",
|
||||
)
|
||||
await setup_integration(hass, config_entry)
|
||||
|
||||
webdav_client.move.assert_called_once_with("/wrong%20path", "/wrong path")
|
||||
|
||||
|
||||
async def test_migrate_non_wrong_path(
|
||||
hass: HomeAssistant, webdav_client: AsyncMock
|
||||
) -> None:
|
||||
"""Test no migration of correct folder path."""
|
||||
webdav_client.list_with_properties.return_value = [
|
||||
{"/correct path": []},
|
||||
]
|
||||
webdav_client.check.side_effect = lambda path: path == "/correct path"
|
||||
|
||||
config_entry = MockConfigEntry(
|
||||
title="user@webdav.demo",
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_URL: "https://webdav.demo",
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "supersecretpassword",
|
||||
CONF_BACKUP_PATH: "/correct path",
|
||||
},
|
||||
entry_id="01JKXV07ASC62D620DGYNG2R8H",
|
||||
)
|
||||
|
||||
await setup_integration(hass, config_entry)
|
||||
|
||||
webdav_client.move.assert_not_called()
|
||||
|
||||
|
||||
async def test_migrate_error(
|
||||
hass: HomeAssistant,
|
||||
webdav_client: AsyncMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test migration of wrong encoded folder path with error."""
|
||||
webdav_client.list_with_properties.return_value = [
|
||||
{"/wrong%20path": []},
|
||||
]
|
||||
webdav_client.move.side_effect = WebDavError("Failed to move")
|
||||
|
||||
config_entry = MockConfigEntry(
|
||||
title="user@webdav.demo",
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_URL: "https://webdav.demo",
|
||||
CONF_USERNAME: "user",
|
||||
CONF_PASSWORD: "supersecretpassword",
|
||||
CONF_BACKUP_PATH: "/wrong path",
|
||||
},
|
||||
entry_id="01JKXV07ASC62D620DGYNG2R8H",
|
||||
)
|
||||
await setup_integration(hass, config_entry)
|
||||
|
||||
assert config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert (
|
||||
'Failed to migrate wrong encoded folder "/wrong%20path" to "/wrong path"'
|
||||
in caplog.text
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user