mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
2025.3.4 (#141081)
This commit is contained in:
commit
2f244b2b66
@ -101,9 +101,11 @@ def hostname_from_url(url: str) -> str:
|
|||||||
|
|
||||||
def _host_validator(config: dict[str, str]) -> dict[str, str]:
|
def _host_validator(config: dict[str, str]) -> dict[str, str]:
|
||||||
"""Validate that a host is properly configured."""
|
"""Validate that a host is properly configured."""
|
||||||
if config[CONF_HOST].startswith("elks://"):
|
if config[CONF_HOST].startswith(("elks://", "elksv1_2://")):
|
||||||
if CONF_USERNAME not in config or CONF_PASSWORD not in config:
|
if CONF_USERNAME not in config or CONF_PASSWORD not in config:
|
||||||
raise vol.Invalid("Specify username and password for elks://")
|
raise vol.Invalid(
|
||||||
|
"Specify username and password for elks:// or elksv1_2://"
|
||||||
|
)
|
||||||
elif not config[CONF_HOST].startswith("elk://") and not config[
|
elif not config[CONF_HOST].startswith("elk://") and not config[
|
||||||
CONF_HOST
|
CONF_HOST
|
||||||
].startswith("serial://"):
|
].startswith("serial://"):
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import mimetypes
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from google import genai # type: ignore[attr-defined]
|
from google import genai # type: ignore[attr-defined]
|
||||||
@ -83,7 +84,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
)
|
)
|
||||||
if not Path(filename).exists():
|
if not Path(filename).exists():
|
||||||
raise HomeAssistantError(f"`{filename}` does not exist")
|
raise HomeAssistantError(f"`{filename}` does not exist")
|
||||||
prompt_parts.append(client.files.upload(file=filename))
|
mimetype = mimetypes.guess_type(filename)[0]
|
||||||
|
with open(filename, "rb") as file:
|
||||||
|
uploaded_file = client.files.upload(
|
||||||
|
file=file, config={"mime_type": mimetype}
|
||||||
|
)
|
||||||
|
prompt_parts.append(uploaded_file)
|
||||||
|
|
||||||
await hass.async_add_executor_job(append_files_to_prompt)
|
await hass.async_add_executor_job(append_files_to_prompt)
|
||||||
|
|
||||||
|
@ -629,14 +629,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: HomeConnectConfigEntry)
|
|||||||
home_connect_client = HomeConnectClient(config_entry_auth)
|
home_connect_client = HomeConnectClient(config_entry_auth)
|
||||||
|
|
||||||
coordinator = HomeConnectCoordinator(hass, entry, home_connect_client)
|
coordinator = HomeConnectCoordinator(hass, entry, home_connect_client)
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_setup()
|
||||||
|
|
||||||
entry.runtime_data = coordinator
|
entry.runtime_data = coordinator
|
||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
entry.runtime_data.start_event_listener()
|
entry.runtime_data.start_event_listener()
|
||||||
|
|
||||||
|
entry.async_create_background_task(
|
||||||
|
hass,
|
||||||
|
coordinator.async_refresh(),
|
||||||
|
f"home_connect-initial-full-refresh-{entry.entry_id}",
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -137,41 +137,6 @@ def setup_home_connect_entry(
|
|||||||
defaultdict(list)
|
defaultdict(list)
|
||||||
)
|
)
|
||||||
|
|
||||||
entities: list[HomeConnectEntity] = []
|
|
||||||
for appliance in entry.runtime_data.data.values():
|
|
||||||
entities_to_add = get_entities_for_appliance(entry, appliance)
|
|
||||||
if get_option_entities_for_appliance:
|
|
||||||
entities_to_add.extend(get_option_entities_for_appliance(entry, appliance))
|
|
||||||
for event_key in (
|
|
||||||
EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM,
|
|
||||||
EventKey.BSH_COMMON_ROOT_SELECTED_PROGRAM,
|
|
||||||
):
|
|
||||||
changed_options_listener_remove_callback = (
|
|
||||||
entry.runtime_data.async_add_listener(
|
|
||||||
partial(
|
|
||||||
_create_option_entities,
|
|
||||||
entry,
|
|
||||||
appliance,
|
|
||||||
known_entity_unique_ids,
|
|
||||||
get_option_entities_for_appliance,
|
|
||||||
async_add_entities,
|
|
||||||
),
|
|
||||||
(appliance.info.ha_id, event_key),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
entry.async_on_unload(changed_options_listener_remove_callback)
|
|
||||||
changed_options_listener_remove_callbacks[appliance.info.ha_id].append(
|
|
||||||
changed_options_listener_remove_callback
|
|
||||||
)
|
|
||||||
known_entity_unique_ids.update(
|
|
||||||
{
|
|
||||||
cast(str, entity.unique_id): appliance.info.ha_id
|
|
||||||
for entity in entities_to_add
|
|
||||||
}
|
|
||||||
)
|
|
||||||
entities.extend(entities_to_add)
|
|
||||||
async_add_entities(entities)
|
|
||||||
|
|
||||||
entry.async_on_unload(
|
entry.async_on_unload(
|
||||||
entry.runtime_data.async_add_special_listener(
|
entry.runtime_data.async_add_special_listener(
|
||||||
partial(
|
partial(
|
||||||
|
@ -10,6 +10,7 @@ from .utils import bsh_key_to_translation_key
|
|||||||
|
|
||||||
DOMAIN = "home_connect"
|
DOMAIN = "home_connect"
|
||||||
|
|
||||||
|
API_DEFAULT_RETRY_AFTER = 60
|
||||||
|
|
||||||
APPLIANCES_WITH_PROGRAMS = (
|
APPLIANCES_WITH_PROGRAMS = (
|
||||||
"CleaningRobot",
|
"CleaningRobot",
|
||||||
@ -284,6 +285,7 @@ SPIN_SPEED_OPTIONS = {
|
|||||||
"LaundryCare.Washer.EnumType.SpinSpeed.Off",
|
"LaundryCare.Washer.EnumType.SpinSpeed.Off",
|
||||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM400",
|
"LaundryCare.Washer.EnumType.SpinSpeed.RPM400",
|
||||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM600",
|
"LaundryCare.Washer.EnumType.SpinSpeed.RPM600",
|
||||||
|
"LaundryCare.Washer.EnumType.SpinSpeed.RPM700",
|
||||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM800",
|
"LaundryCare.Washer.EnumType.SpinSpeed.RPM800",
|
||||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM900",
|
"LaundryCare.Washer.EnumType.SpinSpeed.RPM900",
|
||||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1000",
|
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1000",
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
from asyncio import sleep as asyncio_sleep
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
@ -29,6 +29,7 @@ from aiohomeconnect.model.error import (
|
|||||||
HomeConnectApiError,
|
HomeConnectApiError,
|
||||||
HomeConnectError,
|
HomeConnectError,
|
||||||
HomeConnectRequestError,
|
HomeConnectRequestError,
|
||||||
|
TooManyRequestsError,
|
||||||
UnauthorizedError,
|
UnauthorizedError,
|
||||||
)
|
)
|
||||||
from aiohomeconnect.model.program import EnumerateProgram, ProgramDefinitionOption
|
from aiohomeconnect.model.program import EnumerateProgram, ProgramDefinitionOption
|
||||||
@ -36,11 +37,11 @@ from propcache.api import cached_property
|
|||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
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.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import APPLIANCES_WITH_PROGRAMS, DOMAIN
|
from .const import API_DEFAULT_RETRY_AFTER, APPLIANCES_WITH_PROGRAMS, DOMAIN
|
||||||
from .utils import get_dict_from_home_connect_error
|
from .utils import get_dict_from_home_connect_error
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -154,7 +155,7 @@ class HomeConnectCoordinator(
|
|||||||
f"home_connect-events_listener_task-{self.config_entry.entry_id}",
|
f"home_connect-events_listener_task-{self.config_entry.entry_id}",
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _event_listener(self) -> None:
|
async def _event_listener(self) -> None: # noqa: C901
|
||||||
"""Match event with listener for event type."""
|
"""Match event with listener for event type."""
|
||||||
retry_time = 10
|
retry_time = 10
|
||||||
while True:
|
while True:
|
||||||
@ -269,7 +270,7 @@ class HomeConnectCoordinator(
|
|||||||
type(error).__name__,
|
type(error).__name__,
|
||||||
retry_time,
|
retry_time,
|
||||||
)
|
)
|
||||||
await asyncio.sleep(retry_time)
|
await asyncio_sleep(retry_time)
|
||||||
retry_time = min(retry_time * 2, 3600)
|
retry_time = min(retry_time * 2, 3600)
|
||||||
except HomeConnectApiError as error:
|
except HomeConnectApiError as error:
|
||||||
_LOGGER.error("Error while listening for events: %s", error)
|
_LOGGER.error("Error while listening for events: %s", error)
|
||||||
@ -278,6 +279,13 @@ class HomeConnectCoordinator(
|
|||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Trigger to delete the possible depaired device entities
|
||||||
|
# from known_entities variable at common.py
|
||||||
|
for listener, context in self._special_listeners.values():
|
||||||
|
assert isinstance(context, tuple)
|
||||||
|
if EventKey.BSH_COMMON_APPLIANCE_DEPAIRED in context:
|
||||||
|
listener()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _call_event_listener(self, event_message: EventMessage) -> None:
|
def _call_event_listener(self, event_message: EventMessage) -> None:
|
||||||
"""Call listener for event."""
|
"""Call listener for event."""
|
||||||
@ -295,6 +303,42 @@ class HomeConnectCoordinator(
|
|||||||
|
|
||||||
async def _async_update_data(self) -> dict[str, HomeConnectApplianceData]:
|
async def _async_update_data(self) -> dict[str, HomeConnectApplianceData]:
|
||||||
"""Fetch data from Home Connect."""
|
"""Fetch data from Home Connect."""
|
||||||
|
await self._async_setup()
|
||||||
|
|
||||||
|
for appliance_data in self.data.values():
|
||||||
|
appliance = appliance_data.info
|
||||||
|
ha_id = appliance.ha_id
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
self.data[ha_id] = await self._get_appliance_data(
|
||||||
|
appliance, self.data.get(ha_id)
|
||||||
|
)
|
||||||
|
except TooManyRequestsError as err:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Rate limit exceeded on initial fetch: %s",
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
await asyncio_sleep(err.retry_after or API_DEFAULT_RETRY_AFTER)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
for listener, context in self._special_listeners.values():
|
||||||
|
assert isinstance(context, tuple)
|
||||||
|
if EventKey.BSH_COMMON_APPLIANCE_PAIRED in context:
|
||||||
|
listener()
|
||||||
|
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
async def async_setup(self) -> None:
|
||||||
|
"""Set up the devices."""
|
||||||
|
try:
|
||||||
|
await self._async_setup()
|
||||||
|
except UpdateFailed as err:
|
||||||
|
raise ConfigEntryNotReady from err
|
||||||
|
|
||||||
|
async def _async_setup(self) -> None:
|
||||||
|
"""Set up the devices."""
|
||||||
|
old_appliances = set(self.data.keys())
|
||||||
try:
|
try:
|
||||||
appliances = await self.client.get_home_appliances()
|
appliances = await self.client.get_home_appliances()
|
||||||
except UnauthorizedError as error:
|
except UnauthorizedError as error:
|
||||||
@ -312,12 +356,38 @@ class HomeConnectCoordinator(
|
|||||||
translation_placeholders=get_dict_from_home_connect_error(error),
|
translation_placeholders=get_dict_from_home_connect_error(error),
|
||||||
) from error
|
) from error
|
||||||
|
|
||||||
return {
|
for appliance in appliances.homeappliances:
|
||||||
appliance.ha_id: await self._get_appliance_data(
|
self.device_registry.async_get_or_create(
|
||||||
appliance, self.data.get(appliance.ha_id)
|
config_entry_id=self.config_entry.entry_id,
|
||||||
|
identifiers={(DOMAIN, appliance.ha_id)},
|
||||||
|
manufacturer=appliance.brand,
|
||||||
|
name=appliance.name,
|
||||||
|
model=appliance.vib,
|
||||||
)
|
)
|
||||||
for appliance in appliances.homeappliances
|
if appliance.ha_id not in self.data:
|
||||||
}
|
self.data[appliance.ha_id] = HomeConnectApplianceData(
|
||||||
|
commands=set(),
|
||||||
|
events={},
|
||||||
|
info=appliance,
|
||||||
|
options={},
|
||||||
|
programs=[],
|
||||||
|
settings={},
|
||||||
|
status={},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.data[appliance.ha_id].info.connected = appliance.connected
|
||||||
|
old_appliances.remove(appliance.ha_id)
|
||||||
|
|
||||||
|
for ha_id in old_appliances:
|
||||||
|
self.data.pop(ha_id, None)
|
||||||
|
device = self.device_registry.async_get_device(
|
||||||
|
identifiers={(DOMAIN, ha_id)}
|
||||||
|
)
|
||||||
|
if device:
|
||||||
|
self.device_registry.async_update_device(
|
||||||
|
device_id=device.id,
|
||||||
|
remove_config_entry_id=self.config_entry.entry_id,
|
||||||
|
)
|
||||||
|
|
||||||
async def _get_appliance_data(
|
async def _get_appliance_data(
|
||||||
self,
|
self,
|
||||||
@ -339,6 +409,8 @@ class HomeConnectCoordinator(
|
|||||||
await self.client.get_settings(appliance.ha_id)
|
await self.client.get_settings(appliance.ha_id)
|
||||||
).settings
|
).settings
|
||||||
}
|
}
|
||||||
|
except TooManyRequestsError:
|
||||||
|
raise
|
||||||
except HomeConnectError as error:
|
except HomeConnectError as error:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Error fetching settings for %s: %s",
|
"Error fetching settings for %s: %s",
|
||||||
@ -353,6 +425,8 @@ class HomeConnectCoordinator(
|
|||||||
status.key: status
|
status.key: status
|
||||||
for status in (await self.client.get_status(appliance.ha_id)).status
|
for status in (await self.client.get_status(appliance.ha_id)).status
|
||||||
}
|
}
|
||||||
|
except TooManyRequestsError:
|
||||||
|
raise
|
||||||
except HomeConnectError as error:
|
except HomeConnectError as error:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Error fetching status for %s: %s",
|
"Error fetching status for %s: %s",
|
||||||
@ -369,6 +443,8 @@ class HomeConnectCoordinator(
|
|||||||
if appliance.type in APPLIANCES_WITH_PROGRAMS:
|
if appliance.type in APPLIANCES_WITH_PROGRAMS:
|
||||||
try:
|
try:
|
||||||
all_programs = await self.client.get_all_programs(appliance.ha_id)
|
all_programs = await self.client.get_all_programs(appliance.ha_id)
|
||||||
|
except TooManyRequestsError:
|
||||||
|
raise
|
||||||
except HomeConnectError as error:
|
except HomeConnectError as error:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Error fetching programs for %s: %s",
|
"Error fetching programs for %s: %s",
|
||||||
@ -427,6 +503,8 @@ class HomeConnectCoordinator(
|
|||||||
await self.client.get_available_commands(appliance.ha_id)
|
await self.client.get_available_commands(appliance.ha_id)
|
||||||
).commands
|
).commands
|
||||||
}
|
}
|
||||||
|
except TooManyRequestsError:
|
||||||
|
raise
|
||||||
except HomeConnectError:
|
except HomeConnectError:
|
||||||
commands = set()
|
commands = set()
|
||||||
|
|
||||||
@ -461,6 +539,8 @@ class HomeConnectCoordinator(
|
|||||||
).options
|
).options
|
||||||
or []
|
or []
|
||||||
}
|
}
|
||||||
|
except TooManyRequestsError:
|
||||||
|
raise
|
||||||
except HomeConnectError as error:
|
except HomeConnectError as error:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Error fetching options for %s: %s",
|
"Error fetching options for %s: %s",
|
||||||
|
@ -1,21 +1,28 @@
|
|||||||
"""Home Connect entity base class."""
|
"""Home Connect entity base class."""
|
||||||
|
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
from collections.abc import Callable, Coroutine
|
||||||
import contextlib
|
import contextlib
|
||||||
|
from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
from typing import cast
|
from typing import Any, Concatenate, cast
|
||||||
|
|
||||||
from aiohomeconnect.model import EventKey, OptionKey
|
from aiohomeconnect.model import EventKey, OptionKey
|
||||||
from aiohomeconnect.model.error import ActiveProgramNotSetError, HomeConnectError
|
from aiohomeconnect.model.error import (
|
||||||
|
ActiveProgramNotSetError,
|
||||||
|
HomeConnectError,
|
||||||
|
TooManyRequestsError,
|
||||||
|
)
|
||||||
|
|
||||||
from homeassistant.const import STATE_UNAVAILABLE
|
from homeassistant.const import STATE_UNAVAILABLE
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity import EntityDescription
|
from homeassistant.helpers.entity import EntityDescription
|
||||||
|
from homeassistant.helpers.event import async_call_later
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import API_DEFAULT_RETRY_AFTER, DOMAIN
|
||||||
from .coordinator import HomeConnectApplianceData, HomeConnectCoordinator
|
from .coordinator import HomeConnectApplianceData, HomeConnectCoordinator
|
||||||
from .utils import get_dict_from_home_connect_error
|
from .utils import get_dict_from_home_connect_error
|
||||||
|
|
||||||
@ -127,3 +134,34 @@ class HomeConnectOptionEntity(HomeConnectEntity):
|
|||||||
def bsh_key(self) -> OptionKey:
|
def bsh_key(self) -> OptionKey:
|
||||||
"""Return the BSH key."""
|
"""Return the BSH key."""
|
||||||
return cast(OptionKey, self.entity_description.key)
|
return cast(OptionKey, self.entity_description.key)
|
||||||
|
|
||||||
|
|
||||||
|
def constraint_fetcher[_EntityT: HomeConnectEntity, **_P](
|
||||||
|
func: Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, Any]],
|
||||||
|
) -> Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, None]]:
|
||||||
|
"""Decorate the function to catch Home Connect too many requests error and retry later.
|
||||||
|
|
||||||
|
If it needs to be called later, it will call async_write_ha_state function
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def handler_to_return(
|
||||||
|
self: _EntityT, *args: _P.args, **kwargs: _P.kwargs
|
||||||
|
) -> None:
|
||||||
|
async def handler(_datetime: datetime | None = None) -> None:
|
||||||
|
try:
|
||||||
|
await func(self, *args, **kwargs)
|
||||||
|
except TooManyRequestsError as err:
|
||||||
|
if (retry_after := err.retry_after) is None:
|
||||||
|
retry_after = API_DEFAULT_RETRY_AFTER
|
||||||
|
async_call_later(self.hass, retry_after, handler)
|
||||||
|
except HomeConnectError as err:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Error fetching constraints for %s: %s", self.entity_id, err
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if _datetime is not None:
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
await handler()
|
||||||
|
|
||||||
|
return handler_to_return
|
||||||
|
@ -25,7 +25,7 @@ from .const import (
|
|||||||
UNIT_MAP,
|
UNIT_MAP,
|
||||||
)
|
)
|
||||||
from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
|
from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
|
||||||
from .entity import HomeConnectEntity, HomeConnectOptionEntity
|
from .entity import HomeConnectEntity, HomeConnectOptionEntity, constraint_fetcher
|
||||||
from .utils import get_dict_from_home_connect_error
|
from .utils import get_dict_from_home_connect_error
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -189,19 +189,25 @@ class HomeConnectNumberEntity(HomeConnectEntity, NumberEntity):
|
|||||||
},
|
},
|
||||||
) from err
|
) from err
|
||||||
|
|
||||||
|
@constraint_fetcher
|
||||||
async def async_fetch_constraints(self) -> None:
|
async def async_fetch_constraints(self) -> None:
|
||||||
"""Fetch the max and min values and step for the number entity."""
|
"""Fetch the max and min values and step for the number entity."""
|
||||||
try:
|
setting_key = cast(SettingKey, self.bsh_key)
|
||||||
|
data = self.appliance.settings.get(setting_key)
|
||||||
|
if not data or not data.unit or not data.constraints:
|
||||||
data = await self.coordinator.client.get_setting(
|
data = await self.coordinator.client.get_setting(
|
||||||
self.appliance.info.ha_id, setting_key=SettingKey(self.bsh_key)
|
self.appliance.info.ha_id, setting_key=setting_key
|
||||||
)
|
)
|
||||||
except HomeConnectError as err:
|
if data.unit:
|
||||||
_LOGGER.error("An error occurred: %s", err)
|
self._attr_native_unit_of_measurement = data.unit
|
||||||
else:
|
|
||||||
self.set_constraints(data)
|
self.set_constraints(data)
|
||||||
|
|
||||||
def set_constraints(self, setting: GetSetting) -> None:
|
def set_constraints(self, setting: GetSetting) -> None:
|
||||||
"""Set constraints for the number entity."""
|
"""Set constraints for the number entity."""
|
||||||
|
if setting.unit:
|
||||||
|
self._attr_native_unit_of_measurement = UNIT_MAP.get(
|
||||||
|
setting.unit, setting.unit
|
||||||
|
)
|
||||||
if not (constraints := setting.constraints):
|
if not (constraints := setting.constraints):
|
||||||
return
|
return
|
||||||
if constraints.max:
|
if constraints.max:
|
||||||
@ -222,10 +228,10 @@ class HomeConnectNumberEntity(HomeConnectEntity, NumberEntity):
|
|||||||
"""When entity is added to hass."""
|
"""When entity is added to hass."""
|
||||||
await super().async_added_to_hass()
|
await super().async_added_to_hass()
|
||||||
data = self.appliance.settings[cast(SettingKey, self.bsh_key)]
|
data = self.appliance.settings[cast(SettingKey, self.bsh_key)]
|
||||||
self._attr_native_unit_of_measurement = data.unit
|
|
||||||
self.set_constraints(data)
|
self.set_constraints(data)
|
||||||
if (
|
if (
|
||||||
not hasattr(self, "_attr_native_min_value")
|
not hasattr(self, "_attr_native_unit_of_measurement")
|
||||||
|
or not hasattr(self, "_attr_native_min_value")
|
||||||
or not hasattr(self, "_attr_native_max_value")
|
or not hasattr(self, "_attr_native_max_value")
|
||||||
or not hasattr(self, "_attr_native_step")
|
or not hasattr(self, "_attr_native_step")
|
||||||
):
|
):
|
||||||
@ -253,7 +259,6 @@ class HomeConnectOptionNumberEntity(HomeConnectOptionEntity, NumberEntity):
|
|||||||
or candidate_unit != self._attr_native_unit_of_measurement
|
or candidate_unit != self._attr_native_unit_of_measurement
|
||||||
):
|
):
|
||||||
self._attr_native_unit_of_measurement = candidate_unit
|
self._attr_native_unit_of_measurement = candidate_unit
|
||||||
self.__dict__.pop("unit_of_measurement", None)
|
|
||||||
option_constraints = option_definition.constraints
|
option_constraints = option_definition.constraints
|
||||||
if option_constraints:
|
if option_constraints:
|
||||||
if (
|
if (
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"""Provides a select platform for Home Connect."""
|
"""Provides a select platform for Home Connect."""
|
||||||
|
|
||||||
from collections.abc import Callable, Coroutine
|
from collections.abc import Callable, Coroutine
|
||||||
import contextlib
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
import logging
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from aiohomeconnect.client import Client as HomeConnectClient
|
from aiohomeconnect.client import Client as HomeConnectClient
|
||||||
@ -47,9 +47,11 @@ from .coordinator import (
|
|||||||
HomeConnectConfigEntry,
|
HomeConnectConfigEntry,
|
||||||
HomeConnectCoordinator,
|
HomeConnectCoordinator,
|
||||||
)
|
)
|
||||||
from .entity import HomeConnectEntity, HomeConnectOptionEntity
|
from .entity import HomeConnectEntity, HomeConnectOptionEntity, constraint_fetcher
|
||||||
from .utils import bsh_key_to_translation_key, get_dict_from_home_connect_error
|
from .utils import bsh_key_to_translation_key, get_dict_from_home_connect_error
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
PARALLEL_UPDATES = 1
|
PARALLEL_UPDATES = 1
|
||||||
|
|
||||||
FUNCTIONAL_LIGHT_COLOR_TEMPERATURE_ENUM = {
|
FUNCTIONAL_LIGHT_COLOR_TEMPERATURE_ENUM = {
|
||||||
@ -413,6 +415,7 @@ class HomeConnectSelectEntity(HomeConnectEntity, SelectEntity):
|
|||||||
"""Select setting class for Home Connect."""
|
"""Select setting class for Home Connect."""
|
||||||
|
|
||||||
entity_description: HomeConnectSelectEntityDescription
|
entity_description: HomeConnectSelectEntityDescription
|
||||||
|
_original_option_keys: set[str | None]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -421,6 +424,7 @@ class HomeConnectSelectEntity(HomeConnectEntity, SelectEntity):
|
|||||||
desc: HomeConnectSelectEntityDescription,
|
desc: HomeConnectSelectEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the entity."""
|
"""Initialize the entity."""
|
||||||
|
self._original_option_keys = set(desc.values_translation_key)
|
||||||
super().__init__(
|
super().__init__(
|
||||||
coordinator,
|
coordinator,
|
||||||
appliance,
|
appliance,
|
||||||
@ -458,23 +462,29 @@ class HomeConnectSelectEntity(HomeConnectEntity, SelectEntity):
|
|||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""When entity is added to hass."""
|
"""When entity is added to hass."""
|
||||||
await super().async_added_to_hass()
|
await super().async_added_to_hass()
|
||||||
|
await self.async_fetch_options()
|
||||||
|
|
||||||
|
@constraint_fetcher
|
||||||
|
async def async_fetch_options(self) -> None:
|
||||||
|
"""Fetch options from the API."""
|
||||||
setting = self.appliance.settings.get(cast(SettingKey, self.bsh_key))
|
setting = self.appliance.settings.get(cast(SettingKey, self.bsh_key))
|
||||||
if (
|
if (
|
||||||
not setting
|
not setting
|
||||||
or not setting.constraints
|
or not setting.constraints
|
||||||
or not setting.constraints.allowed_values
|
or not setting.constraints.allowed_values
|
||||||
):
|
):
|
||||||
with contextlib.suppress(HomeConnectError):
|
setting = await self.coordinator.client.get_setting(
|
||||||
setting = await self.coordinator.client.get_setting(
|
self.appliance.info.ha_id,
|
||||||
self.appliance.info.ha_id,
|
setting_key=cast(SettingKey, self.bsh_key),
|
||||||
setting_key=cast(SettingKey, self.bsh_key),
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if setting and setting.constraints and setting.constraints.allowed_values:
|
if setting and setting.constraints and setting.constraints.allowed_values:
|
||||||
|
self._original_option_keys = set(setting.constraints.allowed_values)
|
||||||
self._attr_options = [
|
self._attr_options = [
|
||||||
self.entity_description.values_translation_key[option]
|
self.entity_description.values_translation_key[option]
|
||||||
for option in setting.constraints.allowed_values
|
for option in self._original_option_keys
|
||||||
if option in self.entity_description.values_translation_key
|
if option is not None
|
||||||
|
and option in self.entity_description.values_translation_key
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -491,7 +501,7 @@ class HomeConnectSelectOptionEntity(HomeConnectOptionEntity, SelectEntity):
|
|||||||
desc: HomeConnectSelectEntityDescription,
|
desc: HomeConnectSelectEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the entity."""
|
"""Initialize the entity."""
|
||||||
self._original_option_keys = set(desc.values_translation_key.keys())
|
self._original_option_keys = set(desc.values_translation_key)
|
||||||
super().__init__(
|
super().__init__(
|
||||||
coordinator,
|
coordinator,
|
||||||
appliance,
|
appliance,
|
||||||
@ -524,5 +534,5 @@ class HomeConnectSelectOptionEntity(HomeConnectOptionEntity, SelectEntity):
|
|||||||
self.entity_description.values_translation_key[option]
|
self.entity_description.values_translation_key[option]
|
||||||
for option in self._original_option_keys
|
for option in self._original_option_keys
|
||||||
if option is not None
|
if option is not None
|
||||||
|
and option in self.entity_description.values_translation_key
|
||||||
]
|
]
|
||||||
self.__dict__.pop("options", None)
|
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
"""Provides a sensor for Home Connect."""
|
"""Provides a sensor for Home Connect."""
|
||||||
|
|
||||||
import contextlib
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from aiohomeconnect.model import EventKey, StatusKey
|
from aiohomeconnect.model import EventKey, StatusKey
|
||||||
from aiohomeconnect.model.error import HomeConnectError
|
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
@ -28,7 +27,9 @@ from .const import (
|
|||||||
UNIT_MAP,
|
UNIT_MAP,
|
||||||
)
|
)
|
||||||
from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
|
from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
|
||||||
from .entity import HomeConnectEntity
|
from .entity import HomeConnectEntity, constraint_fetcher
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
PARALLEL_UPDATES = 0
|
PARALLEL_UPDATES = 0
|
||||||
|
|
||||||
@ -335,16 +336,14 @@ class HomeConnectSensor(HomeConnectEntity, SensorEntity):
|
|||||||
else:
|
else:
|
||||||
await self.fetch_unit()
|
await self.fetch_unit()
|
||||||
|
|
||||||
|
@constraint_fetcher
|
||||||
async def fetch_unit(self) -> None:
|
async def fetch_unit(self) -> None:
|
||||||
"""Fetch the unit of measurement."""
|
"""Fetch the unit of measurement."""
|
||||||
with contextlib.suppress(HomeConnectError):
|
data = await self.coordinator.client.get_status_value(
|
||||||
data = await self.coordinator.client.get_status_value(
|
self.appliance.info.ha_id, status_key=cast(StatusKey, self.bsh_key)
|
||||||
self.appliance.info.ha_id, status_key=cast(StatusKey, self.bsh_key)
|
)
|
||||||
)
|
if data.unit:
|
||||||
if data.unit:
|
self._attr_native_unit_of_measurement = UNIT_MAP.get(data.unit, data.unit)
|
||||||
self._attr_native_unit_of_measurement = UNIT_MAP.get(
|
|
||||||
data.unit, data.unit
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class HomeConnectProgramSensor(HomeConnectSensor):
|
class HomeConnectProgramSensor(HomeConnectSensor):
|
||||||
|
@ -468,11 +468,11 @@ set_program_and_options:
|
|||||||
translation_key: venting_level
|
translation_key: venting_level
|
||||||
options:
|
options:
|
||||||
- cooking_hood_enum_type_stage_fan_off
|
- cooking_hood_enum_type_stage_fan_off
|
||||||
- cooking_hood_enum_type_stage_fan_stage01
|
- cooking_hood_enum_type_stage_fan_stage_01
|
||||||
- cooking_hood_enum_type_stage_fan_stage02
|
- cooking_hood_enum_type_stage_fan_stage_02
|
||||||
- cooking_hood_enum_type_stage_fan_stage03
|
- cooking_hood_enum_type_stage_fan_stage_03
|
||||||
- cooking_hood_enum_type_stage_fan_stage04
|
- cooking_hood_enum_type_stage_fan_stage_04
|
||||||
- cooking_hood_enum_type_stage_fan_stage05
|
- cooking_hood_enum_type_stage_fan_stage_05
|
||||||
cooking_hood_option_intensive_level:
|
cooking_hood_option_intensive_level:
|
||||||
example: cooking_hood_enum_type_intensive_stage_intensive_stage1
|
example: cooking_hood_enum_type_intensive_stage_intensive_stage1
|
||||||
required: false
|
required: false
|
||||||
@ -528,7 +528,7 @@ set_program_and_options:
|
|||||||
collapsed: true
|
collapsed: true
|
||||||
fields:
|
fields:
|
||||||
laundry_care_washer_option_temperature:
|
laundry_care_washer_option_temperature:
|
||||||
example: laundry_care_washer_enum_type_temperature_g_c40
|
example: laundry_care_washer_enum_type_temperature_g_c_40
|
||||||
required: false
|
required: false
|
||||||
selector:
|
selector:
|
||||||
select:
|
select:
|
||||||
@ -536,14 +536,14 @@ set_program_and_options:
|
|||||||
translation_key: washer_temperature
|
translation_key: washer_temperature
|
||||||
options:
|
options:
|
||||||
- laundry_care_washer_enum_type_temperature_cold
|
- laundry_care_washer_enum_type_temperature_cold
|
||||||
- laundry_care_washer_enum_type_temperature_g_c20
|
- laundry_care_washer_enum_type_temperature_g_c_20
|
||||||
- laundry_care_washer_enum_type_temperature_g_c30
|
- laundry_care_washer_enum_type_temperature_g_c_30
|
||||||
- laundry_care_washer_enum_type_temperature_g_c40
|
- laundry_care_washer_enum_type_temperature_g_c_40
|
||||||
- laundry_care_washer_enum_type_temperature_g_c50
|
- laundry_care_washer_enum_type_temperature_g_c_50
|
||||||
- laundry_care_washer_enum_type_temperature_g_c60
|
- laundry_care_washer_enum_type_temperature_g_c_60
|
||||||
- laundry_care_washer_enum_type_temperature_g_c70
|
- laundry_care_washer_enum_type_temperature_g_c_70
|
||||||
- laundry_care_washer_enum_type_temperature_g_c80
|
- laundry_care_washer_enum_type_temperature_g_c_80
|
||||||
- laundry_care_washer_enum_type_temperature_g_c90
|
- laundry_care_washer_enum_type_temperature_g_c_90
|
||||||
- laundry_care_washer_enum_type_temperature_ul_cold
|
- laundry_care_washer_enum_type_temperature_ul_cold
|
||||||
- laundry_care_washer_enum_type_temperature_ul_warm
|
- laundry_care_washer_enum_type_temperature_ul_warm
|
||||||
- laundry_care_washer_enum_type_temperature_ul_hot
|
- laundry_care_washer_enum_type_temperature_ul_hot
|
||||||
@ -557,13 +557,15 @@ set_program_and_options:
|
|||||||
translation_key: spin_speed
|
translation_key: spin_speed
|
||||||
options:
|
options:
|
||||||
- laundry_care_washer_enum_type_spin_speed_off
|
- laundry_care_washer_enum_type_spin_speed_off
|
||||||
- laundry_care_washer_enum_type_spin_speed_r_p_m400
|
- laundry_care_washer_enum_type_spin_speed_r_p_m_400
|
||||||
- laundry_care_washer_enum_type_spin_speed_r_p_m600
|
- laundry_care_washer_enum_type_spin_speed_r_p_m_600
|
||||||
- laundry_care_washer_enum_type_spin_speed_r_p_m800
|
- laundry_care_washer_enum_type_spin_speed_r_p_m_700
|
||||||
- laundry_care_washer_enum_type_spin_speed_r_p_m1000
|
- laundry_care_washer_enum_type_spin_speed_r_p_m_800
|
||||||
- laundry_care_washer_enum_type_spin_speed_r_p_m1200
|
- laundry_care_washer_enum_type_spin_speed_r_p_m_900
|
||||||
- laundry_care_washer_enum_type_spin_speed_r_p_m1400
|
- laundry_care_washer_enum_type_spin_speed_r_p_m_1000
|
||||||
- laundry_care_washer_enum_type_spin_speed_r_p_m1600
|
- laundry_care_washer_enum_type_spin_speed_r_p_m_1200
|
||||||
|
- laundry_care_washer_enum_type_spin_speed_r_p_m_1400
|
||||||
|
- laundry_care_washer_enum_type_spin_speed_r_p_m_1600
|
||||||
- laundry_care_washer_enum_type_spin_speed_ul_off
|
- laundry_care_washer_enum_type_spin_speed_ul_off
|
||||||
- laundry_care_washer_enum_type_spin_speed_ul_low
|
- laundry_care_washer_enum_type_spin_speed_ul_low
|
||||||
- laundry_care_washer_enum_type_spin_speed_ul_medium
|
- laundry_care_washer_enum_type_spin_speed_ul_medium
|
||||||
|
@ -417,11 +417,11 @@
|
|||||||
"venting_level": {
|
"venting_level": {
|
||||||
"options": {
|
"options": {
|
||||||
"cooking_hood_enum_type_stage_fan_off": "Fan off",
|
"cooking_hood_enum_type_stage_fan_off": "Fan off",
|
||||||
"cooking_hood_enum_type_stage_fan_stage01": "Fan stage 1",
|
"cooking_hood_enum_type_stage_fan_stage_01": "Fan stage 1",
|
||||||
"cooking_hood_enum_type_stage_fan_stage02": "Fan stage 2",
|
"cooking_hood_enum_type_stage_fan_stage_02": "Fan stage 2",
|
||||||
"cooking_hood_enum_type_stage_fan_stage03": "Fan stage 3",
|
"cooking_hood_enum_type_stage_fan_stage_03": "Fan stage 3",
|
||||||
"cooking_hood_enum_type_stage_fan_stage04": "Fan stage 4",
|
"cooking_hood_enum_type_stage_fan_stage_04": "Fan stage 4",
|
||||||
"cooking_hood_enum_type_stage_fan_stage05": "Fan stage 5"
|
"cooking_hood_enum_type_stage_fan_stage_05": "Fan stage 5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"intensive_level": {
|
"intensive_level": {
|
||||||
@ -441,14 +441,14 @@
|
|||||||
"washer_temperature": {
|
"washer_temperature": {
|
||||||
"options": {
|
"options": {
|
||||||
"laundry_care_washer_enum_type_temperature_cold": "Cold",
|
"laundry_care_washer_enum_type_temperature_cold": "Cold",
|
||||||
"laundry_care_washer_enum_type_temperature_g_c20": "20ºC clothes",
|
"laundry_care_washer_enum_type_temperature_g_c_20": "20ºC clothes",
|
||||||
"laundry_care_washer_enum_type_temperature_g_c30": "30ºC clothes",
|
"laundry_care_washer_enum_type_temperature_g_c_30": "30ºC clothes",
|
||||||
"laundry_care_washer_enum_type_temperature_g_c40": "40ºC clothes",
|
"laundry_care_washer_enum_type_temperature_g_c_40": "40ºC clothes",
|
||||||
"laundry_care_washer_enum_type_temperature_g_c50": "50ºC clothes",
|
"laundry_care_washer_enum_type_temperature_g_c_50": "50ºC clothes",
|
||||||
"laundry_care_washer_enum_type_temperature_g_c60": "60ºC clothes",
|
"laundry_care_washer_enum_type_temperature_g_c_60": "60ºC clothes",
|
||||||
"laundry_care_washer_enum_type_temperature_g_c70": "70ºC clothes",
|
"laundry_care_washer_enum_type_temperature_g_c_70": "70ºC clothes",
|
||||||
"laundry_care_washer_enum_type_temperature_g_c80": "80ºC clothes",
|
"laundry_care_washer_enum_type_temperature_g_c_80": "80ºC clothes",
|
||||||
"laundry_care_washer_enum_type_temperature_g_c90": "90ºC clothes",
|
"laundry_care_washer_enum_type_temperature_g_c_90": "90ºC clothes",
|
||||||
"laundry_care_washer_enum_type_temperature_ul_cold": "Cold",
|
"laundry_care_washer_enum_type_temperature_ul_cold": "Cold",
|
||||||
"laundry_care_washer_enum_type_temperature_ul_warm": "Warm",
|
"laundry_care_washer_enum_type_temperature_ul_warm": "Warm",
|
||||||
"laundry_care_washer_enum_type_temperature_ul_hot": "Hot",
|
"laundry_care_washer_enum_type_temperature_ul_hot": "Hot",
|
||||||
@ -458,14 +458,15 @@
|
|||||||
"spin_speed": {
|
"spin_speed": {
|
||||||
"options": {
|
"options": {
|
||||||
"laundry_care_washer_enum_type_spin_speed_off": "Off",
|
"laundry_care_washer_enum_type_spin_speed_off": "Off",
|
||||||
"laundry_care_washer_enum_type_spin_speed_r_p_m400": "400 rpm",
|
"laundry_care_washer_enum_type_spin_speed_r_p_m_400": "400 rpm",
|
||||||
"laundry_care_washer_enum_type_spin_speed_r_p_m600": "600 rpm",
|
"laundry_care_washer_enum_type_spin_speed_r_p_m_600": "600 rpm",
|
||||||
"laundry_care_washer_enum_type_spin_speed_r_p_m800": "800 rpm",
|
"laundry_care_washer_enum_type_spin_speed_r_p_m_700": "700 rpm",
|
||||||
"laundry_care_washer_enum_type_spin_speed_r_p_m900": "900 rpm",
|
"laundry_care_washer_enum_type_spin_speed_r_p_m_800": "800 rpm",
|
||||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1000": "1000 rpm",
|
"laundry_care_washer_enum_type_spin_speed_r_p_m_900": "900 rpm",
|
||||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1200": "1200 rpm",
|
"laundry_care_washer_enum_type_spin_speed_r_p_m_1000": "1000 rpm",
|
||||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1400": "1400 rpm",
|
"laundry_care_washer_enum_type_spin_speed_r_p_m_1200": "1200 rpm",
|
||||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1600": "1600 rpm",
|
"laundry_care_washer_enum_type_spin_speed_r_p_m_1400": "1400 rpm",
|
||||||
|
"laundry_care_washer_enum_type_spin_speed_r_p_m_1600": "1600 rpm",
|
||||||
"laundry_care_washer_enum_type_spin_speed_ul_off": "Off",
|
"laundry_care_washer_enum_type_spin_speed_ul_off": "Off",
|
||||||
"laundry_care_washer_enum_type_spin_speed_ul_low": "Low",
|
"laundry_care_washer_enum_type_spin_speed_ul_low": "Low",
|
||||||
"laundry_care_washer_enum_type_spin_speed_ul_medium": "Medium",
|
"laundry_care_washer_enum_type_spin_speed_ul_medium": "Medium",
|
||||||
@ -1383,11 +1384,11 @@
|
|||||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::cooking_hood_option_venting_level::name%]",
|
"name": "[%key:component::home_connect::services::set_program_and_options::fields::cooking_hood_option_venting_level::name%]",
|
||||||
"state": {
|
"state": {
|
||||||
"cooking_hood_enum_type_stage_fan_off": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_off%]",
|
"cooking_hood_enum_type_stage_fan_off": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_off%]",
|
||||||
"cooking_hood_enum_type_stage_fan_stage01": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage01%]",
|
"cooking_hood_enum_type_stage_fan_stage_01": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage_01%]",
|
||||||
"cooking_hood_enum_type_stage_fan_stage02": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage02%]",
|
"cooking_hood_enum_type_stage_fan_stage_02": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage_02%]",
|
||||||
"cooking_hood_enum_type_stage_fan_stage03": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage03%]",
|
"cooking_hood_enum_type_stage_fan_stage_03": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage_03%]",
|
||||||
"cooking_hood_enum_type_stage_fan_stage04": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage04%]",
|
"cooking_hood_enum_type_stage_fan_stage_04": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage_04%]",
|
||||||
"cooking_hood_enum_type_stage_fan_stage05": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage05%]"
|
"cooking_hood_enum_type_stage_fan_stage_05": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage_05%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"intensive_level": {
|
"intensive_level": {
|
||||||
@ -1410,14 +1411,14 @@
|
|||||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::laundry_care_washer_option_temperature::name%]",
|
"name": "[%key:component::home_connect::services::set_program_and_options::fields::laundry_care_washer_option_temperature::name%]",
|
||||||
"state": {
|
"state": {
|
||||||
"laundry_care_washer_enum_type_temperature_cold": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_cold%]",
|
"laundry_care_washer_enum_type_temperature_cold": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_cold%]",
|
||||||
"laundry_care_washer_enum_type_temperature_g_c20": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c20%]",
|
"laundry_care_washer_enum_type_temperature_g_c_20": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c_20%]",
|
||||||
"laundry_care_washer_enum_type_temperature_g_c30": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c30%]",
|
"laundry_care_washer_enum_type_temperature_g_c_30": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c_30%]",
|
||||||
"laundry_care_washer_enum_type_temperature_g_c40": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c40%]",
|
"laundry_care_washer_enum_type_temperature_g_c_40": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c_40%]",
|
||||||
"laundry_care_washer_enum_type_temperature_g_c50": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c50%]",
|
"laundry_care_washer_enum_type_temperature_g_c_50": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c_50%]",
|
||||||
"laundry_care_washer_enum_type_temperature_g_c60": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c60%]",
|
"laundry_care_washer_enum_type_temperature_g_c_60": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c_60%]",
|
||||||
"laundry_care_washer_enum_type_temperature_g_c70": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c70%]",
|
"laundry_care_washer_enum_type_temperature_g_c_70": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c_70%]",
|
||||||
"laundry_care_washer_enum_type_temperature_g_c80": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c80%]",
|
"laundry_care_washer_enum_type_temperature_g_c_80": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c_80%]",
|
||||||
"laundry_care_washer_enum_type_temperature_g_c90": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c90%]",
|
"laundry_care_washer_enum_type_temperature_g_c_90": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c_90%]",
|
||||||
"laundry_care_washer_enum_type_temperature_ul_cold": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_ul_cold%]",
|
"laundry_care_washer_enum_type_temperature_ul_cold": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_ul_cold%]",
|
||||||
"laundry_care_washer_enum_type_temperature_ul_warm": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_ul_warm%]",
|
"laundry_care_washer_enum_type_temperature_ul_warm": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_ul_warm%]",
|
||||||
"laundry_care_washer_enum_type_temperature_ul_hot": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_ul_hot%]",
|
"laundry_care_washer_enum_type_temperature_ul_hot": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_ul_hot%]",
|
||||||
@ -1428,14 +1429,15 @@
|
|||||||
"name": "[%key:component::home_connect::services::set_program_and_options::fields::laundry_care_washer_option_spin_speed::name%]",
|
"name": "[%key:component::home_connect::services::set_program_and_options::fields::laundry_care_washer_option_spin_speed::name%]",
|
||||||
"state": {
|
"state": {
|
||||||
"laundry_care_washer_enum_type_spin_speed_off": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_off%]",
|
"laundry_care_washer_enum_type_spin_speed_off": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_off%]",
|
||||||
"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_m_400": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_400%]",
|
||||||
"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_m_600": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_600%]",
|
||||||
"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_m_700": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_700%]",
|
||||||
"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_m_800": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_800%]",
|
||||||
"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_m_900": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_900%]",
|
||||||
"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_m_1000": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_1000%]",
|
||||||
"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_m_1200": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_1200%]",
|
||||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1600": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m1600%]",
|
"laundry_care_washer_enum_type_spin_speed_r_p_m_1400": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_1400%]",
|
||||||
|
"laundry_care_washer_enum_type_spin_speed_r_p_m_1600": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_1600%]",
|
||||||
"laundry_care_washer_enum_type_spin_speed_ul_off": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_off%]",
|
"laundry_care_washer_enum_type_spin_speed_ul_off": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_off%]",
|
||||||
"laundry_care_washer_enum_type_spin_speed_ul_low": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_low%]",
|
"laundry_care_washer_enum_type_spin_speed_ul_low": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_low%]",
|
||||||
"laundry_care_washer_enum_type_spin_speed_ul_medium": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_medium%]",
|
"laundry_care_washer_enum_type_spin_speed_ul_medium": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_medium%]",
|
||||||
|
@ -61,6 +61,42 @@ async def async_setup_entry(hass: HomeAssistant, entry: LaMarzoccoConfigEntry) -
|
|||||||
client=client,
|
client=client,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# initialize the firmware update coordinator early to check the firmware version
|
||||||
|
firmware_device = LaMarzoccoMachine(
|
||||||
|
model=entry.data[CONF_MODEL],
|
||||||
|
serial_number=entry.unique_id,
|
||||||
|
name=entry.data[CONF_NAME],
|
||||||
|
cloud_client=cloud_client,
|
||||||
|
)
|
||||||
|
|
||||||
|
firmware_coordinator = LaMarzoccoFirmwareUpdateCoordinator(
|
||||||
|
hass, entry, firmware_device
|
||||||
|
)
|
||||||
|
await firmware_coordinator.async_config_entry_first_refresh()
|
||||||
|
gateway_version = version.parse(
|
||||||
|
firmware_device.firmware[FirmwareType.GATEWAY].current_version
|
||||||
|
)
|
||||||
|
|
||||||
|
if gateway_version >= version.parse("v5.0.9"):
|
||||||
|
# remove host from config entry, it is not supported anymore
|
||||||
|
data = {k: v for k, v in entry.data.items() if k != CONF_HOST}
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
entry,
|
||||||
|
data=data,
|
||||||
|
)
|
||||||
|
|
||||||
|
elif gateway_version < version.parse("v3.4-rc5"):
|
||||||
|
# incompatible gateway firmware, create an issue
|
||||||
|
ir.async_create_issue(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
"unsupported_gateway_firmware",
|
||||||
|
is_fixable=False,
|
||||||
|
severity=ir.IssueSeverity.ERROR,
|
||||||
|
translation_key="unsupported_gateway_firmware",
|
||||||
|
translation_placeholders={"gateway_version": str(gateway_version)},
|
||||||
|
)
|
||||||
|
|
||||||
# initialize local API
|
# initialize local API
|
||||||
local_client: LaMarzoccoLocalClient | None = None
|
local_client: LaMarzoccoLocalClient | None = None
|
||||||
if (host := entry.data.get(CONF_HOST)) is not None:
|
if (host := entry.data.get(CONF_HOST)) is not None:
|
||||||
@ -117,30 +153,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: LaMarzoccoConfigEntry) -
|
|||||||
|
|
||||||
coordinators = LaMarzoccoRuntimeData(
|
coordinators = LaMarzoccoRuntimeData(
|
||||||
LaMarzoccoConfigUpdateCoordinator(hass, entry, device, local_client),
|
LaMarzoccoConfigUpdateCoordinator(hass, entry, device, local_client),
|
||||||
LaMarzoccoFirmwareUpdateCoordinator(hass, entry, device),
|
firmware_coordinator,
|
||||||
LaMarzoccoStatisticsUpdateCoordinator(hass, entry, device),
|
LaMarzoccoStatisticsUpdateCoordinator(hass, entry, device),
|
||||||
)
|
)
|
||||||
|
|
||||||
# API does not like concurrent requests, so no asyncio.gather here
|
# API does not like concurrent requests, so no asyncio.gather here
|
||||||
await coordinators.config_coordinator.async_config_entry_first_refresh()
|
await coordinators.config_coordinator.async_config_entry_first_refresh()
|
||||||
await coordinators.firmware_coordinator.async_config_entry_first_refresh()
|
|
||||||
await coordinators.statistics_coordinator.async_config_entry_first_refresh()
|
await coordinators.statistics_coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
entry.runtime_data = coordinators
|
entry.runtime_data = coordinators
|
||||||
|
|
||||||
gateway_version = device.firmware[FirmwareType.GATEWAY].current_version
|
|
||||||
if version.parse(gateway_version) < version.parse("v3.4-rc5"):
|
|
||||||
# incompatible gateway firmware, create an issue
|
|
||||||
ir.async_create_issue(
|
|
||||||
hass,
|
|
||||||
DOMAIN,
|
|
||||||
"unsupported_gateway_firmware",
|
|
||||||
is_fixable=False,
|
|
||||||
severity=ir.IssueSeverity.ERROR,
|
|
||||||
translation_key="unsupported_gateway_firmware",
|
|
||||||
translation_placeholders={"gateway_version": gateway_version},
|
|
||||||
)
|
|
||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
async def update_listener(
|
async def update_listener(
|
||||||
|
@ -37,5 +37,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["pylamarzocco"],
|
"loggers": ["pylamarzocco"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["pylamarzocco==1.4.7"]
|
"requirements": ["pylamarzocco==1.4.9"]
|
||||||
}
|
}
|
||||||
|
@ -144,9 +144,12 @@ KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
|
|||||||
set_value_fn=lambda machine, value, key: machine.set_prebrew_time(
|
set_value_fn=lambda machine, value, key: machine.set_prebrew_time(
|
||||||
prebrew_off_time=value, key=key
|
prebrew_off_time=value, key=key
|
||||||
),
|
),
|
||||||
native_value_fn=lambda config, key: config.prebrew_configuration[key].off_time,
|
native_value_fn=lambda config, key: config.prebrew_configuration[key][
|
||||||
|
0
|
||||||
|
].off_time,
|
||||||
available_fn=lambda device: len(device.config.prebrew_configuration) > 0
|
available_fn=lambda device: len(device.config.prebrew_configuration) > 0
|
||||||
and device.config.prebrew_mode == PrebrewMode.PREBREW,
|
and device.config.prebrew_mode
|
||||||
|
in (PrebrewMode.PREBREW, PrebrewMode.PREBREW_ENABLED),
|
||||||
supported_fn=lambda coordinator: coordinator.device.model
|
supported_fn=lambda coordinator: coordinator.device.model
|
||||||
!= MachineModel.GS3_MP,
|
!= MachineModel.GS3_MP,
|
||||||
),
|
),
|
||||||
@ -162,9 +165,12 @@ KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
|
|||||||
set_value_fn=lambda machine, value, key: machine.set_prebrew_time(
|
set_value_fn=lambda machine, value, key: machine.set_prebrew_time(
|
||||||
prebrew_on_time=value, key=key
|
prebrew_on_time=value, key=key
|
||||||
),
|
),
|
||||||
native_value_fn=lambda config, key: config.prebrew_configuration[key].off_time,
|
native_value_fn=lambda config, key: config.prebrew_configuration[key][
|
||||||
|
0
|
||||||
|
].off_time,
|
||||||
available_fn=lambda device: len(device.config.prebrew_configuration) > 0
|
available_fn=lambda device: len(device.config.prebrew_configuration) > 0
|
||||||
and device.config.prebrew_mode == PrebrewMode.PREBREW,
|
and device.config.prebrew_mode
|
||||||
|
in (PrebrewMode.PREBREW, PrebrewMode.PREBREW_ENABLED),
|
||||||
supported_fn=lambda coordinator: coordinator.device.model
|
supported_fn=lambda coordinator: coordinator.device.model
|
||||||
!= MachineModel.GS3_MP,
|
!= MachineModel.GS3_MP,
|
||||||
),
|
),
|
||||||
@ -180,8 +186,8 @@ KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
|
|||||||
set_value_fn=lambda machine, value, key: machine.set_preinfusion_time(
|
set_value_fn=lambda machine, value, key: machine.set_preinfusion_time(
|
||||||
preinfusion_time=value, key=key
|
preinfusion_time=value, key=key
|
||||||
),
|
),
|
||||||
native_value_fn=lambda config, key: config.prebrew_configuration[
|
native_value_fn=lambda config, key: config.prebrew_configuration[key][
|
||||||
key
|
1
|
||||||
].preinfusion_time,
|
].preinfusion_time,
|
||||||
available_fn=lambda device: len(device.config.prebrew_configuration) > 0
|
available_fn=lambda device: len(device.config.prebrew_configuration) > 0
|
||||||
and device.config.prebrew_mode == PrebrewMode.PREINFUSION,
|
and device.config.prebrew_mode == PrebrewMode.PREINFUSION,
|
||||||
|
@ -38,6 +38,7 @@ STEAM_LEVEL_LM_TO_HA = {value: key for key, value in STEAM_LEVEL_HA_TO_LM.items(
|
|||||||
PREBREW_MODE_HA_TO_LM = {
|
PREBREW_MODE_HA_TO_LM = {
|
||||||
"disabled": PrebrewMode.DISABLED,
|
"disabled": PrebrewMode.DISABLED,
|
||||||
"prebrew": PrebrewMode.PREBREW,
|
"prebrew": PrebrewMode.PREBREW,
|
||||||
|
"prebrew_enabled": PrebrewMode.PREBREW_ENABLED,
|
||||||
"preinfusion": PrebrewMode.PREINFUSION,
|
"preinfusion": PrebrewMode.PREINFUSION,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,6 +148,7 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"prebrew": "Prebrew",
|
"prebrew": "Prebrew",
|
||||||
|
"prebrew_enabled": "Prebrew",
|
||||||
"preinfusion": "Preinfusion"
|
"preinfusion": "Preinfusion"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -97,11 +97,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: OneDriveConfigEntry) ->
|
|||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
async def update_listener(hass: HomeAssistant, entry: OneDriveConfigEntry) -> None:
|
|
||||||
await hass.config_entries.async_reload(entry.entry_id)
|
|
||||||
|
|
||||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
|
||||||
|
|
||||||
def async_notify_backup_listeners() -> None:
|
def async_notify_backup_listeners() -> None:
|
||||||
for listener in hass.data.get(DATA_BACKUP_AGENT_LISTENERS, []):
|
for listener in hass.data.get(DATA_BACKUP_AGENT_LISTENERS, []):
|
||||||
listener()
|
listener()
|
||||||
|
@ -83,7 +83,16 @@ class PlaybackProxyView(HomeAssistantView):
|
|||||||
_LOGGER.warning("Reolink playback proxy error: %s", str(err))
|
_LOGGER.warning("Reolink playback proxy error: %s", str(err))
|
||||||
return web.Response(body=str(err), status=HTTPStatus.BAD_REQUEST)
|
return web.Response(body=str(err), status=HTTPStatus.BAD_REQUEST)
|
||||||
|
|
||||||
|
headers = dict(request.headers)
|
||||||
|
headers.pop("Host", None)
|
||||||
|
headers.pop("Referer", None)
|
||||||
|
|
||||||
if _LOGGER.isEnabledFor(logging.DEBUG):
|
if _LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Requested Playback Proxy Method %s, Headers: %s",
|
||||||
|
request.method,
|
||||||
|
headers,
|
||||||
|
)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Opening VOD stream from %s: %s",
|
"Opening VOD stream from %s: %s",
|
||||||
host.api.camera_name(ch),
|
host.api.camera_name(ch),
|
||||||
@ -93,6 +102,7 @@ class PlaybackProxyView(HomeAssistantView):
|
|||||||
try:
|
try:
|
||||||
reolink_response = await self.session.get(
|
reolink_response = await self.session.get(
|
||||||
reolink_url,
|
reolink_url,
|
||||||
|
headers=headers,
|
||||||
timeout=ClientTimeout(
|
timeout=ClientTimeout(
|
||||||
connect=15, sock_connect=15, sock_read=5, total=None
|
connect=15, sock_connect=15, sock_read=5, total=None
|
||||||
),
|
),
|
||||||
@ -118,18 +128,25 @@ class PlaybackProxyView(HomeAssistantView):
|
|||||||
]:
|
]:
|
||||||
err_str = f"Reolink playback expected video/mp4 but got {reolink_response.content_type}"
|
err_str = f"Reolink playback expected video/mp4 but got {reolink_response.content_type}"
|
||||||
_LOGGER.error(err_str)
|
_LOGGER.error(err_str)
|
||||||
|
if reolink_response.content_type == "text/html":
|
||||||
|
text = await reolink_response.text()
|
||||||
|
_LOGGER.debug(text)
|
||||||
return web.Response(body=err_str, status=HTTPStatus.BAD_REQUEST)
|
return web.Response(body=err_str, status=HTTPStatus.BAD_REQUEST)
|
||||||
|
|
||||||
response = web.StreamResponse(
|
response_headers = dict(reolink_response.headers)
|
||||||
status=200,
|
_LOGGER.debug(
|
||||||
reason="OK",
|
"Response Playback Proxy Status %s:%s, Headers: %s",
|
||||||
headers={
|
reolink_response.status,
|
||||||
"Content-Type": "video/mp4",
|
reolink_response.reason,
|
||||||
},
|
response_headers,
|
||||||
)
|
)
|
||||||
|
response_headers["Content-Type"] = "video/mp4"
|
||||||
|
|
||||||
if reolink_response.content_length is not None:
|
response = web.StreamResponse(
|
||||||
response.content_length = reolink_response.content_length
|
status=reolink_response.status,
|
||||||
|
reason=reolink_response.reason,
|
||||||
|
headers=response_headers,
|
||||||
|
)
|
||||||
|
|
||||||
await response.prepare(request)
|
await response.prepare(request)
|
||||||
|
|
||||||
@ -141,7 +158,8 @@ class PlaybackProxyView(HomeAssistantView):
|
|||||||
"Timeout while reading Reolink playback from %s, writing EOF",
|
"Timeout while reading Reolink playback from %s, writing EOF",
|
||||||
host.api.nvr_name,
|
host.api.nvr_name,
|
||||||
)
|
)
|
||||||
|
finally:
|
||||||
|
reolink_response.release()
|
||||||
|
|
||||||
reolink_response.release()
|
|
||||||
await response.write_eof()
|
await response.write_eof()
|
||||||
return response
|
return response
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import ssl
|
import ssl
|
||||||
|
|
||||||
from smart_meter_texas import Account, Client, ClientSSLContext
|
from smart_meter_texas import Account, Client
|
||||||
from smart_meter_texas.exceptions import (
|
from smart_meter_texas.exceptions import (
|
||||||
SmartMeterTexasAPIError,
|
SmartMeterTexasAPIError,
|
||||||
SmartMeterTexasAuthError,
|
SmartMeterTexasAuthError,
|
||||||
@ -16,6 +16,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
|
|||||||
from homeassistant.helpers import aiohttp_client
|
from homeassistant.helpers import aiohttp_client
|
||||||
from homeassistant.helpers.debounce import Debouncer
|
from homeassistant.helpers.debounce import Debouncer
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
from homeassistant.util.ssl import get_default_context
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
DATA_COORDINATOR,
|
DATA_COORDINATOR,
|
||||||
@ -38,8 +39,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
account = Account(username, password)
|
account = Account(username, password)
|
||||||
|
|
||||||
client_ssl_context = ClientSSLContext()
|
ssl_context = get_default_context()
|
||||||
ssl_context = await client_ssl_context.get_ssl_context()
|
|
||||||
|
|
||||||
smart_meter_texas_data = SmartMeterTexasData(hass, entry, account, ssl_context)
|
smart_meter_texas_data = SmartMeterTexasData(hass, entry, account, ssl_context)
|
||||||
try:
|
try:
|
||||||
|
@ -4,7 +4,7 @@ import logging
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aiohttp import ClientError
|
from aiohttp import ClientError
|
||||||
from smart_meter_texas import Account, Client, ClientSSLContext
|
from smart_meter_texas import Account, Client
|
||||||
from smart_meter_texas.exceptions import (
|
from smart_meter_texas.exceptions import (
|
||||||
SmartMeterTexasAPIError,
|
SmartMeterTexasAPIError,
|
||||||
SmartMeterTexasAuthError,
|
SmartMeterTexasAuthError,
|
||||||
@ -16,6 +16,7 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import aiohttp_client
|
from homeassistant.helpers import aiohttp_client
|
||||||
|
from homeassistant.util.ssl import get_default_context
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
@ -31,8 +32,7 @@ async def validate_input(hass: HomeAssistant, data):
|
|||||||
|
|
||||||
Data has the keys from DATA_SCHEMA with values provided by the user.
|
Data has the keys from DATA_SCHEMA with values provided by the user.
|
||||||
"""
|
"""
|
||||||
client_ssl_context = ClientSSLContext()
|
ssl_context = get_default_context()
|
||||||
ssl_context = await client_ssl_context.get_ssl_context()
|
|
||||||
client_session = aiohttp_client.async_get_clientsession(hass)
|
client_session = aiohttp_client.async_get_clientsession(hass)
|
||||||
account = Account(data["username"], data["password"])
|
account = Account(data["username"], data["password"])
|
||||||
client = Client(client_session, account, ssl_context)
|
client = Client(client_session, account, ssl_context)
|
||||||
|
@ -139,7 +139,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: SmartThingsConfigEntry)
|
|||||||
entry.data[CONF_TOKEN][CONF_INSTALLED_APP_ID],
|
entry.data[CONF_TOKEN][CONF_INSTALLED_APP_ID],
|
||||||
)
|
)
|
||||||
except SmartThingsSinkError as err:
|
except SmartThingsSinkError as err:
|
||||||
_LOGGER.debug("Couldn't create a new subscription: %s", err)
|
_LOGGER.exception("Couldn't create a new subscription")
|
||||||
raise ConfigEntryNotReady from err
|
raise ConfigEntryNotReady from err
|
||||||
subscription_id = subscription.subscription_id
|
subscription_id = subscription.subscription_id
|
||||||
_handle_new_subscription_identifier(subscription_id)
|
_handle_new_subscription_identifier(subscription_id)
|
||||||
|
@ -565,12 +565,15 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity):
|
|||||||
def _determine_hvac_modes(self) -> list[HVACMode]:
|
def _determine_hvac_modes(self) -> list[HVACMode]:
|
||||||
"""Determine the supported HVAC modes."""
|
"""Determine the supported HVAC modes."""
|
||||||
modes = [HVACMode.OFF]
|
modes = [HVACMode.OFF]
|
||||||
modes.extend(
|
if (
|
||||||
state
|
ac_modes := self.get_attribute_value(
|
||||||
for mode in self.get_attribute_value(
|
|
||||||
Capability.AIR_CONDITIONER_MODE, Attribute.SUPPORTED_AC_MODES
|
Capability.AIR_CONDITIONER_MODE, Attribute.SUPPORTED_AC_MODES
|
||||||
)
|
)
|
||||||
if (state := AC_MODE_TO_STATE.get(mode)) is not None
|
) is not None:
|
||||||
if state not in modes
|
modes.extend(
|
||||||
)
|
state
|
||||||
|
for mode in ac_modes
|
||||||
|
if (state := AC_MODE_TO_STATE.get(mode)) is not None
|
||||||
|
if state not in modes
|
||||||
|
)
|
||||||
return modes
|
return modes
|
||||||
|
@ -23,7 +23,7 @@ async def async_get_config_entry_diagnostics(
|
|||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Return diagnostics for a config entry."""
|
"""Return diagnostics for a config entry."""
|
||||||
client = entry.runtime_data.client
|
client = entry.runtime_data.client
|
||||||
return await client.get_raw_devices()
|
return {"devices": await client.get_raw_devices()}
|
||||||
|
|
||||||
|
|
||||||
async def async_get_device_diagnostics(
|
async def async_get_device_diagnostics(
|
||||||
|
@ -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.2"]
|
"requirements": ["pysmartthings==2.7.4"]
|
||||||
}
|
}
|
||||||
|
@ -132,6 +132,7 @@ class SmartThingsSensorEntityDescription(SensorEntityDescription):
|
|||||||
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
|
exists_fn: Callable[[Status], bool] | None = None
|
||||||
|
use_temperature_unit: bool = False
|
||||||
|
|
||||||
|
|
||||||
CAPABILITY_TO_SENSORS: dict[
|
CAPABILITY_TO_SENSORS: dict[
|
||||||
@ -573,8 +574,9 @@ CAPABILITY_TO_SENSORS: dict[
|
|||||||
key=Attribute.OVEN_SETPOINT,
|
key=Attribute.OVEN_SETPOINT,
|
||||||
translation_key="oven_setpoint",
|
translation_key="oven_setpoint",
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
use_temperature_unit=True,
|
||||||
value_fn=lambda value: value if value != 0 else None,
|
# Set the value to None if it is 0 F (-17 C)
|
||||||
|
value_fn=lambda value: None if value in {0, -17} else value,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -1018,7 +1020,10 @@ class SmartThingsSensor(SmartThingsEntity, SensorEntity):
|
|||||||
attribute: Attribute,
|
attribute: Attribute,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Init the class."""
|
"""Init the class."""
|
||||||
super().__init__(client, device, {capability})
|
capabilities_to_subscribe = {capability}
|
||||||
|
if entity_description.use_temperature_unit:
|
||||||
|
capabilities_to_subscribe.add(Capability.TEMPERATURE_MEASUREMENT)
|
||||||
|
super().__init__(client, device, capabilities_to_subscribe)
|
||||||
self._attr_unique_id = f"{device.device.device_id}{entity_description.unique_id_separator}{entity_description.key}"
|
self._attr_unique_id = f"{device.device.device_id}{entity_description.unique_id_separator}{entity_description.key}"
|
||||||
self._attribute = attribute
|
self._attribute = attribute
|
||||||
self.capability = capability
|
self.capability = capability
|
||||||
@ -1033,7 +1038,12 @@ class SmartThingsSensor(SmartThingsEntity, SensorEntity):
|
|||||||
@property
|
@property
|
||||||
def native_unit_of_measurement(self) -> str | None:
|
def native_unit_of_measurement(self) -> str | None:
|
||||||
"""Return the unit this state is expressed in."""
|
"""Return the unit this state is expressed in."""
|
||||||
unit = self._internal_state[self.capability][self._attribute].unit
|
if self.entity_description.use_temperature_unit:
|
||||||
|
unit = self._internal_state[Capability.TEMPERATURE_MEASUREMENT][
|
||||||
|
Attribute.TEMPERATURE
|
||||||
|
].unit
|
||||||
|
else:
|
||||||
|
unit = self._internal_state[self.capability][self._attribute].unit
|
||||||
return (
|
return (
|
||||||
UNITS.get(unit, unit)
|
UNITS.get(unit, unit)
|
||||||
if unit
|
if unit
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["snoo"],
|
"loggers": ["snoo"],
|
||||||
"quality_scale": "bronze",
|
"quality_scale": "bronze",
|
||||||
"requirements": ["python-snoo==0.6.1"]
|
"requirements": ["python-snoo==0.6.4"]
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,7 @@ class SonosFavorites(SonosHouseholdCoordinator):
|
|||||||
@soco_error()
|
@soco_error()
|
||||||
def update_cache(self, soco: SoCo, update_id: int | None = None) -> bool:
|
def update_cache(self, soco: SoCo, update_id: int | None = None) -> bool:
|
||||||
"""Update cache of known favorites and return if cache has changed."""
|
"""Update cache of known favorites and return if cache has changed."""
|
||||||
new_favorites = soco.music_library.get_sonos_favorites()
|
new_favorites = soco.music_library.get_sonos_favorites(full_album_art_uri=True)
|
||||||
|
|
||||||
# Polled update_id values do not match event_id values
|
# Polled update_id values do not match event_id values
|
||||||
# Each speaker can return a different polled update_id
|
# Each speaker can return a different polled update_id
|
||||||
|
@ -165,6 +165,8 @@ async def async_browse_media(
|
|||||||
favorites_folder_payload,
|
favorites_folder_payload,
|
||||||
speaker.favorites,
|
speaker.favorites,
|
||||||
media_content_id,
|
media_content_id,
|
||||||
|
media,
|
||||||
|
get_browse_image_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
@ -443,7 +445,10 @@ def favorites_payload(favorites: SonosFavorites) -> BrowseMedia:
|
|||||||
|
|
||||||
|
|
||||||
def favorites_folder_payload(
|
def favorites_folder_payload(
|
||||||
favorites: SonosFavorites, media_content_id: str
|
favorites: SonosFavorites,
|
||||||
|
media_content_id: str,
|
||||||
|
media: SonosMedia,
|
||||||
|
get_browse_image_url: GetBrowseImageUrlType,
|
||||||
) -> BrowseMedia:
|
) -> BrowseMedia:
|
||||||
"""Create response payload to describe all items of a type of favorite.
|
"""Create response payload to describe all items of a type of favorite.
|
||||||
|
|
||||||
@ -463,7 +468,14 @@ def favorites_folder_payload(
|
|||||||
media_content_type="favorite_item_id",
|
media_content_type="favorite_item_id",
|
||||||
can_play=True,
|
can_play=True,
|
||||||
can_expand=False,
|
can_expand=False,
|
||||||
thumbnail=getattr(favorite, "album_art_uri", None),
|
thumbnail=get_thumbnail_url_full(
|
||||||
|
media=media,
|
||||||
|
is_internal=True,
|
||||||
|
media_content_type="favorite_item_id",
|
||||||
|
media_content_id=favorite.item_id,
|
||||||
|
get_browse_image_url=get_browse_image_url,
|
||||||
|
item=favorite,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -39,5 +39,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/switchbot",
|
"documentation": "https://www.home-assistant.io/integrations/switchbot",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["switchbot"],
|
"loggers": ["switchbot"],
|
||||||
"requirements": ["PySwitchbot==0.56.1"]
|
"requirements": ["PySwitchbot==0.57.1"]
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
self._device = "tls://"
|
self._device = "tls://"
|
||||||
else:
|
else:
|
||||||
self._device = ""
|
self._device = ""
|
||||||
if user_input[CONF_PASSWORD] != "":
|
if CONF_PASSWORD in user_input and user_input[CONF_PASSWORD] != "":
|
||||||
self._device += f"{user_input[CONF_PASSWORD]}@"
|
self._device += f"{user_input[CONF_PASSWORD]}@"
|
||||||
self._device += f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}"
|
self._device += f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}"
|
||||||
self._async_abort_entries_match({CONF_PORT: self._device})
|
self._async_abort_entries_match({CONF_PORT: self._device})
|
||||||
|
@ -71,9 +71,7 @@ NUMBER_TYPES: dict[str, WallboxNumberEntityDescription] = {
|
|||||||
CHARGER_MAX_ICP_CURRENT_KEY: WallboxNumberEntityDescription(
|
CHARGER_MAX_ICP_CURRENT_KEY: WallboxNumberEntityDescription(
|
||||||
key=CHARGER_MAX_ICP_CURRENT_KEY,
|
key=CHARGER_MAX_ICP_CURRENT_KEY,
|
||||||
translation_key="maximum_icp_current",
|
translation_key="maximum_icp_current",
|
||||||
max_value_fn=lambda coordinator: cast(
|
max_value_fn=lambda _: 255,
|
||||||
float, coordinator.data[CHARGER_MAX_AVAILABLE_POWER_KEY]
|
|
||||||
),
|
|
||||||
min_value_fn=lambda _: 6,
|
min_value_fn=lambda _: 6,
|
||||||
set_value_fn=lambda coordinator: coordinator.async_set_icp_current,
|
set_value_fn=lambda coordinator: coordinator.async_set_icp_current,
|
||||||
native_step=1,
|
native_step=1,
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
"zha",
|
"zha",
|
||||||
"universal_silabs_flasher"
|
"universal_silabs_flasher"
|
||||||
],
|
],
|
||||||
"requirements": ["zha==0.0.52"],
|
"requirements": ["zha==0.0.53"],
|
||||||
"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 = "3"
|
PATCH_VERSION: Final = "4"
|
||||||
__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)
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2025.3.3"
|
version = "2025.3.4"
|
||||||
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"
|
||||||
|
10
requirements_all.txt
generated
10
requirements_all.txt
generated
@ -84,7 +84,7 @@ PyQRCode==1.2.1
|
|||||||
PyRMVtransport==0.3.3
|
PyRMVtransport==0.3.3
|
||||||
|
|
||||||
# homeassistant.components.switchbot
|
# homeassistant.components.switchbot
|
||||||
PySwitchbot==0.56.1
|
PySwitchbot==0.57.1
|
||||||
|
|
||||||
# homeassistant.components.switchmate
|
# homeassistant.components.switchmate
|
||||||
PySwitchmate==0.5.1
|
PySwitchmate==0.5.1
|
||||||
@ -2077,7 +2077,7 @@ pykwb==0.0.8
|
|||||||
pylacrosse==0.4
|
pylacrosse==0.4
|
||||||
|
|
||||||
# homeassistant.components.lamarzocco
|
# homeassistant.components.lamarzocco
|
||||||
pylamarzocco==1.4.7
|
pylamarzocco==1.4.9
|
||||||
|
|
||||||
# homeassistant.components.lastfm
|
# homeassistant.components.lastfm
|
||||||
pylast==5.1.0
|
pylast==5.1.0
|
||||||
@ -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.2
|
pysmartthings==2.7.4
|
||||||
|
|
||||||
# homeassistant.components.smarty
|
# homeassistant.components.smarty
|
||||||
pysmarty2==0.10.2
|
pysmarty2==0.10.2
|
||||||
@ -2467,7 +2467,7 @@ python-roborock==2.12.2
|
|||||||
python-smarttub==0.0.39
|
python-smarttub==0.0.39
|
||||||
|
|
||||||
# homeassistant.components.snoo
|
# homeassistant.components.snoo
|
||||||
python-snoo==0.6.1
|
python-snoo==0.6.4
|
||||||
|
|
||||||
# homeassistant.components.songpal
|
# homeassistant.components.songpal
|
||||||
python-songpal==0.16.2
|
python-songpal==0.16.2
|
||||||
@ -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.52
|
zha==0.0.53
|
||||||
|
|
||||||
# homeassistant.components.zhong_hong
|
# homeassistant.components.zhong_hong
|
||||||
zhong-hong-hvac==1.0.13
|
zhong-hong-hvac==1.0.13
|
||||||
|
10
requirements_test_all.txt
generated
10
requirements_test_all.txt
generated
@ -81,7 +81,7 @@ PyQRCode==1.2.1
|
|||||||
PyRMVtransport==0.3.3
|
PyRMVtransport==0.3.3
|
||||||
|
|
||||||
# homeassistant.components.switchbot
|
# homeassistant.components.switchbot
|
||||||
PySwitchbot==0.56.1
|
PySwitchbot==0.57.1
|
||||||
|
|
||||||
# homeassistant.components.syncthru
|
# homeassistant.components.syncthru
|
||||||
PySyncThru==0.8.0
|
PySyncThru==0.8.0
|
||||||
@ -1691,7 +1691,7 @@ pykrakenapi==0.1.8
|
|||||||
pykulersky==0.5.2
|
pykulersky==0.5.2
|
||||||
|
|
||||||
# homeassistant.components.lamarzocco
|
# homeassistant.components.lamarzocco
|
||||||
pylamarzocco==1.4.7
|
pylamarzocco==1.4.9
|
||||||
|
|
||||||
# homeassistant.components.lastfm
|
# homeassistant.components.lastfm
|
||||||
pylast==5.1.0
|
pylast==5.1.0
|
||||||
@ -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.2
|
pysmartthings==2.7.4
|
||||||
|
|
||||||
# homeassistant.components.smarty
|
# homeassistant.components.smarty
|
||||||
pysmarty2==0.10.2
|
pysmarty2==0.10.2
|
||||||
@ -2000,7 +2000,7 @@ python-roborock==2.12.2
|
|||||||
python-smarttub==0.0.39
|
python-smarttub==0.0.39
|
||||||
|
|
||||||
# homeassistant.components.snoo
|
# homeassistant.components.snoo
|
||||||
python-snoo==0.6.1
|
python-snoo==0.6.4
|
||||||
|
|
||||||
# homeassistant.components.songpal
|
# homeassistant.components.songpal
|
||||||
python-songpal==0.16.2
|
python-songpal==0.16.2
|
||||||
@ -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.52
|
zha==0.0.53
|
||||||
|
|
||||||
# homeassistant.components.zwave_js
|
# homeassistant.components.zwave_js
|
||||||
zwave-js-server-python==0.60.1
|
zwave-js-server-python==0.60.1
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Tests for the Google Generative AI Conversation integration."""
|
"""Tests for the Google Generative AI Conversation integration."""
|
||||||
|
|
||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, mock_open, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from requests.exceptions import Timeout
|
from requests.exceptions import Timeout
|
||||||
@ -71,6 +71,8 @@ async def test_generate_content_service_with_image(
|
|||||||
),
|
),
|
||||||
patch("pathlib.Path.exists", return_value=True),
|
patch("pathlib.Path.exists", return_value=True),
|
||||||
patch.object(hass.config, "is_allowed_path", return_value=True),
|
patch.object(hass.config, "is_allowed_path", return_value=True),
|
||||||
|
patch("builtins.open", mock_open(read_data="this is an image")),
|
||||||
|
patch("mimetypes.guess_type", return_value=["image/jpeg"]),
|
||||||
):
|
):
|
||||||
response = await hass.services.async_call(
|
response = await hass.services.async_call(
|
||||||
"google_generative_ai_conversation",
|
"google_generative_ai_conversation",
|
||||||
|
@ -2,13 +2,10 @@
|
|||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aiohomeconnect.model import ArrayOfHomeAppliances, ArrayOfStatus
|
from aiohomeconnect.model import ArrayOfStatus
|
||||||
|
|
||||||
from tests.common import load_json_object_fixture
|
from tests.common import load_json_object_fixture
|
||||||
|
|
||||||
MOCK_APPLIANCES = ArrayOfHomeAppliances.from_dict(
|
|
||||||
load_json_object_fixture("home_connect/appliances.json")["data"] # type: ignore[arg-type]
|
|
||||||
)
|
|
||||||
MOCK_PROGRAMS: dict[str, Any] = load_json_object_fixture("home_connect/programs.json")
|
MOCK_PROGRAMS: dict[str, Any] = load_json_object_fixture("home_connect/programs.json")
|
||||||
MOCK_SETTINGS: dict[str, Any] = load_json_object_fixture("home_connect/settings.json")
|
MOCK_SETTINGS: dict[str, Any] = load_json_object_fixture("home_connect/settings.json")
|
||||||
MOCK_STATUS = ArrayOfStatus.from_dict(
|
MOCK_STATUS = ArrayOfStatus.from_dict(
|
||||||
|
@ -11,6 +11,7 @@ from aiohomeconnect.client import Client as HomeConnectClient
|
|||||||
from aiohomeconnect.model import (
|
from aiohomeconnect.model import (
|
||||||
ArrayOfCommands,
|
ArrayOfCommands,
|
||||||
ArrayOfEvents,
|
ArrayOfEvents,
|
||||||
|
ArrayOfHomeAppliances,
|
||||||
ArrayOfOptions,
|
ArrayOfOptions,
|
||||||
ArrayOfPrograms,
|
ArrayOfPrograms,
|
||||||
ArrayOfSettings,
|
ArrayOfSettings,
|
||||||
@ -39,15 +40,9 @@ from homeassistant.const import Platform
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from . import (
|
from . import MOCK_AVAILABLE_COMMANDS, MOCK_PROGRAMS, MOCK_SETTINGS, MOCK_STATUS
|
||||||
MOCK_APPLIANCES,
|
|
||||||
MOCK_AVAILABLE_COMMANDS,
|
|
||||||
MOCK_PROGRAMS,
|
|
||||||
MOCK_SETTINGS,
|
|
||||||
MOCK_STATUS,
|
|
||||||
)
|
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry, load_fixture
|
||||||
|
|
||||||
CLIENT_ID = "1234"
|
CLIENT_ID = "1234"
|
||||||
CLIENT_SECRET = "5678"
|
CLIENT_SECRET = "5678"
|
||||||
@ -148,14 +143,6 @@ async def mock_integration_setup(
|
|||||||
return run
|
return run
|
||||||
|
|
||||||
|
|
||||||
def _get_specific_appliance_side_effect(ha_id: str) -> HomeAppliance:
|
|
||||||
"""Get specific appliance side effect."""
|
|
||||||
for appliance in copy.deepcopy(MOCK_APPLIANCES).homeappliances:
|
|
||||||
if appliance.ha_id == ha_id:
|
|
||||||
return appliance
|
|
||||||
raise HomeConnectApiError("error.key", "error description")
|
|
||||||
|
|
||||||
|
|
||||||
def _get_set_program_side_effect(
|
def _get_set_program_side_effect(
|
||||||
event_queue: asyncio.Queue[list[EventMessage]], event_key: EventKey
|
event_queue: asyncio.Queue[list[EventMessage]], event_key: EventKey
|
||||||
):
|
):
|
||||||
@ -271,68 +258,12 @@ def _get_set_program_options_side_effect(
|
|||||||
return set_program_options_side_effect
|
return set_program_options_side_effect
|
||||||
|
|
||||||
|
|
||||||
async def _get_all_programs_side_effect(ha_id: str) -> ArrayOfPrograms:
|
|
||||||
"""Get all programs."""
|
|
||||||
appliance_type = next(
|
|
||||||
appliance
|
|
||||||
for appliance in MOCK_APPLIANCES.homeappliances
|
|
||||||
if appliance.ha_id == ha_id
|
|
||||||
).type
|
|
||||||
if appliance_type not in MOCK_PROGRAMS:
|
|
||||||
raise HomeConnectApiError("error.key", "error description")
|
|
||||||
|
|
||||||
return ArrayOfPrograms(
|
|
||||||
[
|
|
||||||
EnumerateProgram.from_dict(program)
|
|
||||||
for program in MOCK_PROGRAMS[appliance_type]["data"]["programs"]
|
|
||||||
],
|
|
||||||
Program.from_dict(MOCK_PROGRAMS[appliance_type]["data"]["programs"][0]),
|
|
||||||
Program.from_dict(MOCK_PROGRAMS[appliance_type]["data"]["programs"][0]),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def _get_settings_side_effect(ha_id: str) -> ArrayOfSettings:
|
|
||||||
"""Get settings."""
|
|
||||||
return ArrayOfSettings.from_dict(
|
|
||||||
MOCK_SETTINGS.get(
|
|
||||||
next(
|
|
||||||
appliance
|
|
||||||
for appliance in MOCK_APPLIANCES.homeappliances
|
|
||||||
if appliance.ha_id == ha_id
|
|
||||||
).type,
|
|
||||||
{},
|
|
||||||
).get("data", {"settings": []})
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def _get_setting_side_effect(ha_id: str, setting_key: SettingKey):
|
|
||||||
"""Get setting."""
|
|
||||||
for appliance in MOCK_APPLIANCES.homeappliances:
|
|
||||||
if appliance.ha_id == ha_id:
|
|
||||||
settings = MOCK_SETTINGS.get(
|
|
||||||
next(
|
|
||||||
appliance
|
|
||||||
for appliance in MOCK_APPLIANCES.homeappliances
|
|
||||||
if appliance.ha_id == ha_id
|
|
||||||
).type,
|
|
||||||
{},
|
|
||||||
).get("data", {"settings": []})
|
|
||||||
for setting_dict in cast(list[dict], settings["settings"]):
|
|
||||||
if setting_dict["key"] == setting_key:
|
|
||||||
return GetSetting.from_dict(setting_dict)
|
|
||||||
raise HomeConnectApiError("error.key", "error description")
|
|
||||||
|
|
||||||
|
|
||||||
async def _get_available_commands_side_effect(ha_id: str) -> ArrayOfCommands:
|
|
||||||
"""Get available commands."""
|
|
||||||
for appliance in MOCK_APPLIANCES.homeappliances:
|
|
||||||
if appliance.ha_id == ha_id and appliance.type in MOCK_AVAILABLE_COMMANDS:
|
|
||||||
return ArrayOfCommands.from_dict(MOCK_AVAILABLE_COMMANDS[appliance.type])
|
|
||||||
raise HomeConnectApiError("error.key", "error description")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="client")
|
@pytest.fixture(name="client")
|
||||||
def mock_client(request: pytest.FixtureRequest) -> MagicMock:
|
def mock_client(
|
||||||
|
appliances: list[HomeAppliance],
|
||||||
|
appliance: HomeAppliance | None,
|
||||||
|
request: pytest.FixtureRequest,
|
||||||
|
) -> MagicMock:
|
||||||
"""Fixture to mock Client from HomeConnect."""
|
"""Fixture to mock Client from HomeConnect."""
|
||||||
|
|
||||||
mock = MagicMock(
|
mock = MagicMock(
|
||||||
@ -369,17 +300,78 @@ def mock_client(request: pytest.FixtureRequest) -> MagicMock:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
appliances = [appliance] if appliance else appliances
|
||||||
|
|
||||||
async def stream_all_events() -> AsyncGenerator[EventMessage]:
|
async def stream_all_events() -> AsyncGenerator[EventMessage]:
|
||||||
"""Mock stream_all_events."""
|
"""Mock stream_all_events."""
|
||||||
while True:
|
while True:
|
||||||
for event in await event_queue.get():
|
for event in await event_queue.get():
|
||||||
yield event
|
yield event
|
||||||
|
|
||||||
mock.get_home_appliances = AsyncMock(return_value=copy.deepcopy(MOCK_APPLIANCES))
|
mock.get_home_appliances = AsyncMock(return_value=ArrayOfHomeAppliances(appliances))
|
||||||
|
|
||||||
|
def _get_specific_appliance_side_effect(ha_id: str) -> HomeAppliance:
|
||||||
|
"""Get specific appliance side effect."""
|
||||||
|
for appliance_ in appliances:
|
||||||
|
if appliance_.ha_id == ha_id:
|
||||||
|
return appliance_
|
||||||
|
raise HomeConnectApiError("error.key", "error description")
|
||||||
|
|
||||||
mock.get_specific_appliance = AsyncMock(
|
mock.get_specific_appliance = AsyncMock(
|
||||||
side_effect=_get_specific_appliance_side_effect
|
side_effect=_get_specific_appliance_side_effect
|
||||||
)
|
)
|
||||||
mock.stream_all_events = stream_all_events
|
mock.stream_all_events = stream_all_events
|
||||||
|
|
||||||
|
async def _get_all_programs_side_effect(ha_id: str) -> ArrayOfPrograms:
|
||||||
|
"""Get all programs."""
|
||||||
|
appliance_type = next(
|
||||||
|
appliance for appliance in appliances if appliance.ha_id == ha_id
|
||||||
|
).type
|
||||||
|
if appliance_type not in MOCK_PROGRAMS:
|
||||||
|
raise HomeConnectApiError("error.key", "error description")
|
||||||
|
|
||||||
|
return ArrayOfPrograms(
|
||||||
|
[
|
||||||
|
EnumerateProgram.from_dict(program)
|
||||||
|
for program in MOCK_PROGRAMS[appliance_type]["data"]["programs"]
|
||||||
|
],
|
||||||
|
Program.from_dict(MOCK_PROGRAMS[appliance_type]["data"]["programs"][0]),
|
||||||
|
Program.from_dict(MOCK_PROGRAMS[appliance_type]["data"]["programs"][0]),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _get_settings_side_effect(ha_id: str) -> ArrayOfSettings:
|
||||||
|
"""Get settings."""
|
||||||
|
return ArrayOfSettings.from_dict(
|
||||||
|
MOCK_SETTINGS.get(
|
||||||
|
next(
|
||||||
|
appliance for appliance in appliances if appliance.ha_id == ha_id
|
||||||
|
).type,
|
||||||
|
{},
|
||||||
|
).get("data", {"settings": []})
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _get_setting_side_effect(ha_id: str, setting_key: SettingKey):
|
||||||
|
"""Get setting."""
|
||||||
|
for appliance_ in appliances:
|
||||||
|
if appliance_.ha_id == ha_id:
|
||||||
|
settings = MOCK_SETTINGS.get(
|
||||||
|
appliance_.type,
|
||||||
|
{},
|
||||||
|
).get("data", {"settings": []})
|
||||||
|
for setting_dict in cast(list[dict], settings["settings"]):
|
||||||
|
if setting_dict["key"] == setting_key:
|
||||||
|
return GetSetting.from_dict(setting_dict)
|
||||||
|
raise HomeConnectApiError("error.key", "error description")
|
||||||
|
|
||||||
|
async def _get_available_commands_side_effect(ha_id: str) -> ArrayOfCommands:
|
||||||
|
"""Get available commands."""
|
||||||
|
for appliance_ in appliances:
|
||||||
|
if appliance_.ha_id == ha_id and appliance_.type in MOCK_AVAILABLE_COMMANDS:
|
||||||
|
return ArrayOfCommands.from_dict(
|
||||||
|
MOCK_AVAILABLE_COMMANDS[appliance_.type]
|
||||||
|
)
|
||||||
|
raise HomeConnectApiError("error.key", "error description")
|
||||||
|
|
||||||
mock.start_program = AsyncMock(
|
mock.start_program = AsyncMock(
|
||||||
side_effect=_get_set_program_side_effect(
|
side_effect=_get_set_program_side_effect(
|
||||||
event_queue, EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM
|
event_queue, EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM
|
||||||
@ -431,7 +423,11 @@ def mock_client(request: pytest.FixtureRequest) -> MagicMock:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="client_with_exception")
|
@pytest.fixture(name="client_with_exception")
|
||||||
def mock_client_with_exception(request: pytest.FixtureRequest) -> MagicMock:
|
def mock_client_with_exception(
|
||||||
|
appliances: list[HomeAppliance],
|
||||||
|
appliance: HomeAppliance | None,
|
||||||
|
request: pytest.FixtureRequest,
|
||||||
|
) -> MagicMock:
|
||||||
"""Fixture to mock Client from HomeConnect that raise exceptions."""
|
"""Fixture to mock Client from HomeConnect that raise exceptions."""
|
||||||
mock = MagicMock(
|
mock = MagicMock(
|
||||||
autospec=HomeConnectClient,
|
autospec=HomeConnectClient,
|
||||||
@ -449,7 +445,8 @@ def mock_client_with_exception(request: pytest.FixtureRequest) -> MagicMock:
|
|||||||
for event in await event_queue.get():
|
for event in await event_queue.get():
|
||||||
yield event
|
yield event
|
||||||
|
|
||||||
mock.get_home_appliances = AsyncMock(return_value=copy.deepcopy(MOCK_APPLIANCES))
|
appliances = [appliance] if appliance else appliances
|
||||||
|
mock.get_home_appliances = AsyncMock(return_value=ArrayOfHomeAppliances(appliances))
|
||||||
mock.stream_all_events = stream_all_events
|
mock.stream_all_events = stream_all_events
|
||||||
|
|
||||||
mock.start_program = AsyncMock(side_effect=exception)
|
mock.start_program = AsyncMock(side_effect=exception)
|
||||||
@ -477,12 +474,52 @@ def mock_client_with_exception(request: pytest.FixtureRequest) -> MagicMock:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="appliance_ha_id")
|
@pytest.fixture(name="appliance_ha_id")
|
||||||
def mock_appliance_ha_id(request: pytest.FixtureRequest) -> str:
|
def mock_appliance_ha_id(
|
||||||
"""Fixture to mock Appliance."""
|
appliances: list[HomeAppliance], request: pytest.FixtureRequest
|
||||||
app = "Washer"
|
) -> str:
|
||||||
|
"""Fixture to get the ha_id of an appliance."""
|
||||||
|
appliance_type = "Washer"
|
||||||
if hasattr(request, "param") and request.param:
|
if hasattr(request, "param") and request.param:
|
||||||
app = request.param
|
appliance_type = request.param
|
||||||
for appliance in MOCK_APPLIANCES.homeappliances:
|
for appliance in appliances:
|
||||||
if appliance.type == app:
|
if appliance.type == appliance_type:
|
||||||
return appliance.ha_id
|
return appliance.ha_id
|
||||||
raise ValueError(f"Appliance {app} not found")
|
raise ValueError(f"Appliance {appliance_type} not found")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="appliances")
|
||||||
|
def mock_appliances(
|
||||||
|
appliances_data: str, request: pytest.FixtureRequest
|
||||||
|
) -> list[HomeAppliance]:
|
||||||
|
"""Fixture to mock the returned appliances."""
|
||||||
|
appliances = ArrayOfHomeAppliances.from_json(appliances_data).homeappliances
|
||||||
|
appliance_types = {appliance.type for appliance in appliances}
|
||||||
|
if hasattr(request, "param") and request.param:
|
||||||
|
appliance_types = request.param
|
||||||
|
return [appliance for appliance in appliances if appliance.type in appliance_types]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="appliance")
|
||||||
|
def mock_appliance(
|
||||||
|
appliances_data: str, request: pytest.FixtureRequest
|
||||||
|
) -> HomeAppliance | None:
|
||||||
|
"""Fixture to mock a single specific appliance to return."""
|
||||||
|
appliance_type = None
|
||||||
|
if hasattr(request, "param") and request.param:
|
||||||
|
appliance_type = request.param
|
||||||
|
return next(
|
||||||
|
(
|
||||||
|
appliance
|
||||||
|
for appliance in ArrayOfHomeAppliances.from_json(
|
||||||
|
appliances_data
|
||||||
|
).homeappliances
|
||||||
|
if appliance.type == appliance_type
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="appliances_data")
|
||||||
|
def appliances_data_fixture() -> str:
|
||||||
|
"""Fixture to return a the string for an array of appliances."""
|
||||||
|
return load_fixture("appliances.json", integration=DOMAIN)
|
||||||
|
@ -1,123 +1,121 @@
|
|||||||
{
|
{
|
||||||
"data": {
|
"homeappliances": [
|
||||||
"homeappliances": [
|
{
|
||||||
{
|
"name": "FridgeFreezer",
|
||||||
"name": "FridgeFreezer",
|
"brand": "SIEMENS",
|
||||||
"brand": "SIEMENS",
|
"vib": "HCS05FRF1",
|
||||||
"vib": "HCS05FRF1",
|
"connected": true,
|
||||||
"connected": true,
|
"type": "FridgeFreezer",
|
||||||
"type": "FridgeFreezer",
|
"enumber": "HCS05FRF1/03",
|
||||||
"enumber": "HCS05FRF1/03",
|
"haId": "SIEMENS-HCS05FRF1-304F4F9E541D"
|
||||||
"haId": "SIEMENS-HCS05FRF1-304F4F9E541D"
|
},
|
||||||
},
|
{
|
||||||
{
|
"name": "Dishwasher",
|
||||||
"name": "Dishwasher",
|
"brand": "SIEMENS",
|
||||||
"brand": "SIEMENS",
|
"vib": "HCS02DWH1",
|
||||||
"vib": "HCS02DWH1",
|
"connected": true,
|
||||||
"connected": true,
|
"type": "Dishwasher",
|
||||||
"type": "Dishwasher",
|
"enumber": "HCS02DWH1/03",
|
||||||
"enumber": "HCS02DWH1/03",
|
"haId": "SIEMENS-HCS02DWH1-6BE58C26DCC1"
|
||||||
"haId": "SIEMENS-HCS02DWH1-6BE58C26DCC1"
|
},
|
||||||
},
|
{
|
||||||
{
|
"name": "Oven",
|
||||||
"name": "Oven",
|
"brand": "BOSCH",
|
||||||
"brand": "BOSCH",
|
"vib": "HCS01OVN1",
|
||||||
"vib": "HCS01OVN1",
|
"connected": true,
|
||||||
"connected": true,
|
"type": "Oven",
|
||||||
"type": "Oven",
|
"enumber": "HCS01OVN1/03",
|
||||||
"enumber": "HCS01OVN1/03",
|
"haId": "BOSCH-HCS01OVN1-43E0065FE245"
|
||||||
"haId": "BOSCH-HCS01OVN1-43E0065FE245"
|
},
|
||||||
},
|
{
|
||||||
{
|
"name": "Washer",
|
||||||
"name": "Washer",
|
"brand": "SIEMENS",
|
||||||
"brand": "SIEMENS",
|
"vib": "HCS03WCH1",
|
||||||
"vib": "HCS03WCH1",
|
"connected": true,
|
||||||
"connected": true,
|
"type": "Washer",
|
||||||
"type": "Washer",
|
"enumber": "HCS03WCH1/03",
|
||||||
"enumber": "HCS03WCH1/03",
|
"haId": "SIEMENS-HCS03WCH1-7BC6383CF794"
|
||||||
"haId": "SIEMENS-HCS03WCH1-7BC6383CF794"
|
},
|
||||||
},
|
{
|
||||||
{
|
"name": "Dryer",
|
||||||
"name": "Dryer",
|
"brand": "BOSCH",
|
||||||
"brand": "BOSCH",
|
"vib": "HCS04DYR1",
|
||||||
"vib": "HCS04DYR1",
|
"connected": true,
|
||||||
"connected": true,
|
"type": "Dryer",
|
||||||
"type": "Dryer",
|
"enumber": "HCS04DYR1/03",
|
||||||
"enumber": "HCS04DYR1/03",
|
"haId": "BOSCH-HCS04DYR1-831694AE3C5A"
|
||||||
"haId": "BOSCH-HCS04DYR1-831694AE3C5A"
|
},
|
||||||
},
|
{
|
||||||
{
|
"name": "CoffeeMaker",
|
||||||
"name": "CoffeeMaker",
|
"brand": "BOSCH",
|
||||||
"brand": "BOSCH",
|
"vib": "HCS06COM1",
|
||||||
"vib": "HCS06COM1",
|
"connected": true,
|
||||||
"connected": true,
|
"type": "CoffeeMaker",
|
||||||
"type": "CoffeeMaker",
|
"enumber": "HCS06COM1/03",
|
||||||
"enumber": "HCS06COM1/03",
|
"haId": "BOSCH-HCS06COM1-D70390681C2C"
|
||||||
"haId": "BOSCH-HCS06COM1-D70390681C2C"
|
},
|
||||||
},
|
{
|
||||||
{
|
"name": "WasherDryer",
|
||||||
"name": "WasherDryer",
|
"brand": "BOSCH",
|
||||||
"brand": "BOSCH",
|
"vib": "HCS000001",
|
||||||
"vib": "HCS000001",
|
"connected": true,
|
||||||
"connected": true,
|
"type": "WasherDryer",
|
||||||
"type": "WasherDryer",
|
"enumber": "HCS000000/01",
|
||||||
"enumber": "HCS000000/01",
|
"haId": "BOSCH-HCS000000-D00000000001"
|
||||||
"haId": "BOSCH-HCS000000-D00000000001"
|
},
|
||||||
},
|
{
|
||||||
{
|
"name": "Refrigerator",
|
||||||
"name": "Refrigerator",
|
"brand": "BOSCH",
|
||||||
"brand": "BOSCH",
|
"vib": "HCS000002",
|
||||||
"vib": "HCS000002",
|
"connected": true,
|
||||||
"connected": true,
|
"type": "Refrigerator",
|
||||||
"type": "Refrigerator",
|
"enumber": "HCS000000/02",
|
||||||
"enumber": "HCS000000/02",
|
"haId": "BOSCH-HCS000000-D00000000002"
|
||||||
"haId": "BOSCH-HCS000000-D00000000002"
|
},
|
||||||
},
|
{
|
||||||
{
|
"name": "Freezer",
|
||||||
"name": "Freezer",
|
"brand": "BOSCH",
|
||||||
"brand": "BOSCH",
|
"vib": "HCS000003",
|
||||||
"vib": "HCS000003",
|
"connected": true,
|
||||||
"connected": true,
|
"type": "Freezer",
|
||||||
"type": "Freezer",
|
"enumber": "HCS000000/03",
|
||||||
"enumber": "HCS000000/03",
|
"haId": "BOSCH-HCS000000-D00000000003"
|
||||||
"haId": "BOSCH-HCS000000-D00000000003"
|
},
|
||||||
},
|
{
|
||||||
{
|
"name": "Hood",
|
||||||
"name": "Hood",
|
"brand": "BOSCH",
|
||||||
"brand": "BOSCH",
|
"vib": "HCS000004",
|
||||||
"vib": "HCS000004",
|
"connected": true,
|
||||||
"connected": true,
|
"type": "Hood",
|
||||||
"type": "Hood",
|
"enumber": "HCS000000/04",
|
||||||
"enumber": "HCS000000/04",
|
"haId": "BOSCH-HCS000000-D00000000004"
|
||||||
"haId": "BOSCH-HCS000000-D00000000004"
|
},
|
||||||
},
|
{
|
||||||
{
|
"name": "Hob",
|
||||||
"name": "Hob",
|
"brand": "BOSCH",
|
||||||
"brand": "BOSCH",
|
"vib": "HCS000005",
|
||||||
"vib": "HCS000005",
|
"connected": true,
|
||||||
"connected": true,
|
"type": "Hob",
|
||||||
"type": "Hob",
|
"enumber": "HCS000000/05",
|
||||||
"enumber": "HCS000000/05",
|
"haId": "BOSCH-HCS000000-D00000000005"
|
||||||
"haId": "BOSCH-HCS000000-D00000000005"
|
},
|
||||||
},
|
{
|
||||||
{
|
"name": "CookProcessor",
|
||||||
"name": "CookProcessor",
|
"brand": "BOSCH",
|
||||||
"brand": "BOSCH",
|
"vib": "HCS000006",
|
||||||
"vib": "HCS000006",
|
"connected": true,
|
||||||
"connected": true,
|
"type": "CookProcessor",
|
||||||
"type": "CookProcessor",
|
"enumber": "HCS000000/06",
|
||||||
"enumber": "HCS000000/06",
|
"haId": "BOSCH-HCS000000-D00000000006"
|
||||||
"haId": "BOSCH-HCS000000-D00000000006"
|
},
|
||||||
},
|
{
|
||||||
{
|
"name": "DNE",
|
||||||
"name": "DNE",
|
"brand": "BOSCH",
|
||||||
"brand": "BOSCH",
|
"vib": "HCS000000",
|
||||||
"vib": "HCS000000",
|
"connected": true,
|
||||||
"connected": true,
|
"type": "DNE",
|
||||||
"type": "DNE",
|
"enumber": "HCS000000/00",
|
||||||
"enumber": "HCS000000/00",
|
"haId": "BOSCH-000000000-000000000000"
|
||||||
"haId": "BOSCH-000000000-000000000000"
|
}
|
||||||
}
|
]
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
"""Test for Home Connect coordinator."""
|
"""Test for Home Connect coordinator."""
|
||||||
|
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
import copy
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Any
|
from typing import Any, cast
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
from aiohomeconnect.model import (
|
from aiohomeconnect.model import (
|
||||||
ArrayOfEvents,
|
ArrayOfEvents,
|
||||||
|
ArrayOfHomeAppliances,
|
||||||
ArrayOfSettings,
|
ArrayOfSettings,
|
||||||
ArrayOfStatus,
|
ArrayOfStatus,
|
||||||
Event,
|
Event,
|
||||||
EventKey,
|
EventKey,
|
||||||
EventMessage,
|
EventMessage,
|
||||||
EventType,
|
EventType,
|
||||||
|
HomeAppliance,
|
||||||
)
|
)
|
||||||
from aiohomeconnect.model.error import (
|
from aiohomeconnect.model.error import (
|
||||||
EventStreamInterruptedError,
|
EventStreamInterruptedError,
|
||||||
@ -28,6 +29,7 @@ from homeassistant.components.home_connect.const import (
|
|||||||
BSH_DOOR_STATE_OPEN,
|
BSH_DOOR_STATE_OPEN,
|
||||||
BSH_EVENT_PRESENT_STATE_PRESENT,
|
BSH_EVENT_PRESENT_STATE_PRESENT,
|
||||||
BSH_POWER_OFF,
|
BSH_POWER_OFF,
|
||||||
|
DOMAIN,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntries, ConfigEntryState
|
from homeassistant.config_entries import ConfigEntries, ConfigEntryState
|
||||||
from homeassistant.const import EVENT_STATE_REPORTED, Platform
|
from homeassistant.const import EVENT_STATE_REPORTED, Platform
|
||||||
@ -37,12 +39,10 @@ from homeassistant.core import (
|
|||||||
HomeAssistant,
|
HomeAssistant,
|
||||||
callback,
|
callback,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import MOCK_APPLIANCES
|
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
@ -81,16 +81,21 @@ async def test_coordinator_update_failing_get_appliances(
|
|||||||
|
|
||||||
@pytest.mark.usefixtures("setup_credentials")
|
@pytest.mark.usefixtures("setup_credentials")
|
||||||
@pytest.mark.parametrize("platforms", [("binary_sensor",)])
|
@pytest.mark.parametrize("platforms", [("binary_sensor",)])
|
||||||
@pytest.mark.parametrize("appliance_ha_id", ["Washer"], indirect=True)
|
@pytest.mark.parametrize("appliance", ["Washer"], indirect=True)
|
||||||
async def test_coordinator_failure_refresh_and_stream(
|
async def test_coordinator_failure_refresh_and_stream(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: MockConfigEntry,
|
config_entry: MockConfigEntry,
|
||||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||||
client: MagicMock,
|
client: MagicMock,
|
||||||
freezer: FrozenDateTimeFactory,
|
freezer: FrozenDateTimeFactory,
|
||||||
appliance_ha_id: str,
|
appliance: HomeAppliance,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test entity available state via coordinator refresh and event stream."""
|
"""Test entity available state via coordinator refresh and event stream."""
|
||||||
|
appliance_data = (
|
||||||
|
cast(str, appliance.to_json())
|
||||||
|
.replace("ha_id", "haId")
|
||||||
|
.replace("e_number", "enumber")
|
||||||
|
)
|
||||||
entity_id_1 = "binary_sensor.washer_remote_control"
|
entity_id_1 = "binary_sensor.washer_remote_control"
|
||||||
entity_id_2 = "binary_sensor.washer_remote_start"
|
entity_id_2 = "binary_sensor.washer_remote_start"
|
||||||
await async_setup_component(hass, "homeassistant", {})
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
@ -121,7 +126,9 @@ async def test_coordinator_failure_refresh_and_stream(
|
|||||||
# Test that the entity becomes available again after a successful update.
|
# Test that the entity becomes available again after a successful update.
|
||||||
|
|
||||||
client.get_home_appliances.side_effect = None
|
client.get_home_appliances.side_effect = None
|
||||||
client.get_home_appliances.return_value = copy.deepcopy(MOCK_APPLIANCES)
|
client.get_home_appliances.return_value = ArrayOfHomeAppliances(
|
||||||
|
[HomeAppliance.from_json(appliance_data)]
|
||||||
|
)
|
||||||
|
|
||||||
# Move time forward to pass the debounce time.
|
# Move time forward to pass the debounce time.
|
||||||
freezer.tick(timedelta(hours=1))
|
freezer.tick(timedelta(hours=1))
|
||||||
@ -166,11 +173,13 @@ async def test_coordinator_failure_refresh_and_stream(
|
|||||||
|
|
||||||
# Now make the entity available again.
|
# Now make the entity available again.
|
||||||
client.get_home_appliances.side_effect = None
|
client.get_home_appliances.side_effect = None
|
||||||
client.get_home_appliances.return_value = copy.deepcopy(MOCK_APPLIANCES)
|
client.get_home_appliances.return_value = ArrayOfHomeAppliances(
|
||||||
|
[HomeAppliance.from_json(appliance_data)]
|
||||||
|
)
|
||||||
|
|
||||||
# One event should make all entities for this appliance available again.
|
# One event should make all entities for this appliance available again.
|
||||||
event_message = EventMessage(
|
event_message = EventMessage(
|
||||||
appliance_ha_id,
|
appliance.ha_id,
|
||||||
EventType.STATUS,
|
EventType.STATUS,
|
||||||
ArrayOfEvents(
|
ArrayOfEvents(
|
||||||
[
|
[
|
||||||
@ -399,6 +408,9 @@ async def test_event_listener_error(
|
|||||||
assert not config_entry._background_tasks
|
assert not config_entry._background_tasks
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("setup_credentials")
|
||||||
|
@pytest.mark.parametrize("platforms", [("sensor",)])
|
||||||
|
@pytest.mark.parametrize("appliance", ["Washer"], indirect=True)
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"exception",
|
"exception",
|
||||||
[HomeConnectRequestError(), EventStreamInterruptedError()],
|
[HomeConnectRequestError(), EventStreamInterruptedError()],
|
||||||
@ -429,11 +441,10 @@ async def test_event_listener_resilience(
|
|||||||
after_event_expected_state: str,
|
after_event_expected_state: str,
|
||||||
exception: HomeConnectError,
|
exception: HomeConnectError,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
appliance: HomeAppliance,
|
||||||
|
client: MagicMock,
|
||||||
config_entry: MockConfigEntry,
|
config_entry: MockConfigEntry,
|
||||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||||
setup_credentials: None,
|
|
||||||
client: MagicMock,
|
|
||||||
appliance_ha_id: str,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test that the event listener is resilient to interruptions."""
|
"""Test that the event listener is resilient to interruptions."""
|
||||||
future = hass.loop.create_future()
|
future = hass.loop.create_future()
|
||||||
@ -467,7 +478,7 @@ async def test_event_listener_resilience(
|
|||||||
await client.add_events(
|
await client.add_events(
|
||||||
[
|
[
|
||||||
EventMessage(
|
EventMessage(
|
||||||
appliance_ha_id,
|
appliance.ha_id,
|
||||||
EventType.STATUS,
|
EventType.STATUS,
|
||||||
ArrayOfEvents(
|
ArrayOfEvents(
|
||||||
[
|
[
|
||||||
@ -489,3 +500,44 @@ async def test_event_listener_resilience(
|
|||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
assert state
|
assert state
|
||||||
assert state.state == after_event_expected_state
|
assert state.state == after_event_expected_state
|
||||||
|
|
||||||
|
|
||||||
|
async def test_devices_updated_on_refresh(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||||
|
setup_credentials: None,
|
||||||
|
client: MagicMock,
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
) -> None:
|
||||||
|
"""Test handling of devices added or deleted while event stream is down."""
|
||||||
|
appliances: list[HomeAppliance] = (
|
||||||
|
client.get_home_appliances.return_value.homeappliances
|
||||||
|
)
|
||||||
|
assert len(appliances) >= 3
|
||||||
|
client.get_home_appliances = AsyncMock(
|
||||||
|
return_value=ArrayOfHomeAppliances(appliances[:2]),
|
||||||
|
)
|
||||||
|
|
||||||
|
await async_setup_component(hass, "homeassistant", {})
|
||||||
|
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||||
|
await integration_setup(client)
|
||||||
|
assert config_entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
for appliance in appliances[:2]:
|
||||||
|
assert device_registry.async_get_device({(DOMAIN, appliance.ha_id)})
|
||||||
|
assert not device_registry.async_get_device({(DOMAIN, appliances[2].ha_id)})
|
||||||
|
|
||||||
|
client.get_home_appliances = AsyncMock(
|
||||||
|
return_value=ArrayOfHomeAppliances(appliances[1:3]),
|
||||||
|
)
|
||||||
|
await hass.services.async_call(
|
||||||
|
"homeassistant",
|
||||||
|
"update_entity",
|
||||||
|
{"entity_id": "switch.dishwasher_power"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not device_registry.async_get_device({(DOMAIN, appliances[0].ha_id)})
|
||||||
|
for appliance in appliances[2:3]:
|
||||||
|
assert device_registry.async_get_device({(DOMAIN, appliance.ha_id)})
|
||||||
|
@ -3,11 +3,15 @@
|
|||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import AsyncMock, 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,
|
||||||
|
TooManyRequestsError,
|
||||||
|
UnauthorizedError,
|
||||||
|
)
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy.assertion import SnapshotAssertion
|
from syrupy.assertion import SnapshotAssertion
|
||||||
@ -355,6 +359,48 @@ async def test_client_error(
|
|||||||
assert client_with_exception.get_home_appliances.call_count == 1
|
assert client_with_exception.get_home_appliances.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"raising_exception_method",
|
||||||
|
[
|
||||||
|
"get_settings",
|
||||||
|
"get_status",
|
||||||
|
"get_all_programs",
|
||||||
|
"get_available_commands",
|
||||||
|
"get_available_program",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_client_rate_limit_error(
|
||||||
|
raising_exception_method: str,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||||
|
setup_credentials: None,
|
||||||
|
client: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test client errors during setup integration."""
|
||||||
|
retry_after = 42
|
||||||
|
|
||||||
|
original_mock = getattr(client, raising_exception_method)
|
||||||
|
mock = AsyncMock()
|
||||||
|
|
||||||
|
async def side_effect(*args, **kwargs):
|
||||||
|
if mock.call_count <= 1:
|
||||||
|
raise TooManyRequestsError("error.key", retry_after=retry_after)
|
||||||
|
return await original_mock(*args, **kwargs)
|
||||||
|
|
||||||
|
mock.side_effect = side_effect
|
||||||
|
setattr(client, raising_exception_method, mock)
|
||||||
|
|
||||||
|
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.home_connect.coordinator.asyncio_sleep",
|
||||||
|
) as asyncio_sleep_mock:
|
||||||
|
assert await integration_setup(client)
|
||||||
|
assert config_entry.state == ConfigEntryState.LOADED
|
||||||
|
assert mock.call_count >= 2
|
||||||
|
asyncio_sleep_mock.assert_called_once_with(retry_after)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"service_call",
|
"service_call",
|
||||||
SERVICE_KV_CALL_PARAMS + SERVICE_COMMAND_CALL_PARAMS + SERVICE_PROGRAM_CALL_PARAMS,
|
SERVICE_KV_CALL_PARAMS + SERVICE_COMMAND_CALL_PARAMS + SERVICE_PROGRAM_CALL_PARAMS,
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
import random
|
import random
|
||||||
from unittest.mock import AsyncMock, MagicMock
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
from aiohomeconnect.model import (
|
from aiohomeconnect.model import (
|
||||||
ArrayOfEvents,
|
ArrayOfEvents,
|
||||||
@ -22,6 +22,7 @@ from aiohomeconnect.model.error import (
|
|||||||
HomeConnectApiError,
|
HomeConnectApiError,
|
||||||
HomeConnectError,
|
HomeConnectError,
|
||||||
SelectedProgramNotSetError,
|
SelectedProgramNotSetError,
|
||||||
|
TooManyRequestsError,
|
||||||
)
|
)
|
||||||
from aiohomeconnect.model.program import (
|
from aiohomeconnect.model.program import (
|
||||||
ProgramDefinitionConstraints,
|
ProgramDefinitionConstraints,
|
||||||
@ -47,7 +48,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -340,6 +341,98 @@ async def test_number_entity_functionality(
|
|||||||
assert hass.states.is_state(entity_id, str(float(value)))
|
assert hass.states.is_state(entity_id, str(float(value)))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("appliance_ha_id", ["FridgeFreezer"], indirect=True)
|
||||||
|
@pytest.mark.parametrize("retry_after", [0, None])
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
(
|
||||||
|
"entity_id",
|
||||||
|
"setting_key",
|
||||||
|
"type",
|
||||||
|
"min_value",
|
||||||
|
"max_value",
|
||||||
|
"step_size",
|
||||||
|
"unit_of_measurement",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
f"{NUMBER_DOMAIN.lower()}.fridgefreezer_refrigerator_temperature",
|
||||||
|
SettingKey.REFRIGERATION_FRIDGE_FREEZER_SETPOINT_TEMPERATURE_REFRIGERATOR,
|
||||||
|
"Double",
|
||||||
|
7,
|
||||||
|
15,
|
||||||
|
5,
|
||||||
|
"°C",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@patch("homeassistant.components.home_connect.entity.API_DEFAULT_RETRY_AFTER", new=0)
|
||||||
|
async def test_fetch_constraints_after_rate_limit_error(
|
||||||
|
retry_after: int | None,
|
||||||
|
appliance_ha_id: str,
|
||||||
|
entity_id: str,
|
||||||
|
setting_key: SettingKey,
|
||||||
|
type: str,
|
||||||
|
min_value: int,
|
||||||
|
max_value: int,
|
||||||
|
step_size: int,
|
||||||
|
unit_of_measurement: str,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||||
|
setup_credentials: None,
|
||||||
|
client: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test that, if a API rate limit error is raised, the constraints are fetched later."""
|
||||||
|
|
||||||
|
def get_settings_side_effect(ha_id: str):
|
||||||
|
if ha_id != appliance_ha_id:
|
||||||
|
return ArrayOfSettings([])
|
||||||
|
return ArrayOfSettings(
|
||||||
|
[
|
||||||
|
GetSetting(
|
||||||
|
key=setting_key,
|
||||||
|
raw_key=setting_key.value,
|
||||||
|
value=random.randint(min_value, max_value),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
client.get_settings = AsyncMock(side_effect=get_settings_side_effect)
|
||||||
|
client.get_setting = AsyncMock(
|
||||||
|
side_effect=[
|
||||||
|
TooManyRequestsError("error.key", retry_after=retry_after),
|
||||||
|
GetSetting(
|
||||||
|
key=setting_key,
|
||||||
|
raw_key=setting_key.value,
|
||||||
|
value=random.randint(min_value, max_value),
|
||||||
|
unit=unit_of_measurement,
|
||||||
|
type=type,
|
||||||
|
constraints=SettingConstraints(
|
||||||
|
min=min_value,
|
||||||
|
max=max_value,
|
||||||
|
step_size=step_size,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert config_entry.state is ConfigEntryState.NOT_LOADED
|
||||||
|
assert await integration_setup(client)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
assert client.get_setting.call_count == 2
|
||||||
|
|
||||||
|
entity_state = hass.states.get(entity_id)
|
||||||
|
assert entity_state
|
||||||
|
attributes = entity_state.attributes
|
||||||
|
assert attributes["min"] == min_value
|
||||||
|
assert attributes["max"] == max_value
|
||||||
|
assert attributes["step"] == step_size
|
||||||
|
assert attributes["unit_of_measurement"] == unit_of_measurement
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("entity_id", "setting_key", "mock_attr"),
|
("entity_id", "setting_key", "mock_attr"),
|
||||||
[
|
[
|
||||||
|
@ -21,6 +21,7 @@ from aiohomeconnect.model.error import (
|
|||||||
ActiveProgramNotSetError,
|
ActiveProgramNotSetError,
|
||||||
HomeConnectError,
|
HomeConnectError,
|
||||||
SelectedProgramNotSetError,
|
SelectedProgramNotSetError,
|
||||||
|
TooManyRequestsError,
|
||||||
)
|
)
|
||||||
from aiohomeconnect.model.program import (
|
from aiohomeconnect.model.program import (
|
||||||
EnumerateProgram,
|
EnumerateProgram,
|
||||||
@ -50,7 +51,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -521,9 +522,18 @@ async def test_select_functionality(
|
|||||||
(
|
(
|
||||||
"select.hood_ambient_light_color",
|
"select.hood_ambient_light_color",
|
||||||
SettingKey.BSH_COMMON_AMBIENT_LIGHT_COLOR,
|
SettingKey.BSH_COMMON_AMBIENT_LIGHT_COLOR,
|
||||||
[f"BSH.Common.EnumType.AmbientLightColor.Color{i}" for i in range(50)],
|
[f"BSH.Common.EnumType.AmbientLightColor.Color{i}" for i in range(1, 50)],
|
||||||
{str(i) for i in range(1, 50)},
|
{str(i) for i in range(1, 50)},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"select.hood_ambient_light_color",
|
||||||
|
SettingKey.BSH_COMMON_AMBIENT_LIGHT_COLOR,
|
||||||
|
[
|
||||||
|
"A.Non.Documented.Option",
|
||||||
|
"BSH.Common.EnumType.AmbientLightColor.Color42",
|
||||||
|
],
|
||||||
|
{"42"},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_fetch_allowed_values(
|
async def test_fetch_allowed_values(
|
||||||
@ -566,6 +576,139 @@ async def test_fetch_allowed_values(
|
|||||||
assert set(entity_state.attributes[ATTR_OPTIONS]) == expected_options
|
assert set(entity_state.attributes[ATTR_OPTIONS]) == expected_options
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("appliance_ha_id", ["Hood"], indirect=True)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
(
|
||||||
|
"entity_id",
|
||||||
|
"setting_key",
|
||||||
|
"allowed_values",
|
||||||
|
"expected_options",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"select.hood_ambient_light_color",
|
||||||
|
SettingKey.BSH_COMMON_AMBIENT_LIGHT_COLOR,
|
||||||
|
[f"BSH.Common.EnumType.AmbientLightColor.Color{i}" for i in range(50)],
|
||||||
|
{str(i) for i in range(1, 50)},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_fetch_allowed_values_after_rate_limit_error(
|
||||||
|
appliance_ha_id: str,
|
||||||
|
entity_id: str,
|
||||||
|
setting_key: SettingKey,
|
||||||
|
allowed_values: list[str | None],
|
||||||
|
expected_options: set[str],
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||||
|
setup_credentials: None,
|
||||||
|
client: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test fetch allowed values."""
|
||||||
|
|
||||||
|
def get_settings_side_effect(ha_id: str):
|
||||||
|
if ha_id != appliance_ha_id:
|
||||||
|
return ArrayOfSettings([])
|
||||||
|
return ArrayOfSettings(
|
||||||
|
[
|
||||||
|
GetSetting(
|
||||||
|
key=setting_key,
|
||||||
|
raw_key=setting_key.value,
|
||||||
|
value="", # Not important
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
client.get_settings = AsyncMock(side_effect=get_settings_side_effect)
|
||||||
|
client.get_setting = AsyncMock(
|
||||||
|
side_effect=[
|
||||||
|
TooManyRequestsError("error.key", retry_after=0),
|
||||||
|
GetSetting(
|
||||||
|
key=setting_key,
|
||||||
|
raw_key=setting_key.value,
|
||||||
|
value="", # Not important
|
||||||
|
constraints=SettingConstraints(
|
||||||
|
allowed_values=allowed_values,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert config_entry.state is ConfigEntryState.NOT_LOADED
|
||||||
|
assert await integration_setup(client)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
assert client.get_setting.call_count == 2
|
||||||
|
|
||||||
|
entity_state = hass.states.get(entity_id)
|
||||||
|
assert entity_state
|
||||||
|
assert set(entity_state.attributes[ATTR_OPTIONS]) == expected_options
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("appliance_ha_id", ["Hood"], indirect=True)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
(
|
||||||
|
"entity_id",
|
||||||
|
"setting_key",
|
||||||
|
"exception",
|
||||||
|
"expected_options",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"select.hood_ambient_light_color",
|
||||||
|
SettingKey.BSH_COMMON_AMBIENT_LIGHT_COLOR,
|
||||||
|
HomeConnectError(),
|
||||||
|
{
|
||||||
|
"b_s_h_common_enum_type_ambient_light_color_custom_color",
|
||||||
|
*{str(i) for i in range(1, 100)},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_default_values_after_fetch_allowed_values_error(
|
||||||
|
appliance_ha_id: str,
|
||||||
|
entity_id: str,
|
||||||
|
setting_key: SettingKey,
|
||||||
|
exception: Exception,
|
||||||
|
expected_options: set[str],
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||||
|
setup_credentials: None,
|
||||||
|
client: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test fetch allowed values."""
|
||||||
|
|
||||||
|
def get_settings_side_effect(ha_id: str):
|
||||||
|
if ha_id != appliance_ha_id:
|
||||||
|
return ArrayOfSettings([])
|
||||||
|
return ArrayOfSettings(
|
||||||
|
[
|
||||||
|
GetSetting(
|
||||||
|
key=setting_key,
|
||||||
|
raw_key=setting_key.value,
|
||||||
|
value="", # Not important
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
client.get_settings = AsyncMock(side_effect=get_settings_side_effect)
|
||||||
|
client.get_setting = AsyncMock(side_effect=exception)
|
||||||
|
|
||||||
|
assert config_entry.state is ConfigEntryState.NOT_LOADED
|
||||||
|
assert await integration_setup(client)
|
||||||
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
assert client.get_setting.call_count == 1
|
||||||
|
|
||||||
|
entity_state = hass.states.get(entity_id)
|
||||||
|
assert entity_state
|
||||||
|
assert set(entity_state.attributes[ATTR_OPTIONS]) == expected_options
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("entity_id", "setting_key", "allowed_value", "value_to_set", "mock_attr"),
|
("entity_id", "setting_key", "allowed_value", "value_to_set", "mock_attr"),
|
||||||
[
|
[
|
||||||
@ -679,6 +822,17 @@ async def test_select_entity_error(
|
|||||||
"laundry_care_washer_enum_type_temperature_ul_extra_hot",
|
"laundry_care_washer_enum_type_temperature_ul_extra_hot",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"select.washer_temperature",
|
||||||
|
OptionKey.LAUNDRY_CARE_WASHER_TEMPERATURE,
|
||||||
|
[
|
||||||
|
"A.Non.Documented.Option",
|
||||||
|
"LaundryCare.Washer.EnumType.Temperature.UlWarm",
|
||||||
|
],
|
||||||
|
{
|
||||||
|
"laundry_care_washer_enum_type_temperature_ul_warm",
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_options_functionality(
|
async def test_options_functionality(
|
||||||
|
@ -13,7 +13,7 @@ from aiohomeconnect.model import (
|
|||||||
Status,
|
Status,
|
||||||
StatusKey,
|
StatusKey,
|
||||||
)
|
)
|
||||||
from aiohomeconnect.model.error import HomeConnectApiError
|
from aiohomeconnect.model.error import HomeConnectApiError, TooManyRequestsError
|
||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -26,12 +26,13 @@ from homeassistant.components.home_connect.const import (
|
|||||||
BSH_EVENT_PRESENT_STATE_PRESENT,
|
BSH_EVENT_PRESENT_STATE_PRESENT,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.home_connect.coordinator import HomeConnectError
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, Platform
|
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
TEST_HC_APP = "Dishwasher"
|
TEST_HC_APP = "Dishwasher"
|
||||||
|
|
||||||
@ -724,3 +725,122 @@ async def test_sensor_unit_fetching(
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert client.get_status_value.call_count == get_status_value_call_count
|
assert client.get_status_value.call_count == get_status_value_call_count
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
(
|
||||||
|
"appliance_ha_id",
|
||||||
|
"entity_id",
|
||||||
|
"status_key",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"Oven",
|
||||||
|
"sensor.oven_current_oven_cavity_temperature",
|
||||||
|
StatusKey.COOKING_OVEN_CURRENT_CAVITY_TEMPERATURE,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
indirect=["appliance_ha_id"],
|
||||||
|
)
|
||||||
|
async def test_sensor_unit_fetching_error(
|
||||||
|
appliance_ha_id: str,
|
||||||
|
entity_id: str,
|
||||||
|
status_key: StatusKey,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||||
|
setup_credentials: None,
|
||||||
|
client: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test that the sensor entities are capable of fetching units."""
|
||||||
|
|
||||||
|
async def get_status_mock(ha_id: str) -> ArrayOfStatus:
|
||||||
|
if ha_id != appliance_ha_id:
|
||||||
|
return ArrayOfStatus([])
|
||||||
|
return ArrayOfStatus(
|
||||||
|
[
|
||||||
|
Status(
|
||||||
|
key=status_key,
|
||||||
|
raw_key=status_key.value,
|
||||||
|
value=0,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
client.get_status = AsyncMock(side_effect=get_status_mock)
|
||||||
|
client.get_status_value = AsyncMock(side_effect=HomeConnectError())
|
||||||
|
|
||||||
|
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||||
|
assert await integration_setup(client)
|
||||||
|
assert config_entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
assert hass.states.get(entity_id)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
(
|
||||||
|
"appliance_ha_id",
|
||||||
|
"entity_id",
|
||||||
|
"status_key",
|
||||||
|
"unit",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"Oven",
|
||||||
|
"sensor.oven_current_oven_cavity_temperature",
|
||||||
|
StatusKey.COOKING_OVEN_CURRENT_CAVITY_TEMPERATURE,
|
||||||
|
"°C",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
indirect=["appliance_ha_id"],
|
||||||
|
)
|
||||||
|
async def test_sensor_unit_fetching_after_rate_limit_error(
|
||||||
|
appliance_ha_id: str,
|
||||||
|
entity_id: str,
|
||||||
|
status_key: StatusKey,
|
||||||
|
unit: str,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||||
|
setup_credentials: None,
|
||||||
|
client: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test that the sensor entities are capable of fetching units."""
|
||||||
|
|
||||||
|
async def get_status_mock(ha_id: str) -> ArrayOfStatus:
|
||||||
|
if ha_id != appliance_ha_id:
|
||||||
|
return ArrayOfStatus([])
|
||||||
|
return ArrayOfStatus(
|
||||||
|
[
|
||||||
|
Status(
|
||||||
|
key=status_key,
|
||||||
|
raw_key=status_key.value,
|
||||||
|
value=0,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
client.get_status = AsyncMock(side_effect=get_status_mock)
|
||||||
|
client.get_status_value = AsyncMock(
|
||||||
|
side_effect=[
|
||||||
|
TooManyRequestsError("error.key", retry_after=0),
|
||||||
|
Status(
|
||||||
|
key=status_key,
|
||||||
|
raw_key=status_key.value,
|
||||||
|
value=0,
|
||||||
|
unit=unit,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||||
|
assert await integration_setup(client)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert config_entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
assert client.get_status_value.call_count == 2
|
||||||
|
|
||||||
|
entity_state = hass.states.get(entity_id)
|
||||||
|
assert entity_state
|
||||||
|
assert entity_state.attributes["unit_of_measurement"] == unit
|
||||||
|
@ -101,28 +101,60 @@
|
|||||||
"mode": "TypeB",
|
"mode": "TypeB",
|
||||||
"Group1": [
|
"Group1": [
|
||||||
{
|
{
|
||||||
|
"mode": "TypeA",
|
||||||
"groupNumber": "Group1",
|
"groupNumber": "Group1",
|
||||||
"doseType": "DoseA",
|
"doseType": "DoseA",
|
||||||
"preWetTime": 0.5,
|
"preWetTime": 0.5,
|
||||||
"preWetHoldTime": 1
|
"preWetHoldTime": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"mode": "TypeB",
|
||||||
|
"groupNumber": "Group1",
|
||||||
|
"doseType": "DoseA",
|
||||||
|
"preWetTime": 0,
|
||||||
|
"preWetHoldTime": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "TypeA",
|
||||||
"groupNumber": "Group1",
|
"groupNumber": "Group1",
|
||||||
"doseType": "DoseB",
|
"doseType": "DoseB",
|
||||||
"preWetTime": 0.5,
|
"preWetTime": 0.5,
|
||||||
"preWetHoldTime": 1
|
"preWetHoldTime": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"mode": "TypeB",
|
||||||
"groupNumber": "Group1",
|
"groupNumber": "Group1",
|
||||||
"doseType": "DoseC",
|
"doseType": "DoseB",
|
||||||
"preWetTime": 3.2999999523162842,
|
"preWetTime": 0,
|
||||||
"preWetHoldTime": 3.2999999523162842
|
"preWetHoldTime": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"mode": "TypeA",
|
||||||
|
"groupNumber": "Group1",
|
||||||
|
"doseType": "DoseC",
|
||||||
|
"preWetTime": 3.3,
|
||||||
|
"preWetHoldTime": 3.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "TypeB",
|
||||||
|
"groupNumber": "Group1",
|
||||||
|
"doseType": "DoseC",
|
||||||
|
"preWetTime": 0,
|
||||||
|
"preWetHoldTime": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "TypeA",
|
||||||
"groupNumber": "Group1",
|
"groupNumber": "Group1",
|
||||||
"doseType": "DoseD",
|
"doseType": "DoseD",
|
||||||
"preWetTime": 2,
|
"preWetTime": 2,
|
||||||
"preWetHoldTime": 2
|
"preWetHoldTime": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "TypeB",
|
||||||
|
"groupNumber": "Group1",
|
||||||
|
"doseType": "DoseD",
|
||||||
|
"preWetTime": 0,
|
||||||
|
"preWetHoldTime": 4
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -82,10 +82,18 @@
|
|||||||
"mode": "TypeB",
|
"mode": "TypeB",
|
||||||
"Group1": [
|
"Group1": [
|
||||||
{
|
{
|
||||||
|
"mode": "TypeA",
|
||||||
"groupNumber": "Group1",
|
"groupNumber": "Group1",
|
||||||
"doseType": "DoseA",
|
"doseType": "Continuous",
|
||||||
"preWetTime": 2,
|
"preWetTime": 2,
|
||||||
"preWetHoldTime": 3
|
"preWetHoldTime": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "TypeB",
|
||||||
|
"groupNumber": "Group1",
|
||||||
|
"doseType": "Continuous",
|
||||||
|
"preWetTime": 0,
|
||||||
|
"preWetHoldTime": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -27,22 +27,46 @@
|
|||||||
}),
|
}),
|
||||||
'plumbed_in': True,
|
'plumbed_in': True,
|
||||||
'prebrew_configuration': dict({
|
'prebrew_configuration': dict({
|
||||||
'1': dict({
|
'1': list([
|
||||||
'off_time': 1,
|
dict({
|
||||||
'on_time': 0.5,
|
'off_time': 1,
|
||||||
}),
|
'on_time': 0.5,
|
||||||
'2': dict({
|
}),
|
||||||
'off_time': 1,
|
dict({
|
||||||
'on_time': 0.5,
|
'off_time': 4,
|
||||||
}),
|
'on_time': 0,
|
||||||
'3': dict({
|
}),
|
||||||
'off_time': 3.299999952316284,
|
]),
|
||||||
'on_time': 3.299999952316284,
|
'2': list([
|
||||||
}),
|
dict({
|
||||||
'4': dict({
|
'off_time': 1,
|
||||||
'off_time': 2,
|
'on_time': 0.5,
|
||||||
'on_time': 2,
|
}),
|
||||||
}),
|
dict({
|
||||||
|
'off_time': 4,
|
||||||
|
'on_time': 0,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
'3': list([
|
||||||
|
dict({
|
||||||
|
'off_time': 3.3,
|
||||||
|
'on_time': 3.3,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'off_time': 4,
|
||||||
|
'on_time': 0,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
'4': list([
|
||||||
|
dict({
|
||||||
|
'off_time': 2,
|
||||||
|
'on_time': 2,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'off_time': 4,
|
||||||
|
'on_time': 0,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
'prebrew_mode': 'TypeB',
|
'prebrew_mode': 'TypeB',
|
||||||
'scale': None,
|
'scale': None,
|
||||||
|
@ -419,7 +419,7 @@
|
|||||||
'state': '121',
|
'state': '121',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-Enabled-set_prebrew_time-kwargs0-GS3 AV][GS012345_prebrew_off_time_key_1-state]
|
# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-TypeA-set_prebrew_time-kwargs0-GS3 AV][GS012345_prebrew_off_time_key_1-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -438,7 +438,7 @@
|
|||||||
'state': '1',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-Enabled-set_prebrew_time-kwargs0-GS3 AV][GS012345_prebrew_off_time_key_2-state]
|
# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-TypeA-set_prebrew_time-kwargs0-GS3 AV][GS012345_prebrew_off_time_key_2-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -457,7 +457,7 @@
|
|||||||
'state': '1',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-Enabled-set_prebrew_time-kwargs0-GS3 AV][GS012345_prebrew_off_time_key_3-state]
|
# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-TypeA-set_prebrew_time-kwargs0-GS3 AV][GS012345_prebrew_off_time_key_3-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -473,10 +473,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '3.29999995231628',
|
'state': '3.3',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-Enabled-set_prebrew_time-kwargs0-GS3 AV][GS012345_prebrew_off_time_key_4-state]
|
# name: test_pre_brew_infusion_key_numbers[prebrew_off_time-6-TypeA-set_prebrew_time-kwargs0-GS3 AV][GS012345_prebrew_off_time_key_4-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -495,7 +495,7 @@
|
|||||||
'state': '2',
|
'state': '2',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-Enabled-set_prebrew_time-kwargs1-GS3 AV][GS012345_prebrew_on_time_key_1-state]
|
# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-TypeA-set_prebrew_time-kwargs1-GS3 AV][GS012345_prebrew_on_time_key_1-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -514,7 +514,7 @@
|
|||||||
'state': '1',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-Enabled-set_prebrew_time-kwargs1-GS3 AV][GS012345_prebrew_on_time_key_2-state]
|
# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-TypeA-set_prebrew_time-kwargs1-GS3 AV][GS012345_prebrew_on_time_key_2-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -533,7 +533,7 @@
|
|||||||
'state': '1',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-Enabled-set_prebrew_time-kwargs1-GS3 AV][GS012345_prebrew_on_time_key_3-state]
|
# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-TypeA-set_prebrew_time-kwargs1-GS3 AV][GS012345_prebrew_on_time_key_3-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -549,10 +549,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '3.29999995231628',
|
'state': '3.3',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-Enabled-set_prebrew_time-kwargs1-GS3 AV][GS012345_prebrew_on_time_key_4-state]
|
# name: test_pre_brew_infusion_key_numbers[prebrew_on_time-6-TypeA-set_prebrew_time-kwargs1-GS3 AV][GS012345_prebrew_on_time_key_4-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -587,7 +587,7 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '1',
|
'state': '4',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-TypeB-set_preinfusion_time-kwargs2-GS3 AV][GS012345_preinfusion_time_key_2-state]
|
# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-TypeB-set_preinfusion_time-kwargs2-GS3 AV][GS012345_preinfusion_time_key_2-state]
|
||||||
@ -606,7 +606,7 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '1',
|
'state': '4',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-TypeB-set_preinfusion_time-kwargs2-GS3 AV][GS012345_preinfusion_time_key_3-state]
|
# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-TypeB-set_preinfusion_time-kwargs2-GS3 AV][GS012345_preinfusion_time_key_3-state]
|
||||||
@ -625,7 +625,7 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '3.29999995231628',
|
'state': '4',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-TypeB-set_preinfusion_time-kwargs2-GS3 AV][GS012345_preinfusion_time_key_4-state]
|
# name: test_pre_brew_infusion_key_numbers[preinfusion_time-7-TypeB-set_preinfusion_time-kwargs2-GS3 AV][GS012345_preinfusion_time_key_4-state]
|
||||||
@ -644,10 +644,10 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '2',
|
'state': '4',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[prebrew_off_time-set_prebrew_time-Enabled-6-kwargs0-Linea Mini]
|
# name: test_pre_brew_infusion_numbers[prebrew_off_time-set_prebrew_time-TypeA-6-kwargs0-Linea Mini]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -666,7 +666,7 @@
|
|||||||
'state': '3',
|
'state': '3',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[prebrew_off_time-set_prebrew_time-Enabled-6-kwargs0-Linea Mini].1
|
# name: test_pre_brew_infusion_numbers[prebrew_off_time-set_prebrew_time-TypeA-6-kwargs0-Linea Mini].1
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
@ -705,7 +705,7 @@
|
|||||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[prebrew_off_time-set_prebrew_time-Enabled-6-kwargs0-Micra]
|
# name: test_pre_brew_infusion_numbers[prebrew_off_time-set_prebrew_time-TypeA-6-kwargs0-Micra]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -724,7 +724,7 @@
|
|||||||
'state': '1',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[prebrew_off_time-set_prebrew_time-Enabled-6-kwargs0-Micra].1
|
# name: test_pre_brew_infusion_numbers[prebrew_off_time-set_prebrew_time-TypeA-6-kwargs0-Micra].1
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
@ -763,7 +763,7 @@
|
|||||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[prebrew_on_time-set_prebrew_time-Enabled-6-kwargs1-Linea Mini]
|
# name: test_pre_brew_infusion_numbers[prebrew_on_time-set_prebrew_time-TypeA-6-kwargs1-Linea Mini]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -782,7 +782,7 @@
|
|||||||
'state': '3',
|
'state': '3',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[prebrew_on_time-set_prebrew_time-Enabled-6-kwargs1-Linea Mini].1
|
# name: test_pre_brew_infusion_numbers[prebrew_on_time-set_prebrew_time-TypeA-6-kwargs1-Linea Mini].1
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
@ -821,7 +821,7 @@
|
|||||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[prebrew_on_time-set_prebrew_time-Enabled-6-kwargs1-Micra]
|
# name: test_pre_brew_infusion_numbers[prebrew_on_time-set_prebrew_time-TypeA-6-kwargs1-Micra]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'duration',
|
'device_class': 'duration',
|
||||||
@ -840,7 +840,7 @@
|
|||||||
'state': '1',
|
'state': '1',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[prebrew_on_time-set_prebrew_time-Enabled-6-kwargs1-Micra].1
|
# name: test_pre_brew_infusion_numbers[prebrew_on_time-set_prebrew_time-TypeA-6-kwargs1-Micra].1
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
@ -953,7 +953,7 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '1',
|
'state': '4',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_pre_brew_infusion_numbers[preinfusion_time-set_preinfusion_time-TypeB-7-kwargs2-Micra].1
|
# name: test_pre_brew_infusion_numbers[preinfusion_time-set_preinfusion_time-TypeB-7-kwargs2-Micra].1
|
||||||
|
@ -170,12 +170,18 @@ async def test_bluetooth_is_set_from_discovery(
|
|||||||
"homeassistant.components.lamarzocco.async_discovered_service_info",
|
"homeassistant.components.lamarzocco.async_discovered_service_info",
|
||||||
return_value=[service_info],
|
return_value=[service_info],
|
||||||
) as discovery,
|
) as discovery,
|
||||||
patch("homeassistant.components.lamarzocco.LaMarzoccoMachine") as init_device,
|
patch(
|
||||||
|
"homeassistant.components.lamarzocco.LaMarzoccoMachine"
|
||||||
|
) as mock_machine_class,
|
||||||
):
|
):
|
||||||
|
mock_machine = MagicMock()
|
||||||
|
mock_machine.get_firmware = AsyncMock()
|
||||||
|
mock_machine.firmware = mock_lamarzocco.firmware
|
||||||
|
mock_machine_class.return_value = mock_machine
|
||||||
await async_init_integration(hass, mock_config_entry)
|
await async_init_integration(hass, mock_config_entry)
|
||||||
discovery.assert_called_once()
|
discovery.assert_called_once()
|
||||||
init_device.assert_called_once()
|
assert mock_machine_class.call_count == 2
|
||||||
_, kwargs = init_device.call_args
|
_, kwargs = mock_machine_class.call_args
|
||||||
assert kwargs["bluetooth_client"] is not None
|
assert kwargs["bluetooth_client"] is not None
|
||||||
assert mock_config_entry.data[CONF_NAME] == service_info.name
|
assert mock_config_entry.data[CONF_NAME] == service_info.name
|
||||||
assert mock_config_entry.data[CONF_MAC] == service_info.address
|
assert mock_config_entry.data[CONF_MAC] == service_info.address
|
||||||
@ -223,6 +229,19 @@ async def test_gateway_version_issue(
|
|||||||
assert (issue is not None) == issue_exists
|
assert (issue is not None) == issue_exists
|
||||||
|
|
||||||
|
|
||||||
|
async def test_conf_host_removed_for_new_gateway(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_lamarzocco: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Make sure we get the issue for certain gateway firmware versions."""
|
||||||
|
mock_lamarzocco.firmware[FirmwareType.GATEWAY].current_version = "v5.0.9"
|
||||||
|
|
||||||
|
await async_init_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
|
assert CONF_HOST not in mock_config_entry.data
|
||||||
|
|
||||||
|
|
||||||
async def test_device(
|
async def test_device(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_lamarzocco: MagicMock,
|
mock_lamarzocco: MagicMock,
|
||||||
|
@ -46,8 +46,12 @@ def get_mock_session(
|
|||||||
|
|
||||||
mock_response = Mock()
|
mock_response = Mock()
|
||||||
mock_response.content_length = content_length
|
mock_response.content_length = content_length
|
||||||
|
mock_response.headers = {}
|
||||||
|
mock_response.status = 200
|
||||||
|
mock_response.reason = "OK"
|
||||||
mock_response.content_type = content_type
|
mock_response.content_type = content_type
|
||||||
mock_response.content.iter_chunked = Mock(return_value=content)
|
mock_response.content.iter_chunked = Mock(return_value=content)
|
||||||
|
mock_response.text = AsyncMock(return_value="test")
|
||||||
|
|
||||||
mock_session = Mock()
|
mock_session = Mock()
|
||||||
mock_session.get = AsyncMock(return_value=mock_response)
|
mock_session.get = AsyncMock(return_value=mock_response)
|
||||||
@ -178,16 +182,18 @@ async def test_playback_proxy_timeout(
|
|||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(("content_type"), [("video/x-flv"), ("text/html")])
|
||||||
async def test_playback_wrong_content(
|
async def test_playback_wrong_content(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
reolink_connect: MagicMock,
|
reolink_connect: MagicMock,
|
||||||
config_entry: MockConfigEntry,
|
config_entry: MockConfigEntry,
|
||||||
hass_client: ClientSessionGenerator,
|
hass_client: ClientSessionGenerator,
|
||||||
|
content_type: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test playback proxy URL with a wrong content type in the response."""
|
"""Test playback proxy URL with a wrong content type in the response."""
|
||||||
reolink_connect.get_vod_source.return_value = (TEST_MIME_TYPE_MP4, TEST_URL)
|
reolink_connect.get_vod_source.return_value = (TEST_MIME_TYPE_MP4, TEST_URL)
|
||||||
|
|
||||||
mock_session = get_mock_session(content_type="video/x-flv")
|
mock_session = get_mock_session(content_type=content_type)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.reolink.views.async_get_clientsession",
|
"homeassistant.components.reolink.views.async_get_clientsession",
|
||||||
|
@ -110,6 +110,7 @@ def mock_smartthings() -> Generator[AsyncMock]:
|
|||||||
"da_rvc_normal_000001",
|
"da_rvc_normal_000001",
|
||||||
"da_ks_microwave_0101x",
|
"da_ks_microwave_0101x",
|
||||||
"da_ks_range_0101x",
|
"da_ks_range_0101x",
|
||||||
|
"da_ks_oven_01061",
|
||||||
"hue_color_temperature_bulb",
|
"hue_color_temperature_bulb",
|
||||||
"hue_rgbw_color_bulb",
|
"hue_rgbw_color_bulb",
|
||||||
"c2c_shade",
|
"c2c_shade",
|
||||||
@ -131,6 +132,7 @@ def mock_smartthings() -> Generator[AsyncMock]:
|
|||||||
"abl_light_b_001",
|
"abl_light_b_001",
|
||||||
"tplink_p110",
|
"tplink_p110",
|
||||||
"ikea_kadrilj",
|
"ikea_kadrilj",
|
||||||
|
"aux_ac",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
def device_fixture(
|
def device_fixture(
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"components": {
|
||||||
|
"main": {
|
||||||
|
"partyvoice23922.vtempset": {
|
||||||
|
"vtemp": {
|
||||||
|
"value": 20,
|
||||||
|
"unit": "C",
|
||||||
|
"timestamp": "2024-12-05T20:03:33.161Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"airConditionerFanMode": {
|
||||||
|
"fanMode": {
|
||||||
|
"value": "auto",
|
||||||
|
"timestamp": "2024-12-05T20:03:32.930Z"
|
||||||
|
},
|
||||||
|
"supportedAcFanModes": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"availableAcFanModes": {
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"temperatureMeasurement": {
|
||||||
|
"temperatureRange": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"temperature": {
|
||||||
|
"value": 20.0,
|
||||||
|
"unit": "C",
|
||||||
|
"timestamp": "2024-12-05T20:03:33.066Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"airConditionerMode": {
|
||||||
|
"availableAcModes": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"supportedAcModes": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"airConditionerMode": {
|
||||||
|
"value": "cool",
|
||||||
|
"timestamp": "2024-12-05T20:03:32.845Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fanSpeed": {
|
||||||
|
"fanSpeed": {
|
||||||
|
"value": 0,
|
||||||
|
"timestamp": "2024-12-05T20:03:33.334Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"thermostatCoolingSetpoint": {
|
||||||
|
"coolingSetpointRange": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"coolingSetpoint": {
|
||||||
|
"value": 20.0,
|
||||||
|
"unit": "C",
|
||||||
|
"timestamp": "2024-12-05T20:03:33.243Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"switch": {
|
||||||
|
"switch": {
|
||||||
|
"value": "off",
|
||||||
|
"timestamp": "2024-12-05T20:03:32.662Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,566 @@
|
|||||||
|
{
|
||||||
|
"components": {
|
||||||
|
"main": {
|
||||||
|
"ovenSetpoint": {
|
||||||
|
"ovenSetpointRange": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"ovenSetpoint": {
|
||||||
|
"value": 220,
|
||||||
|
"timestamp": "2025-03-15T12:06:07.818Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"refresh": {},
|
||||||
|
"samsungce.doorState": {
|
||||||
|
"doorState": {
|
||||||
|
"value": "closed",
|
||||||
|
"timestamp": "2025-03-15T09:25:35.157Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"samsungce.microwavePower": {
|
||||||
|
"supportedPowerLevels": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"powerLevel": {
|
||||||
|
"value": "0W",
|
||||||
|
"timestamp": "2025-03-15T12:06:07.803Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"samsungce.waterReservoir": {
|
||||||
|
"slotState": {
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"samsungce.kitchenDeviceDefaults": {
|
||||||
|
"defaultOperationTime": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"defaultOvenMode": {
|
||||||
|
"value": "Convection",
|
||||||
|
"timestamp": "2025-03-15T12:06:07.758Z"
|
||||||
|
},
|
||||||
|
"defaultOvenSetpoint": {
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"execute": {
|
||||||
|
"data": {
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"samsungce.deviceIdentification": {
|
||||||
|
"micomAssayCode": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"modelName": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"serialNumber": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"serialNumberExtra": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"modelClassificationCode": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"releaseYear": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"binaryId": {
|
||||||
|
"value": "TP1X_DA-KS-OVEN-01061",
|
||||||
|
"timestamp": "2025-03-13T20:35:02.073Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"samsungce.ovenDrainageRequirement": {
|
||||||
|
"drainageRequirement": {
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ocf": {
|
||||||
|
"st": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"mndt": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"mnfv": {
|
||||||
|
"value": "AKS-WW-TP1X-21-OVEN_40211229",
|
||||||
|
"timestamp": "2025-01-08T17:29:14.260Z"
|
||||||
|
},
|
||||||
|
"mnhw": {
|
||||||
|
"value": "Realtek",
|
||||||
|
"timestamp": "2025-01-08T17:29:14.260Z"
|
||||||
|
},
|
||||||
|
"di": {
|
||||||
|
"value": "9447959a-0dfa-6b27-d40d-650da525c53f",
|
||||||
|
"timestamp": "2025-01-08T17:29:14.260Z"
|
||||||
|
},
|
||||||
|
"mnsl": {
|
||||||
|
"value": "http://www.samsung.com",
|
||||||
|
"timestamp": "2025-01-08T17:29:14.260Z"
|
||||||
|
},
|
||||||
|
"dmv": {
|
||||||
|
"value": "res.1.1.0,sh.1.1.0",
|
||||||
|
"timestamp": "2025-01-08T17:29:14.260Z"
|
||||||
|
},
|
||||||
|
"n": {
|
||||||
|
"value": "[oven] Samsung",
|
||||||
|
"timestamp": "2025-01-08T17:29:14.260Z"
|
||||||
|
},
|
||||||
|
"mnmo": {
|
||||||
|
"value": "TP1X_DA-KS-OVEN-01061|40457041|50030018001611000A00000000000000",
|
||||||
|
"timestamp": "2025-01-08T17:29:14.260Z"
|
||||||
|
},
|
||||||
|
"vid": {
|
||||||
|
"value": "DA-KS-OVEN-01061",
|
||||||
|
"timestamp": "2025-01-08T17:29:14.260Z"
|
||||||
|
},
|
||||||
|
"mnmn": {
|
||||||
|
"value": "Samsung Electronics",
|
||||||
|
"timestamp": "2025-01-08T17:29:14.260Z"
|
||||||
|
},
|
||||||
|
"mnml": {
|
||||||
|
"value": "http://www.samsung.com",
|
||||||
|
"timestamp": "2025-01-08T17:29:14.260Z"
|
||||||
|
},
|
||||||
|
"mnpv": {
|
||||||
|
"value": "DAWIT 3.0",
|
||||||
|
"timestamp": "2025-01-08T17:29:14.260Z"
|
||||||
|
},
|
||||||
|
"mnos": {
|
||||||
|
"value": "TizenRT 3.1",
|
||||||
|
"timestamp": "2025-01-08T17:29:14.260Z"
|
||||||
|
},
|
||||||
|
"pi": {
|
||||||
|
"value": "9447959a-0dfa-6b27-d40d-650da525c53f",
|
||||||
|
"timestamp": "2025-01-08T17:29:14.260Z"
|
||||||
|
},
|
||||||
|
"icv": {
|
||||||
|
"value": "core.1.1.0",
|
||||||
|
"timestamp": "2025-01-08T17:29:14.260Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"remoteControlStatus": {
|
||||||
|
"remoteControlEnabled": {
|
||||||
|
"value": "true",
|
||||||
|
"timestamp": "2025-03-15T09:47:55.406Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"samsungce.kitchenDeviceIdentification": {
|
||||||
|
"regionCode": {
|
||||||
|
"value": "EU",
|
||||||
|
"timestamp": "2025-03-15T12:06:07.758Z"
|
||||||
|
},
|
||||||
|
"modelCode": {
|
||||||
|
"value": "NQ7000B-/EU7",
|
||||||
|
"timestamp": "2025-03-15T12:06:07.758Z"
|
||||||
|
},
|
||||||
|
"fuel": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"value": "oven",
|
||||||
|
"timestamp": "2025-01-08T17:29:12.924Z"
|
||||||
|
},
|
||||||
|
"representativeComponent": {
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"samsungce.kitchenModeSpecification": {
|
||||||
|
"specification": {
|
||||||
|
"value": {
|
||||||
|
"single": [
|
||||||
|
{
|
||||||
|
"mode": "NoOperation",
|
||||||
|
"supportedOperations": [],
|
||||||
|
"supportedOptions": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "Autocook",
|
||||||
|
"supportedOperations": [],
|
||||||
|
"supportedOptions": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "Convection",
|
||||||
|
"supportedOperations": ["start", "set"],
|
||||||
|
"supportedOptions": {
|
||||||
|
"temperature": {
|
||||||
|
"C": {
|
||||||
|
"min": 40,
|
||||||
|
"max": 230,
|
||||||
|
"default": 160,
|
||||||
|
"resolution": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"operationTime": {
|
||||||
|
"min": "00:01:00",
|
||||||
|
"max": "10:00:00",
|
||||||
|
"default": "01:00:00",
|
||||||
|
"resolution": "00:01:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "FanConventional",
|
||||||
|
"supportedOperations": ["start", "set"],
|
||||||
|
"supportedOptions": {
|
||||||
|
"temperature": {
|
||||||
|
"C": {
|
||||||
|
"min": 40,
|
||||||
|
"max": 230,
|
||||||
|
"default": 180,
|
||||||
|
"resolution": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"operationTime": {
|
||||||
|
"min": "00:01:00",
|
||||||
|
"max": "10:00:00",
|
||||||
|
"default": "01:00:00",
|
||||||
|
"resolution": "00:01:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "LargeGrill",
|
||||||
|
"supportedOperations": ["start", "set"],
|
||||||
|
"supportedOptions": {
|
||||||
|
"temperature": {
|
||||||
|
"C": {
|
||||||
|
"min": 150,
|
||||||
|
"max": 230,
|
||||||
|
"default": 220,
|
||||||
|
"resolution": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"operationTime": {
|
||||||
|
"min": "00:01:00",
|
||||||
|
"max": "10:00:00",
|
||||||
|
"default": "01:00:00",
|
||||||
|
"resolution": "00:01:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "FanGrill",
|
||||||
|
"supportedOperations": ["start", "set"],
|
||||||
|
"supportedOptions": {
|
||||||
|
"temperature": {
|
||||||
|
"C": {
|
||||||
|
"min": 40,
|
||||||
|
"max": 230,
|
||||||
|
"default": 180,
|
||||||
|
"resolution": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"operationTime": {
|
||||||
|
"min": "00:01:00",
|
||||||
|
"max": "10:00:00",
|
||||||
|
"default": "01:00:00",
|
||||||
|
"resolution": "00:01:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "MicroWaveGrill",
|
||||||
|
"supportedOperations": ["set"],
|
||||||
|
"supportedOptions": {
|
||||||
|
"temperature": {
|
||||||
|
"C": {
|
||||||
|
"min": 40,
|
||||||
|
"max": 200,
|
||||||
|
"default": 200,
|
||||||
|
"resolution": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"operationTime": {
|
||||||
|
"min": "00:00:10",
|
||||||
|
"max": "01:30:00",
|
||||||
|
"default": "00:00:30",
|
||||||
|
"resolution": "00:00:10"
|
||||||
|
},
|
||||||
|
"powerLevel": {
|
||||||
|
"default": "300W",
|
||||||
|
"supportedValues": ["100W", "180W", "300W", "450W", "600W"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "MicroWaveConvection",
|
||||||
|
"supportedOperations": ["set"],
|
||||||
|
"supportedOptions": {
|
||||||
|
"temperature": {
|
||||||
|
"C": {
|
||||||
|
"min": 40,
|
||||||
|
"max": 200,
|
||||||
|
"default": 180,
|
||||||
|
"resolution": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"operationTime": {
|
||||||
|
"min": "00:00:10",
|
||||||
|
"max": "01:30:00",
|
||||||
|
"default": "00:00:30",
|
||||||
|
"resolution": "00:00:10"
|
||||||
|
},
|
||||||
|
"powerLevel": {
|
||||||
|
"default": "300W",
|
||||||
|
"supportedValues": ["100W", "180W", "300W", "450W", "600W"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "AirFryer",
|
||||||
|
"supportedOperations": ["start", "set"],
|
||||||
|
"supportedOptions": {
|
||||||
|
"temperature": {
|
||||||
|
"C": {
|
||||||
|
"min": 150,
|
||||||
|
"max": 230,
|
||||||
|
"default": 220,
|
||||||
|
"resolution": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"operationTime": {
|
||||||
|
"min": "00:01:00",
|
||||||
|
"max": "10:00:00",
|
||||||
|
"default": "01:00:00",
|
||||||
|
"resolution": "00:01:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "MicroWave",
|
||||||
|
"supportedOperations": ["set"],
|
||||||
|
"supportedOptions": {
|
||||||
|
"operationTime": {
|
||||||
|
"min": "00:00:10",
|
||||||
|
"max": "01:30:00",
|
||||||
|
"default": "00:00:30",
|
||||||
|
"resolution": "00:00:10"
|
||||||
|
},
|
||||||
|
"powerLevel": {
|
||||||
|
"default": "800W",
|
||||||
|
"supportedValues": [
|
||||||
|
"100W",
|
||||||
|
"180W",
|
||||||
|
"300W",
|
||||||
|
"450W",
|
||||||
|
"600W",
|
||||||
|
"700W",
|
||||||
|
"800W"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "Deodorization",
|
||||||
|
"supportedOperations": ["start", "set"],
|
||||||
|
"supportedOptions": {
|
||||||
|
"operationTime": {
|
||||||
|
"min": "00:00:10",
|
||||||
|
"max": "00:15:00",
|
||||||
|
"default": "00:05:00",
|
||||||
|
"resolution": "00:00:10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "KeepWarm",
|
||||||
|
"supportedOperations": ["start", "set"],
|
||||||
|
"supportedOptions": {
|
||||||
|
"temperature": {
|
||||||
|
"C": {
|
||||||
|
"min": 60,
|
||||||
|
"max": 100,
|
||||||
|
"default": 60,
|
||||||
|
"resolution": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"operationTime": {
|
||||||
|
"min": "00:01:00",
|
||||||
|
"max": "10:00:00",
|
||||||
|
"default": "01:00:00",
|
||||||
|
"resolution": "00:01:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "SteamClean",
|
||||||
|
"supportedOperations": ["set"],
|
||||||
|
"supportedOptions": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timestamp": "2025-01-08T17:29:14.757Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"custom.disabledCapabilities": {
|
||||||
|
"disabledCapabilities": {
|
||||||
|
"value": [
|
||||||
|
"samsungce.waterReservoir",
|
||||||
|
"samsungce.ovenDrainageRequirement"
|
||||||
|
],
|
||||||
|
"timestamp": "2025-03-15T12:06:07.758Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"samsungce.definedRecipe": {
|
||||||
|
"definedRecipe": {
|
||||||
|
"value": {
|
||||||
|
"cavityId": "0",
|
||||||
|
"recipeType": "0",
|
||||||
|
"categoryId": 0,
|
||||||
|
"itemId": 0,
|
||||||
|
"servingSize": 0,
|
||||||
|
"browingLevel": 0,
|
||||||
|
"option": 0
|
||||||
|
},
|
||||||
|
"timestamp": "2025-03-15T12:06:07.803Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"samsungce.driverVersion": {
|
||||||
|
"versionNumber": {
|
||||||
|
"value": 22100101,
|
||||||
|
"timestamp": "2025-01-08T17:29:12.924Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"samsungce.softwareUpdate": {
|
||||||
|
"targetModule": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"otnDUID": {
|
||||||
|
"value": "43CB2ZD4VUEGW",
|
||||||
|
"timestamp": "2025-03-13T20:35:02.073Z"
|
||||||
|
},
|
||||||
|
"lastUpdatedDate": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"availableModules": {
|
||||||
|
"value": [],
|
||||||
|
"timestamp": "2025-03-13T20:35:02.073Z"
|
||||||
|
},
|
||||||
|
"newVersionAvailable": {
|
||||||
|
"value": false,
|
||||||
|
"timestamp": "2025-03-13T20:35:02.073Z"
|
||||||
|
},
|
||||||
|
"operatingState": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"progress": {
|
||||||
|
"value": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"temperatureMeasurement": {
|
||||||
|
"temperatureRange": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"temperature": {
|
||||||
|
"value": 30,
|
||||||
|
"unit": "C",
|
||||||
|
"timestamp": "2025-03-15T12:06:32.918Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"samsungce.ovenOperatingState": {
|
||||||
|
"completionTime": {
|
||||||
|
"value": "2025-03-15T12:06:09.550Z",
|
||||||
|
"timestamp": "2025-03-15T12:06:09.554Z"
|
||||||
|
},
|
||||||
|
"operatingState": {
|
||||||
|
"value": "running",
|
||||||
|
"timestamp": "2025-03-15T12:06:07.866Z"
|
||||||
|
},
|
||||||
|
"progress": {
|
||||||
|
"value": 0,
|
||||||
|
"timestamp": "2025-03-15T12:06:07.866Z"
|
||||||
|
},
|
||||||
|
"ovenJobState": {
|
||||||
|
"value": "preheat",
|
||||||
|
"timestamp": "2025-03-15T12:06:07.803Z"
|
||||||
|
},
|
||||||
|
"operationTime": {
|
||||||
|
"value": "00:00:00",
|
||||||
|
"timestamp": "2025-03-15T12:06:07.866Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ovenMode": {
|
||||||
|
"supportedOvenModes": {
|
||||||
|
"value": ["Others", "Bake", "Broil", "ConvectionBroil", "warming"],
|
||||||
|
"timestamp": "2025-01-08T17:29:14.757Z"
|
||||||
|
},
|
||||||
|
"ovenMode": {
|
||||||
|
"value": "Bake",
|
||||||
|
"timestamp": "2025-03-15T12:06:07.758Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ovenOperatingState": {
|
||||||
|
"completionTime": {
|
||||||
|
"value": "2025-03-15T12:06:09.550Z",
|
||||||
|
"timestamp": "2025-03-15T12:06:09.554Z"
|
||||||
|
},
|
||||||
|
"machineState": {
|
||||||
|
"value": "running",
|
||||||
|
"timestamp": "2025-03-15T12:06:07.866Z"
|
||||||
|
},
|
||||||
|
"progress": {
|
||||||
|
"value": 0,
|
||||||
|
"unit": "%",
|
||||||
|
"timestamp": "2025-03-15T12:06:07.866Z"
|
||||||
|
},
|
||||||
|
"supportedMachineStates": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"ovenJobState": {
|
||||||
|
"value": "preheat",
|
||||||
|
"timestamp": "2025-03-15T12:06:07.803Z"
|
||||||
|
},
|
||||||
|
"operationTime": {
|
||||||
|
"value": 0,
|
||||||
|
"timestamp": "2025-03-15T12:06:07.866Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"samsungce.ovenMode": {
|
||||||
|
"supportedOvenModes": {
|
||||||
|
"value": [
|
||||||
|
"NoOperation",
|
||||||
|
"Autocook",
|
||||||
|
"Convection",
|
||||||
|
"FanConventional",
|
||||||
|
"LargeGrill",
|
||||||
|
"FanGrill",
|
||||||
|
"MicroWaveGrill",
|
||||||
|
"MicroWaveConvection",
|
||||||
|
"AirFryer",
|
||||||
|
"MicroWave",
|
||||||
|
"Deodorization",
|
||||||
|
"KeepWarm",
|
||||||
|
"SteamClean"
|
||||||
|
],
|
||||||
|
"timestamp": "2025-01-08T17:29:14.757Z"
|
||||||
|
},
|
||||||
|
"ovenMode": {
|
||||||
|
"value": "Convection",
|
||||||
|
"timestamp": "2025-03-15T12:06:07.758Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"samsungce.lamp": {
|
||||||
|
"brightnessLevel": {
|
||||||
|
"value": "high",
|
||||||
|
"timestamp": "2025-03-15T12:06:07.956Z"
|
||||||
|
},
|
||||||
|
"supportedBrightnessLevel": {
|
||||||
|
"value": ["off", "high"],
|
||||||
|
"timestamp": "2025-03-15T12:06:07.758Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"samsungce.kidsLock": {
|
||||||
|
"lockState": {
|
||||||
|
"value": "unlocked",
|
||||||
|
"timestamp": "2025-03-13T20:35:02.170Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
81
tests/components/smartthings/fixtures/devices/aux_ac.json
Normal file
81
tests/components/smartthings/fixtures/devices/aux_ac.json
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"deviceId": "bf53a150-f8a4-45d1-aac4-86252475d551",
|
||||||
|
"name": "vedgeaircon.v1",
|
||||||
|
"label": "AUX A/C on-off",
|
||||||
|
"manufacturerName": "SmartThingsCommunity",
|
||||||
|
"presentationId": "ab252042-5669-3c2c-8b1b-d606bbcc9e04",
|
||||||
|
"deviceManufacturerCode": "SmartThings Community",
|
||||||
|
"locationId": "5db1e3d8-ea26-44b4-8ed0-1ba9c841fd57",
|
||||||
|
"ownerId": "5404aa57-6a68-4fe2-83ff-168ef769d1c7",
|
||||||
|
"roomId": "564cdd9a-fa9f-4187-902f-95656ef22989",
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"id": "main",
|
||||||
|
"label": "main",
|
||||||
|
"capabilities": [
|
||||||
|
{
|
||||||
|
"id": "switch",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "airConditionerMode",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "thermostatCoolingSetpoint",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "airConditionerFanMode",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fanSpeed",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "temperatureMeasurement",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "partyvoice23922.vtempset",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"name": "AirConditioner",
|
||||||
|
"categoryType": "manufacturer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"createTime": "2024-06-19T20:18:45.407Z",
|
||||||
|
"parentDeviceId": "e699599d-30f8-4cf0-8de7-6dbdba6a665f",
|
||||||
|
"profile": {
|
||||||
|
"id": "87f0ac35-e024-3c0a-8153-78ca27a6fe0c"
|
||||||
|
},
|
||||||
|
"lan": {
|
||||||
|
"networkId": "vEdge_A/C_1718828324.999",
|
||||||
|
"driverId": "0fd9a9a4-8863-4a83-97a7-5a288ff0f5a6",
|
||||||
|
"executingLocally": true,
|
||||||
|
"hubId": "e699599d-30f8-4cf0-8de7-6dbdba6a665f",
|
||||||
|
"provisioningState": "TYPED"
|
||||||
|
},
|
||||||
|
"type": "LAN",
|
||||||
|
"restrictionTier": 0,
|
||||||
|
"allowed": null,
|
||||||
|
"indoorMap": {
|
||||||
|
"coordinates": [130.0, 36.0, 378.0],
|
||||||
|
"rotation": [270.0, 0.0, 0.0],
|
||||||
|
"visible": true,
|
||||||
|
"data": null
|
||||||
|
},
|
||||||
|
"executionContext": "LOCAL",
|
||||||
|
"relationships": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_links": {}
|
||||||
|
}
|
@ -0,0 +1,153 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"deviceId": "9447959a-0dfa-6b27-d40d-650da525c53f",
|
||||||
|
"name": "[oven] Samsung",
|
||||||
|
"label": "Oven",
|
||||||
|
"manufacturerName": "Samsung Electronics",
|
||||||
|
"presentationId": "DA-KS-OVEN-01061",
|
||||||
|
"deviceManufacturerCode": "Samsung Electronics",
|
||||||
|
"locationId": "a81dc8da-5a3f-43b6-8c8a-1309f37eeeb9",
|
||||||
|
"ownerId": "97ee2149-9de0-3287-8245-24d6fd1609aa",
|
||||||
|
"roomId": "eb2167dd-8b8d-4131-b59e-5dd391b2e151",
|
||||||
|
"deviceTypeName": "Samsung OCF Oven",
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"id": "main",
|
||||||
|
"label": "main",
|
||||||
|
"capabilities": [
|
||||||
|
{
|
||||||
|
"id": "ocf",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "execute",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "refresh",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "remoteControlStatus",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ovenSetpoint",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ovenMode",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ovenOperatingState",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "temperatureMeasurement",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "samsungce.deviceIdentification",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "samsungce.doorState",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "samsungce.definedRecipe",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "samsungce.kitchenDeviceIdentification",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "samsungce.kitchenDeviceDefaults",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "samsungce.driverVersion",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "samsungce.ovenMode",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "samsungce.ovenOperatingState",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "samsungce.microwavePower",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "samsungce.lamp",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "samsungce.kitchenModeSpecification",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "samsungce.kidsLock",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "samsungce.softwareUpdate",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "samsungce.waterReservoir",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "samsungce.ovenDrainageRequirement",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "custom.disabledCapabilities",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"name": "Oven",
|
||||||
|
"categoryType": "manufacturer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"createTime": "2025-01-08T17:29:12.549Z",
|
||||||
|
"profile": {
|
||||||
|
"id": "eb34598f-f96a-3420-a90a-71693052eaa3"
|
||||||
|
},
|
||||||
|
"ocf": {
|
||||||
|
"ocfDeviceType": "oic.d.oven",
|
||||||
|
"name": "[oven] Samsung",
|
||||||
|
"specVersion": "core.1.1.0",
|
||||||
|
"verticalDomainSpecVersion": "res.1.1.0,sh.1.1.0",
|
||||||
|
"manufacturerName": "Samsung Electronics",
|
||||||
|
"modelNumber": "TP1X_DA-KS-OVEN-01061|40457041|50030018001611000A00000000000000",
|
||||||
|
"platformVersion": "DAWIT 3.0",
|
||||||
|
"platformOS": "TizenRT 3.1",
|
||||||
|
"hwVersion": "Realtek",
|
||||||
|
"firmwareVersion": "AKS-WW-TP1X-21-OVEN_40211229",
|
||||||
|
"vendorId": "DA-KS-OVEN-01061",
|
||||||
|
"vendorResourceClientServerVersion": "Realtek Release 3.1.211122",
|
||||||
|
"lastSignupTime": "2025-01-08T17:29:08.536664213Z",
|
||||||
|
"transferCandidate": false,
|
||||||
|
"additionalAuthCodeRequired": false
|
||||||
|
},
|
||||||
|
"type": "OCF",
|
||||||
|
"restrictionTier": 0,
|
||||||
|
"allowed": null,
|
||||||
|
"executionContext": "CLOUD",
|
||||||
|
"relationships": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_links": {}
|
||||||
|
}
|
@ -1,4 +1,68 @@
|
|||||||
# serializer version: 1
|
# serializer version: 1
|
||||||
|
# name: test_all_entities[aux_ac][climate.aux_a_c_on_off-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'fan_modes': None,
|
||||||
|
'hvac_modes': list([
|
||||||
|
<HVACMode.OFF: 'off'>,
|
||||||
|
]),
|
||||||
|
'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.aux_a_c_on_off',
|
||||||
|
'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: 393>,
|
||||||
|
'translation_key': None,
|
||||||
|
'unique_id': 'bf53a150-f8a4-45d1-aac4-86252475d551',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[aux_ac][climate.aux_a_c_on_off-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'current_temperature': 20.0,
|
||||||
|
'fan_mode': 'auto',
|
||||||
|
'fan_modes': None,
|
||||||
|
'friendly_name': 'AUX A/C on-off',
|
||||||
|
'hvac_modes': list([
|
||||||
|
<HVACMode.OFF: 'off'>,
|
||||||
|
]),
|
||||||
|
'max_temp': 35,
|
||||||
|
'min_temp': 7,
|
||||||
|
'supported_features': <ClimateEntityFeature: 393>,
|
||||||
|
'temperature': 20.0,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'climate.aux_a_c_on_off',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'off',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_all_entities[bosch_radiator_thermostat_ii][climate.radiator_thermostat_ii_m_wohnzimmer-entry]
|
# name: test_all_entities[bosch_radiator_thermostat_ii][climate.radiator_thermostat_ii_m_wohnzimmer-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
@ -1,307 +1,311 @@
|
|||||||
# serializer version: 1
|
# serializer version: 1
|
||||||
# name: test_config_entry_diagnostics[da_ac_rac_000001]
|
# name: test_config_entry_diagnostics[da_ac_rac_000001]
|
||||||
dict({
|
dict({
|
||||||
'_links': dict({
|
'devices': list([
|
||||||
}),
|
|
||||||
'items': list([
|
|
||||||
dict({
|
dict({
|
||||||
'allowed': list([
|
'_links': dict({
|
||||||
]),
|
}),
|
||||||
'components': list([
|
'items': list([
|
||||||
dict({
|
dict({
|
||||||
'capabilities': list([
|
'allowed': list([
|
||||||
|
]),
|
||||||
|
'components': list([
|
||||||
dict({
|
dict({
|
||||||
'id': 'ocf',
|
'capabilities': list([
|
||||||
'version': 1,
|
dict({
|
||||||
|
'id': 'ocf',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'switch',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'airConditionerMode',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'airConditionerFanMode',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'fanOscillationMode',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'airQualitySensor',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'temperatureMeasurement',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'thermostatCoolingSetpoint',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'relativeHumidityMeasurement',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'dustSensor',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'veryFineDustSensor',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'audioVolume',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'remoteControlStatus',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'powerConsumptionReport',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'demandResponseLoadControl',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'refresh',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'execute',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'custom.spiMode',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'custom.thermostatSetpointControl',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'custom.airConditionerOptionalMode',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'custom.airConditionerTropicalNightMode',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'custom.autoCleaningMode',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'custom.deviceReportStateConfiguration',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'custom.energyType',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'custom.dustFilter',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'custom.airConditionerOdorController',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'custom.deodorFilter',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'custom.disabledComponents',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'custom.disabledCapabilities',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'samsungce.deviceIdentification',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'samsungce.dongleSoftwareInstallation',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'samsungce.softwareUpdate',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'samsungce.selfCheck',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'id': 'samsungce.driverVersion',
|
||||||
|
'version': 1,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
'categories': list([
|
||||||
|
dict({
|
||||||
|
'categoryType': 'manufacturer',
|
||||||
|
'name': 'AirConditioner',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
'id': 'main',
|
||||||
|
'label': 'main',
|
||||||
}),
|
}),
|
||||||
dict({
|
dict({
|
||||||
'id': 'switch',
|
'capabilities': list([
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'switch',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'airConditionerMode',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'airConditionerMode',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'airConditionerFanMode',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'airConditionerFanMode',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'fanOscillationMode',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'fanOscillationMode',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'airQualitySensor',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'temperatureMeasurement',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'temperatureMeasurement',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'thermostatCoolingSetpoint',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'thermostatCoolingSetpoint',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'relativeHumidityMeasurement',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'relativeHumidityMeasurement',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'airQualitySensor',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'dustSensor',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'dustSensor',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'veryFineDustSensor',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'veryFineDustSensor',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'audioVolume',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'odorSensor',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'remoteControlStatus',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'remoteControlStatus',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'powerConsumptionReport',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'audioVolume',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'demandResponseLoadControl',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'custom.thermostatSetpointControl',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'refresh',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'custom.autoCleaningMode',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'execute',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'custom.airConditionerTropicalNightMode',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'custom.spiMode',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'custom.disabledCapabilities',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'custom.thermostatSetpointControl',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'ocf',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'custom.airConditionerOptionalMode',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'powerConsumptionReport',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'custom.airConditionerTropicalNightMode',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'demandResponseLoadControl',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'custom.autoCleaningMode',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'custom.spiMode',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'custom.deviceReportStateConfiguration',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'custom.airConditionerOptionalMode',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'custom.energyType',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'custom.deviceReportStateConfiguration',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'custom.dustFilter',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'custom.energyType',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'custom.airConditionerOdorController',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'custom.dustFilter',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'custom.deodorFilter',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'custom.airConditionerOdorController',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'custom.disabledComponents',
|
}),
|
||||||
'version': 1,
|
dict({
|
||||||
}),
|
'id': 'custom.deodorFilter',
|
||||||
dict({
|
'version': 1,
|
||||||
'id': 'custom.disabledCapabilities',
|
}),
|
||||||
'version': 1,
|
]),
|
||||||
}),
|
'categories': list([
|
||||||
dict({
|
dict({
|
||||||
'id': 'samsungce.deviceIdentification',
|
'categoryType': 'manufacturer',
|
||||||
'version': 1,
|
'name': 'Other',
|
||||||
}),
|
}),
|
||||||
dict({
|
]),
|
||||||
'id': 'samsungce.dongleSoftwareInstallation',
|
'id': '1',
|
||||||
'version': 1,
|
'label': '1',
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'samsungce.softwareUpdate',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'samsungce.selfCheck',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'samsungce.driverVersion',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
'categories': list([
|
'createTime': '2021-04-06T16:43:34.753Z',
|
||||||
dict({
|
'deviceId': '96a5ef74-5832-a84b-f1f7-ca799957065d',
|
||||||
'categoryType': 'manufacturer',
|
'deviceManufacturerCode': 'Samsung Electronics',
|
||||||
'name': 'AirConditioner',
|
'deviceTypeName': 'Samsung OCF Air Conditioner',
|
||||||
}),
|
'executionContext': 'CLOUD',
|
||||||
]),
|
'label': 'AC Office Granit',
|
||||||
'id': 'main',
|
'locationId': '58d3fd7c-c512-4da3-b500-ef269382756c',
|
||||||
'label': 'main',
|
'manufacturerName': 'Samsung Electronics',
|
||||||
}),
|
'name': '[room a/c] Samsung',
|
||||||
dict({
|
'ocf': dict({
|
||||||
'capabilities': list([
|
'additionalAuthCodeRequired': False,
|
||||||
dict({
|
'lastSignupTime': '2025-01-08T02:32:04.631093137Z',
|
||||||
'id': 'switch',
|
'manufacturerName': 'Samsung Electronics',
|
||||||
'version': 1,
|
'ocfDeviceType': 'x.com.st.d.sensor.light',
|
||||||
}),
|
'transferCandidate': False,
|
||||||
dict({
|
'vendorId': 'VD-Sensor.Light-2023',
|
||||||
'id': 'airConditionerMode',
|
}),
|
||||||
'version': 1,
|
'ownerId': 'f9a28d7c-1ed5-d9e9-a81c-18971ec081db',
|
||||||
}),
|
'presentationId': 'DA-AC-RAC-000001',
|
||||||
dict({
|
'profile': dict({
|
||||||
'id': 'airConditionerFanMode',
|
'id': '60fbc713-8da5-315d-b31a-6d6dcde4be7b',
|
||||||
'version': 1,
|
}),
|
||||||
}),
|
'restrictionTier': 0,
|
||||||
dict({
|
'roomId': '85a79db4-9cf2-4f09-a5b2-cd70a5c0cef0',
|
||||||
'id': 'fanOscillationMode',
|
'type': 'OCF',
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'temperatureMeasurement',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'thermostatCoolingSetpoint',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'relativeHumidityMeasurement',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'airQualitySensor',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'dustSensor',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'veryFineDustSensor',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'odorSensor',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'remoteControlStatus',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'audioVolume',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'custom.thermostatSetpointControl',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'custom.autoCleaningMode',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'custom.airConditionerTropicalNightMode',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'custom.disabledCapabilities',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'ocf',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'powerConsumptionReport',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'demandResponseLoadControl',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'custom.spiMode',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'custom.airConditionerOptionalMode',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'custom.deviceReportStateConfiguration',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'custom.energyType',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'custom.dustFilter',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'custom.airConditionerOdorController',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
dict({
|
|
||||||
'id': 'custom.deodorFilter',
|
|
||||||
'version': 1,
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
'categories': list([
|
|
||||||
dict({
|
|
||||||
'categoryType': 'manufacturer',
|
|
||||||
'name': 'Other',
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
'id': '1',
|
|
||||||
'label': '1',
|
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
'createTime': '2021-04-06T16:43:34.753Z',
|
|
||||||
'deviceId': '96a5ef74-5832-a84b-f1f7-ca799957065d',
|
|
||||||
'deviceManufacturerCode': 'Samsung Electronics',
|
|
||||||
'deviceTypeName': 'Samsung OCF Air Conditioner',
|
|
||||||
'executionContext': 'CLOUD',
|
|
||||||
'label': 'AC Office Granit',
|
|
||||||
'locationId': '58d3fd7c-c512-4da3-b500-ef269382756c',
|
|
||||||
'manufacturerName': 'Samsung Electronics',
|
|
||||||
'name': '[room a/c] Samsung',
|
|
||||||
'ocf': dict({
|
|
||||||
'additionalAuthCodeRequired': False,
|
|
||||||
'lastSignupTime': '2025-01-08T02:32:04.631093137Z',
|
|
||||||
'manufacturerName': 'Samsung Electronics',
|
|
||||||
'ocfDeviceType': 'x.com.st.d.sensor.light',
|
|
||||||
'transferCandidate': False,
|
|
||||||
'vendorId': 'VD-Sensor.Light-2023',
|
|
||||||
}),
|
|
||||||
'ownerId': 'f9a28d7c-1ed5-d9e9-a81c-18971ec081db',
|
|
||||||
'presentationId': 'DA-AC-RAC-000001',
|
|
||||||
'profile': dict({
|
|
||||||
'id': '60fbc713-8da5-315d-b31a-6d6dcde4be7b',
|
|
||||||
}),
|
|
||||||
'restrictionTier': 0,
|
|
||||||
'roomId': '85a79db4-9cf2-4f09-a5b2-cd70a5c0cef0',
|
|
||||||
'type': 'OCF',
|
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
})
|
})
|
||||||
|
@ -68,6 +68,39 @@
|
|||||||
'via_device_id': None,
|
'via_device_id': None,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_devices[aux_ac]
|
||||||
|
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',
|
||||||
|
'bf53a150-f8a4-45d1-aac4-86252475d551',
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
'is_new': False,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'manufacturer': None,
|
||||||
|
'model': None,
|
||||||
|
'model_id': None,
|
||||||
|
'name': 'AUX A/C on-off',
|
||||||
|
'name_by_user': None,
|
||||||
|
'primary_config_entry': <ANY>,
|
||||||
|
'serial_number': None,
|
||||||
|
'suggested_area': None,
|
||||||
|
'sw_version': None,
|
||||||
|
'via_device_id': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_devices[base_electric_meter]
|
# name: test_devices[base_electric_meter]
|
||||||
DeviceRegistryEntrySnapshot({
|
DeviceRegistryEntrySnapshot({
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
@ -398,6 +431,39 @@
|
|||||||
'via_device_id': None,
|
'via_device_id': None,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_devices[da_ks_oven_01061]
|
||||||
|
DeviceRegistryEntrySnapshot({
|
||||||
|
'area_id': None,
|
||||||
|
'config_entries': <ANY>,
|
||||||
|
'config_entries_subentries': <ANY>,
|
||||||
|
'configuration_url': 'https://account.smartthings.com',
|
||||||
|
'connections': set({
|
||||||
|
}),
|
||||||
|
'disabled_by': None,
|
||||||
|
'entry_type': None,
|
||||||
|
'hw_version': 'Realtek',
|
||||||
|
'id': <ANY>,
|
||||||
|
'identifiers': set({
|
||||||
|
tuple(
|
||||||
|
'smartthings',
|
||||||
|
'9447959a-0dfa-6b27-d40d-650da525c53f',
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
'is_new': False,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'manufacturer': 'Samsung Electronics',
|
||||||
|
'model': 'TP1X_DA-KS-OVEN-01061',
|
||||||
|
'model_id': None,
|
||||||
|
'name': 'Oven',
|
||||||
|
'name_by_user': None,
|
||||||
|
'primary_config_entry': <ANY>,
|
||||||
|
'serial_number': None,
|
||||||
|
'suggested_area': None,
|
||||||
|
'sw_version': 'AKS-WW-TP1X-21-OVEN_40211229',
|
||||||
|
'via_device_id': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_devices[da_ks_range_0101x]
|
# name: test_devices[da_ks_range_0101x]
|
||||||
DeviceRegistryEntrySnapshot({
|
DeviceRegistryEntrySnapshot({
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
|
@ -154,6 +154,58 @@
|
|||||||
'state': 'unknown',
|
'state': 'unknown',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_all_entities[aux_ac][sensor.aux_a_c_on_off_temperature-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.aux_a_c_on_off_temperature',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Temperature',
|
||||||
|
'platform': 'smartthings',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': None,
|
||||||
|
'unique_id': 'bf53a150-f8a4-45d1-aac4-86252475d551.temperature',
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[aux_ac][sensor.aux_a_c_on_off_temperature-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'temperature',
|
||||||
|
'friendly_name': 'AUX A/C on-off Temperature',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.aux_a_c_on_off_temperature',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '20.0',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_all_entities[base_electric_meter][sensor.aeon_energy_monitor_energy-entry]
|
# name: test_all_entities[base_electric_meter][sensor.aeon_energy_monitor_energy-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
@ -2085,6 +2137,404 @@
|
|||||||
'state': '-17',
|
'state': '-17',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_all_entities[da_ks_oven_01061][sensor.oven_completion_time-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.oven_completion_time',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Completion time',
|
||||||
|
'platform': 'smartthings',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'completion_time',
|
||||||
|
'unique_id': '9447959a-0dfa-6b27-d40d-650da525c53f.completionTime',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[da_ks_oven_01061][sensor.oven_completion_time-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'timestamp',
|
||||||
|
'friendly_name': 'Oven Completion time',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.oven_completion_time',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '2025-03-15T12:06:09+00:00',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[da_ks_oven_01061][sensor.oven_job_state-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'options': list([
|
||||||
|
'cleaning',
|
||||||
|
'cooking',
|
||||||
|
'cooling',
|
||||||
|
'draining',
|
||||||
|
'preheat',
|
||||||
|
'ready',
|
||||||
|
'rinsing',
|
||||||
|
'finished',
|
||||||
|
'scheduled_start',
|
||||||
|
'warming',
|
||||||
|
'defrosting',
|
||||||
|
'sensing',
|
||||||
|
'searing',
|
||||||
|
'fast_preheat',
|
||||||
|
'scheduled_end',
|
||||||
|
'stone_heating',
|
||||||
|
'time_hold_preheat',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.oven_job_state',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Job state',
|
||||||
|
'platform': 'smartthings',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'oven_job_state',
|
||||||
|
'unique_id': '9447959a-0dfa-6b27-d40d-650da525c53f.ovenJobState',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[da_ks_oven_01061][sensor.oven_job_state-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'enum',
|
||||||
|
'friendly_name': 'Oven Job state',
|
||||||
|
'options': list([
|
||||||
|
'cleaning',
|
||||||
|
'cooking',
|
||||||
|
'cooling',
|
||||||
|
'draining',
|
||||||
|
'preheat',
|
||||||
|
'ready',
|
||||||
|
'rinsing',
|
||||||
|
'finished',
|
||||||
|
'scheduled_start',
|
||||||
|
'warming',
|
||||||
|
'defrosting',
|
||||||
|
'sensing',
|
||||||
|
'searing',
|
||||||
|
'fast_preheat',
|
||||||
|
'scheduled_end',
|
||||||
|
'stone_heating',
|
||||||
|
'time_hold_preheat',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.oven_job_state',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'preheat',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[da_ks_oven_01061][sensor.oven_machine_state-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'options': list([
|
||||||
|
'ready',
|
||||||
|
'running',
|
||||||
|
'paused',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.oven_machine_state',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Machine state',
|
||||||
|
'platform': 'smartthings',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'oven_machine_state',
|
||||||
|
'unique_id': '9447959a-0dfa-6b27-d40d-650da525c53f.machineState',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[da_ks_oven_01061][sensor.oven_machine_state-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'enum',
|
||||||
|
'friendly_name': 'Oven Machine state',
|
||||||
|
'options': list([
|
||||||
|
'ready',
|
||||||
|
'running',
|
||||||
|
'paused',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.oven_machine_state',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'running',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[da_ks_oven_01061][sensor.oven_oven_mode-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'options': list([
|
||||||
|
'conventional',
|
||||||
|
'bake',
|
||||||
|
'bottom_heat',
|
||||||
|
'convection_bake',
|
||||||
|
'convection_roast',
|
||||||
|
'broil',
|
||||||
|
'convection_broil',
|
||||||
|
'steam_cook',
|
||||||
|
'steam_bake',
|
||||||
|
'steam_roast',
|
||||||
|
'steam_bottom_heat_plus_convection',
|
||||||
|
'microwave',
|
||||||
|
'microwave_plus_grill',
|
||||||
|
'microwave_plus_convection',
|
||||||
|
'microwave_plus_hot_blast',
|
||||||
|
'microwave_plus_hot_blast_2',
|
||||||
|
'slim_middle',
|
||||||
|
'slim_strong',
|
||||||
|
'slow_cook',
|
||||||
|
'proof',
|
||||||
|
'dehydrate',
|
||||||
|
'others',
|
||||||
|
'strong_steam',
|
||||||
|
'descale',
|
||||||
|
'rinse',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||||
|
'entity_id': 'sensor.oven_oven_mode',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Oven mode',
|
||||||
|
'platform': 'smartthings',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'oven_mode',
|
||||||
|
'unique_id': '9447959a-0dfa-6b27-d40d-650da525c53f.ovenMode',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[da_ks_oven_01061][sensor.oven_oven_mode-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'enum',
|
||||||
|
'friendly_name': 'Oven Oven mode',
|
||||||
|
'options': list([
|
||||||
|
'conventional',
|
||||||
|
'bake',
|
||||||
|
'bottom_heat',
|
||||||
|
'convection_bake',
|
||||||
|
'convection_roast',
|
||||||
|
'broil',
|
||||||
|
'convection_broil',
|
||||||
|
'steam_cook',
|
||||||
|
'steam_bake',
|
||||||
|
'steam_roast',
|
||||||
|
'steam_bottom_heat_plus_convection',
|
||||||
|
'microwave',
|
||||||
|
'microwave_plus_grill',
|
||||||
|
'microwave_plus_convection',
|
||||||
|
'microwave_plus_hot_blast',
|
||||||
|
'microwave_plus_hot_blast_2',
|
||||||
|
'slim_middle',
|
||||||
|
'slim_strong',
|
||||||
|
'slow_cook',
|
||||||
|
'proof',
|
||||||
|
'dehydrate',
|
||||||
|
'others',
|
||||||
|
'strong_steam',
|
||||||
|
'descale',
|
||||||
|
'rinse',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.oven_oven_mode',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'bake',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[da_ks_oven_01061][sensor.oven_set_point-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.oven_set_point',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Set point',
|
||||||
|
'platform': 'smartthings',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'oven_setpoint',
|
||||||
|
'unique_id': '9447959a-0dfa-6b27-d40d-650da525c53f.ovenSetpoint',
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[da_ks_oven_01061][sensor.oven_set_point-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'temperature',
|
||||||
|
'friendly_name': 'Oven Set point',
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.oven_set_point',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '220',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[da_ks_oven_01061][sensor.oven_temperature-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.oven_temperature',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Temperature',
|
||||||
|
'platform': 'smartthings',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': None,
|
||||||
|
'unique_id': '9447959a-0dfa-6b27-d40d-650da525c53f.temperature',
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[da_ks_oven_01061][sensor.oven_temperature-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'temperature',
|
||||||
|
'friendly_name': 'Oven Temperature',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.oven_temperature',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '30',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_completion_time-entry]
|
# name: test_all_entities[da_ks_range_0101x][sensor.vulcan_completion_time-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
@ -30,9 +30,9 @@ async def test_config_entry_diagnostics(
|
|||||||
snapshot: SnapshotAssertion,
|
snapshot: SnapshotAssertion,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test generating diagnostics for a device entry."""
|
"""Test generating diagnostics for a device entry."""
|
||||||
mock_smartthings.get_raw_devices.return_value = load_json_object_fixture(
|
mock_smartthings.get_raw_devices.return_value = [
|
||||||
"devices/da_ac_rac_000001.json", DOMAIN
|
load_json_object_fixture("devices/da_ac_rac_000001.json", DOMAIN)
|
||||||
)
|
]
|
||||||
await setup_integration(hass, mock_config_entry)
|
await setup_integration(hass, mock_config_entry)
|
||||||
assert (
|
assert (
|
||||||
await get_diagnostics_for_config_entry(hass, hass_client, mock_config_entry)
|
await get_diagnostics_for_config_entry(hass, hass_client, mock_config_entry)
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
"title": "1984",
|
"title": "1984",
|
||||||
"parent_id": "FV:2",
|
"parent_id": "FV:2",
|
||||||
"item_id": "FV:2/8",
|
"item_id": "FV:2/8",
|
||||||
|
"album_art_uri": "http://192.168.42.2:1400/getaa?u=x-file-cifs%3a%2f%2f192.168.42.2%2fmusic%2fiTunes%2520Music%2fAerosmith%2f1984&v=742",
|
||||||
"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=\"A:ALBUMARTIST/Aerosmith/1984\" parentID=\"A:ALBUMARTIST/Aerosmith\" restricted=\"true\"><dc:title>1984</dc:title><upnp:class>object.container.album.musicAlbum</upnp:class><desc id=\"cdudn\" nameSpace=\"urn:schemas-rinconnetworks-com:metadata-1-0/\">RINCON_AssociatedZPUDN</desc></item></DIDL-Lite>",
|
"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=\"A:ALBUMARTIST/Aerosmith/1984\" parentID=\"A:ALBUMARTIST/Aerosmith\" restricted=\"true\"><dc:title>1984</dc:title><upnp:class>object.container.album.musicAlbum</upnp:class><desc id=\"cdudn\" nameSpace=\"urn:schemas-rinconnetworks-com:metadata-1-0/\">RINCON_AssociatedZPUDN</desc></item></DIDL-Lite>",
|
||||||
"resources": [
|
"resources": [
|
||||||
{
|
{
|
||||||
|
@ -44,6 +44,31 @@
|
|||||||
'title': 'Favorites',
|
'title': 'Favorites',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_browse_media_favorites[object.container.album.musicAlbum-favorites_folder]
|
||||||
|
dict({
|
||||||
|
'can_expand': True,
|
||||||
|
'can_play': False,
|
||||||
|
'children': list([
|
||||||
|
dict({
|
||||||
|
'can_expand': False,
|
||||||
|
'can_play': True,
|
||||||
|
'children_media_class': None,
|
||||||
|
'media_class': 'album',
|
||||||
|
'media_content_id': 'FV:2/8',
|
||||||
|
'media_content_type': 'favorite_item_id',
|
||||||
|
'thumbnail': 'http://192.168.42.2:1400/getaa?u=x-file-cifs://192.168.42.2/music/iTunes%20Music/Aerosmith/1984&v=742',
|
||||||
|
'title': '1984',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
'children_media_class': 'album',
|
||||||
|
'media_class': 'directory',
|
||||||
|
'media_content_id': '',
|
||||||
|
'media_content_type': 'favorites',
|
||||||
|
'not_shown': 0,
|
||||||
|
'thumbnail': None,
|
||||||
|
'title': 'Albums',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_browse_media_favorites[object.item.audioItem.audioBook-favorites_folder]
|
# name: test_browse_media_favorites[object.item.audioItem.audioBook-favorites_folder]
|
||||||
dict({
|
dict({
|
||||||
'can_expand': True,
|
'can_expand': True,
|
||||||
|
@ -190,6 +190,10 @@ async def test_browse_media_library_albums(
|
|||||||
"object.item.audioItem.audioBook",
|
"object.item.audioItem.audioBook",
|
||||||
"favorites_folder",
|
"favorites_folder",
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"object.container.album.musicAlbum",
|
||||||
|
"favorites_folder",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_browse_media_favorites(
|
async def test_browse_media_favorites(
|
||||||
|
@ -59,43 +59,30 @@ def mock_controller_connection_failed():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("controller")
|
@pytest.mark.usefixtures("controller")
|
||||||
async def test_user_network_succes(hass: HomeAssistant) -> None:
|
@pytest.mark.parametrize(
|
||||||
"""Test user network config."""
|
("inputParams", "expected"),
|
||||||
# inttial menu show
|
[
|
||||||
result = await hass.config_entries.flow.async_init(
|
(
|
||||||
DOMAIN, context={"source": SOURCE_USER}
|
{
|
||||||
)
|
CONF_TLS: True,
|
||||||
assert result
|
CONF_PASSWORD: "password",
|
||||||
assert result.get("flow_id")
|
},
|
||||||
assert result.get("type") is FlowResultType.MENU
|
"tls://password@velbus:6000",
|
||||||
assert result.get("step_id") == "user"
|
),
|
||||||
assert result.get("menu_options") == ["network", "usbselect"]
|
(
|
||||||
# select the network option
|
{
|
||||||
result = await hass.config_entries.flow.async_configure(
|
CONF_TLS: True,
|
||||||
result.get("flow_id"),
|
CONF_PASSWORD: "",
|
||||||
{"next_step_id": "network"},
|
},
|
||||||
)
|
"tls://velbus:6000",
|
||||||
assert result.get("type") is FlowResultType.FORM
|
),
|
||||||
# fill in the network form
|
({CONF_TLS: True}, "tls://velbus:6000"),
|
||||||
result = await hass.config_entries.flow.async_configure(
|
({CONF_TLS: False}, "velbus:6000"),
|
||||||
result.get("flow_id"),
|
],
|
||||||
{
|
)
|
||||||
CONF_TLS: False,
|
async def test_user_network_succes(
|
||||||
CONF_HOST: "velbus",
|
hass: HomeAssistant, inputParams: str, expected: str
|
||||||
CONF_PORT: 6000,
|
) -> None:
|
||||||
CONF_PASSWORD: "",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
assert result
|
|
||||||
assert result.get("type") is FlowResultType.CREATE_ENTRY
|
|
||||||
assert result.get("title") == "Velbus Network"
|
|
||||||
data = result.get("data")
|
|
||||||
assert data
|
|
||||||
assert data[CONF_PORT] == "velbus:6000"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("controller")
|
|
||||||
async def test_user_network_succes_tls(hass: HomeAssistant) -> None:
|
|
||||||
"""Test user network config."""
|
"""Test user network config."""
|
||||||
# inttial menu show
|
# inttial menu show
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
@ -116,10 +103,9 @@ async def test_user_network_succes_tls(hass: HomeAssistant) -> None:
|
|||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result.get("flow_id"),
|
result.get("flow_id"),
|
||||||
{
|
{
|
||||||
CONF_TLS: True,
|
|
||||||
CONF_HOST: "velbus",
|
CONF_HOST: "velbus",
|
||||||
CONF_PORT: 6000,
|
CONF_PORT: 6000,
|
||||||
CONF_PASSWORD: "password",
|
**inputParams,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert result
|
assert result
|
||||||
@ -127,7 +113,7 @@ async def test_user_network_succes_tls(hass: HomeAssistant) -> None:
|
|||||||
assert result.get("title") == "Velbus Network"
|
assert result.get("title") == "Velbus Network"
|
||||||
data = result.get("data")
|
data = result.get("data")
|
||||||
assert data
|
assert data
|
||||||
assert data[CONF_PORT] == "tls://password@velbus:6000"
|
assert data[CONF_PORT] == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("controller")
|
@pytest.mark.usefixtures("controller")
|
||||||
|
@ -179,7 +179,16 @@
|
|||||||
}),
|
}),
|
||||||
'0x0010': dict({
|
'0x0010': dict({
|
||||||
'attribute': "ZCLAttributeDef(id=0x0010, name='cie_addr', type=<class 'zigpy.types.named.EUI64'>, zcl_type=<DataTypeId.EUI64: 240>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=True, is_manufacturer_specific=False)",
|
'attribute': "ZCLAttributeDef(id=0x0010, name='cie_addr', type=<class 'zigpy.types.named.EUI64'>, zcl_type=<DataTypeId.EUI64: 240>, access=<ZCLAttributeAccess.Read|Write: 3>, mandatory=True, is_manufacturer_specific=False)",
|
||||||
'value': None,
|
'value': list([
|
||||||
|
50,
|
||||||
|
79,
|
||||||
|
50,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
141,
|
||||||
|
21,
|
||||||
|
0,
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
'0x0011': dict({
|
'0x0011': dict({
|
||||||
'attribute': "ZCLAttributeDef(id=0x0011, name='zone_id', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
|
'attribute': "ZCLAttributeDef(id=0x0011, name='zone_id', type=<class 'zigpy.types.basic.uint8_t'>, zcl_type=<DataTypeId.uint8: 32>, access=<ZCLAttributeAccess.Read: 1>, mandatory=True, is_manufacturer_specific=False)",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user