mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 11:47:06 +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"
|
IDLE = "idle"
|
||||||
CREATE_BACKUP = "create_backup"
|
CREATE_BACKUP = "create_backup"
|
||||||
|
BLOCKED = "blocked"
|
||||||
RECEIVE_BACKUP = "receive_backup"
|
RECEIVE_BACKUP = "receive_backup"
|
||||||
RESTORE_BACKUP = "restore_backup"
|
RESTORE_BACKUP = "restore_backup"
|
||||||
|
|
||||||
@ -226,6 +227,13 @@ class RestoreBackupEvent(ManagerStateEvent):
|
|||||||
state: RestoreBackupState
|
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):
|
class BackupPlatformProtocol(Protocol):
|
||||||
"""Define the format that backup platforms can have."""
|
"""Define the format that backup platforms can have."""
|
||||||
|
|
||||||
@ -340,7 +348,7 @@ class BackupManager:
|
|||||||
self.remove_next_delete_event: Callable[[], None] | None = None
|
self.remove_next_delete_event: Callable[[], None] | None = None
|
||||||
|
|
||||||
# Latest backup event and backup event subscribers
|
# 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.last_non_idle_event: ManagerStateEvent | None = None
|
||||||
self._backup_event_subscriptions = hass.data[
|
self._backup_event_subscriptions = hass.data[
|
||||||
DATA_BACKUP
|
DATA_BACKUP
|
||||||
@ -354,10 +362,19 @@ class BackupManager:
|
|||||||
self.known_backups.load(stored["backups"])
|
self.known_backups.load(stored["backups"])
|
||||||
|
|
||||||
await self._reader_writer.async_validate_config(config=self.config)
|
await self._reader_writer.async_validate_config(config=self.config)
|
||||||
|
|
||||||
await self._reader_writer.async_resume_restore_progress_after_restart(
|
await self._reader_writer.async_resume_restore_progress_after_restart(
|
||||||
on_progress=self.async_on_backup_event
|
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()
|
await self.load_platforms()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -1293,7 +1310,7 @@ class BackupManager:
|
|||||||
if (current_state := self.state) != (new_state := event.manager_state):
|
if (current_state := self.state) != (new_state := event.manager_state):
|
||||||
LOGGER.debug("Backup state: %s -> %s", current_state, new_state)
|
LOGGER.debug("Backup state: %s -> %s", current_state, new_state)
|
||||||
self.last_event = event
|
self.last_event = event
|
||||||
if not isinstance(event, IdleEvent):
|
if not isinstance(event, (BlockedEvent, IdleEvent)):
|
||||||
self.last_non_idle_event = event
|
self.last_non_idle_event = event
|
||||||
for subscription in self._backup_event_subscriptions:
|
for subscription in self._backup_event_subscriptions:
|
||||||
subscription(event)
|
subscription(event)
|
||||||
|
@ -77,9 +77,12 @@ class BringEventEntity(BringBaseEntity, EventEntity):
|
|||||||
attributes = asdict(activity.content)
|
attributes = asdict(activity.content)
|
||||||
|
|
||||||
attributes["last_activity_by"] = next(
|
attributes["last_activity_by"] = next(
|
||||||
|
(
|
||||||
x.name
|
x.name
|
||||||
for x in bring_list.users.users
|
for x in bring_list.users.users
|
||||||
if x.publicUuid == activity.content.publicUserUuid
|
if x.publicUuid == activity.content.publicUserUuid
|
||||||
|
),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._trigger_event(
|
self._trigger_event(
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aioelectricitymaps import (
|
from aioelectricitymaps import (
|
||||||
ElectricityMaps,
|
ElectricityMaps,
|
||||||
ElectricityMapsError,
|
|
||||||
ElectricityMapsInvalidTokenError,
|
ElectricityMapsInvalidTokenError,
|
||||||
ElectricityMapsNoDataError,
|
ElectricityMapsNoDataError,
|
||||||
)
|
)
|
||||||
@ -36,6 +36,8 @@ TYPE_USE_HOME = "use_home_location"
|
|||||||
TYPE_SPECIFY_COORDINATES = "specify_coordinates"
|
TYPE_SPECIFY_COORDINATES = "specify_coordinates"
|
||||||
TYPE_SPECIFY_COUNTRY = "specify_country_code"
|
TYPE_SPECIFY_COUNTRY = "specify_country_code"
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ElectricityMapsConfigFlow(ConfigFlow, domain=DOMAIN):
|
class ElectricityMapsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow for Co2signal."""
|
"""Handle a config flow for Co2signal."""
|
||||||
@ -158,7 +160,8 @@ class ElectricityMapsConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
errors["base"] = "invalid_auth"
|
errors["base"] = "invalid_auth"
|
||||||
except ElectricityMapsNoDataError:
|
except ElectricityMapsNoDataError:
|
||||||
errors["base"] = "no_data"
|
errors["base"] = "no_data"
|
||||||
except ElectricityMapsError:
|
except Exception:
|
||||||
|
_LOGGER.exception("Unexpected error occurred while checking API key")
|
||||||
errors["base"] = "unknown"
|
errors["base"] = "unknown"
|
||||||
else:
|
else:
|
||||||
if self.source == SOURCE_REAUTH:
|
if self.source == SOURCE_REAUTH:
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["sense_energy"],
|
"loggers": ["sense_energy"],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["sense-energy==0.13.6"]
|
"requirements": ["sense-energy==0.13.7"]
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["evohome", "evohomeasync", "evohomeasync2"],
|
"loggers": ["evohome", "evohomeasync", "evohomeasync2"],
|
||||||
"quality_scale": "legacy",
|
"quality_scale": "legacy",
|
||||||
"requirements": ["evohome-async==1.0.2"]
|
"requirements": ["evohome-async==1.0.4"]
|
||||||
}
|
}
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/fujitsu_fglair",
|
"documentation": "https://www.home-assistant.io/integrations/fujitsu_fglair",
|
||||||
"iot_class": "cloud_polling",
|
"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)
|
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(
|
async def _async_handle_message(
|
||||||
self,
|
self,
|
||||||
user_input: conversation.ConversationInput,
|
user_input: conversation.ConversationInput,
|
||||||
@ -435,7 +442,10 @@ class GoogleGenerativeAIConversationEntity(
|
|||||||
tool_name = tool_call.name
|
tool_name = tool_call.name
|
||||||
tool_args = _escape_decode(tool_call.args)
|
tool_args = _escape_decode(tool_call.args)
|
||||||
tool_calls.append(
|
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(
|
chat_request = _create_google_tool_response_content(
|
||||||
|
@ -135,5 +135,5 @@
|
|||||||
"dependencies": ["bluetooth_adapters"],
|
"dependencies": ["bluetooth_adapters"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/govee_ble",
|
"documentation": "https://www.home-assistant.io/integrations/govee_ble",
|
||||||
"iot_class": "local_push",
|
"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
|
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.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers import selector
|
from homeassistant.helpers import selector
|
||||||
@ -141,8 +146,10 @@ class HeosFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
hostname = urlparse(discovery_info.ssdp_location).hostname
|
hostname = urlparse(discovery_info.ssdp_location).hostname
|
||||||
assert hostname is not None
|
assert hostname is not None
|
||||||
|
|
||||||
# Abort early when discovered host is part of the current system
|
# Abort early when discovery is ignored or host is part of the current system
|
||||||
if entry and hostname in _get_current_hosts(entry):
|
if entry and (
|
||||||
|
entry.source == SOURCE_IGNORE or hostname in _get_current_hosts(entry)
|
||||||
|
):
|
||||||
return self.async_abort(reason="single_instance_allowed")
|
return self.async_abort(reason="single_instance_allowed")
|
||||||
|
|
||||||
# Connect to discovered host and get system information
|
# 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
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> ConfigFlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Obtain host and validate connection."""
|
"""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")
|
self._abort_if_unique_id_configured(error="single_instance_allowed")
|
||||||
# Try connecting to host if provided
|
# Try connecting to host if provided
|
||||||
errors: dict[str, str] = {}
|
errors: dict[str, str] = {}
|
||||||
|
@ -159,13 +159,12 @@ class HeosCoordinator(DataUpdateCoordinator[None]):
|
|||||||
|
|
||||||
async def _async_on_reconnected(self) -> None:
|
async def _async_on_reconnected(self) -> None:
|
||||||
"""Handle when reconnected so resources are updated and entities marked available."""
|
"""Handle when reconnected so resources are updated and entities marked available."""
|
||||||
await self._async_update_players()
|
|
||||||
await self._async_update_sources()
|
await self._async_update_sources()
|
||||||
_LOGGER.warning("Successfully reconnected to HEOS host %s", self.host)
|
_LOGGER.warning("Successfully reconnected to HEOS host %s", self.host)
|
||||||
self.async_update_listeners()
|
self.async_update_listeners()
|
||||||
|
|
||||||
async def _async_on_controller_event(
|
async def _async_on_controller_event(
|
||||||
self, event: str, data: PlayerUpdateResult | None
|
self, event: str, data: PlayerUpdateResult | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle a controller event, such as players or groups changed."""
|
"""Handle a controller event, such as players or groups changed."""
|
||||||
if event == const.EVENT_PLAYERS_CHANGED:
|
if event == const.EVENT_PLAYERS_CHANGED:
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["pyheos"],
|
"loggers": ["pyheos"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["pyheos==1.0.2"],
|
"requirements": ["pyheos==1.0.3"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"st": "urn:schemas-denon-com:device:ACT-Denon:1"
|
"st": "urn:schemas-denon-com:device:ACT-Denon:1"
|
||||||
|
@ -16,11 +16,17 @@ from aiohomeconnect.model import (
|
|||||||
SettingKey,
|
SettingKey,
|
||||||
)
|
)
|
||||||
from aiohomeconnect.model.error import HomeConnectError
|
from aiohomeconnect.model.error import HomeConnectError
|
||||||
|
import aiohttp
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import ATTR_DEVICE_ID, Platform
|
from homeassistant.const import ATTR_DEVICE_ID, Platform
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
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 (
|
from homeassistant.helpers import (
|
||||||
config_entry_oauth2_flow,
|
config_entry_oauth2_flow,
|
||||||
config_validation as cv,
|
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)
|
session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation)
|
||||||
|
|
||||||
config_entry_auth = AsyncConfigEntryAuth(hass, session)
|
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)
|
home_connect_client = HomeConnectClient(config_entry_auth)
|
||||||
|
|
||||||
|
@ -285,6 +285,7 @@ SPIN_SPEED_OPTIONS = {
|
|||||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM400",
|
"LaundryCare.Washer.EnumType.SpinSpeed.RPM400",
|
||||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM600",
|
"LaundryCare.Washer.EnumType.SpinSpeed.RPM600",
|
||||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM800",
|
"LaundryCare.Washer.EnumType.SpinSpeed.RPM800",
|
||||||
|
"LaundryCare.Washer.EnumType.SpinSpeed.RPM900",
|
||||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1000",
|
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1000",
|
||||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1200",
|
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1200",
|
||||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1400",
|
"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_m400": "400 rpm",
|
||||||
"laundry_care_washer_enum_type_spin_speed_r_p_m600": "600 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_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_m1000": "1000 rpm",
|
||||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1200": "1200 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",
|
"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_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_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_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_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_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%]",
|
"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",
|
"documentation": "https://www.home-assistant.io/integrations/hydrawise",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["pydrawise"],
|
"loggers": ["pydrawise"],
|
||||||
"requirements": ["pydrawise==2025.2.0"]
|
"requirements": ["pydrawise==2025.3.0"]
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import socket
|
|||||||
import ssl
|
import ssl
|
||||||
import time
|
import time
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
import certifi
|
import certifi
|
||||||
|
|
||||||
@ -292,7 +293,7 @@ class MqttClientSetup:
|
|||||||
"""
|
"""
|
||||||
# We don't import on the top because some integrations
|
# We don't import on the top because some integrations
|
||||||
# should be able to optionally rely on MQTT.
|
# 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
|
# pylint: disable-next=import-outside-toplevel
|
||||||
from .async_client import AsyncMQTTClient
|
from .async_client import AsyncMQTTClient
|
||||||
@ -309,9 +310,10 @@ class MqttClientSetup:
|
|||||||
clean_session = True
|
clean_session = True
|
||||||
|
|
||||||
if (client_id := config.get(CONF_CLIENT_ID)) is None:
|
if (client_id := config.get(CONF_CLIENT_ID)) is None:
|
||||||
# PAHO MQTT relies on the MQTT server to generate random client IDs.
|
# PAHO MQTT relies on the MQTT server to generate random client ID
|
||||||
# However, that feature is not mandatory so we generate our own.
|
# for protocol version 3.1, however, that feature is not mandatory
|
||||||
client_id = None
|
# so we generate our own.
|
||||||
|
client_id = mqtt._base62(uuid4().int, padding=22) # noqa: SLF001
|
||||||
transport: str = config.get(CONF_TRANSPORT, DEFAULT_TRANSPORT)
|
transport: str = config.get(CONF_TRANSPORT, DEFAULT_TRANSPORT)
|
||||||
self._client = AsyncMQTTClient(
|
self._client = AsyncMQTTClient(
|
||||||
callback_api_version=mqtt.CallbackAPIVersion.VERSION2,
|
callback_api_version=mqtt.CallbackAPIVersion.VERSION2,
|
||||||
|
@ -31,7 +31,6 @@ from homeassistant.components.light import (
|
|||||||
LightEntity,
|
LightEntity,
|
||||||
LightEntityFeature,
|
LightEntityFeature,
|
||||||
brightness_supported,
|
brightness_supported,
|
||||||
color_supported,
|
|
||||||
valid_supported_color_modes,
|
valid_supported_color_modes,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
@ -293,7 +292,7 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
|
|||||||
elif values["state"] is None:
|
elif values["state"] is None:
|
||||||
self._attr_is_on = 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)
|
self._update_color(values)
|
||||||
|
|
||||||
if brightness_supported(self.supported_color_modes):
|
if brightness_supported(self.supported_color_modes):
|
||||||
|
@ -11,7 +11,6 @@ import voluptuous as vol
|
|||||||
from homeassistant.components import sensor
|
from homeassistant.components import sensor
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
CONF_STATE_CLASS,
|
CONF_STATE_CLASS,
|
||||||
DEVICE_CLASS_UNITS,
|
|
||||||
DEVICE_CLASSES_SCHEMA,
|
DEVICE_CLASSES_SCHEMA,
|
||||||
ENTITY_ID_FORMAT,
|
ENTITY_ID_FORMAT,
|
||||||
STATE_CLASSES_SCHEMA,
|
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}'"
|
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
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
@ -276,22 +276,26 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
|
|||||||
self._attr_state = MediaPlayerState(player.state.value)
|
self._attr_state = MediaPlayerState(player.state.value)
|
||||||
else:
|
else:
|
||||||
self._attr_state = MediaPlayerState(STATE_OFF)
|
self._attr_state = MediaPlayerState(STATE_OFF)
|
||||||
group_members_entity_ids: list[str] = []
|
|
||||||
|
group_members: list[str] = []
|
||||||
if player.group_childs:
|
if player.group_childs:
|
||||||
|
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
|
# translate MA group_childs to HA group_members as entity id's
|
||||||
entity_registry = er.async_get(self.hass)
|
entity_registry = er.async_get(self.hass)
|
||||||
group_members_entity_ids = [
|
group_members_entity_ids: list[str] = [
|
||||||
entity_id
|
entity_id
|
||||||
for child_id in player.group_childs
|
for child_id in group_members
|
||||||
if (
|
if (
|
||||||
entity_id := entity_registry.async_get_entity_id(
|
entity_id := entity_registry.async_get_entity_id(
|
||||||
self.platform.domain, DOMAIN, child_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 = group_members_entity_ids
|
||||||
self._attr_group_members = sorted(group_members_entity_ids)
|
|
||||||
self._attr_volume_level = (
|
self._attr_volume_level = (
|
||||||
player.volume_level / 100 if player.volume_level is not None else None
|
player.volume_level / 100 if player.volume_level is not None else None
|
||||||
)
|
)
|
||||||
|
@ -58,6 +58,9 @@
|
|||||||
"switch": {
|
"switch": {
|
||||||
"hold": {
|
"hold": {
|
||||||
"name": "Hold"
|
"name": "Hold"
|
||||||
|
},
|
||||||
|
"emergency_heat": {
|
||||||
|
"name": "Emergency heat"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import contextlib
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@ -58,7 +59,7 @@ class OneWireHub:
|
|||||||
|
|
||||||
owproxy: protocol._Proxy
|
owproxy: protocol._Proxy
|
||||||
devices: list[OWDeviceDescription]
|
devices: list[OWDeviceDescription]
|
||||||
_version: str
|
_version: str | None = None
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, config_entry: OneWireConfigEntry) -> None:
|
def __init__(self, hass: HomeAssistant, config_entry: OneWireConfigEntry) -> None:
|
||||||
"""Initialize."""
|
"""Initialize."""
|
||||||
@ -74,6 +75,8 @@ class OneWireHub:
|
|||||||
port = self._config_entry.data[CONF_PORT]
|
port = self._config_entry.data[CONF_PORT]
|
||||||
_LOGGER.debug("Initializing connection to %s:%s", host, port)
|
_LOGGER.debug("Initializing connection to %s:%s", host, port)
|
||||||
self.owproxy = protocol.proxy(host, port)
|
self.owproxy = protocol.proxy(host, port)
|
||||||
|
with contextlib.suppress(protocol.OwnetError):
|
||||||
|
# Version is not available on all servers
|
||||||
self._version = self.owproxy.read(protocol.PTH_VERSION).decode()
|
self._version = self.owproxy.read(protocol.PTH_VERSION).decode()
|
||||||
self.devices = _discover_devices(self.owproxy)
|
self.devices = _discover_devices(self.owproxy)
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["roborock"],
|
"loggers": ["roborock"],
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"python-roborock==2.11.1",
|
"python-roborock==2.12.2",
|
||||||
"vacuum-map-parser-roborock==0.1.2"
|
"vacuum-map-parser-roborock==0.1.2"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -20,5 +20,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/sense",
|
"documentation": "https://www.home-assistant.io/integrations/sense",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["sense_energy"],
|
"loggers": ["sense_energy"],
|
||||||
"requirements": ["sense-energy==0.13.6"]
|
"requirements": ["sense-energy==0.13.7"]
|
||||||
}
|
}
|
||||||
|
@ -16,12 +16,18 @@ from pysmartthings import (
|
|||||||
Scene,
|
Scene,
|
||||||
SmartThings,
|
SmartThings,
|
||||||
SmartThingsAuthenticationFailedError,
|
SmartThingsAuthenticationFailedError,
|
||||||
|
SmartThingsSinkError,
|
||||||
Status,
|
Status,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN, Platform
|
from homeassistant.const import (
|
||||||
from homeassistant.core import HomeAssistant
|
CONF_ACCESS_TOKEN,
|
||||||
|
CONF_TOKEN,
|
||||||
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
|
Platform,
|
||||||
|
)
|
||||||
|
from homeassistant.core import Event, HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
@ -33,6 +39,7 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
|
|||||||
from .const import (
|
from .const import (
|
||||||
CONF_INSTALLED_APP_ID,
|
CONF_INSTALLED_APP_ID,
|
||||||
CONF_LOCATION_ID,
|
CONF_LOCATION_ID,
|
||||||
|
CONF_SUBSCRIPTION_ID,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
EVENT_BUTTON,
|
EVENT_BUTTON,
|
||||||
MAIN,
|
MAIN,
|
||||||
@ -99,6 +106,54 @@ async def async_setup_entry(hass: HomeAssistant, entry: SmartThingsConfigEntry)
|
|||||||
|
|
||||||
client.refresh_token_function = _refresh_token
|
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] = {}
|
device_status: dict[str, FullDevice] = {}
|
||||||
try:
|
try:
|
||||||
devices = await client.get_devices()
|
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)
|
client.add_unspecified_device_event_listener(handle_button_press)
|
||||||
)
|
)
|
||||||
|
|
||||||
entry.async_create_background_task(
|
async def _handle_shutdown(_: Event) -> None:
|
||||||
hass,
|
"""Handle shutdown."""
|
||||||
client.subscribe(
|
await client.delete_subscription(subscription_id)
|
||||||
entry.data[CONF_LOCATION_ID], entry.data[CONF_TOKEN][CONF_INSTALLED_APP_ID]
|
|
||||||
),
|
entry.async_on_unload(
|
||||||
"smartthings_webhook",
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _handle_shutdown)
|
||||||
)
|
)
|
||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
@ -176,6 +231,9 @@ async def async_unload_entry(
|
|||||||
hass: HomeAssistant, entry: SmartThingsConfigEntry
|
hass: HomeAssistant, entry: SmartThingsConfigEntry
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Unload a config entry."""
|
"""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)
|
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[
|
KEEP_CAPABILITY_QUIRK: dict[
|
||||||
Capability | str, Callable[[dict[Attribute | str, Status]], bool]
|
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: (
|
Capability.WASHER_OPERATING_STATE: (
|
||||||
lambda status: status[Attribute.SUPPORTED_MACHINE_STATES].value is not None
|
lambda status: status[Attribute.SUPPORTED_MACHINE_STATES].value is not None
|
||||||
),
|
),
|
||||||
Capability.DEMAND_RESPONSE_LOAD_CONTROL: lambda _: True,
|
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(
|
def process_status(
|
||||||
status: dict[str, dict[Capability | str, dict[Attribute | str, 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])
|
or not KEEP_CAPABILITY_QUIRK[capability](main_component[capability])
|
||||||
):
|
):
|
||||||
del 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
|
return status
|
||||||
|
@ -251,6 +251,8 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity):
|
|||||||
@property
|
@property
|
||||||
def hvac_action(self) -> HVACAction | None:
|
def hvac_action(self) -> HVACAction | None:
|
||||||
"""Return the current running hvac operation if supported."""
|
"""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(
|
return OPERATING_STATE_TO_ACTION.get(
|
||||||
self.get_attribute_value(
|
self.get_attribute_value(
|
||||||
Capability.THERMOSTAT_OPERATING_STATE,
|
Capability.THERMOSTAT_OPERATING_STATE,
|
||||||
@ -270,11 +272,15 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity):
|
|||||||
@property
|
@property
|
||||||
def hvac_modes(self) -> list[HVACMode]:
|
def hvac_modes(self) -> list[HVACMode]:
|
||||||
"""Return the list of available operation modes."""
|
"""Return the list of available operation modes."""
|
||||||
return [
|
if (
|
||||||
state
|
supported_thermostat_modes := self.get_attribute_value(
|
||||||
for mode in self.get_attribute_value(
|
|
||||||
Capability.THERMOSTAT_MODE, Attribute.SUPPORTED_THERMOSTAT_MODES
|
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
|
if (state := AC_MODE_TO_STATE.get(mode)) is not None
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -312,10 +318,14 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity):
|
|||||||
@property
|
@property
|
||||||
def temperature_unit(self) -> str:
|
def temperature_unit(self) -> str:
|
||||||
"""Return the unit of measurement."""
|
"""Return the unit of measurement."""
|
||||||
unit = self._internal_state[Capability.TEMPERATURE_MEASUREMENT][
|
# 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
|
Attribute.TEMPERATURE
|
||||||
].unit
|
].unit
|
||||||
assert unit
|
) is None:
|
||||||
|
return UnitOfTemperature.CELSIUS
|
||||||
return UNIT_MAP[unit]
|
return UNIT_MAP[unit]
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,4 +33,5 @@ CONF_REFRESH_TOKEN = "refresh_token"
|
|||||||
MAIN = "main"
|
MAIN = "main"
|
||||||
OLD_DATA = "old_data"
|
OLD_DATA = "old_data"
|
||||||
|
|
||||||
|
CONF_SUBSCRIPTION_ID = "subscription_id"
|
||||||
EVENT_BUTTON = "smartthings.button"
|
EVENT_BUTTON = "smartthings.button"
|
||||||
|
@ -147,9 +147,16 @@ class SmartThingsLight(SmartThingsEntity, LightEntity, RestoreEntity):
|
|||||||
"""Update entity attributes when the device status has changed."""
|
"""Update entity attributes when the device status has changed."""
|
||||||
# Brightness and transition
|
# Brightness and transition
|
||||||
if brightness_supported(self._attr_supported_color_modes):
|
if brightness_supported(self._attr_supported_color_modes):
|
||||||
|
if (
|
||||||
|
brightness := self.get_attribute_value(
|
||||||
|
Capability.SWITCH_LEVEL, Attribute.LEVEL
|
||||||
|
)
|
||||||
|
) is None:
|
||||||
|
self._attr_brightness = None
|
||||||
|
else:
|
||||||
self._attr_brightness = int(
|
self._attr_brightness = int(
|
||||||
convert_scale(
|
convert_scale(
|
||||||
self.get_attribute_value(Capability.SWITCH_LEVEL, Attribute.LEVEL),
|
brightness,
|
||||||
100,
|
100,
|
||||||
255,
|
255,
|
||||||
0,
|
0,
|
||||||
@ -162,9 +169,14 @@ class SmartThingsLight(SmartThingsEntity, LightEntity, RestoreEntity):
|
|||||||
)
|
)
|
||||||
# Color
|
# Color
|
||||||
if ColorMode.HS in self._attr_supported_color_modes:
|
if ColorMode.HS in self._attr_supported_color_modes:
|
||||||
|
if (
|
||||||
|
hue := self.get_attribute_value(Capability.COLOR_CONTROL, Attribute.HUE)
|
||||||
|
) is None:
|
||||||
|
self._attr_hs_color = None
|
||||||
|
else:
|
||||||
self._attr_hs_color = (
|
self._attr_hs_color = (
|
||||||
convert_scale(
|
convert_scale(
|
||||||
self.get_attribute_value(Capability.COLOR_CONTROL, Attribute.HUE),
|
hue,
|
||||||
100,
|
100,
|
||||||
360,
|
360,
|
||||||
),
|
),
|
||||||
@ -217,6 +229,10 @@ class SmartThingsLight(SmartThingsEntity, LightEntity, RestoreEntity):
|
|||||||
super()._update_handler(event)
|
super()._update_handler(event)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool | None:
|
||||||
"""Return true if light is on."""
|
"""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",
|
"documentation": "https://www.home-assistant.io/integrations/smartthings",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["pysmartthings"],
|
"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 collections.abc import Callable, Mapping
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
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 (
|
from homeassistant.components.sensor import (
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
@ -57,6 +57,7 @@ JOB_STATE_MAP = {
|
|||||||
"freezeProtection": "freeze_protection",
|
"freezeProtection": "freeze_protection",
|
||||||
"preDrain": "pre_drain",
|
"preDrain": "pre_drain",
|
||||||
"preWash": "pre_wash",
|
"preWash": "pre_wash",
|
||||||
|
"prewash": "pre_wash",
|
||||||
"wrinklePrevent": "wrinkle_prevent",
|
"wrinklePrevent": "wrinkle_prevent",
|
||||||
"unknown": None,
|
"unknown": None,
|
||||||
}
|
}
|
||||||
@ -130,6 +131,7 @@ class SmartThingsSensorEntityDescription(SensorEntityDescription):
|
|||||||
unique_id_separator: str = "."
|
unique_id_separator: str = "."
|
||||||
capability_ignore_list: list[set[Capability]] | None = None
|
capability_ignore_list: list[set[Capability]] | None = None
|
||||||
options_attribute: Attribute | None = None
|
options_attribute: Attribute | None = None
|
||||||
|
exists_fn: Callable[[Status], bool] | None = None
|
||||||
|
|
||||||
|
|
||||||
CAPABILITY_TO_SENSORS: dict[
|
CAPABILITY_TO_SENSORS: dict[
|
||||||
@ -560,6 +562,8 @@ CAPABILITY_TO_SENSORS: dict[
|
|||||||
SmartThingsSensorEntityDescription(
|
SmartThingsSensorEntityDescription(
|
||||||
key=Attribute.COMPLETION_TIME,
|
key=Attribute.COMPLETION_TIME,
|
||||||
translation_key="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,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
value_fn=lambda value: value["energy"] / 1000,
|
value_fn=lambda value: value["energy"] / 1000,
|
||||||
suggested_display_precision=2,
|
suggested_display_precision=2,
|
||||||
|
exists_fn=lambda status: (
|
||||||
|
(value := cast(dict | None, status.value)) is not None
|
||||||
|
and "energy" in value
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SmartThingsSensorEntityDescription(
|
SmartThingsSensorEntityDescription(
|
||||||
key="power_meter",
|
key="power_meter",
|
||||||
@ -589,6 +597,10 @@ CAPABILITY_TO_SENSORS: dict[
|
|||||||
value_fn=lambda value: value["power"],
|
value_fn=lambda value: value["power"],
|
||||||
extra_state_attributes_fn=power_attributes,
|
extra_state_attributes_fn=power_attributes,
|
||||||
suggested_display_precision=2,
|
suggested_display_precision=2,
|
||||||
|
exists_fn=lambda status: (
|
||||||
|
(value := cast(dict | None, status.value)) is not None
|
||||||
|
and "power" in value
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SmartThingsSensorEntityDescription(
|
SmartThingsSensorEntityDescription(
|
||||||
key="deltaEnergy_meter",
|
key="deltaEnergy_meter",
|
||||||
@ -598,6 +610,10 @@ CAPABILITY_TO_SENSORS: dict[
|
|||||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
value_fn=lambda value: value["deltaEnergy"] / 1000,
|
value_fn=lambda value: value["deltaEnergy"] / 1000,
|
||||||
suggested_display_precision=2,
|
suggested_display_precision=2,
|
||||||
|
exists_fn=lambda status: (
|
||||||
|
(value := cast(dict | None, status.value)) is not None
|
||||||
|
and "deltaEnergy" in value
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SmartThingsSensorEntityDescription(
|
SmartThingsSensorEntityDescription(
|
||||||
key="powerEnergy_meter",
|
key="powerEnergy_meter",
|
||||||
@ -607,6 +623,10 @@ CAPABILITY_TO_SENSORS: dict[
|
|||||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
value_fn=lambda value: value["powerEnergy"] / 1000,
|
value_fn=lambda value: value["powerEnergy"] / 1000,
|
||||||
suggested_display_precision=2,
|
suggested_display_precision=2,
|
||||||
|
exists_fn=lambda status: (
|
||||||
|
(value := cast(dict | None, status.value)) is not None
|
||||||
|
and "powerEnergy" in value
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SmartThingsSensorEntityDescription(
|
SmartThingsSensorEntityDescription(
|
||||||
key="energySaved_meter",
|
key="energySaved_meter",
|
||||||
@ -616,6 +636,10 @@ CAPABILITY_TO_SENSORS: dict[
|
|||||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
value_fn=lambda value: value["energySaved"] / 1000,
|
value_fn=lambda value: value["energySaved"] / 1000,
|
||||||
suggested_display_precision=2,
|
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
|
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:
|
def options(self) -> list[str] | None:
|
||||||
"""Return the options for this sensor."""
|
"""Return the options for this sensor."""
|
||||||
if self.entity_description.options_attribute:
|
if self.entity_description.options_attribute:
|
||||||
options = self.get_attribute_value(
|
if (
|
||||||
|
options := self.get_attribute_value(
|
||||||
self.capability, self.entity_description.options_attribute
|
self.capability, self.entity_description.options_attribute
|
||||||
)
|
)
|
||||||
|
) is None:
|
||||||
|
return []
|
||||||
return [option.lower() for option in options]
|
return [option.lower() for option in options]
|
||||||
return super().options
|
return super().options
|
||||||
|
@ -32,6 +32,7 @@ SONOS_TRACKS = "tracks"
|
|||||||
SONOS_COMPOSER = "composers"
|
SONOS_COMPOSER = "composers"
|
||||||
SONOS_RADIO = "radio"
|
SONOS_RADIO = "radio"
|
||||||
SONOS_OTHER_ITEM = "other items"
|
SONOS_OTHER_ITEM = "other items"
|
||||||
|
SONOS_AUDIO_BOOK = "audio book"
|
||||||
|
|
||||||
SONOS_STATE_PLAYING = "PLAYING"
|
SONOS_STATE_PLAYING = "PLAYING"
|
||||||
SONOS_STATE_TRANSITIONING = "TRANSITIONING"
|
SONOS_STATE_TRANSITIONING = "TRANSITIONING"
|
||||||
@ -67,6 +68,7 @@ SONOS_TO_MEDIA_CLASSES = {
|
|||||||
"object.item": MediaClass.TRACK,
|
"object.item": MediaClass.TRACK,
|
||||||
"object.item.audioItem.musicTrack": MediaClass.TRACK,
|
"object.item.audioItem.musicTrack": MediaClass.TRACK,
|
||||||
"object.item.audioItem.audioBroadcast": MediaClass.GENRE,
|
"object.item.audioItem.audioBroadcast": MediaClass.GENRE,
|
||||||
|
"object.item.audioItem.audioBook": MediaClass.TRACK,
|
||||||
}
|
}
|
||||||
|
|
||||||
SONOS_TO_MEDIA_TYPES = {
|
SONOS_TO_MEDIA_TYPES = {
|
||||||
@ -84,6 +86,7 @@ SONOS_TO_MEDIA_TYPES = {
|
|||||||
"object.container.playlistContainer.sameArtist": MediaType.ARTIST,
|
"object.container.playlistContainer.sameArtist": MediaType.ARTIST,
|
||||||
"object.container.playlistContainer": MediaType.PLAYLIST,
|
"object.container.playlistContainer": MediaType.PLAYLIST,
|
||||||
"object.item.audioItem.musicTrack": MediaType.TRACK,
|
"object.item.audioItem.musicTrack": MediaType.TRACK,
|
||||||
|
"object.item.audioItem.audioBook": MediaType.TRACK,
|
||||||
}
|
}
|
||||||
|
|
||||||
MEDIA_TYPES_TO_SONOS: dict[MediaType | str, str] = {
|
MEDIA_TYPES_TO_SONOS: dict[MediaType | str, str] = {
|
||||||
@ -113,6 +116,7 @@ SONOS_TYPES_MAPPING = {
|
|||||||
"object.item": SONOS_OTHER_ITEM,
|
"object.item": SONOS_OTHER_ITEM,
|
||||||
"object.item.audioItem.musicTrack": SONOS_TRACKS,
|
"object.item.audioItem.musicTrack": SONOS_TRACKS,
|
||||||
"object.item.audioItem.audioBroadcast": SONOS_RADIO,
|
"object.item.audioItem.audioBroadcast": SONOS_RADIO,
|
||||||
|
"object.item.audioItem.audioBook": SONOS_AUDIO_BOOK,
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBRARY_TITLES_MAPPING = {
|
LIBRARY_TITLES_MAPPING = {
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/teslemetry",
|
"documentation": "https://www.home-assistant.io/integrations/teslemetry",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["tesla-fleet-api"],
|
"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"
|
"velbus-protocol"
|
||||||
],
|
],
|
||||||
"quality_scale": "bronze",
|
"quality_scale": "bronze",
|
||||||
"requirements": ["velbus-aio==2025.1.1"],
|
"requirements": ["velbus-aio==2025.3.0"],
|
||||||
"usb": [
|
"usb": [
|
||||||
{
|
{
|
||||||
"vid": "10CF",
|
"vid": "10CF",
|
||||||
|
@ -13,7 +13,11 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
|
||||||
|
|
||||||
from .const import CONF_BACKUP_PATH, DATA_BACKUP_AGENT_LISTENERS, DOMAIN
|
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]
|
type WebDavConfigEntry = ConfigEntry[Client]
|
||||||
|
|
||||||
@ -46,10 +50,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: WebDavConfigEntry) -> bo
|
|||||||
translation_key="cannot_connect",
|
translation_key="cannot_connect",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
path = entry.data.get(CONF_BACKUP_PATH, "/")
|
||||||
|
await async_migrate_wrong_folder_path(client, path)
|
||||||
|
|
||||||
# Ensure the backup directory exists
|
# Ensure the backup directory exists
|
||||||
if not await async_ensure_path_exists(
|
if not await async_ensure_path_exists(client, path):
|
||||||
client, entry.data.get(CONF_BACKUP_PATH, "/")
|
|
||||||
):
|
|
||||||
raise ConfigEntryNotReady(
|
raise ConfigEntryNotReady(
|
||||||
translation_domain=DOMAIN,
|
translation_domain=DOMAIN,
|
||||||
translation_key="cannot_access_or_create_backup_path",
|
translation_key="cannot_access_or_create_backup_path",
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
"""Helper functions for the WebDAV component."""
|
"""Helper functions for the WebDAV component."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from aiowebdav2.client import Client, ClientOptions
|
from aiowebdav2.client import Client, ClientOptions
|
||||||
|
from aiowebdav2.exceptions import WebDavError
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_create_client(
|
def async_create_client(
|
||||||
@ -36,3 +44,24 @@ async def async_ensure_path_exists(client: Client, path: str) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
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",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aiowebdav2"],
|
"loggers": ["aiowebdav2"],
|
||||||
"quality_scale": "bronze",
|
"quality_scale": "bronze",
|
||||||
"requirements": ["aiowebdav2==0.4.1"]
|
"requirements": ["aiowebdav2==0.4.2"]
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,9 @@
|
|||||||
},
|
},
|
||||||
"cannot_access_or_create_backup_path": {
|
"cannot_access_or_create_backup_path": {
|
||||||
"message": "Cannot access or create backup path. Please check the path and permissions."
|
"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",
|
"zha",
|
||||||
"universal_silabs_flasher"
|
"universal_silabs_flasher"
|
||||||
],
|
],
|
||||||
"requirements": ["zha==0.0.51"],
|
"requirements": ["zha==0.0.52"],
|
||||||
"usb": [
|
"usb": [
|
||||||
{
|
{
|
||||||
"vid": "10C4",
|
"vid": "10C4",
|
||||||
|
@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2025
|
MAJOR_VERSION: Final = 2025
|
||||||
MINOR_VERSION: Final = 3
|
MINOR_VERSION: Final = 3
|
||||||
PATCH_VERSION: Final = "1"
|
PATCH_VERSION: Final = "2"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 13, 0)
|
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
|
home-assistant-intents==2025.3.5
|
||||||
httpx==0.28.1
|
httpx==0.28.1
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
Jinja2==3.1.5
|
Jinja2==3.1.6
|
||||||
lru-dict==1.3.0
|
lru-dict==1.3.0
|
||||||
mutagen==1.47.0
|
mutagen==1.47.0
|
||||||
orjson==3.10.12
|
orjson==3.10.12
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2025.3.1"
|
version = "2025.3.2"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
@ -52,7 +52,7 @@ dependencies = [
|
|||||||
"httpx==0.28.1",
|
"httpx==0.28.1",
|
||||||
"home-assistant-bluetooth==1.13.1",
|
"home-assistant-bluetooth==1.13.1",
|
||||||
"ifaddr==0.2.0",
|
"ifaddr==0.2.0",
|
||||||
"Jinja2==3.1.5",
|
"Jinja2==3.1.6",
|
||||||
"lru-dict==1.3.0",
|
"lru-dict==1.3.0",
|
||||||
"PyJWT==2.10.1",
|
"PyJWT==2.10.1",
|
||||||
# PyJWT has loose dependency. We want the latest one.
|
# 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
|
httpx==0.28.1
|
||||||
home-assistant-bluetooth==1.13.1
|
home-assistant-bluetooth==1.13.1
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
Jinja2==3.1.5
|
Jinja2==3.1.6
|
||||||
lru-dict==1.3.0
|
lru-dict==1.3.0
|
||||||
PyJWT==2.10.1
|
PyJWT==2.10.1
|
||||||
cryptography==44.0.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
|
aiowatttime==0.1.1
|
||||||
|
|
||||||
# homeassistant.components.webdav
|
# homeassistant.components.webdav
|
||||||
aiowebdav2==0.4.1
|
aiowebdav2==0.4.2
|
||||||
|
|
||||||
# homeassistant.components.webostv
|
# homeassistant.components.webostv
|
||||||
aiowebostv==0.7.3
|
aiowebostv==0.7.3
|
||||||
@ -557,7 +557,7 @@ av==13.1.0
|
|||||||
axis==64
|
axis==64
|
||||||
|
|
||||||
# homeassistant.components.fujitsu_fglair
|
# homeassistant.components.fujitsu_fglair
|
||||||
ayla-iot-unofficial==1.4.5
|
ayla-iot-unofficial==1.4.7
|
||||||
|
|
||||||
# homeassistant.components.azure_event_hub
|
# homeassistant.components.azure_event_hub
|
||||||
azure-eventhub==5.11.1
|
azure-eventhub==5.11.1
|
||||||
@ -899,7 +899,7 @@ eufylife-ble-client==0.1.8
|
|||||||
# evdev==1.6.1
|
# evdev==1.6.1
|
||||||
|
|
||||||
# homeassistant.components.evohome
|
# homeassistant.components.evohome
|
||||||
evohome-async==1.0.2
|
evohome-async==1.0.4
|
||||||
|
|
||||||
# homeassistant.components.bryant_evolution
|
# homeassistant.components.bryant_evolution
|
||||||
evolutionhttp==0.0.18
|
evolutionhttp==0.0.18
|
||||||
@ -1058,7 +1058,7 @@ goslide-api==0.7.0
|
|||||||
gotailwind==0.3.0
|
gotailwind==0.3.0
|
||||||
|
|
||||||
# homeassistant.components.govee_ble
|
# homeassistant.components.govee_ble
|
||||||
govee-ble==0.43.0
|
govee-ble==0.43.1
|
||||||
|
|
||||||
# homeassistant.components.govee_light_local
|
# homeassistant.components.govee_light_local
|
||||||
govee-local-api==2.0.1
|
govee-local-api==2.0.1
|
||||||
@ -1906,7 +1906,7 @@ pydiscovergy==3.0.2
|
|||||||
pydoods==1.0.2
|
pydoods==1.0.2
|
||||||
|
|
||||||
# homeassistant.components.hydrawise
|
# homeassistant.components.hydrawise
|
||||||
pydrawise==2025.2.0
|
pydrawise==2025.3.0
|
||||||
|
|
||||||
# homeassistant.components.android_ip_webcam
|
# homeassistant.components.android_ip_webcam
|
||||||
pydroid-ipcam==2.0.0
|
pydroid-ipcam==2.0.0
|
||||||
@ -1996,7 +1996,7 @@ pygti==0.9.4
|
|||||||
pyhaversion==22.8.0
|
pyhaversion==22.8.0
|
||||||
|
|
||||||
# homeassistant.components.heos
|
# homeassistant.components.heos
|
||||||
pyheos==1.0.2
|
pyheos==1.0.3
|
||||||
|
|
||||||
# homeassistant.components.hive
|
# homeassistant.components.hive
|
||||||
pyhive-integration==1.0.2
|
pyhive-integration==1.0.2
|
||||||
@ -2310,7 +2310,7 @@ pysma==0.7.5
|
|||||||
pysmappee==0.2.29
|
pysmappee==0.2.29
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartthings==2.7.0
|
pysmartthings==2.7.2
|
||||||
|
|
||||||
# homeassistant.components.smarty
|
# homeassistant.components.smarty
|
||||||
pysmarty2==0.10.2
|
pysmarty2==0.10.2
|
||||||
@ -2461,7 +2461,7 @@ python-rabbitair==0.0.8
|
|||||||
python-ripple-api==0.0.3
|
python-ripple-api==0.0.3
|
||||||
|
|
||||||
# homeassistant.components.roborock
|
# homeassistant.components.roborock
|
||||||
python-roborock==2.11.1
|
python-roborock==2.12.2
|
||||||
|
|
||||||
# homeassistant.components.smarttub
|
# homeassistant.components.smarttub
|
||||||
python-smarttub==0.0.39
|
python-smarttub==0.0.39
|
||||||
@ -2694,7 +2694,7 @@ sendgrid==6.8.2
|
|||||||
|
|
||||||
# homeassistant.components.emulated_kasa
|
# homeassistant.components.emulated_kasa
|
||||||
# homeassistant.components.sense
|
# homeassistant.components.sense
|
||||||
sense-energy==0.13.6
|
sense-energy==0.13.7
|
||||||
|
|
||||||
# homeassistant.components.sensirion_ble
|
# homeassistant.components.sensirion_ble
|
||||||
sensirion-ble==0.1.1
|
sensirion-ble==0.1.1
|
||||||
@ -2881,7 +2881,7 @@ tesla-powerwall==0.5.2
|
|||||||
tesla-wall-connector==1.0.2
|
tesla-wall-connector==1.0.2
|
||||||
|
|
||||||
# homeassistant.components.teslemetry
|
# homeassistant.components.teslemetry
|
||||||
teslemetry-stream==0.6.10
|
teslemetry-stream==0.6.12
|
||||||
|
|
||||||
# homeassistant.components.tessie
|
# homeassistant.components.tessie
|
||||||
tessie-api==0.1.1
|
tessie-api==0.1.1
|
||||||
@ -3000,7 +3000,7 @@ vallox-websocket-api==5.3.0
|
|||||||
vehicle==2.2.2
|
vehicle==2.2.2
|
||||||
|
|
||||||
# homeassistant.components.velbus
|
# homeassistant.components.velbus
|
||||||
velbus-aio==2025.1.1
|
velbus-aio==2025.3.0
|
||||||
|
|
||||||
# homeassistant.components.venstar
|
# homeassistant.components.venstar
|
||||||
venstarcolortouch==0.19
|
venstarcolortouch==0.19
|
||||||
@ -3149,7 +3149,7 @@ zeroconf==0.145.1
|
|||||||
zeversolar==0.3.2
|
zeversolar==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zha==0.0.51
|
zha==0.0.52
|
||||||
|
|
||||||
# homeassistant.components.zhong_hong
|
# homeassistant.components.zhong_hong
|
||||||
zhong-hong-hvac==1.0.13
|
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
|
aiowatttime==0.1.1
|
||||||
|
|
||||||
# homeassistant.components.webdav
|
# homeassistant.components.webdav
|
||||||
aiowebdav2==0.4.1
|
aiowebdav2==0.4.2
|
||||||
|
|
||||||
# homeassistant.components.webostv
|
# homeassistant.components.webostv
|
||||||
aiowebostv==0.7.3
|
aiowebostv==0.7.3
|
||||||
@ -506,7 +506,7 @@ av==13.1.0
|
|||||||
axis==64
|
axis==64
|
||||||
|
|
||||||
# homeassistant.components.fujitsu_fglair
|
# homeassistant.components.fujitsu_fglair
|
||||||
ayla-iot-unofficial==1.4.5
|
ayla-iot-unofficial==1.4.7
|
||||||
|
|
||||||
# homeassistant.components.azure_event_hub
|
# homeassistant.components.azure_event_hub
|
||||||
azure-eventhub==5.11.1
|
azure-eventhub==5.11.1
|
||||||
@ -765,7 +765,7 @@ eternalegypt==0.0.16
|
|||||||
eufylife-ble-client==0.1.8
|
eufylife-ble-client==0.1.8
|
||||||
|
|
||||||
# homeassistant.components.evohome
|
# homeassistant.components.evohome
|
||||||
evohome-async==1.0.2
|
evohome-async==1.0.4
|
||||||
|
|
||||||
# homeassistant.components.bryant_evolution
|
# homeassistant.components.bryant_evolution
|
||||||
evolutionhttp==0.0.18
|
evolutionhttp==0.0.18
|
||||||
@ -908,7 +908,7 @@ goslide-api==0.7.0
|
|||||||
gotailwind==0.3.0
|
gotailwind==0.3.0
|
||||||
|
|
||||||
# homeassistant.components.govee_ble
|
# homeassistant.components.govee_ble
|
||||||
govee-ble==0.43.0
|
govee-ble==0.43.1
|
||||||
|
|
||||||
# homeassistant.components.govee_light_local
|
# homeassistant.components.govee_light_local
|
||||||
govee-local-api==2.0.1
|
govee-local-api==2.0.1
|
||||||
@ -1556,7 +1556,7 @@ pydexcom==0.2.3
|
|||||||
pydiscovergy==3.0.2
|
pydiscovergy==3.0.2
|
||||||
|
|
||||||
# homeassistant.components.hydrawise
|
# homeassistant.components.hydrawise
|
||||||
pydrawise==2025.2.0
|
pydrawise==2025.3.0
|
||||||
|
|
||||||
# homeassistant.components.android_ip_webcam
|
# homeassistant.components.android_ip_webcam
|
||||||
pydroid-ipcam==2.0.0
|
pydroid-ipcam==2.0.0
|
||||||
@ -1625,7 +1625,7 @@ pygti==0.9.4
|
|||||||
pyhaversion==22.8.0
|
pyhaversion==22.8.0
|
||||||
|
|
||||||
# homeassistant.components.heos
|
# homeassistant.components.heos
|
||||||
pyheos==1.0.2
|
pyheos==1.0.3
|
||||||
|
|
||||||
# homeassistant.components.hive
|
# homeassistant.components.hive
|
||||||
pyhive-integration==1.0.2
|
pyhive-integration==1.0.2
|
||||||
@ -1882,7 +1882,7 @@ pysma==0.7.5
|
|||||||
pysmappee==0.2.29
|
pysmappee==0.2.29
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartthings==2.7.0
|
pysmartthings==2.7.2
|
||||||
|
|
||||||
# homeassistant.components.smarty
|
# homeassistant.components.smarty
|
||||||
pysmarty2==0.10.2
|
pysmarty2==0.10.2
|
||||||
@ -1994,7 +1994,7 @@ python-picnic-api2==1.2.2
|
|||||||
python-rabbitair==0.0.8
|
python-rabbitair==0.0.8
|
||||||
|
|
||||||
# homeassistant.components.roborock
|
# homeassistant.components.roborock
|
||||||
python-roborock==2.11.1
|
python-roborock==2.12.2
|
||||||
|
|
||||||
# homeassistant.components.smarttub
|
# homeassistant.components.smarttub
|
||||||
python-smarttub==0.0.39
|
python-smarttub==0.0.39
|
||||||
@ -2173,7 +2173,7 @@ securetar==2025.2.1
|
|||||||
|
|
||||||
# homeassistant.components.emulated_kasa
|
# homeassistant.components.emulated_kasa
|
||||||
# homeassistant.components.sense
|
# homeassistant.components.sense
|
||||||
sense-energy==0.13.6
|
sense-energy==0.13.7
|
||||||
|
|
||||||
# homeassistant.components.sensirion_ble
|
# homeassistant.components.sensirion_ble
|
||||||
sensirion-ble==0.1.1
|
sensirion-ble==0.1.1
|
||||||
@ -2321,7 +2321,7 @@ tesla-powerwall==0.5.2
|
|||||||
tesla-wall-connector==1.0.2
|
tesla-wall-connector==1.0.2
|
||||||
|
|
||||||
# homeassistant.components.teslemetry
|
# homeassistant.components.teslemetry
|
||||||
teslemetry-stream==0.6.10
|
teslemetry-stream==0.6.12
|
||||||
|
|
||||||
# homeassistant.components.tessie
|
# homeassistant.components.tessie
|
||||||
tessie-api==0.1.1
|
tessie-api==0.1.1
|
||||||
@ -2416,7 +2416,7 @@ vallox-websocket-api==5.3.0
|
|||||||
vehicle==2.2.2
|
vehicle==2.2.2
|
||||||
|
|
||||||
# homeassistant.components.velbus
|
# homeassistant.components.velbus
|
||||||
velbus-aio==2025.1.1
|
velbus-aio==2025.3.0
|
||||||
|
|
||||||
# homeassistant.components.venstar
|
# homeassistant.components.venstar
|
||||||
venstarcolortouch==0.19
|
venstarcolortouch==0.19
|
||||||
@ -2538,7 +2538,7 @@ zeroconf==0.145.1
|
|||||||
zeversolar==0.3.2
|
zeversolar==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zha==0.0.51
|
zha==0.0.52
|
||||||
|
|
||||||
# homeassistant.components.zwave_js
|
# homeassistant.components.zwave_js
|
||||||
zwave-js-server-python==0.60.1
|
zwave-js-server-python==0.60.1
|
||||||
|
@ -47,7 +47,8 @@ from homeassistant.components.backup.manager import (
|
|||||||
WrittenBackup,
|
WrittenBackup,
|
||||||
)
|
)
|
||||||
from homeassistant.components.backup.util import password_to_key
|
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.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import issue_registry as ir
|
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!"
|
"Unexpected error deleting backup restore result file: <class 'OSError'> Boom!"
|
||||||
in caplog.text
|
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",
|
"country": "UnitedKingdom",
|
||||||
"postcode": "E1 1AA",
|
"postcode": "E1 1AA",
|
||||||
"locationType": "Residential",
|
"locationType": "Residential",
|
||||||
"useDaylightSaveSwitching": true,
|
|
||||||
"timeZone": {
|
"timeZone": {
|
||||||
"timeZoneId": "GMTStandardTime",
|
"timeZoneId": "PacificSAStandardTime",
|
||||||
"displayName": "(UTC+00:00) Dublin, Edinburgh, Lisbon, London",
|
"displayName": "(UTC-04:00) Santiago",
|
||||||
"offsetMinutes": 0,
|
"offsetMinutes": -240,
|
||||||
"currentOffsetMinutes": 60,
|
"currentOffsetMinutes": -180,
|
||||||
"supportsDaylightSaving": true
|
"supportsDaylightSaving": true
|
||||||
},
|
},
|
||||||
|
"useDaylightSaveSwitching": true,
|
||||||
"locationOwner": {
|
"locationOwner": {
|
||||||
"userId": "2263181",
|
"userId": "2263181",
|
||||||
"username": "user_2263181@gmail.com",
|
"username": "user_2263181@gmail.com",
|
||||||
|
@ -168,10 +168,10 @@
|
|||||||
'target_heat_temperature': 16.0,
|
'target_heat_temperature': 16.0,
|
||||||
}),
|
}),
|
||||||
'setpoints': dict({
|
'setpoints': dict({
|
||||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||||
'next_sp_temp': 18.6,
|
'next_sp_temp': 16.0,
|
||||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||||
'this_sp_temp': 16.0,
|
'this_sp_temp': 18.1,
|
||||||
}),
|
}),
|
||||||
'temperature_status': dict({
|
'temperature_status': dict({
|
||||||
'is_available': True,
|
'is_available': True,
|
||||||
@ -215,10 +215,10 @@
|
|||||||
'target_heat_temperature': 17.0,
|
'target_heat_temperature': 17.0,
|
||||||
}),
|
}),
|
||||||
'setpoints': dict({
|
'setpoints': dict({
|
||||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||||
'next_sp_temp': 18.6,
|
'next_sp_temp': 16.0,
|
||||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||||
'this_sp_temp': 16.0,
|
'this_sp_temp': 18.1,
|
||||||
}),
|
}),
|
||||||
'temperature_status': dict({
|
'temperature_status': dict({
|
||||||
'is_available': False,
|
'is_available': False,
|
||||||
@ -257,19 +257,19 @@
|
|||||||
'activeFaults': tuple(
|
'activeFaults': tuple(
|
||||||
dict({
|
dict({
|
||||||
'fault_type': 'TempZoneActuatorLowBattery',
|
'fault_type': 'TempZoneActuatorLowBattery',
|
||||||
'since': '2022-03-02T04:50:20+00:00',
|
'since': '2022-03-02T04:50:20-03:00',
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
'setpoint_status': dict({
|
'setpoint_status': dict({
|
||||||
'setpoint_mode': 'TemporaryOverride',
|
'setpoint_mode': 'TemporaryOverride',
|
||||||
'target_heat_temperature': 21.0,
|
'target_heat_temperature': 21.0,
|
||||||
'until': '2022-03-07T19:00:00+00:00',
|
'until': '2022-03-07T16:00:00-03:00',
|
||||||
}),
|
}),
|
||||||
'setpoints': dict({
|
'setpoints': dict({
|
||||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||||
'next_sp_temp': 18.6,
|
'next_sp_temp': 16.0,
|
||||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||||
'this_sp_temp': 16.0,
|
'this_sp_temp': 18.1,
|
||||||
}),
|
}),
|
||||||
'temperature_status': dict({
|
'temperature_status': dict({
|
||||||
'is_available': True,
|
'is_available': True,
|
||||||
@ -313,10 +313,10 @@
|
|||||||
'target_heat_temperature': 17.0,
|
'target_heat_temperature': 17.0,
|
||||||
}),
|
}),
|
||||||
'setpoints': dict({
|
'setpoints': dict({
|
||||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||||
'next_sp_temp': 18.6,
|
'next_sp_temp': 16.0,
|
||||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||||
'this_sp_temp': 16.0,
|
'this_sp_temp': 18.1,
|
||||||
}),
|
}),
|
||||||
'temperature_status': dict({
|
'temperature_status': dict({
|
||||||
'is_available': True,
|
'is_available': True,
|
||||||
@ -360,10 +360,10 @@
|
|||||||
'target_heat_temperature': 17.0,
|
'target_heat_temperature': 17.0,
|
||||||
}),
|
}),
|
||||||
'setpoints': dict({
|
'setpoints': dict({
|
||||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||||
'next_sp_temp': 18.6,
|
'next_sp_temp': 16.0,
|
||||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||||
'this_sp_temp': 16.0,
|
'this_sp_temp': 18.1,
|
||||||
}),
|
}),
|
||||||
'temperature_status': dict({
|
'temperature_status': dict({
|
||||||
'is_available': True,
|
'is_available': True,
|
||||||
@ -407,10 +407,10 @@
|
|||||||
'target_heat_temperature': 16.0,
|
'target_heat_temperature': 16.0,
|
||||||
}),
|
}),
|
||||||
'setpoints': dict({
|
'setpoints': dict({
|
||||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||||
'next_sp_temp': 18.6,
|
'next_sp_temp': 16.0,
|
||||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||||
'this_sp_temp': 16.0,
|
'this_sp_temp': 18.1,
|
||||||
}),
|
}),
|
||||||
'temperature_status': dict({
|
'temperature_status': dict({
|
||||||
'is_available': True,
|
'is_available': True,
|
||||||
@ -450,7 +450,7 @@
|
|||||||
'activeFaults': tuple(
|
'activeFaults': tuple(
|
||||||
dict({
|
dict({
|
||||||
'fault_type': 'TempZoneActuatorCommunicationLost',
|
'fault_type': 'TempZoneActuatorCommunicationLost',
|
||||||
'since': '2022-03-02T15:56:01+00:00',
|
'since': '2022-03-02T15:56:01-03:00',
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
'setpoint_status': dict({
|
'setpoint_status': dict({
|
||||||
@ -458,10 +458,10 @@
|
|||||||
'target_heat_temperature': 17.0,
|
'target_heat_temperature': 17.0,
|
||||||
}),
|
}),
|
||||||
'setpoints': dict({
|
'setpoints': dict({
|
||||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||||
'next_sp_temp': 18.6,
|
'next_sp_temp': 16.0,
|
||||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||||
'this_sp_temp': 16.0,
|
'this_sp_temp': 18.1,
|
||||||
}),
|
}),
|
||||||
'temperature_status': dict({
|
'temperature_status': dict({
|
||||||
'is_available': True,
|
'is_available': True,
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
# name: test_set_operation_mode[botched]
|
# name: test_set_operation_mode[botched]
|
||||||
list([
|
list([
|
||||||
dict({
|
dict({
|
||||||
'until': HAFakeDatetime(2024, 7, 10, 12, 0, tzinfo=datetime.timezone.utc),
|
'until': HAFakeDatetime(2024, 7, 10, 12, 30, tzinfo=datetime.timezone.utc),
|
||||||
}),
|
}),
|
||||||
dict({
|
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',
|
'dhw_id': '3933910',
|
||||||
'setpoints': dict({
|
'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',
|
'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',
|
'this_sp_state': 'On',
|
||||||
}),
|
}),
|
||||||
'state_status': dict({
|
'state_status': dict({
|
||||||
|
@ -11,7 +11,7 @@ import pytest
|
|||||||
|
|
||||||
from homeassistant.auth.models import RefreshToken
|
from homeassistant.auth.models import RefreshToken
|
||||||
from homeassistant.components.hassio.handler import HassIO, HassioAPIError
|
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.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
@ -75,7 +75,6 @@ def hassio_stubs(
|
|||||||
"homeassistant.components.hassio.issues.SupervisorIssues.setup",
|
"homeassistant.components.hassio.issues.SupervisorIssues.setup",
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
hass.set_state(CoreState.starting)
|
|
||||||
hass.loop.run_until_complete(async_setup_component(hass, "hassio", {}))
|
hass.loop.run_until_complete(async_setup_component(hass, "hassio", {}))
|
||||||
|
|
||||||
return hass_api.call_args[0][1]
|
return hass_api.call_args[0][1]
|
||||||
|
@ -106,6 +106,7 @@
|
|||||||
'model': 'HEOS Drive HS2',
|
'model': 'HEOS Drive HS2',
|
||||||
'name': 'Test Player',
|
'name': 'Test Player',
|
||||||
'network': 'wired',
|
'network': 'wired',
|
||||||
|
'preferred_host': True,
|
||||||
'serial': '**REDACTED**',
|
'serial': '**REDACTED**',
|
||||||
'supported_version': True,
|
'supported_version': True,
|
||||||
'version': '1.0.0',
|
'version': '1.0.0',
|
||||||
@ -116,6 +117,7 @@
|
|||||||
'model': 'HEOS Drive HS2',
|
'model': 'HEOS Drive HS2',
|
||||||
'name': 'Test Player',
|
'name': 'Test Player',
|
||||||
'network': 'wired',
|
'network': 'wired',
|
||||||
|
'preferred_host': True,
|
||||||
'serial': '**REDACTED**',
|
'serial': '**REDACTED**',
|
||||||
'supported_version': True,
|
'supported_version': True,
|
||||||
'version': '1.0.0',
|
'version': '1.0.0',
|
||||||
@ -125,6 +127,7 @@
|
|||||||
'model': 'Speaker',
|
'model': 'Speaker',
|
||||||
'name': 'Test Player 2',
|
'name': 'Test Player 2',
|
||||||
'network': 'wifi',
|
'network': 'wifi',
|
||||||
|
'preferred_host': False,
|
||||||
'serial': '**REDACTED**',
|
'serial': '**REDACTED**',
|
||||||
'supported_version': True,
|
'supported_version': True,
|
||||||
'version': '1.0.0',
|
'version': '1.0.0',
|
||||||
@ -137,6 +140,7 @@
|
|||||||
'model': 'HEOS Drive HS2',
|
'model': 'HEOS Drive HS2',
|
||||||
'name': 'Test Player',
|
'name': 'Test Player',
|
||||||
'network': 'wired',
|
'network': 'wired',
|
||||||
|
'preferred_host': True,
|
||||||
'serial': '**REDACTED**',
|
'serial': '**REDACTED**',
|
||||||
'supported_version': True,
|
'supported_version': True,
|
||||||
'version': '1.0.0',
|
'version': '1.0.0',
|
||||||
|
@ -14,7 +14,12 @@ from pyheos import (
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.heos.const import DOMAIN
|
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.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.data_entry_flow import FlowResultType
|
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
|
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(
|
async def test_discovery(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
discovery_data: SsdpServiceInfo,
|
discovery_data: SsdpServiceInfo,
|
||||||
@ -160,6 +194,22 @@ async def test_discovery_aborts_same_system(
|
|||||||
assert config_entry.data[CONF_HOST] == "127.0.0.1"
|
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(
|
async def test_discovery_fails_to_connect_aborts(
|
||||||
hass: HomeAssistant, discovery_data: SsdpServiceInfo, controller: MockHeos
|
hass: HomeAssistant, discovery_data: SsdpServiceInfo, controller: MockHeos
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -285,11 +285,11 @@ async def test_reconnected_new_entities_created(
|
|||||||
players = controller.players.copy()
|
players = controller.players.copy()
|
||||||
players[3] = player_factory(3, "Test Player 3", "HEOS Link")
|
players[3] = player_factory(3, "Test Player 3", "HEOS Link")
|
||||||
controller.mock_set_players(players)
|
controller.mock_set_players(players)
|
||||||
controller.load_players.return_value = PlayerUpdateResult([3], [], {})
|
update = PlayerUpdateResult([3], [], {})
|
||||||
|
|
||||||
# Simulate reconnection
|
# Simulate reconnection
|
||||||
await controller.dispatcher.wait_send(
|
await controller.dispatcher.wait_send(
|
||||||
SignalType.HEOS_EVENT, SignalHeosEvent.CONNECTED
|
SignalType.CONTROLLER_EVENT, const.EVENT_PLAYERS_CHANGED, update
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
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")
|
state = hass.states.get("media_player.test_player")
|
||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.state == STATE_IDLE
|
assert state.state == STATE_IDLE
|
||||||
assert controller.load_players.call_count == 1
|
|
||||||
|
|
||||||
# Disconnected
|
# Disconnected
|
||||||
controller.load_players.reset_mock()
|
controller.load_players.reset_mock()
|
||||||
@ -170,11 +169,8 @@ async def test_updates_from_connection_event(
|
|||||||
state = hass.states.get("media_player.test_player")
|
state = hass.states.get("media_player.test_player")
|
||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.state == STATE_UNAVAILABLE
|
assert state.state == STATE_UNAVAILABLE
|
||||||
assert controller.load_players.call_count == 0
|
|
||||||
|
|
||||||
# Connected handles refresh failure
|
# Reconnect and state updates
|
||||||
controller.load_players.reset_mock()
|
|
||||||
controller.load_players.side_effect = CommandFailedError("", "Failure", 1)
|
|
||||||
player.available = True
|
player.available = True
|
||||||
await controller.dispatcher.wait_send(
|
await controller.dispatcher.wait_send(
|
||||||
SignalType.HEOS_EVENT, SignalHeosEvent.CONNECTED
|
SignalType.HEOS_EVENT, SignalHeosEvent.CONNECTED
|
||||||
@ -183,38 +179,6 @@ async def test_updates_from_connection_event(
|
|||||||
state = hass.states.get("media_player.test_player")
|
state = hass.states.get("media_player.test_player")
|
||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.state == STATE_IDLE
|
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(
|
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.const import OAUTH2_TOKEN
|
||||||
from aiohomeconnect.model import OptionKey, ProgramKey, SettingKey, StatusKey
|
from aiohomeconnect.model import OptionKey, ProgramKey, SettingKey, StatusKey
|
||||||
from aiohomeconnect.model.error import HomeConnectError, UnauthorizedError
|
from aiohomeconnect.model.error import HomeConnectError, UnauthorizedError
|
||||||
|
import aiohttp
|
||||||
import pytest
|
import pytest
|
||||||
import requests_mock
|
|
||||||
import respx
|
|
||||||
from syrupy.assertion import SnapshotAssertion
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
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])
|
@pytest.mark.parametrize("token_expiration_time", [12345])
|
||||||
@respx.mock
|
|
||||||
async def test_token_refresh_success(
|
async def test_token_refresh_success(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
platforms: list[Platform],
|
platforms: list[Platform],
|
||||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||||
config_entry: MockConfigEntry,
|
config_entry: MockConfigEntry,
|
||||||
aioclient_mock: AiohttpClientMocker,
|
aioclient_mock: AiohttpClientMocker,
|
||||||
requests_mock: requests_mock.Mocker,
|
|
||||||
setup_credentials: None,
|
setup_credentials: None,
|
||||||
client: MagicMock,
|
client: MagicMock,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -236,7 +233,6 @@ async def test_token_refresh_success(
|
|||||||
|
|
||||||
assert config_entry.data["token"]["access_token"] == FAKE_ACCESS_TOKEN
|
assert config_entry.data["token"]["access_token"] == FAKE_ACCESS_TOKEN
|
||||||
|
|
||||||
requests_mock.post(OAUTH2_TOKEN, json=SERVER_ACCESS_TOKEN)
|
|
||||||
aioclient_mock.post(
|
aioclient_mock.post(
|
||||||
OAUTH2_TOKEN,
|
OAUTH2_TOKEN,
|
||||||
json=SERVER_ACCESS_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(
|
@pytest.mark.parametrize(
|
||||||
("exception", "expected_state"),
|
("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
|
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(
|
@pytest.mark.parametrize(
|
||||||
"mqtt_config_entry_data",
|
"mqtt_config_entry_data",
|
||||||
[
|
[
|
||||||
|
@ -432,6 +432,65 @@ async def test_brightness_only(
|
|||||||
assert state.state == STATE_OFF
|
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(
|
@pytest.mark.parametrize(
|
||||||
"hass_config",
|
"hass_config",
|
||||||
[
|
[
|
||||||
|
@ -870,32 +870,6 @@ async def test_invalid_device_class(
|
|||||||
assert "expected SensorDeviceClass or one of" in caplog.text
|
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(
|
@pytest.mark.parametrize(
|
||||||
"hass_config",
|
"hass_config",
|
||||||
[
|
[
|
||||||
|
@ -109,8 +109,8 @@
|
|||||||
'entity_picture_local': None,
|
'entity_picture_local': None,
|
||||||
'friendly_name': 'Test Group Player 1',
|
'friendly_name': 'Test Group Player 1',
|
||||||
'group_members': list([
|
'group_members': list([
|
||||||
'media_player.my_super_test_player_2',
|
|
||||||
'media_player.test_player_1',
|
'media_player.test_player_1',
|
||||||
|
'media_player.my_super_test_player_2',
|
||||||
]),
|
]),
|
||||||
'icon': 'mdi:speaker-multiple',
|
'icon': 'mdi:speaker-multiple',
|
||||||
'is_volume_muted': False,
|
'is_volume_muted': False,
|
||||||
|
@ -9,6 +9,7 @@ from pysmartthings.models import (
|
|||||||
DeviceStatus,
|
DeviceStatus,
|
||||||
LocationResponse,
|
LocationResponse,
|
||||||
SceneResponse,
|
SceneResponse,
|
||||||
|
Subscription,
|
||||||
)
|
)
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -78,6 +79,9 @@ def mock_smartthings() -> Generator[AsyncMock]:
|
|||||||
client.get_locations.return_value = LocationResponse.from_json(
|
client.get_locations.return_value = LocationResponse.from_json(
|
||||||
load_fixture("locations.json", DOMAIN)
|
load_fixture("locations.json", DOMAIN)
|
||||||
).items
|
).items
|
||||||
|
client.create_subscription.return_value = Subscription.from_json(
|
||||||
|
load_fixture("subscription.json", DOMAIN)
|
||||||
|
)
|
||||||
yield client
|
yield client
|
||||||
|
|
||||||
|
|
||||||
@ -100,6 +104,7 @@ def mock_smartthings() -> Generator[AsyncMock]:
|
|||||||
"iphone",
|
"iphone",
|
||||||
"da_wm_dw_000001",
|
"da_wm_dw_000001",
|
||||||
"da_wm_wd_000001",
|
"da_wm_wd_000001",
|
||||||
|
"da_wm_wd_000001_1",
|
||||||
"da_wm_wm_000001",
|
"da_wm_wm_000001",
|
||||||
"da_wm_wm_000001_1",
|
"da_wm_wm_000001_1",
|
||||||
"da_rvc_normal_000001",
|
"da_rvc_normal_000001",
|
||||||
@ -115,9 +120,15 @@ def mock_smartthings() -> Generator[AsyncMock]:
|
|||||||
"sensibo_airconditioner_1",
|
"sensibo_airconditioner_1",
|
||||||
"ecobee_sensor",
|
"ecobee_sensor",
|
||||||
"ecobee_thermostat",
|
"ecobee_thermostat",
|
||||||
|
"ecobee_thermostat_offline",
|
||||||
"fake_fan",
|
"fake_fan",
|
||||||
"generic_fan_3_speed",
|
"generic_fan_3_speed",
|
||||||
"heatit_ztrm3_thermostat",
|
"heatit_ztrm3_thermostat",
|
||||||
|
"generic_ef00_v1",
|
||||||
|
"bosch_radiator_thermostat_ii",
|
||||||
|
"im_speaker_ai_0001",
|
||||||
|
"abl_light_b_001",
|
||||||
|
"tplink_p110",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
def device_fixture(
|
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
|
# 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]
|
# name: test_all_entities[da_ac_rac_000001][climate.ac_office_granit-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
@ -369,6 +432,131 @@
|
|||||||
'state': 'heat',
|
'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]
|
# name: test_all_entities[heatit_ztrm3_thermostat][climate.hall_thermostat-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
@ -2,6 +2,39 @@
|
|||||||
# name: test_button_event[button]
|
# 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>
|
<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]
|
# name: test_devices[aeotec_home_energy_meter_gen5]
|
||||||
DeviceRegistryEntrySnapshot({
|
DeviceRegistryEntrySnapshot({
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
@ -68,6 +101,39 @@
|
|||||||
'via_device_id': None,
|
'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]
|
# name: test_devices[c2c_arlo_pro_3_switch]
|
||||||
DeviceRegistryEntrySnapshot({
|
DeviceRegistryEntrySnapshot({
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
@ -464,6 +530,39 @@
|
|||||||
'via_device_id': None,
|
'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]
|
# name: test_devices[da_wm_wm_000001]
|
||||||
DeviceRegistryEntrySnapshot({
|
DeviceRegistryEntrySnapshot({
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
@ -596,6 +695,39 @@
|
|||||||
'via_device_id': None,
|
'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]
|
# name: test_devices[fake_fan]
|
||||||
DeviceRegistryEntrySnapshot({
|
DeviceRegistryEntrySnapshot({
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
@ -662,6 +794,39 @@
|
|||||||
'via_device_id': None,
|
'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]
|
# name: test_devices[generic_fan_3_speed]
|
||||||
DeviceRegistryEntrySnapshot({
|
DeviceRegistryEntrySnapshot({
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
@ -794,6 +959,39 @@
|
|||||||
'via_device_id': None,
|
'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]
|
# name: test_devices[iphone]
|
||||||
DeviceRegistryEntrySnapshot({
|
DeviceRegistryEntrySnapshot({
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
@ -959,6 +1157,39 @@
|
|||||||
'via_device_id': None,
|
'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]
|
# name: test_devices[vd_network_audio_002s]
|
||||||
DeviceRegistryEntrySnapshot({
|
DeviceRegistryEntrySnapshot({
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
|
@ -1,4 +1,74 @@
|
|||||||
# serializer version: 1
|
# 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]
|
# name: test_all_entities[centralite][light.dimmer_debian-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -234,6 +234,53 @@
|
|||||||
'state': 'off',
|
'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]
|
# name: test_all_entities[da_wm_wm_000001][switch.washer-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
@ -328,6 +375,53 @@
|
|||||||
'state': 'on',
|
'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]
|
# name: test_all_entities[sensibo_airconditioner_1][switch.office-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
@ -422,6 +516,53 @@
|
|||||||
'state': 'on',
|
'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]
|
# name: test_all_entities[vd_network_audio_002s][switch.soundbar_living-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
@ -10,6 +10,7 @@ from homeassistant.components.smartthings.const import (
|
|||||||
CONF_INSTALLED_APP_ID,
|
CONF_INSTALLED_APP_ID,
|
||||||
CONF_LOCATION_ID,
|
CONF_LOCATION_ID,
|
||||||
CONF_REFRESH_TOKEN,
|
CONF_REFRESH_TOKEN,
|
||||||
|
CONF_SUBSCRIPTION_ID,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import SOURCE_USER, ConfigEntryState
|
from homeassistant.config_entries import SOURCE_USER, ConfigEntryState
|
||||||
@ -508,6 +509,7 @@ async def test_migration(
|
|||||||
"installed_app_id": "123123123-2be1-4e40-b257-e4ef59083324",
|
"installed_app_id": "123123123-2be1-4e40-b257-e4ef59083324",
|
||||||
},
|
},
|
||||||
CONF_LOCATION_ID: "397678e5-9995-4a39-9d9f-ae6ba310236c",
|
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.unique_id == "397678e5-9995-4a39-9d9f-ae6ba310236c"
|
||||||
assert mock_old_config_entry.version == 3
|
assert mock_old_config_entry.version == 3
|
||||||
|
@ -2,18 +2,21 @@
|
|||||||
|
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
from pysmartthings import Attribute, Capability
|
from pysmartthings import Attribute, Capability, SmartThingsSinkError
|
||||||
|
from pysmartthings.models import Subscription
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.components.smartthings import EVENT_BUTTON
|
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.core import Event, HomeAssistant
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
|
||||||
from . import setup_integration, trigger_update
|
from . import setup_integration, trigger_update
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry, load_fixture
|
||||||
|
|
||||||
|
|
||||||
async def test_devices(
|
async def test_devices(
|
||||||
@ -63,6 +66,178 @@ async def test_button_event(
|
|||||||
assert events[0] == snapshot
|
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"])
|
@pytest.mark.parametrize("device_fixture", ["da_ac_rac_000001"])
|
||||||
async def test_removing_stale_devices(
|
async def test_removing_stale_devices(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -34,5 +34,23 @@
|
|||||||
"protocol_info": "a:b:c:d"
|
"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
|
# 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
|
# name: test_browse_media_library
|
||||||
list([
|
list([
|
||||||
dict({
|
dict({
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.components.media_player import BrowseMedia, MediaClass, MediaType
|
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["success"]
|
||||||
assert response["result"]["children"] == snapshot
|
assert response["result"]["children"] == snapshot
|
||||||
assert soco_mock.music_library.browse_by_idstring.call_count == 1
|
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."""
|
"""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.download_iter.side_effect = _download_mock
|
||||||
mock.upload_iter.return_value = None
|
mock.upload_iter.return_value = None
|
||||||
mock.clean.return_value = None
|
mock.clean.return_value = None
|
||||||
|
mock.move.return_value = None
|
||||||
yield mock
|
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