mirror of
https://github.com/home-assistant/core.git
synced 2026-01-03 14:25:28 +01:00
Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a12915fc14 | ||
|
|
3d5e4b980f | ||
|
|
f2f653efcf | ||
|
|
b5c7bdd98f | ||
|
|
38e6133202 | ||
|
|
8541dc5bde | ||
|
|
5327996bad | ||
|
|
4ddc43a9d9 | ||
|
|
e6dea4179b | ||
|
|
0318b85517 | ||
|
|
29987d443e | ||
|
|
cbfd8707b9 | ||
|
|
5f158f5c87 | ||
|
|
d67ccd2fce | ||
|
|
29c9d3804b | ||
|
|
76d478c84f | ||
|
|
5d9d6f099c | ||
|
|
e4b31640b3 | ||
|
|
c43f6a67d0 | ||
|
|
0bbab63193 | ||
|
|
06188b8fbd | ||
|
|
bbbb5cadd4 | ||
|
|
52fcdda429 | ||
|
|
7d93ceb0f0 | ||
|
|
873e4b77eb | ||
|
|
61f0eabcbb | ||
|
|
134b5319e1 | ||
|
|
ee78e21950 | ||
|
|
323bc54efc | ||
|
|
fd2dee3c11 | ||
|
|
fc53322c07 | ||
|
|
faf9977abb | ||
|
|
7336c8fc07 | ||
|
|
5cfaeda95b | ||
|
|
a78e9039c6 | ||
|
|
227f3cea25 | ||
|
|
cab4890246 | ||
|
|
95fd096bdd | ||
|
|
91cf8cb547 | ||
|
|
3ce4f3f918 | ||
|
|
4e89948b5c | ||
|
|
9f95383201 | ||
|
|
7e452521c8 | ||
|
|
991de6f1d0 | ||
|
|
be32e3fe8f | ||
|
|
d6eb61e9ec | ||
|
|
e74fe69d65 | ||
|
|
208406123e | ||
|
|
8bcd135f3d | ||
|
|
e7ea0e435e | ||
|
|
b15b680cfe | ||
|
|
5e26d98bdf | ||
|
|
9f94ee280a | ||
|
|
efa98539fa | ||
|
|
113cd4bfcc | ||
|
|
ccbaf76e44 | ||
|
|
5d9d93d3a1 | ||
|
|
c2c5274aac | ||
|
|
89756394c9 | ||
|
|
352aa88e79 | ||
|
|
714962bd7a | ||
|
|
fb4c50b5dc | ||
|
|
b4794b2029 | ||
|
|
3a8c8accfe | ||
|
|
844adfc590 | ||
|
|
a279e23fb5 | ||
|
|
af9bbd0585 | ||
|
|
1304194f09 | ||
|
|
e909417a3f | ||
|
|
02706c116d | ||
|
|
3af6b5cb4c | ||
|
|
35c1bb1ec5 |
@@ -118,6 +118,7 @@ class BackupManagerState(StrEnum):
|
||||
|
||||
IDLE = "idle"
|
||||
CREATE_BACKUP = "create_backup"
|
||||
BLOCKED = "blocked"
|
||||
RECEIVE_BACKUP = "receive_backup"
|
||||
RESTORE_BACKUP = "restore_backup"
|
||||
|
||||
@@ -226,6 +227,13 @@ class RestoreBackupEvent(ManagerStateEvent):
|
||||
state: RestoreBackupState
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True, slots=True)
|
||||
class BlockedEvent(ManagerStateEvent):
|
||||
"""Backup manager blocked, Home Assistant is starting."""
|
||||
|
||||
manager_state: BackupManagerState = BackupManagerState.BLOCKED
|
||||
|
||||
|
||||
class BackupPlatformProtocol(Protocol):
|
||||
"""Define the format that backup platforms can have."""
|
||||
|
||||
@@ -340,7 +348,7 @@ class BackupManager:
|
||||
self.remove_next_delete_event: Callable[[], None] | None = None
|
||||
|
||||
# Latest backup event and backup event subscribers
|
||||
self.last_event: ManagerStateEvent = IdleEvent()
|
||||
self.last_event: ManagerStateEvent = BlockedEvent()
|
||||
self.last_non_idle_event: ManagerStateEvent | None = None
|
||||
self._backup_event_subscriptions = hass.data[
|
||||
DATA_BACKUP
|
||||
@@ -354,10 +362,19 @@ class BackupManager:
|
||||
self.known_backups.load(stored["backups"])
|
||||
|
||||
await self._reader_writer.async_validate_config(config=self.config)
|
||||
|
||||
await self._reader_writer.async_resume_restore_progress_after_restart(
|
||||
on_progress=self.async_on_backup_event
|
||||
)
|
||||
|
||||
async def set_manager_idle_after_start(hass: HomeAssistant) -> None:
|
||||
"""Set manager to idle after start."""
|
||||
self.async_on_backup_event(IdleEvent())
|
||||
|
||||
if self.state == BackupManagerState.BLOCKED:
|
||||
# If we're not finishing a restore job, set the manager to idle after start
|
||||
start.async_at_started(self.hass, set_manager_idle_after_start)
|
||||
|
||||
await self.load_platforms()
|
||||
|
||||
@property
|
||||
@@ -1293,7 +1310,7 @@ class BackupManager:
|
||||
if (current_state := self.state) != (new_state := event.manager_state):
|
||||
LOGGER.debug("Backup state: %s -> %s", current_state, new_state)
|
||||
self.last_event = event
|
||||
if not isinstance(event, IdleEvent):
|
||||
if not isinstance(event, (BlockedEvent, IdleEvent)):
|
||||
self.last_non_idle_event = event
|
||||
for subscription in self._backup_event_subscriptions:
|
||||
subscription(event)
|
||||
|
||||
@@ -77,9 +77,12 @@ class BringEventEntity(BringBaseEntity, EventEntity):
|
||||
attributes = asdict(activity.content)
|
||||
|
||||
attributes["last_activity_by"] = next(
|
||||
x.name
|
||||
for x in bring_list.users.users
|
||||
if x.publicUuid == activity.content.publicUserUuid
|
||||
(
|
||||
x.name
|
||||
for x in bring_list.users.users
|
||||
if x.publicUuid == activity.content.publicUserUuid
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
self._trigger_event(
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from aioelectricitymaps import (
|
||||
ElectricityMaps,
|
||||
ElectricityMapsError,
|
||||
ElectricityMapsInvalidTokenError,
|
||||
ElectricityMapsNoDataError,
|
||||
)
|
||||
@@ -36,6 +36,8 @@ TYPE_USE_HOME = "use_home_location"
|
||||
TYPE_SPECIFY_COORDINATES = "specify_coordinates"
|
||||
TYPE_SPECIFY_COUNTRY = "specify_country_code"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ElectricityMapsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Co2signal."""
|
||||
@@ -158,7 +160,8 @@ class ElectricityMapsConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors["base"] = "invalid_auth"
|
||||
except ElectricityMapsNoDataError:
|
||||
errors["base"] = "no_data"
|
||||
except ElectricityMapsError:
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected error occurred while checking API key")
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
if self.source == SOURCE_REAUTH:
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["sense_energy"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["sense-energy==0.13.6"]
|
||||
"requirements": ["sense-energy==0.13.7"]
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ from typing import Any
|
||||
import evohomeasync as ec1
|
||||
import evohomeasync2 as ec2
|
||||
from evohomeasync2.const import (
|
||||
SZ_DHW,
|
||||
SZ_GATEWAY_ID,
|
||||
SZ_GATEWAY_INFO,
|
||||
SZ_GATEWAYS,
|
||||
@@ -19,8 +20,9 @@ from evohomeasync2.const import (
|
||||
SZ_TEMPERATURE_CONTROL_SYSTEMS,
|
||||
SZ_TIME_ZONE,
|
||||
SZ_USE_DAYLIGHT_SAVE_SWITCHING,
|
||||
SZ_ZONES,
|
||||
)
|
||||
from evohomeasync2.schemas.typedefs import EvoLocStatusResponseT
|
||||
from evohomeasync2.schemas.typedefs import EvoLocStatusResponseT, EvoTcsConfigResponseT
|
||||
|
||||
from homeassistant.const import CONF_SCAN_INTERVAL
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -113,17 +115,19 @@ class EvoDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
SZ_USE_DAYLIGHT_SAVE_SWITCHING
|
||||
],
|
||||
}
|
||||
tcs_info: EvoTcsConfigResponseT = self.tcs.config # type: ignore[assignment]
|
||||
tcs_info[SZ_ZONES] = [zone.config for zone in self.tcs.zones]
|
||||
if self.tcs.hotwater:
|
||||
tcs_info[SZ_DHW] = self.tcs.hotwater.config
|
||||
gwy_info = {
|
||||
SZ_GATEWAY_ID: self.loc.gateways[0].id,
|
||||
SZ_TEMPERATURE_CONTROL_SYSTEMS: [
|
||||
self.loc.gateways[0].systems[0].config
|
||||
],
|
||||
SZ_TEMPERATURE_CONTROL_SYSTEMS: [tcs_info],
|
||||
}
|
||||
config = {
|
||||
SZ_LOCATION_INFO: loc_info,
|
||||
SZ_GATEWAYS: [{SZ_GATEWAY_INFO: gwy_info}],
|
||||
}
|
||||
self.logger.debug("Config = %s", config)
|
||||
self.logger.debug("Config = %s", [config])
|
||||
|
||||
async def call_client_api(
|
||||
self,
|
||||
@@ -203,10 +207,18 @@ class EvoDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
|
||||
async def _update_v2_schedules(self) -> None:
|
||||
for zone in self.tcs.zones:
|
||||
await zone.get_schedule()
|
||||
try:
|
||||
await zone.get_schedule()
|
||||
except ec2.InvalidScheduleError as err:
|
||||
self.logger.warning(
|
||||
"Zone '%s' has an invalid/missing schedule: %r", zone.name, err
|
||||
)
|
||||
|
||||
if dhw := self.tcs.hotwater:
|
||||
await dhw.get_schedule()
|
||||
try:
|
||||
await dhw.get_schedule()
|
||||
except ec2.InvalidScheduleError as err:
|
||||
self.logger.warning("DHW has an invalid/missing schedule: %r", err)
|
||||
|
||||
async def _async_update_data(self) -> EvoLocStatusResponseT: # type: ignore[override]
|
||||
"""Fetch the latest state of an entire TCC Location.
|
||||
|
||||
@@ -6,6 +6,7 @@ import logging
|
||||
from typing import Any
|
||||
|
||||
import evohomeasync2 as evo
|
||||
from evohomeasync2.schemas.typedefs import DayOfWeekDhwT
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
@@ -102,7 +103,7 @@ class EvoChild(EvoEntity):
|
||||
|
||||
self._evo_tcs = evo_device.tcs
|
||||
|
||||
self._schedule: dict[str, Any] | None = None
|
||||
self._schedule: list[DayOfWeekDhwT] | None = None
|
||||
self._setpoints: dict[str, Any] = {}
|
||||
|
||||
@property
|
||||
@@ -123,6 +124,9 @@ class EvoChild(EvoEntity):
|
||||
Only Zones & DHW controllers (but not the TCS) can have schedules.
|
||||
"""
|
||||
|
||||
if not self._schedule:
|
||||
return self._setpoints
|
||||
|
||||
this_sp_dtm, this_sp_val = self._evo_device.this_switchpoint
|
||||
next_sp_dtm, next_sp_val = self._evo_device.next_switchpoint
|
||||
|
||||
@@ -152,10 +156,10 @@ class EvoChild(EvoEntity):
|
||||
self._evo_device,
|
||||
err,
|
||||
)
|
||||
self._schedule = {}
|
||||
self._schedule = []
|
||||
return
|
||||
else:
|
||||
self._schedule = schedule or {} # mypy hint
|
||||
self._schedule = schedule # type: ignore[assignment]
|
||||
|
||||
_LOGGER.debug("Schedule['%s'] = %s", self.name, schedule)
|
||||
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["evohome", "evohomeasync", "evohomeasync2"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["evohome-async==1.0.2"]
|
||||
"requirements": ["evohome-async==1.0.4"]
|
||||
}
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20250305.0"]
|
||||
"requirements": ["home-assistant-frontend==20250306.0"]
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/fujitsu_fglair",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["ayla-iot-unofficial==1.4.5"]
|
||||
"requirements": ["ayla-iot-unofficial==1.4.7"]
|
||||
}
|
||||
|
||||
@@ -65,9 +65,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
|
||||
prompt_parts = [call.data[CONF_PROMPT]]
|
||||
|
||||
config_entry: GoogleGenerativeAIConfigEntry = hass.config_entries.async_entries(
|
||||
DOMAIN
|
||||
)[0]
|
||||
config_entry: GoogleGenerativeAIConfigEntry = (
|
||||
hass.config_entries.async_loaded_entries(DOMAIN)[0]
|
||||
)
|
||||
|
||||
client = config_entry.runtime_data
|
||||
|
||||
|
||||
@@ -64,28 +64,18 @@ async def async_setup_entry(
|
||||
|
||||
|
||||
SUPPORTED_SCHEMA_KEYS = {
|
||||
"min_items",
|
||||
"example",
|
||||
"property_ordering",
|
||||
"pattern",
|
||||
"minimum",
|
||||
"default",
|
||||
"any_of",
|
||||
"max_length",
|
||||
"title",
|
||||
"min_properties",
|
||||
"min_length",
|
||||
"max_items",
|
||||
"maximum",
|
||||
"nullable",
|
||||
"max_properties",
|
||||
# Gemini API does not support all of the OpenAPI schema
|
||||
# SoT: https://ai.google.dev/api/caching#Schema
|
||||
"type",
|
||||
"description",
|
||||
"enum",
|
||||
"format",
|
||||
"items",
|
||||
"description",
|
||||
"nullable",
|
||||
"enum",
|
||||
"max_items",
|
||||
"min_items",
|
||||
"properties",
|
||||
"required",
|
||||
"items",
|
||||
}
|
||||
|
||||
|
||||
@@ -109,9 +99,7 @@ def _format_schema(schema: dict[str, Any]) -> Schema:
|
||||
key = _camel_to_snake(key)
|
||||
if key not in SUPPORTED_SCHEMA_KEYS:
|
||||
continue
|
||||
if key == "any_of":
|
||||
val = [_format_schema(subschema) for subschema in val]
|
||||
elif key == "type":
|
||||
if key == "type":
|
||||
val = val.upper()
|
||||
elif key == "format":
|
||||
# Gemini API does not support all formats, see: https://ai.google.dev/api/caching#Schema
|
||||
@@ -288,6 +276,13 @@ class GoogleGenerativeAIConversationEntity(
|
||||
):
|
||||
return await self._async_handle_message(user_input, chat_log)
|
||||
|
||||
def _fix_tool_name(self, tool_name: str) -> str:
|
||||
"""Fix tool name if needed."""
|
||||
# The Gemini 2.0+ tokenizer seemingly has a issue with the HassListAddItem tool
|
||||
# name. This makes sure when it incorrectly changes the name, that we change it
|
||||
# back for HA to call.
|
||||
return tool_name if tool_name != "HasListAddItem" else "HassListAddItem"
|
||||
|
||||
async def _async_handle_message(
|
||||
self,
|
||||
user_input: conversation.ConversationInput,
|
||||
@@ -447,7 +442,10 @@ class GoogleGenerativeAIConversationEntity(
|
||||
tool_name = tool_call.name
|
||||
tool_args = _escape_decode(tool_call.args)
|
||||
tool_calls.append(
|
||||
llm.ToolInput(tool_name=tool_name, tool_args=tool_args)
|
||||
llm.ToolInput(
|
||||
tool_name=self._fix_tool_name(tool_name),
|
||||
tool_args=tool_args,
|
||||
)
|
||||
)
|
||||
|
||||
chat_request = _create_google_tool_response_content(
|
||||
|
||||
@@ -135,5 +135,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/govee_ble",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["govee-ble==0.43.0"]
|
||||
"requirements": ["govee-ble==0.43.1"]
|
||||
}
|
||||
|
||||
@@ -14,7 +14,12 @@ from pyheos import (
|
||||
)
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_IGNORE,
|
||||
ConfigFlow,
|
||||
ConfigFlowResult,
|
||||
OptionsFlow,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import selector
|
||||
@@ -141,8 +146,10 @@ class HeosFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
hostname = urlparse(discovery_info.ssdp_location).hostname
|
||||
assert hostname is not None
|
||||
|
||||
# Abort early when discovered host is part of the current system
|
||||
if entry and hostname in _get_current_hosts(entry):
|
||||
# Abort early when discovery is ignored or host is part of the current system
|
||||
if entry and (
|
||||
entry.source == SOURCE_IGNORE or hostname in _get_current_hosts(entry)
|
||||
):
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
|
||||
# Connect to discovered host and get system information
|
||||
@@ -198,7 +205,7 @@ class HeosFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Obtain host and validate connection."""
|
||||
await self.async_set_unique_id(DOMAIN)
|
||||
await self.async_set_unique_id(DOMAIN, raise_on_progress=False)
|
||||
self._abort_if_unique_id_configured(error="single_instance_allowed")
|
||||
# Try connecting to host if provided
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
@@ -159,13 +159,12 @@ class HeosCoordinator(DataUpdateCoordinator[None]):
|
||||
|
||||
async def _async_on_reconnected(self) -> None:
|
||||
"""Handle when reconnected so resources are updated and entities marked available."""
|
||||
await self._async_update_players()
|
||||
await self._async_update_sources()
|
||||
_LOGGER.warning("Successfully reconnected to HEOS host %s", self.host)
|
||||
self.async_update_listeners()
|
||||
|
||||
async def _async_on_controller_event(
|
||||
self, event: str, data: PlayerUpdateResult | None
|
||||
self, event: str, data: PlayerUpdateResult | None = None
|
||||
) -> None:
|
||||
"""Handle a controller event, such as players or groups changed."""
|
||||
if event == const.EVENT_PLAYERS_CHANGED:
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pyheos"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pyheos==1.0.2"],
|
||||
"requirements": ["pyheos==1.0.3"],
|
||||
"ssdp": [
|
||||
{
|
||||
"st": "urn:schemas-denon-com:device:ACT-Denon:1"
|
||||
|
||||
@@ -16,11 +16,17 @@ from aiohomeconnect.model import (
|
||||
SettingKey,
|
||||
)
|
||||
from aiohomeconnect.model.error import HomeConnectError
|
||||
import aiohttp
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import ATTR_DEVICE_ID, Platform
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.exceptions import (
|
||||
ConfigEntryAuthFailed,
|
||||
ConfigEntryNotReady,
|
||||
HomeAssistantError,
|
||||
ServiceValidationError,
|
||||
)
|
||||
from homeassistant.helpers import (
|
||||
config_entry_oauth2_flow,
|
||||
config_validation as cv,
|
||||
@@ -611,6 +617,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: HomeConnectConfigEntry)
|
||||
session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation)
|
||||
|
||||
config_entry_auth = AsyncConfigEntryAuth(hass, session)
|
||||
try:
|
||||
await config_entry_auth.async_get_access_token()
|
||||
except aiohttp.ClientResponseError as err:
|
||||
if 400 <= err.status < 500:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
raise ConfigEntryNotReady from err
|
||||
except aiohttp.ClientError as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
home_connect_client = HomeConnectClient(config_entry_auth)
|
||||
|
||||
|
||||
@@ -285,6 +285,7 @@ SPIN_SPEED_OPTIONS = {
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM400",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM600",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM800",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM900",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1000",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1200",
|
||||
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1400",
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/home_connect",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["aiohomeconnect"],
|
||||
"requirements": ["aiohomeconnect==0.16.2"],
|
||||
"requirements": ["aiohomeconnect==0.16.3"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
||||
@@ -386,6 +386,13 @@ class HomeConnectProgramSensor(HomeConnectSensor):
|
||||
|
||||
def update_native_value(self) -> None:
|
||||
"""Update the program sensor's status."""
|
||||
self.program_running = (
|
||||
status := self.appliance.status.get(StatusKey.BSH_COMMON_OPERATION_STATE)
|
||||
) is not None and status.value in [
|
||||
BSH_OPERATION_STATE_RUN,
|
||||
BSH_OPERATION_STATE_PAUSE,
|
||||
BSH_OPERATION_STATE_FINISHED,
|
||||
]
|
||||
event = self.appliance.events.get(cast(EventKey, self.bsh_key))
|
||||
if event:
|
||||
self._update_native_value(event.value)
|
||||
|
||||
@@ -461,6 +461,7 @@
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m400": "400 rpm",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m600": "600 rpm",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m800": "800 rpm",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m900": "900 rpm",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1000": "1000 rpm",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1200": "1200 rpm",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1400": "1400 rpm",
|
||||
@@ -1430,6 +1431,7 @@
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m400": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m400%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m600": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m600%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m800": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m800%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m900": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m900%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1000": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m1000%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1200": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m1200%]",
|
||||
"laundry_care_washer_enum_type_spin_speed_r_p_m1400": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m1400%]",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Pick Homematic IP access point",
|
||||
"description": "If you are about to register a **Homematic IP HCU1**, please press the button on top of the device before you continue.\n\nThe registration process must be completed within 5 minutes.",
|
||||
"data": {
|
||||
"hapid": "Access point ID (SGTIN)",
|
||||
"pin": "[%key:common::config_flow::data::pin%]",
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/hydrawise",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pydrawise"],
|
||||
"requirements": ["pydrawise==2025.2.0"]
|
||||
"requirements": ["pydrawise==2025.3.0"]
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import socket
|
||||
import ssl
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from uuid import uuid4
|
||||
|
||||
import certifi
|
||||
|
||||
@@ -292,7 +293,7 @@ class MqttClientSetup:
|
||||
"""
|
||||
# We don't import on the top because some integrations
|
||||
# should be able to optionally rely on MQTT.
|
||||
import paho.mqtt.client as mqtt # pylint: disable=import-outside-toplevel
|
||||
from paho.mqtt import client as mqtt # pylint: disable=import-outside-toplevel
|
||||
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
from .async_client import AsyncMQTTClient
|
||||
@@ -309,9 +310,10 @@ class MqttClientSetup:
|
||||
clean_session = True
|
||||
|
||||
if (client_id := config.get(CONF_CLIENT_ID)) is None:
|
||||
# PAHO MQTT relies on the MQTT server to generate random client IDs.
|
||||
# However, that feature is not mandatory so we generate our own.
|
||||
client_id = None
|
||||
# PAHO MQTT relies on the MQTT server to generate random client ID
|
||||
# for protocol version 3.1, however, that feature is not mandatory
|
||||
# so we generate our own.
|
||||
client_id = mqtt._base62(uuid4().int, padding=22) # noqa: SLF001
|
||||
transport: str = config.get(CONF_TRANSPORT, DEFAULT_TRANSPORT)
|
||||
self._client = AsyncMQTTClient(
|
||||
callback_api_version=mqtt.CallbackAPIVersion.VERSION2,
|
||||
|
||||
@@ -31,7 +31,6 @@ from homeassistant.components.light import (
|
||||
LightEntity,
|
||||
LightEntityFeature,
|
||||
brightness_supported,
|
||||
color_supported,
|
||||
valid_supported_color_modes,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
@@ -293,7 +292,7 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
|
||||
elif values["state"] is None:
|
||||
self._attr_is_on = None
|
||||
|
||||
if color_supported(self.supported_color_modes) and "color_mode" in values:
|
||||
if "color_mode" in values:
|
||||
self._update_color(values)
|
||||
|
||||
if brightness_supported(self.supported_color_modes):
|
||||
|
||||
@@ -276,22 +276,26 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
|
||||
self._attr_state = MediaPlayerState(player.state.value)
|
||||
else:
|
||||
self._attr_state = MediaPlayerState(STATE_OFF)
|
||||
group_members_entity_ids: list[str] = []
|
||||
|
||||
group_members: list[str] = []
|
||||
if player.group_childs:
|
||||
# translate MA group_childs to HA group_members as entity id's
|
||||
entity_registry = er.async_get(self.hass)
|
||||
group_members_entity_ids = [
|
||||
entity_id
|
||||
for child_id in player.group_childs
|
||||
if (
|
||||
entity_id := entity_registry.async_get_entity_id(
|
||||
self.platform.domain, DOMAIN, child_id
|
||||
)
|
||||
group_members = player.group_childs
|
||||
elif player.synced_to and (parent := self.mass.players.get(player.synced_to)):
|
||||
group_members = parent.group_childs
|
||||
|
||||
# translate MA group_childs to HA group_members as entity id's
|
||||
entity_registry = er.async_get(self.hass)
|
||||
group_members_entity_ids: list[str] = [
|
||||
entity_id
|
||||
for child_id in group_members
|
||||
if (
|
||||
entity_id := entity_registry.async_get_entity_id(
|
||||
self.platform.domain, DOMAIN, child_id
|
||||
)
|
||||
]
|
||||
# NOTE: we sort the group_members for now,
|
||||
# until the MA API returns them sorted (group_childs is now a set)
|
||||
self._attr_group_members = sorted(group_members_entity_ids)
|
||||
)
|
||||
]
|
||||
|
||||
self._attr_group_members = group_members_entity_ids
|
||||
self._attr_volume_level = (
|
||||
player.volume_level / 100 if player.volume_level is not None else None
|
||||
)
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/nexia",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["nexia"],
|
||||
"requirements": ["nexia==2.2.1"]
|
||||
"requirements": ["nexia==2.2.2"]
|
||||
}
|
||||
|
||||
@@ -58,6 +58,9 @@
|
||||
"switch": {
|
||||
"hold": {
|
||||
"name": "Hold"
|
||||
},
|
||||
"emergency_heat": {
|
||||
"name": "Emergency heat"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
import os
|
||||
@@ -58,7 +59,7 @@ class OneWireHub:
|
||||
|
||||
owproxy: protocol._Proxy
|
||||
devices: list[OWDeviceDescription]
|
||||
_version: str
|
||||
_version: str | None = None
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config_entry: OneWireConfigEntry) -> None:
|
||||
"""Initialize."""
|
||||
@@ -74,7 +75,9 @@ class OneWireHub:
|
||||
port = self._config_entry.data[CONF_PORT]
|
||||
_LOGGER.debug("Initializing connection to %s:%s", host, port)
|
||||
self.owproxy = protocol.proxy(host, port)
|
||||
self._version = self.owproxy.read(protocol.PTH_VERSION).decode()
|
||||
with contextlib.suppress(protocol.OwnetError):
|
||||
# Version is not available on all servers
|
||||
self._version = self.owproxy.read(protocol.PTH_VERSION).decode()
|
||||
self.devices = _discover_devices(self.owproxy)
|
||||
|
||||
async def initialize(self) -> None:
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["roborock"],
|
||||
"requirements": [
|
||||
"python-roborock==2.11.1",
|
||||
"python-roborock==2.12.2",
|
||||
"vacuum-map-parser-roborock==0.1.2"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/sense",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["sense_energy"],
|
||||
"requirements": ["sense-energy==0.13.6"]
|
||||
"requirements": ["sense-energy==0.13.7"]
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, cast
|
||||
@@ -11,15 +12,22 @@ from pysmartthings import (
|
||||
Attribute,
|
||||
Capability,
|
||||
Device,
|
||||
DeviceEvent,
|
||||
Scene,
|
||||
SmartThings,
|
||||
SmartThingsAuthenticationFailedError,
|
||||
SmartThingsSinkError,
|
||||
Status,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.const import (
|
||||
CONF_ACCESS_TOKEN,
|
||||
CONF_TOKEN,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
@@ -28,7 +36,15 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
async_get_config_entry_implementation,
|
||||
)
|
||||
|
||||
from .const import CONF_INSTALLED_APP_ID, CONF_LOCATION_ID, DOMAIN, MAIN, OLD_DATA
|
||||
from .const import (
|
||||
CONF_INSTALLED_APP_ID,
|
||||
CONF_LOCATION_ID,
|
||||
CONF_SUBSCRIPTION_ID,
|
||||
DOMAIN,
|
||||
EVENT_BUTTON,
|
||||
MAIN,
|
||||
OLD_DATA,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -90,6 +106,54 @@ async def async_setup_entry(hass: HomeAssistant, entry: SmartThingsConfigEntry)
|
||||
|
||||
client.refresh_token_function = _refresh_token
|
||||
|
||||
def _handle_max_connections() -> None:
|
||||
_LOGGER.debug("We hit the limit of max connections")
|
||||
hass.config_entries.async_schedule_reload(entry.entry_id)
|
||||
|
||||
client.max_connections_reached_callback = _handle_max_connections
|
||||
|
||||
def _handle_new_subscription_identifier(identifier: str | None) -> None:
|
||||
"""Handle a new subscription identifier."""
|
||||
hass.config_entries.async_update_entry(
|
||||
entry,
|
||||
data={
|
||||
**entry.data,
|
||||
CONF_SUBSCRIPTION_ID: identifier,
|
||||
},
|
||||
)
|
||||
if identifier is not None:
|
||||
_LOGGER.debug("Updating subscription ID to %s", identifier)
|
||||
else:
|
||||
_LOGGER.debug("Removing subscription ID")
|
||||
|
||||
client.new_subscription_id_callback = _handle_new_subscription_identifier
|
||||
|
||||
if (old_identifier := entry.data.get(CONF_SUBSCRIPTION_ID)) is not None:
|
||||
_LOGGER.debug("Trying to delete old subscription %s", old_identifier)
|
||||
await client.delete_subscription(old_identifier)
|
||||
|
||||
_LOGGER.debug("Trying to create a new subscription")
|
||||
try:
|
||||
subscription = await client.create_subscription(
|
||||
entry.data[CONF_LOCATION_ID],
|
||||
entry.data[CONF_TOKEN][CONF_INSTALLED_APP_ID],
|
||||
)
|
||||
except SmartThingsSinkError as err:
|
||||
_LOGGER.debug("Couldn't create a new subscription: %s", err)
|
||||
raise ConfigEntryNotReady from err
|
||||
subscription_id = subscription.subscription_id
|
||||
_handle_new_subscription_identifier(subscription_id)
|
||||
|
||||
entry.async_create_background_task(
|
||||
hass,
|
||||
client.subscribe(
|
||||
entry.data[CONF_LOCATION_ID],
|
||||
entry.data[CONF_TOKEN][CONF_INSTALLED_APP_ID],
|
||||
subscription,
|
||||
),
|
||||
"smartthings_socket",
|
||||
)
|
||||
|
||||
device_status: dict[str, FullDevice] = {}
|
||||
try:
|
||||
devices = await client.get_devices()
|
||||
@@ -114,12 +178,34 @@ async def async_setup_entry(hass: HomeAssistant, entry: SmartThingsConfigEntry)
|
||||
scenes=scenes,
|
||||
)
|
||||
|
||||
entry.async_create_background_task(
|
||||
hass,
|
||||
client.subscribe(
|
||||
entry.data[CONF_LOCATION_ID], entry.data[CONF_TOKEN][CONF_INSTALLED_APP_ID]
|
||||
),
|
||||
"smartthings_webhook",
|
||||
def handle_button_press(event: DeviceEvent) -> None:
|
||||
"""Handle a button press."""
|
||||
if (
|
||||
event.capability is Capability.BUTTON
|
||||
and event.attribute is Attribute.BUTTON
|
||||
):
|
||||
hass.bus.async_fire(
|
||||
EVENT_BUTTON,
|
||||
{
|
||||
"component_id": event.component_id,
|
||||
"device_id": event.device_id,
|
||||
"location_id": event.location_id,
|
||||
"value": event.value,
|
||||
"name": entry.runtime_data.devices[event.device_id].device.label,
|
||||
"data": event.data,
|
||||
},
|
||||
)
|
||||
|
||||
entry.async_on_unload(
|
||||
client.add_unspecified_device_event_listener(handle_button_press)
|
||||
)
|
||||
|
||||
async def _handle_shutdown(_: Event) -> None:
|
||||
"""Handle shutdown."""
|
||||
await client.delete_subscription(subscription_id)
|
||||
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _handle_shutdown)
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
@@ -145,6 +231,9 @@ async def async_unload_entry(
|
||||
hass: HomeAssistant, entry: SmartThingsConfigEntry
|
||||
) -> bool:
|
||||
"""Unload a config entry."""
|
||||
client = entry.runtime_data.client
|
||||
if (subscription_id := entry.data.get(CONF_SUBSCRIPTION_ID)) is not None:
|
||||
await client.delete_subscription(subscription_id)
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
@@ -160,26 +249,39 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
KEEP_CAPABILITY_QUIRK: dict[
|
||||
Capability | str, Callable[[dict[Attribute | str, Status]], bool]
|
||||
] = {
|
||||
Capability.DRYER_OPERATING_STATE: (
|
||||
lambda status: status[Attribute.SUPPORTED_MACHINE_STATES].value is not None
|
||||
),
|
||||
Capability.WASHER_OPERATING_STATE: (
|
||||
lambda status: status[Attribute.SUPPORTED_MACHINE_STATES].value is not None
|
||||
),
|
||||
Capability.DEMAND_RESPONSE_LOAD_CONTROL: lambda _: True,
|
||||
}
|
||||
|
||||
|
||||
def process_status(
|
||||
status: dict[str, dict[Capability | str, dict[Attribute | str, Status]]],
|
||||
) -> dict[str, dict[Capability | str, dict[Attribute | str, Status]]]:
|
||||
"""Remove disabled capabilities from status."""
|
||||
if (main_component := status.get("main")) is None or (
|
||||
if (main_component := status.get(MAIN)) is None:
|
||||
return status
|
||||
if (
|
||||
disabled_capabilities_capability := main_component.get(
|
||||
Capability.CUSTOM_DISABLED_CAPABILITIES
|
||||
)
|
||||
) is None:
|
||||
return status
|
||||
disabled_capabilities = cast(
|
||||
list[Capability | str],
|
||||
disabled_capabilities_capability[Attribute.DISABLED_CAPABILITIES].value,
|
||||
)
|
||||
if disabled_capabilities is not None:
|
||||
for capability in disabled_capabilities:
|
||||
# We still need to make sure the climate entity can work without this capability
|
||||
if (
|
||||
capability in main_component
|
||||
and capability != Capability.DEMAND_RESPONSE_LOAD_CONTROL
|
||||
):
|
||||
del main_component[capability]
|
||||
) is not None:
|
||||
disabled_capabilities = cast(
|
||||
list[Capability | str],
|
||||
disabled_capabilities_capability[Attribute.DISABLED_CAPABILITIES].value,
|
||||
)
|
||||
if disabled_capabilities is not None:
|
||||
for capability in disabled_capabilities:
|
||||
if capability in main_component and (
|
||||
capability not in KEEP_CAPABILITY_QUIRK
|
||||
or not KEEP_CAPABILITY_QUIRK[capability](main_component[capability])
|
||||
):
|
||||
del main_component[capability]
|
||||
return status
|
||||
|
||||
@@ -161,9 +161,7 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity):
|
||||
| ClimateEntityFeature.TURN_OFF
|
||||
| ClimateEntityFeature.TURN_ON
|
||||
)
|
||||
if self.get_attribute_value(
|
||||
Capability.THERMOSTAT_FAN_MODE, Attribute.THERMOSTAT_FAN_MODE
|
||||
):
|
||||
if self.supports_capability(Capability.THERMOSTAT_FAN_MODE):
|
||||
flags |= ClimateEntityFeature.FAN_MODE
|
||||
return flags
|
||||
|
||||
@@ -253,6 +251,8 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity):
|
||||
@property
|
||||
def hvac_action(self) -> HVACAction | None:
|
||||
"""Return the current running hvac operation if supported."""
|
||||
if not self.supports_capability(Capability.THERMOSTAT_OPERATING_STATE):
|
||||
return None
|
||||
return OPERATING_STATE_TO_ACTION.get(
|
||||
self.get_attribute_value(
|
||||
Capability.THERMOSTAT_OPERATING_STATE,
|
||||
@@ -272,11 +272,15 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity):
|
||||
@property
|
||||
def hvac_modes(self) -> list[HVACMode]:
|
||||
"""Return the list of available operation modes."""
|
||||
return [
|
||||
state
|
||||
for mode in self.get_attribute_value(
|
||||
if (
|
||||
supported_thermostat_modes := self.get_attribute_value(
|
||||
Capability.THERMOSTAT_MODE, Attribute.SUPPORTED_THERMOSTAT_MODES
|
||||
)
|
||||
) is None:
|
||||
return []
|
||||
return [
|
||||
state
|
||||
for mode in supported_thermostat_modes
|
||||
if (state := AC_MODE_TO_STATE.get(mode)) is not None
|
||||
]
|
||||
|
||||
@@ -314,10 +318,14 @@ class SmartThingsThermostat(SmartThingsEntity, ClimateEntity):
|
||||
@property
|
||||
def temperature_unit(self) -> str:
|
||||
"""Return the unit of measurement."""
|
||||
unit = self._internal_state[Capability.TEMPERATURE_MEASUREMENT][
|
||||
Attribute.TEMPERATURE
|
||||
].unit
|
||||
assert unit
|
||||
# Offline third party thermostats may not have a unit
|
||||
# Since climate always requires a unit, default to Celsius
|
||||
if (
|
||||
unit := self._internal_state[Capability.TEMPERATURE_MEASUREMENT][
|
||||
Attribute.TEMPERATURE
|
||||
].unit
|
||||
) is None:
|
||||
return UnitOfTemperature.CELSIUS
|
||||
return UNIT_MAP[unit]
|
||||
|
||||
|
||||
@@ -445,12 +453,15 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity):
|
||||
)
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
def extra_state_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return device specific state attributes.
|
||||
|
||||
Include attributes from the Demand Response Load Control (drlc)
|
||||
and Power Consumption capabilities.
|
||||
"""
|
||||
if not self.supports_capability(Capability.DEMAND_RESPONSE_LOAD_CONTROL):
|
||||
return None
|
||||
|
||||
drlc_status = self.get_attribute_value(
|
||||
Capability.DEMAND_RESPONSE_LOAD_CONTROL,
|
||||
Attribute.DEMAND_RESPONSE_LOAD_CONTROL_STATUS,
|
||||
@@ -560,5 +571,6 @@ class SmartThingsAirConditioner(SmartThingsEntity, ClimateEntity):
|
||||
Capability.AIR_CONDITIONER_MODE, Attribute.SUPPORTED_AC_MODES
|
||||
)
|
||||
if (state := AC_MODE_TO_STATE.get(mode)) is not None
|
||||
if state not in modes
|
||||
)
|
||||
return modes
|
||||
|
||||
@@ -32,3 +32,6 @@ CONF_REFRESH_TOKEN = "refresh_token"
|
||||
|
||||
MAIN = "main"
|
||||
OLD_DATA = "old_data"
|
||||
|
||||
CONF_SUBSCRIPTION_ID = "subscription_id"
|
||||
EVENT_BUTTON = "smartthings.button"
|
||||
|
||||
@@ -17,6 +17,15 @@ from .const import DOMAIN
|
||||
EVENT_WAIT_TIME = 5
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant,
|
||||
entry: SmartThingsConfigEntry,
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
client = entry.runtime_data.client
|
||||
return await client.get_raw_devices()
|
||||
|
||||
|
||||
async def async_get_device_diagnostics(
|
||||
hass: HomeAssistant, entry: SmartThingsConfigEntry, device: DeviceEntry
|
||||
) -> dict[str, Any]:
|
||||
@@ -26,7 +35,8 @@ async def async_get_device_diagnostics(
|
||||
identifier for identifier in device.identifiers if identifier[0] == DOMAIN
|
||||
)[1]
|
||||
|
||||
device_status = await client.get_device_status(device_id)
|
||||
device_status = await client.get_raw_device_status(device_id)
|
||||
device_info = await client.get_raw_device(device_id)
|
||||
|
||||
events: list[DeviceEvent] = []
|
||||
|
||||
@@ -39,11 +49,8 @@ async def async_get_device_diagnostics(
|
||||
|
||||
listener()
|
||||
|
||||
status: dict[str, Any] = {}
|
||||
for component, capabilities in device_status.items():
|
||||
status[component] = {}
|
||||
for capability, attributes in capabilities.items():
|
||||
status[component][capability] = {}
|
||||
for attribute, value in attributes.items():
|
||||
status[component][capability][attribute] = asdict(value)
|
||||
return {"events": [asdict(event) for event in events], "status": status}
|
||||
return {
|
||||
"events": [asdict(event) for event in events],
|
||||
"status": device_status,
|
||||
"info": device_info,
|
||||
}
|
||||
|
||||
@@ -48,7 +48,9 @@ class SmartThingsEntity(Entity):
|
||||
self._attr_device_info.update(
|
||||
{
|
||||
"manufacturer": ocf.manufacturer_name,
|
||||
"model": ocf.model_number.split("|")[0],
|
||||
"model": (
|
||||
(ocf.model_number.split("|")[0]) if ocf.model_number else None
|
||||
),
|
||||
"hw_version": ocf.hardware_version,
|
||||
"sw_version": ocf.firmware_version,
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ class SmartThingsFan(SmartThingsEntity, FanEntity):
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if fan is on."""
|
||||
return self.get_attribute_value(Capability.SWITCH, Attribute.SWITCH)
|
||||
return self.get_attribute_value(Capability.SWITCH, Attribute.SWITCH) == "on"
|
||||
|
||||
@property
|
||||
def percentage(self) -> int | None:
|
||||
@@ -132,6 +132,8 @@ class SmartThingsFan(SmartThingsEntity, FanEntity):
|
||||
|
||||
Requires FanEntityFeature.PRESET_MODE.
|
||||
"""
|
||||
if not self.supports_capability(Capability.AIR_CONDITIONER_FAN_MODE):
|
||||
return None
|
||||
return self.get_attribute_value(
|
||||
Capability.AIR_CONDITIONER_FAN_MODE, Attribute.FAN_MODE
|
||||
)
|
||||
@@ -142,6 +144,8 @@ class SmartThingsFan(SmartThingsEntity, FanEntity):
|
||||
|
||||
Requires FanEntityFeature.PRESET_MODE.
|
||||
"""
|
||||
if not self.supports_capability(Capability.AIR_CONDITIONER_FAN_MODE):
|
||||
return None
|
||||
return self.get_attribute_value(
|
||||
Capability.AIR_CONDITIONER_FAN_MODE, Attribute.SUPPORTED_AC_FAN_MODES
|
||||
)
|
||||
|
||||
@@ -147,14 +147,21 @@ class SmartThingsLight(SmartThingsEntity, LightEntity, RestoreEntity):
|
||||
"""Update entity attributes when the device status has changed."""
|
||||
# Brightness and transition
|
||||
if brightness_supported(self._attr_supported_color_modes):
|
||||
self._attr_brightness = int(
|
||||
convert_scale(
|
||||
self.get_attribute_value(Capability.SWITCH_LEVEL, Attribute.LEVEL),
|
||||
100,
|
||||
255,
|
||||
0,
|
||||
if (
|
||||
brightness := self.get_attribute_value(
|
||||
Capability.SWITCH_LEVEL, Attribute.LEVEL
|
||||
)
|
||||
) is None:
|
||||
self._attr_brightness = None
|
||||
else:
|
||||
self._attr_brightness = int(
|
||||
convert_scale(
|
||||
brightness,
|
||||
100,
|
||||
255,
|
||||
0,
|
||||
)
|
||||
)
|
||||
)
|
||||
# Color Temperature
|
||||
if ColorMode.COLOR_TEMP in self._attr_supported_color_modes:
|
||||
self._attr_color_temp_kelvin = self.get_attribute_value(
|
||||
@@ -162,16 +169,21 @@ class SmartThingsLight(SmartThingsEntity, LightEntity, RestoreEntity):
|
||||
)
|
||||
# Color
|
||||
if ColorMode.HS in self._attr_supported_color_modes:
|
||||
self._attr_hs_color = (
|
||||
convert_scale(
|
||||
self.get_attribute_value(Capability.COLOR_CONTROL, Attribute.HUE),
|
||||
100,
|
||||
360,
|
||||
),
|
||||
self.get_attribute_value(
|
||||
Capability.COLOR_CONTROL, Attribute.SATURATION
|
||||
),
|
||||
)
|
||||
if (
|
||||
hue := self.get_attribute_value(Capability.COLOR_CONTROL, Attribute.HUE)
|
||||
) is None:
|
||||
self._attr_hs_color = None
|
||||
else:
|
||||
self._attr_hs_color = (
|
||||
convert_scale(
|
||||
hue,
|
||||
100,
|
||||
360,
|
||||
),
|
||||
self.get_attribute_value(
|
||||
Capability.COLOR_CONTROL, Attribute.SATURATION
|
||||
),
|
||||
)
|
||||
|
||||
async def async_set_color(self, hs_color):
|
||||
"""Set the color of the device."""
|
||||
@@ -217,6 +229,10 @@ class SmartThingsLight(SmartThingsEntity, LightEntity, RestoreEntity):
|
||||
super()._update_handler(event)
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return true if light is on."""
|
||||
return self.get_attribute_value(Capability.SWITCH, Attribute.SWITCH) == "on"
|
||||
if (
|
||||
state := self.get_attribute_value(Capability.SWITCH, Attribute.SWITCH)
|
||||
) is None:
|
||||
return None
|
||||
return state == "on"
|
||||
|
||||
@@ -29,5 +29,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/smartthings",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pysmartthings"],
|
||||
"requirements": ["pysmartthings==2.5.0"]
|
||||
"requirements": ["pysmartthings==2.7.2"]
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ from __future__ import annotations
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
from pysmartthings import Attribute, Capability, SmartThings
|
||||
from pysmartthings import Attribute, Capability, SmartThings, Status
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
@@ -57,6 +57,7 @@ JOB_STATE_MAP = {
|
||||
"freezeProtection": "freeze_protection",
|
||||
"preDrain": "pre_drain",
|
||||
"preWash": "pre_wash",
|
||||
"prewash": "pre_wash",
|
||||
"wrinklePrevent": "wrinkle_prevent",
|
||||
"unknown": None,
|
||||
}
|
||||
@@ -130,7 +131,7 @@ class SmartThingsSensorEntityDescription(SensorEntityDescription):
|
||||
unique_id_separator: str = "."
|
||||
capability_ignore_list: list[set[Capability]] | None = None
|
||||
options_attribute: Attribute | None = None
|
||||
except_if_state_none: bool = False
|
||||
exists_fn: Callable[[Status], bool] | None = None
|
||||
|
||||
|
||||
CAPABILITY_TO_SENSORS: dict[
|
||||
@@ -561,6 +562,8 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
SmartThingsSensorEntityDescription(
|
||||
key=Attribute.COMPLETION_TIME,
|
||||
translation_key="completion_time",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=dt_util.parse_datetime,
|
||||
)
|
||||
],
|
||||
},
|
||||
@@ -581,7 +584,10 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
value_fn=lambda value: value["energy"] / 1000,
|
||||
suggested_display_precision=2,
|
||||
except_if_state_none=True,
|
||||
exists_fn=lambda status: (
|
||||
(value := cast(dict | None, status.value)) is not None
|
||||
and "energy" in value
|
||||
),
|
||||
),
|
||||
SmartThingsSensorEntityDescription(
|
||||
key="power_meter",
|
||||
@@ -591,7 +597,10 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
value_fn=lambda value: value["power"],
|
||||
extra_state_attributes_fn=power_attributes,
|
||||
suggested_display_precision=2,
|
||||
except_if_state_none=True,
|
||||
exists_fn=lambda status: (
|
||||
(value := cast(dict | None, status.value)) is not None
|
||||
and "power" in value
|
||||
),
|
||||
),
|
||||
SmartThingsSensorEntityDescription(
|
||||
key="deltaEnergy_meter",
|
||||
@@ -601,7 +610,10 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
value_fn=lambda value: value["deltaEnergy"] / 1000,
|
||||
suggested_display_precision=2,
|
||||
except_if_state_none=True,
|
||||
exists_fn=lambda status: (
|
||||
(value := cast(dict | None, status.value)) is not None
|
||||
and "deltaEnergy" in value
|
||||
),
|
||||
),
|
||||
SmartThingsSensorEntityDescription(
|
||||
key="powerEnergy_meter",
|
||||
@@ -611,7 +623,10 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
value_fn=lambda value: value["powerEnergy"] / 1000,
|
||||
suggested_display_precision=2,
|
||||
except_if_state_none=True,
|
||||
exists_fn=lambda status: (
|
||||
(value := cast(dict | None, status.value)) is not None
|
||||
and "powerEnergy" in value
|
||||
),
|
||||
),
|
||||
SmartThingsSensorEntityDescription(
|
||||
key="energySaved_meter",
|
||||
@@ -621,7 +636,10 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
value_fn=lambda value: value["energySaved"] / 1000,
|
||||
suggested_display_precision=2,
|
||||
except_if_state_none=True,
|
||||
exists_fn=lambda status: (
|
||||
(value := cast(dict | None, status.value)) is not None
|
||||
and "energySaved" in value
|
||||
),
|
||||
),
|
||||
]
|
||||
},
|
||||
@@ -951,6 +969,7 @@ UNITS = {
|
||||
"F": UnitOfTemperature.FAHRENHEIT,
|
||||
"lux": LIGHT_LUX,
|
||||
"mG": None,
|
||||
"μg/m^3": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
}
|
||||
|
||||
|
||||
@@ -976,8 +995,8 @@ async def async_setup_entry(
|
||||
)
|
||||
)
|
||||
and (
|
||||
not description.except_if_state_none
|
||||
or device.status[MAIN][capability][attribute].value is not None
|
||||
not description.exists_fn
|
||||
or description.exists_fn(device.status[MAIN][capability][attribute])
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1031,8 +1050,11 @@ class SmartThingsSensor(SmartThingsEntity, SensorEntity):
|
||||
def options(self) -> list[str] | None:
|
||||
"""Return the options for this sensor."""
|
||||
if self.entity_description.options_attribute:
|
||||
options = self.get_attribute_value(
|
||||
self.capability, self.entity_description.options_attribute
|
||||
)
|
||||
if (
|
||||
options := self.get_attribute_value(
|
||||
self.capability, self.entity_description.options_attribute
|
||||
)
|
||||
) is None:
|
||||
return []
|
||||
return [option.lower() for option in options]
|
||||
return super().options
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["snoo"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["python-snoo==0.6.0"]
|
||||
"requirements": ["python-snoo==0.6.1"]
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ SONOS_TRACKS = "tracks"
|
||||
SONOS_COMPOSER = "composers"
|
||||
SONOS_RADIO = "radio"
|
||||
SONOS_OTHER_ITEM = "other items"
|
||||
SONOS_AUDIO_BOOK = "audio book"
|
||||
|
||||
SONOS_STATE_PLAYING = "PLAYING"
|
||||
SONOS_STATE_TRANSITIONING = "TRANSITIONING"
|
||||
@@ -67,6 +68,7 @@ SONOS_TO_MEDIA_CLASSES = {
|
||||
"object.item": MediaClass.TRACK,
|
||||
"object.item.audioItem.musicTrack": MediaClass.TRACK,
|
||||
"object.item.audioItem.audioBroadcast": MediaClass.GENRE,
|
||||
"object.item.audioItem.audioBook": MediaClass.TRACK,
|
||||
}
|
||||
|
||||
SONOS_TO_MEDIA_TYPES = {
|
||||
@@ -84,6 +86,7 @@ SONOS_TO_MEDIA_TYPES = {
|
||||
"object.container.playlistContainer.sameArtist": MediaType.ARTIST,
|
||||
"object.container.playlistContainer": MediaType.PLAYLIST,
|
||||
"object.item.audioItem.musicTrack": MediaType.TRACK,
|
||||
"object.item.audioItem.audioBook": MediaType.TRACK,
|
||||
}
|
||||
|
||||
MEDIA_TYPES_TO_SONOS: dict[MediaType | str, str] = {
|
||||
@@ -113,6 +116,7 @@ SONOS_TYPES_MAPPING = {
|
||||
"object.item": SONOS_OTHER_ITEM,
|
||||
"object.item.audioItem.musicTrack": SONOS_TRACKS,
|
||||
"object.item.audioItem.audioBroadcast": SONOS_RADIO,
|
||||
"object.item.audioItem.audioBook": SONOS_AUDIO_BOOK,
|
||||
}
|
||||
|
||||
LIBRARY_TITLES_MAPPING = {
|
||||
|
||||
@@ -43,6 +43,7 @@ SENSORS: tuple[SensorEntityDescription, ...] = (
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
device_class=SensorDeviceClass.DURATION,
|
||||
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||
suggested_unit_of_measurement=UnitOfTime.HOURS,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=STATUS_SENSOR_INFO_TOTAL_GENRES,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/synology_dsm",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["synology_dsm"],
|
||||
"requirements": ["py-synologydsm-api==2.7.0"],
|
||||
"requirements": ["py-synologydsm-api==2.7.1"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Synology",
|
||||
|
||||
@@ -466,6 +466,7 @@ async def async_setup_entry(
|
||||
for energysite in entry.runtime_data.energysites
|
||||
for description in ENERGY_LIVE_DESCRIPTIONS
|
||||
if description.key in energysite.live_coordinator.data
|
||||
or description.key == "percentage_charged"
|
||||
),
|
||||
( # Add energy site history
|
||||
TeslaFleetEnergyHistorySensorEntity(energysite, description)
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/teslemetry",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["tesla-fleet-api"],
|
||||
"requirements": ["tesla-fleet-api==0.9.12", "teslemetry-stream==0.6.10"]
|
||||
"requirements": ["tesla-fleet-api==0.9.12", "teslemetry-stream==0.6.12"]
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ class TeslemetryVehicleSensorEntityDescription(SensorEntityDescription):
|
||||
|
||||
polling: bool = False
|
||||
polling_value_fn: Callable[[StateType], StateType] = lambda x: x
|
||||
polling_available_fn: Callable[[StateType], bool] = lambda x: x is not None
|
||||
nullable: bool = False
|
||||
streaming_key: Signal | None = None
|
||||
streaming_value_fn: Callable[[str | int | float], StateType] = lambda x: x
|
||||
streaming_firmware: str = "2024.26"
|
||||
@@ -210,7 +210,7 @@ VEHICLE_DESCRIPTIONS: tuple[TeslemetryVehicleSensorEntityDescription, ...] = (
|
||||
TeslemetryVehicleSensorEntityDescription(
|
||||
key="drive_state_shift_state",
|
||||
polling=True,
|
||||
polling_available_fn=lambda x: True,
|
||||
nullable=True,
|
||||
polling_value_fn=lambda x: SHIFT_STATES.get(str(x), "p"),
|
||||
streaming_key=Signal.GEAR,
|
||||
streaming_value_fn=lambda x: str(ShiftState.get(x, "P")).lower(),
|
||||
@@ -622,10 +622,10 @@ class TeslemetryStreamSensorEntity(TeslemetryVehicleStreamEntity, RestoreSensor)
|
||||
|
||||
def _async_value_from_stream(self, value) -> None:
|
||||
"""Update the value of the entity."""
|
||||
if value is None:
|
||||
self._attr_native_value = None
|
||||
else:
|
||||
if self.entity_description.nullable or value is not None:
|
||||
self._attr_native_value = self.entity_description.streaming_value_fn(value)
|
||||
else:
|
||||
self._attr_native_value = None
|
||||
|
||||
|
||||
class TeslemetryVehicleSensorEntity(TeslemetryVehicleEntity, SensorEntity):
|
||||
@@ -644,7 +644,7 @@ class TeslemetryVehicleSensorEntity(TeslemetryVehicleEntity, SensorEntity):
|
||||
|
||||
def _async_update_attrs(self) -> None:
|
||||
"""Update the attributes of the sensor."""
|
||||
if self.entity_description.polling_available_fn(self._value):
|
||||
if self.entity_description.nullable or self._value is not None:
|
||||
self._attr_available = True
|
||||
self._attr_native_value = self.entity_description.polling_value_fn(
|
||||
self._value
|
||||
|
||||
@@ -148,7 +148,7 @@ DESCRIPTIONS: tuple[TessieSensorEntityDescription, ...] = (
|
||||
key="drive_state_shift_state",
|
||||
options=["p", "d", "r", "n"],
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
value_fn=lambda x: x.lower() if isinstance(x, str) else x,
|
||||
value_fn=lambda x: x.lower() if isinstance(x, str) else "p",
|
||||
),
|
||||
TessieSensorEntityDescription(
|
||||
key="vehicle_state_odometer",
|
||||
@@ -397,6 +397,7 @@ async def async_setup_entry(
|
||||
for energysite in entry.runtime_data.energysites
|
||||
for description in ENERGY_LIVE_DESCRIPTIONS
|
||||
if description.key in energysite.live_coordinator.data
|
||||
or description.key == "percentage_charged"
|
||||
),
|
||||
( # Add wall connectors
|
||||
TessieWallConnectorSensorEntity(energysite, din, description)
|
||||
@@ -449,7 +450,6 @@ class TessieEnergyLiveSensorEntity(TessieEnergyEntity, SensorEntity):
|
||||
|
||||
def _async_update_attrs(self) -> None:
|
||||
"""Update the attributes of the sensor."""
|
||||
self._attr_available = self._value is not None
|
||||
self._attr_native_value = self.entity_description.value_fn(self._value)
|
||||
|
||||
|
||||
|
||||
@@ -54,5 +54,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/thermobeacon",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["thermobeacon-ble==0.8.0"]
|
||||
"requirements": ["thermobeacon-ble==0.8.1"]
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"velbus-protocol"
|
||||
],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["velbus-aio==2025.1.1"],
|
||||
"requirements": ["velbus-aio==2025.3.0"],
|
||||
"usb": [
|
||||
{
|
||||
"vid": "10CF",
|
||||
|
||||
@@ -13,7 +13,11 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
|
||||
|
||||
from .const import CONF_BACKUP_PATH, DATA_BACKUP_AGENT_LISTENERS, DOMAIN
|
||||
from .helpers import async_create_client, async_ensure_path_exists
|
||||
from .helpers import (
|
||||
async_create_client,
|
||||
async_ensure_path_exists,
|
||||
async_migrate_wrong_folder_path,
|
||||
)
|
||||
|
||||
type WebDavConfigEntry = ConfigEntry[Client]
|
||||
|
||||
@@ -46,10 +50,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: WebDavConfigEntry) -> bo
|
||||
translation_key="cannot_connect",
|
||||
)
|
||||
|
||||
path = entry.data.get(CONF_BACKUP_PATH, "/")
|
||||
await async_migrate_wrong_folder_path(client, path)
|
||||
|
||||
# Ensure the backup directory exists
|
||||
if not await async_ensure_path_exists(
|
||||
client, entry.data.get(CONF_BACKUP_PATH, "/")
|
||||
):
|
||||
if not await async_ensure_path_exists(client, path):
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="cannot_access_or_create_backup_path",
|
||||
|
||||
@@ -171,6 +171,7 @@ class WebDavBackupAgent(BackupAgent):
|
||||
await open_stream(),
|
||||
f"{self._backup_path}/{filename_tar}",
|
||||
timeout=BACKUP_TIMEOUT,
|
||||
content_length=backup.size,
|
||||
)
|
||||
|
||||
_LOGGER.debug(
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
"""Helper functions for the WebDAV component."""
|
||||
|
||||
import logging
|
||||
|
||||
from aiowebdav2.client import Client, ClientOptions
|
||||
from aiowebdav2.exceptions import WebDavError
|
||||
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@callback
|
||||
def async_create_client(
|
||||
@@ -36,3 +44,24 @@ async def async_ensure_path_exists(client: Client, path: str) -> bool:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_migrate_wrong_folder_path(client: Client, path: str) -> None:
|
||||
"""Migrate the wrong encoded folder path to the correct one."""
|
||||
wrong_path = path.replace(" ", "%20")
|
||||
if await client.check(wrong_path):
|
||||
try:
|
||||
await client.move(wrong_path, path)
|
||||
except WebDavError as err:
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="failed_to_migrate_folder",
|
||||
translation_placeholders={
|
||||
"wrong_path": wrong_path,
|
||||
"correct_path": path,
|
||||
},
|
||||
) from err
|
||||
|
||||
_LOGGER.debug(
|
||||
"Migrated wrong encoded folder path from %s to %s", wrong_path, path
|
||||
)
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aiowebdav2"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["aiowebdav2==0.3.1"]
|
||||
"requirements": ["aiowebdav2==0.4.2"]
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
},
|
||||
"cannot_access_or_create_backup_path": {
|
||||
"message": "Cannot access or create backup path. Please check the path and permissions."
|
||||
},
|
||||
"failed_to_migrate_folder": {
|
||||
"message": "Failed to migrate wrong encoded folder \"{wrong_path}\" to \"{correct_path}\"."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"zha",
|
||||
"universal_silabs_flasher"
|
||||
],
|
||||
"requirements": ["zha==0.0.51"],
|
||||
"requirements": ["zha==0.0.52"],
|
||||
"usb": [
|
||||
{
|
||||
"vid": "10C4",
|
||||
|
||||
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2025
|
||||
MINOR_VERSION: Final = 3
|
||||
PATCH_VERSION: Final = "0"
|
||||
PATCH_VERSION: Final = "2"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 13, 0)
|
||||
|
||||
@@ -37,11 +37,11 @@ habluetooth==3.24.1
|
||||
hass-nabucasa==0.92.0
|
||||
hassil==2.2.3
|
||||
home-assistant-bluetooth==1.13.1
|
||||
home-assistant-frontend==20250305.0
|
||||
home-assistant-frontend==20250306.0
|
||||
home-assistant-intents==2025.3.5
|
||||
httpx==0.28.1
|
||||
ifaddr==0.2.0
|
||||
Jinja2==3.1.5
|
||||
Jinja2==3.1.6
|
||||
lru-dict==1.3.0
|
||||
mutagen==1.47.0
|
||||
orjson==3.10.12
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2025.3.0"
|
||||
version = "2025.3.2"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
@@ -52,7 +52,7 @@ dependencies = [
|
||||
"httpx==0.28.1",
|
||||
"home-assistant-bluetooth==1.13.1",
|
||||
"ifaddr==0.2.0",
|
||||
"Jinja2==3.1.5",
|
||||
"Jinja2==3.1.6",
|
||||
"lru-dict==1.3.0",
|
||||
"PyJWT==2.10.1",
|
||||
# PyJWT has loose dependency. We want the latest one.
|
||||
|
||||
2
requirements.txt
generated
2
requirements.txt
generated
@@ -25,7 +25,7 @@ hass-nabucasa==0.92.0
|
||||
httpx==0.28.1
|
||||
home-assistant-bluetooth==1.13.1
|
||||
ifaddr==0.2.0
|
||||
Jinja2==3.1.5
|
||||
Jinja2==3.1.6
|
||||
lru-dict==1.3.0
|
||||
PyJWT==2.10.1
|
||||
cryptography==44.0.1
|
||||
|
||||
36
requirements_all.txt
generated
36
requirements_all.txt
generated
@@ -264,7 +264,7 @@ aioharmony==0.4.1
|
||||
aiohasupervisor==0.3.0
|
||||
|
||||
# homeassistant.components.home_connect
|
||||
aiohomeconnect==0.16.2
|
||||
aiohomeconnect==0.16.3
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
aiohomekit==3.2.8
|
||||
@@ -422,7 +422,7 @@ aiowaqi==3.1.0
|
||||
aiowatttime==0.1.1
|
||||
|
||||
# homeassistant.components.webdav
|
||||
aiowebdav2==0.3.1
|
||||
aiowebdav2==0.4.2
|
||||
|
||||
# homeassistant.components.webostv
|
||||
aiowebostv==0.7.3
|
||||
@@ -557,7 +557,7 @@ av==13.1.0
|
||||
axis==64
|
||||
|
||||
# homeassistant.components.fujitsu_fglair
|
||||
ayla-iot-unofficial==1.4.5
|
||||
ayla-iot-unofficial==1.4.7
|
||||
|
||||
# homeassistant.components.azure_event_hub
|
||||
azure-eventhub==5.11.1
|
||||
@@ -899,7 +899,7 @@ eufylife-ble-client==0.1.8
|
||||
# evdev==1.6.1
|
||||
|
||||
# homeassistant.components.evohome
|
||||
evohome-async==1.0.2
|
||||
evohome-async==1.0.4
|
||||
|
||||
# homeassistant.components.bryant_evolution
|
||||
evolutionhttp==0.0.18
|
||||
@@ -1058,7 +1058,7 @@ goslide-api==0.7.0
|
||||
gotailwind==0.3.0
|
||||
|
||||
# homeassistant.components.govee_ble
|
||||
govee-ble==0.43.0
|
||||
govee-ble==0.43.1
|
||||
|
||||
# homeassistant.components.govee_light_local
|
||||
govee-local-api==2.0.1
|
||||
@@ -1152,7 +1152,7 @@ hole==0.8.0
|
||||
holidays==0.68
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20250305.0
|
||||
home-assistant-frontend==20250306.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2025.3.5
|
||||
@@ -1483,7 +1483,7 @@ nettigo-air-monitor==4.0.0
|
||||
neurio==0.3.1
|
||||
|
||||
# homeassistant.components.nexia
|
||||
nexia==2.2.1
|
||||
nexia==2.2.2
|
||||
|
||||
# homeassistant.components.nextcloud
|
||||
nextcloudmonitor==1.5.1
|
||||
@@ -1755,7 +1755,7 @@ py-schluter==0.1.7
|
||||
py-sucks==0.9.10
|
||||
|
||||
# homeassistant.components.synology_dsm
|
||||
py-synologydsm-api==2.7.0
|
||||
py-synologydsm-api==2.7.1
|
||||
|
||||
# homeassistant.components.atome
|
||||
pyAtome==0.1.1
|
||||
@@ -1906,7 +1906,7 @@ pydiscovergy==3.0.2
|
||||
pydoods==1.0.2
|
||||
|
||||
# homeassistant.components.hydrawise
|
||||
pydrawise==2025.2.0
|
||||
pydrawise==2025.3.0
|
||||
|
||||
# homeassistant.components.android_ip_webcam
|
||||
pydroid-ipcam==2.0.0
|
||||
@@ -1996,7 +1996,7 @@ pygti==0.9.4
|
||||
pyhaversion==22.8.0
|
||||
|
||||
# homeassistant.components.heos
|
||||
pyheos==1.0.2
|
||||
pyheos==1.0.3
|
||||
|
||||
# homeassistant.components.hive
|
||||
pyhive-integration==1.0.2
|
||||
@@ -2310,7 +2310,7 @@ pysma==0.7.5
|
||||
pysmappee==0.2.29
|
||||
|
||||
# homeassistant.components.smartthings
|
||||
pysmartthings==2.5.0
|
||||
pysmartthings==2.7.2
|
||||
|
||||
# homeassistant.components.smarty
|
||||
pysmarty2==0.10.2
|
||||
@@ -2461,13 +2461,13 @@ python-rabbitair==0.0.8
|
||||
python-ripple-api==0.0.3
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==2.11.1
|
||||
python-roborock==2.12.2
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.39
|
||||
|
||||
# homeassistant.components.snoo
|
||||
python-snoo==0.6.0
|
||||
python-snoo==0.6.1
|
||||
|
||||
# homeassistant.components.songpal
|
||||
python-songpal==0.16.2
|
||||
@@ -2694,7 +2694,7 @@ sendgrid==6.8.2
|
||||
|
||||
# homeassistant.components.emulated_kasa
|
||||
# homeassistant.components.sense
|
||||
sense-energy==0.13.6
|
||||
sense-energy==0.13.7
|
||||
|
||||
# homeassistant.components.sensirion_ble
|
||||
sensirion-ble==0.1.1
|
||||
@@ -2881,7 +2881,7 @@ tesla-powerwall==0.5.2
|
||||
tesla-wall-connector==1.0.2
|
||||
|
||||
# homeassistant.components.teslemetry
|
||||
teslemetry-stream==0.6.10
|
||||
teslemetry-stream==0.6.12
|
||||
|
||||
# homeassistant.components.tessie
|
||||
tessie-api==0.1.1
|
||||
@@ -2890,7 +2890,7 @@ tessie-api==0.1.1
|
||||
# tf-models-official==2.5.0
|
||||
|
||||
# homeassistant.components.thermobeacon
|
||||
thermobeacon-ble==0.8.0
|
||||
thermobeacon-ble==0.8.1
|
||||
|
||||
# homeassistant.components.thermopro
|
||||
thermopro-ble==0.11.0
|
||||
@@ -3000,7 +3000,7 @@ vallox-websocket-api==5.3.0
|
||||
vehicle==2.2.2
|
||||
|
||||
# homeassistant.components.velbus
|
||||
velbus-aio==2025.1.1
|
||||
velbus-aio==2025.3.0
|
||||
|
||||
# homeassistant.components.venstar
|
||||
venstarcolortouch==0.19
|
||||
@@ -3149,7 +3149,7 @@ zeroconf==0.145.1
|
||||
zeversolar==0.3.2
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==0.0.51
|
||||
zha==0.0.52
|
||||
|
||||
# homeassistant.components.zhong_hong
|
||||
zhong-hong-hvac==1.0.13
|
||||
|
||||
36
requirements_test_all.txt
generated
36
requirements_test_all.txt
generated
@@ -249,7 +249,7 @@ aioharmony==0.4.1
|
||||
aiohasupervisor==0.3.0
|
||||
|
||||
# homeassistant.components.home_connect
|
||||
aiohomeconnect==0.16.2
|
||||
aiohomeconnect==0.16.3
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
aiohomekit==3.2.8
|
||||
@@ -404,7 +404,7 @@ aiowaqi==3.1.0
|
||||
aiowatttime==0.1.1
|
||||
|
||||
# homeassistant.components.webdav
|
||||
aiowebdav2==0.3.1
|
||||
aiowebdav2==0.4.2
|
||||
|
||||
# homeassistant.components.webostv
|
||||
aiowebostv==0.7.3
|
||||
@@ -506,7 +506,7 @@ av==13.1.0
|
||||
axis==64
|
||||
|
||||
# homeassistant.components.fujitsu_fglair
|
||||
ayla-iot-unofficial==1.4.5
|
||||
ayla-iot-unofficial==1.4.7
|
||||
|
||||
# homeassistant.components.azure_event_hub
|
||||
azure-eventhub==5.11.1
|
||||
@@ -765,7 +765,7 @@ eternalegypt==0.0.16
|
||||
eufylife-ble-client==0.1.8
|
||||
|
||||
# homeassistant.components.evohome
|
||||
evohome-async==1.0.2
|
||||
evohome-async==1.0.4
|
||||
|
||||
# homeassistant.components.bryant_evolution
|
||||
evolutionhttp==0.0.18
|
||||
@@ -908,7 +908,7 @@ goslide-api==0.7.0
|
||||
gotailwind==0.3.0
|
||||
|
||||
# homeassistant.components.govee_ble
|
||||
govee-ble==0.43.0
|
||||
govee-ble==0.43.1
|
||||
|
||||
# homeassistant.components.govee_light_local
|
||||
govee-local-api==2.0.1
|
||||
@@ -981,7 +981,7 @@ hole==0.8.0
|
||||
holidays==0.68
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20250305.0
|
||||
home-assistant-frontend==20250306.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2025.3.5
|
||||
@@ -1246,7 +1246,7 @@ netmap==0.7.0.2
|
||||
nettigo-air-monitor==4.0.0
|
||||
|
||||
# homeassistant.components.nexia
|
||||
nexia==2.2.1
|
||||
nexia==2.2.2
|
||||
|
||||
# homeassistant.components.nextcloud
|
||||
nextcloudmonitor==1.5.1
|
||||
@@ -1453,7 +1453,7 @@ py-nightscout==1.2.2
|
||||
py-sucks==0.9.10
|
||||
|
||||
# homeassistant.components.synology_dsm
|
||||
py-synologydsm-api==2.7.0
|
||||
py-synologydsm-api==2.7.1
|
||||
|
||||
# homeassistant.components.hdmi_cec
|
||||
pyCEC==0.5.2
|
||||
@@ -1556,7 +1556,7 @@ pydexcom==0.2.3
|
||||
pydiscovergy==3.0.2
|
||||
|
||||
# homeassistant.components.hydrawise
|
||||
pydrawise==2025.2.0
|
||||
pydrawise==2025.3.0
|
||||
|
||||
# homeassistant.components.android_ip_webcam
|
||||
pydroid-ipcam==2.0.0
|
||||
@@ -1625,7 +1625,7 @@ pygti==0.9.4
|
||||
pyhaversion==22.8.0
|
||||
|
||||
# homeassistant.components.heos
|
||||
pyheos==1.0.2
|
||||
pyheos==1.0.3
|
||||
|
||||
# homeassistant.components.hive
|
||||
pyhive-integration==1.0.2
|
||||
@@ -1882,7 +1882,7 @@ pysma==0.7.5
|
||||
pysmappee==0.2.29
|
||||
|
||||
# homeassistant.components.smartthings
|
||||
pysmartthings==2.5.0
|
||||
pysmartthings==2.7.2
|
||||
|
||||
# homeassistant.components.smarty
|
||||
pysmarty2==0.10.2
|
||||
@@ -1994,13 +1994,13 @@ python-picnic-api2==1.2.2
|
||||
python-rabbitair==0.0.8
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==2.11.1
|
||||
python-roborock==2.12.2
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.39
|
||||
|
||||
# homeassistant.components.snoo
|
||||
python-snoo==0.6.0
|
||||
python-snoo==0.6.1
|
||||
|
||||
# homeassistant.components.songpal
|
||||
python-songpal==0.16.2
|
||||
@@ -2173,7 +2173,7 @@ securetar==2025.2.1
|
||||
|
||||
# homeassistant.components.emulated_kasa
|
||||
# homeassistant.components.sense
|
||||
sense-energy==0.13.6
|
||||
sense-energy==0.13.7
|
||||
|
||||
# homeassistant.components.sensirion_ble
|
||||
sensirion-ble==0.1.1
|
||||
@@ -2321,13 +2321,13 @@ tesla-powerwall==0.5.2
|
||||
tesla-wall-connector==1.0.2
|
||||
|
||||
# homeassistant.components.teslemetry
|
||||
teslemetry-stream==0.6.10
|
||||
teslemetry-stream==0.6.12
|
||||
|
||||
# homeassistant.components.tessie
|
||||
tessie-api==0.1.1
|
||||
|
||||
# homeassistant.components.thermobeacon
|
||||
thermobeacon-ble==0.8.0
|
||||
thermobeacon-ble==0.8.1
|
||||
|
||||
# homeassistant.components.thermopro
|
||||
thermopro-ble==0.11.0
|
||||
@@ -2416,7 +2416,7 @@ vallox-websocket-api==5.3.0
|
||||
vehicle==2.2.2
|
||||
|
||||
# homeassistant.components.velbus
|
||||
velbus-aio==2025.1.1
|
||||
velbus-aio==2025.3.0
|
||||
|
||||
# homeassistant.components.venstar
|
||||
venstarcolortouch==0.19
|
||||
@@ -2538,7 +2538,7 @@ zeroconf==0.145.1
|
||||
zeversolar==0.3.2
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==0.0.51
|
||||
zha==0.0.52
|
||||
|
||||
# homeassistant.components.zwave_js
|
||||
zwave-js-server-python==0.60.1
|
||||
|
||||
@@ -47,7 +47,8 @@ from homeassistant.components.backup.manager import (
|
||||
WrittenBackup,
|
||||
)
|
||||
from homeassistant.components.backup.util import password_to_key
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STARTED
|
||||
from homeassistant.core import CoreState, HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
@@ -3469,3 +3470,66 @@ async def test_restore_progress_after_restart_fail_to_remove(
|
||||
"Unexpected error deleting backup restore result file: <class 'OSError'> Boom!"
|
||||
in caplog.text
|
||||
)
|
||||
|
||||
|
||||
async def test_manager_blocked_until_home_assistant_started(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test backup manager's state is blocked until Home Assistant has started."""
|
||||
|
||||
hass.set_state(CoreState.not_running)
|
||||
|
||||
await setup_backup_integration(hass)
|
||||
manager = hass.data[DATA_MANAGER]
|
||||
|
||||
assert manager.state == BackupManagerState.BLOCKED
|
||||
assert manager.last_non_idle_event is None
|
||||
|
||||
# Fired when Home Assistant changes to starting state
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
assert manager.state == BackupManagerState.BLOCKED
|
||||
assert manager.last_non_idle_event is None
|
||||
|
||||
# Fired when Home Assistant changes to running state
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
assert manager.state == BackupManagerState.IDLE
|
||||
assert manager.last_non_idle_event is None
|
||||
|
||||
|
||||
async def test_manager_not_blocked_after_restore(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test restore backup progress after restart."""
|
||||
restore_result = {"error": None, "error_type": None, "success": True}
|
||||
|
||||
hass.set_state(CoreState.not_running)
|
||||
with patch(
|
||||
"pathlib.Path.read_bytes", return_value=json.dumps(restore_result).encode()
|
||||
):
|
||||
await setup_backup_integration(hass)
|
||||
|
||||
ws_client = await hass_ws_client(hass)
|
||||
await ws_client.send_json_auto_id({"type": "backup/info"})
|
||||
result = await ws_client.receive_json()
|
||||
assert result["success"] is True
|
||||
assert result["result"] == {
|
||||
"agent_errors": {},
|
||||
"backups": [],
|
||||
"last_attempted_automatic_backup": None,
|
||||
"last_completed_automatic_backup": None,
|
||||
"last_non_idle_event": {
|
||||
"manager_state": "restore_backup",
|
||||
"reason": None,
|
||||
"stage": None,
|
||||
"state": "completed",
|
||||
},
|
||||
"next_automatic_backup": None,
|
||||
"next_automatic_backup_additional": False,
|
||||
"state": "idle",
|
||||
}
|
||||
|
||||
@@ -48,18 +48,18 @@ def location_status_fixture(install: str, loc_id: str | None = None) -> JsonObje
|
||||
return load_json_object_fixture(f"{install}/status_{loc_id}.json", DOMAIN)
|
||||
|
||||
|
||||
def dhw_schedule_fixture(install: str) -> JsonObjectType:
|
||||
def dhw_schedule_fixture(install: str, dhw_id: str | None = None) -> JsonObjectType:
|
||||
"""Load JSON for the schedule of a domesticHotWater zone."""
|
||||
try:
|
||||
return load_json_object_fixture(f"{install}/schedule_dhw.json", DOMAIN)
|
||||
return load_json_object_fixture(f"{install}/schedule_{dhw_id}.json", DOMAIN)
|
||||
except FileNotFoundError:
|
||||
return load_json_object_fixture("default/schedule_dhw.json", DOMAIN)
|
||||
|
||||
|
||||
def zone_schedule_fixture(install: str) -> JsonObjectType:
|
||||
def zone_schedule_fixture(install: str, zon_id: str | None = None) -> JsonObjectType:
|
||||
"""Load JSON for the schedule of a temperatureZone zone."""
|
||||
try:
|
||||
return load_json_object_fixture(f"{install}/schedule_zone.json", DOMAIN)
|
||||
return load_json_object_fixture(f"{install}/schedule_{zon_id}.json", DOMAIN)
|
||||
except FileNotFoundError:
|
||||
return load_json_object_fixture("default/schedule_zone.json", DOMAIN)
|
||||
|
||||
@@ -120,9 +120,9 @@ def mock_make_request(install: str) -> Callable:
|
||||
|
||||
elif "schedule" in url:
|
||||
if url.startswith("domesticHotWater"): # /v2/domesticHotWater/{id}/schedule
|
||||
return dhw_schedule_fixture(install)
|
||||
return dhw_schedule_fixture(install, url[16:23])
|
||||
if url.startswith("temperatureZone"): # /v2/temperatureZone/{id}/schedule
|
||||
return zone_schedule_fixture(install)
|
||||
return zone_schedule_fixture(install, url[16:23])
|
||||
|
||||
pytest.fail(f"Unexpected request: {HTTPMethod.GET} {url}")
|
||||
|
||||
|
||||
@@ -15,8 +15,9 @@ TEST_INSTALLS: Final = (
|
||||
"default", # evohome: multi-zone, with DHW
|
||||
"h032585", # VisionProWifi: no preset modes for TCS, zoneId=systemId
|
||||
"h099625", # RoundThermostat
|
||||
"h139906", # zone with null schedule
|
||||
"sys_004", # RoundModulation
|
||||
)
|
||||
# "botched", # as default: but with activeFaults, ghost zones & unknown types
|
||||
|
||||
TEST_INSTALLS_WITH_DHW: Final = ("default",)
|
||||
TEST_INSTALLS_WITH_DHW: Final = ("default", "botched")
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"dailySchedules": []
|
||||
}
|
||||
@@ -8,14 +8,14 @@
|
||||
"country": "UnitedKingdom",
|
||||
"postcode": "E1 1AA",
|
||||
"locationType": "Residential",
|
||||
"useDaylightSaveSwitching": true,
|
||||
"timeZone": {
|
||||
"timeZoneId": "GMTStandardTime",
|
||||
"displayName": "(UTC+00:00) Dublin, Edinburgh, Lisbon, London",
|
||||
"offsetMinutes": 0,
|
||||
"currentOffsetMinutes": 60,
|
||||
"timeZoneId": "PacificSAStandardTime",
|
||||
"displayName": "(UTC-04:00) Santiago",
|
||||
"offsetMinutes": -240,
|
||||
"currentOffsetMinutes": -180,
|
||||
"supportsDaylightSaving": true
|
||||
},
|
||||
"useDaylightSaveSwitching": true,
|
||||
"locationOwner": {
|
||||
"userId": "2263181",
|
||||
"username": "user_2263181@gmail.com",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"dailySchedules": []
|
||||
}
|
||||
143
tests/components/evohome/fixtures/h139906/schedule_3454855.json
Normal file
143
tests/components/evohome/fixtures/h139906/schedule_3454855.json
Normal file
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"dailySchedules": [
|
||||
{
|
||||
"dayOfWeek": "Monday",
|
||||
"switchpoints": [
|
||||
{
|
||||
"heatSetpoint": 22.0,
|
||||
"timeOfDay": "05:30:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 20.0,
|
||||
"timeOfDay": "08:00:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 22.5,
|
||||
"timeOfDay": "16:00:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 15.0,
|
||||
"timeOfDay": "23:00:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"dayOfWeek": "Tuesday",
|
||||
"switchpoints": [
|
||||
{
|
||||
"heatSetpoint": 22.0,
|
||||
"timeOfDay": "05:30:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 20.0,
|
||||
"timeOfDay": "08:00:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 22.5,
|
||||
"timeOfDay": "16:00:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 15.0,
|
||||
"timeOfDay": "23:00:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"dayOfWeek": "Wednesday",
|
||||
"switchpoints": [
|
||||
{
|
||||
"heatSetpoint": 22.0,
|
||||
"timeOfDay": "05:30:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 20.0,
|
||||
"timeOfDay": "08:00:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 22.5,
|
||||
"timeOfDay": "12:00:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 15.0,
|
||||
"timeOfDay": "23:00:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"dayOfWeek": "Thursday",
|
||||
"switchpoints": [
|
||||
{
|
||||
"heatSetpoint": 22.0,
|
||||
"timeOfDay": "05:30:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 20.0,
|
||||
"timeOfDay": "08:00:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 22.5,
|
||||
"timeOfDay": "16:00:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 15.0,
|
||||
"timeOfDay": "23:00:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"dayOfWeek": "Friday",
|
||||
"switchpoints": [
|
||||
{
|
||||
"heatSetpoint": 22.0,
|
||||
"timeOfDay": "05:30:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 20.0,
|
||||
"timeOfDay": "08:00:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 22.5,
|
||||
"timeOfDay": "16:00:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 15.0,
|
||||
"timeOfDay": "23:00:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"dayOfWeek": "Saturday",
|
||||
"switchpoints": [
|
||||
{
|
||||
"heatSetpoint": 22.0,
|
||||
"timeOfDay": "07:00:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 22.5,
|
||||
"timeOfDay": "16:00:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 15.0,
|
||||
"timeOfDay": "23:00:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"dayOfWeek": "Sunday",
|
||||
"switchpoints": [
|
||||
{
|
||||
"heatSetpoint": 22.0,
|
||||
"timeOfDay": "07:30:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 22.5,
|
||||
"timeOfDay": "16:00:00"
|
||||
},
|
||||
{
|
||||
"heatSetpoint": 15.0,
|
||||
"timeOfDay": "23:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"locationId": "2727366",
|
||||
"gateways": [
|
||||
{
|
||||
"gatewayId": "2513794",
|
||||
"temperatureControlSystems": [
|
||||
{
|
||||
"systemId": "3454856",
|
||||
"zones": [
|
||||
{
|
||||
"zoneId": "3454854",
|
||||
"temperatureStatus": {
|
||||
"temperature": 22.0,
|
||||
"isAvailable": true
|
||||
},
|
||||
"activeFaults": [
|
||||
{
|
||||
"faultType": "TempZoneSensorCommunicationLost",
|
||||
"since": "2025-02-06T11:20:29"
|
||||
}
|
||||
],
|
||||
"setpointStatus": {
|
||||
"targetHeatTemperature": 5.0,
|
||||
"setpointMode": "FollowSchedule"
|
||||
},
|
||||
"name": "Thermostat"
|
||||
},
|
||||
{
|
||||
"zoneId": "3454855",
|
||||
"temperatureStatus": {
|
||||
"temperature": 22.0,
|
||||
"isAvailable": true
|
||||
},
|
||||
"activeFaults": [],
|
||||
"setpointStatus": {
|
||||
"targetHeatTemperature": 20.0,
|
||||
"setpointMode": "FollowSchedule"
|
||||
},
|
||||
"name": "Thermostat 2"
|
||||
}
|
||||
],
|
||||
"activeFaults": [],
|
||||
"systemModeStatus": {
|
||||
"mode": "Auto",
|
||||
"isPermanent": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"activeFaults": []
|
||||
}
|
||||
]
|
||||
}
|
||||
125
tests/components/evohome/fixtures/h139906/user_locations.json
Normal file
125
tests/components/evohome/fixtures/h139906/user_locations.json
Normal file
@@ -0,0 +1,125 @@
|
||||
[
|
||||
{
|
||||
"locationInfo": {
|
||||
"locationId": "2727366",
|
||||
"name": "Vr**********",
|
||||
"streetAddress": "********** *",
|
||||
"city": "*********",
|
||||
"country": "Netherlands",
|
||||
"postcode": "******",
|
||||
"locationType": "Residential",
|
||||
"useDaylightSaveSwitching": true,
|
||||
"timeZone": {
|
||||
"timeZoneId": "WEuropeStandardTime",
|
||||
"displayName": "(UTC+01:00) Amsterdam, Berlijn, Bern, Rome, Stockholm, Wenen",
|
||||
"offsetMinutes": 60,
|
||||
"currentOffsetMinutes": 60,
|
||||
"supportsDaylightSaving": true
|
||||
},
|
||||
"locationOwner": {
|
||||
"userId": "2276512",
|
||||
"username": "nobody@nowhere.com",
|
||||
"firstname": "Gl***",
|
||||
"lastname": "de*****"
|
||||
}
|
||||
},
|
||||
"gateways": [
|
||||
{
|
||||
"gatewayInfo": {
|
||||
"gatewayId": "2513794",
|
||||
"mac": "************",
|
||||
"crc": "****",
|
||||
"isWiFi": false
|
||||
},
|
||||
"temperatureControlSystems": [
|
||||
{
|
||||
"systemId": "3454856",
|
||||
"modelType": "EvoTouch",
|
||||
"zones": [
|
||||
{
|
||||
"zoneId": "3454854",
|
||||
"modelType": "HeatingZone",
|
||||
"setpointCapabilities": {
|
||||
"maxHeatSetpoint": 35.0,
|
||||
"minHeatSetpoint": 5.0,
|
||||
"valueResolution": 0.5,
|
||||
"canControlHeat": true,
|
||||
"canControlCool": false,
|
||||
"allowedSetpointModes": [
|
||||
"PermanentOverride",
|
||||
"FollowSchedule",
|
||||
"TemporaryOverride"
|
||||
],
|
||||
"maxDuration": "1.00:00:00",
|
||||
"timingResolution": "00:10:00"
|
||||
},
|
||||
"scheduleCapabilities": {
|
||||
"maxSwitchpointsPerDay": 6,
|
||||
"minSwitchpointsPerDay": 1,
|
||||
"timingResolution": "00:10:00",
|
||||
"setpointValueResolution": 0.5
|
||||
},
|
||||
"name": "Thermostat",
|
||||
"zoneType": "ZoneTemperatureControl"
|
||||
},
|
||||
{
|
||||
"zoneId": "3454855",
|
||||
"modelType": "RoundWireless",
|
||||
"setpointCapabilities": {
|
||||
"maxHeatSetpoint": 35.0,
|
||||
"minHeatSetpoint": 5.0,
|
||||
"valueResolution": 0.5,
|
||||
"canControlHeat": true,
|
||||
"canControlCool": false,
|
||||
"allowedSetpointModes": [
|
||||
"PermanentOverride",
|
||||
"FollowSchedule",
|
||||
"TemporaryOverride"
|
||||
],
|
||||
"maxDuration": "1.00:00:00",
|
||||
"timingResolution": "00:10:00"
|
||||
},
|
||||
"scheduleCapabilities": {
|
||||
"maxSwitchpointsPerDay": 6,
|
||||
"minSwitchpointsPerDay": 0,
|
||||
"timingResolution": "00:10:00",
|
||||
"setpointValueResolution": 0.5
|
||||
},
|
||||
"name": "Thermostat 2",
|
||||
"zoneType": "Thermostat"
|
||||
}
|
||||
],
|
||||
"allowedSystemModes": [
|
||||
{
|
||||
"systemMode": "Auto",
|
||||
"canBePermanent": true,
|
||||
"canBeTemporary": false
|
||||
},
|
||||
{
|
||||
"systemMode": "AutoWithEco",
|
||||
"canBePermanent": true,
|
||||
"canBeTemporary": true,
|
||||
"maxDuration": "1.00:00:00",
|
||||
"timingResolution": "01:00:00",
|
||||
"timingMode": "Duration"
|
||||
},
|
||||
{
|
||||
"systemMode": "Away",
|
||||
"canBePermanent": true,
|
||||
"canBeTemporary": true,
|
||||
"maxDuration": "99.00:00:00",
|
||||
"timingResolution": "1.00:00:00",
|
||||
"timingMode": "Period"
|
||||
},
|
||||
{
|
||||
"systemMode": "HeatingOff",
|
||||
"canBePermanent": true,
|
||||
"canBeTemporary": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -29,6 +29,16 @@
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_set_hvac_mode[h139906]
|
||||
list([
|
||||
tuple(
|
||||
<SystemMode.HEATING_OFF: 'HeatingOff'>,
|
||||
),
|
||||
tuple(
|
||||
<SystemMode.AUTO: 'Auto'>,
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_set_hvac_mode[minimal]
|
||||
list([
|
||||
tuple(
|
||||
@@ -70,6 +80,13 @@
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_turn_off[h139906]
|
||||
list([
|
||||
tuple(
|
||||
<SystemMode.HEATING_OFF: 'HeatingOff'>,
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_turn_off[minimal]
|
||||
list([
|
||||
tuple(
|
||||
@@ -105,6 +122,13 @@
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_turn_on[h139906]
|
||||
list([
|
||||
tuple(
|
||||
<SystemMode.AUTO: 'Auto'>,
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_ctl_turn_on[minimal]
|
||||
list([
|
||||
tuple(
|
||||
@@ -144,10 +168,10 @@
|
||||
'target_heat_temperature': 16.0,
|
||||
}),
|
||||
'setpoints': dict({
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'next_sp_temp': 18.6,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'this_sp_temp': 16.0,
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'next_sp_temp': 16.0,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'this_sp_temp': 18.1,
|
||||
}),
|
||||
'temperature_status': dict({
|
||||
'is_available': True,
|
||||
@@ -191,10 +215,10 @@
|
||||
'target_heat_temperature': 17.0,
|
||||
}),
|
||||
'setpoints': dict({
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'next_sp_temp': 18.6,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'this_sp_temp': 16.0,
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'next_sp_temp': 16.0,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'this_sp_temp': 18.1,
|
||||
}),
|
||||
'temperature_status': dict({
|
||||
'is_available': False,
|
||||
@@ -233,19 +257,19 @@
|
||||
'activeFaults': tuple(
|
||||
dict({
|
||||
'fault_type': 'TempZoneActuatorLowBattery',
|
||||
'since': '2022-03-02T04:50:20+00:00',
|
||||
'since': '2022-03-02T04:50:20-03:00',
|
||||
}),
|
||||
),
|
||||
'setpoint_status': dict({
|
||||
'setpoint_mode': 'TemporaryOverride',
|
||||
'target_heat_temperature': 21.0,
|
||||
'until': '2022-03-07T19:00:00+00:00',
|
||||
'until': '2022-03-07T16:00:00-03:00',
|
||||
}),
|
||||
'setpoints': dict({
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'next_sp_temp': 18.6,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'this_sp_temp': 16.0,
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'next_sp_temp': 16.0,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'this_sp_temp': 18.1,
|
||||
}),
|
||||
'temperature_status': dict({
|
||||
'is_available': True,
|
||||
@@ -289,10 +313,10 @@
|
||||
'target_heat_temperature': 17.0,
|
||||
}),
|
||||
'setpoints': dict({
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'next_sp_temp': 18.6,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'this_sp_temp': 16.0,
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'next_sp_temp': 16.0,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'this_sp_temp': 18.1,
|
||||
}),
|
||||
'temperature_status': dict({
|
||||
'is_available': True,
|
||||
@@ -336,10 +360,10 @@
|
||||
'target_heat_temperature': 17.0,
|
||||
}),
|
||||
'setpoints': dict({
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'next_sp_temp': 18.6,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'this_sp_temp': 16.0,
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'next_sp_temp': 16.0,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'this_sp_temp': 18.1,
|
||||
}),
|
||||
'temperature_status': dict({
|
||||
'is_available': True,
|
||||
@@ -383,10 +407,10 @@
|
||||
'target_heat_temperature': 16.0,
|
||||
}),
|
||||
'setpoints': dict({
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'next_sp_temp': 18.6,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'this_sp_temp': 16.0,
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'next_sp_temp': 16.0,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'this_sp_temp': 18.1,
|
||||
}),
|
||||
'temperature_status': dict({
|
||||
'is_available': True,
|
||||
@@ -426,7 +450,7 @@
|
||||
'activeFaults': tuple(
|
||||
dict({
|
||||
'fault_type': 'TempZoneActuatorCommunicationLost',
|
||||
'since': '2022-03-02T15:56:01+00:00',
|
||||
'since': '2022-03-02T15:56:01-03:00',
|
||||
}),
|
||||
),
|
||||
'setpoint_status': dict({
|
||||
@@ -434,10 +458,10 @@
|
||||
'target_heat_temperature': 17.0,
|
||||
}),
|
||||
'setpoints': dict({
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 22, 10, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'next_sp_temp': 18.6,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'this_sp_temp': 16.0,
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'next_sp_temp': 16.0,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 7, 0, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'this_sp_temp': 18.1,
|
||||
}),
|
||||
'temperature_status': dict({
|
||||
'is_available': True,
|
||||
@@ -1118,6 +1142,136 @@
|
||||
'state': 'heat',
|
||||
})
|
||||
# ---
|
||||
# name: test_setup_platform[h139906][climate.thermostat-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 22.0,
|
||||
'friendly_name': 'Thermostat',
|
||||
'hvac_modes': list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'preset_mode': 'none',
|
||||
'preset_modes': list([
|
||||
'none',
|
||||
'temporary',
|
||||
'permanent',
|
||||
]),
|
||||
'status': dict({
|
||||
'activeFaults': tuple(
|
||||
dict({
|
||||
'fault_type': 'TempZoneSensorCommunicationLost',
|
||||
'since': '2025-02-06T11:20:29+01:00',
|
||||
}),
|
||||
),
|
||||
'setpoint_status': dict({
|
||||
'setpoint_mode': 'FollowSchedule',
|
||||
'target_heat_temperature': 5.0,
|
||||
}),
|
||||
'setpoints': dict({
|
||||
}),
|
||||
'temperature_status': dict({
|
||||
'is_available': True,
|
||||
'temperature': 22.0,
|
||||
}),
|
||||
'zone_id': '3454854',
|
||||
}),
|
||||
'supported_features': <ClimateEntityFeature: 401>,
|
||||
'temperature': 5.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.thermostat',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_setup_platform[h139906][climate.thermostat_2-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 22.0,
|
||||
'friendly_name': 'Thermostat 2',
|
||||
'hvac_modes': list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'preset_mode': 'none',
|
||||
'preset_modes': list([
|
||||
'none',
|
||||
'temporary',
|
||||
'permanent',
|
||||
]),
|
||||
'status': dict({
|
||||
'activeFaults': tuple(
|
||||
),
|
||||
'setpoint_status': dict({
|
||||
'setpoint_mode': 'FollowSchedule',
|
||||
'target_heat_temperature': 20.0,
|
||||
}),
|
||||
'setpoints': dict({
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 23, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Berlin')),
|
||||
'next_sp_temp': 15.0,
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Berlin')),
|
||||
'this_sp_temp': 22.5,
|
||||
}),
|
||||
'temperature_status': dict({
|
||||
'is_available': True,
|
||||
'temperature': 22.0,
|
||||
}),
|
||||
'zone_id': '3454855',
|
||||
}),
|
||||
'supported_features': <ClimateEntityFeature: 401>,
|
||||
'temperature': 20.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.thermostat_2',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'heat',
|
||||
})
|
||||
# ---
|
||||
# name: test_setup_platform[h139906][climate.vr-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 22.0,
|
||||
'friendly_name': 'Vr**********',
|
||||
'hvac_modes': list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
]),
|
||||
'icon': 'mdi:thermostat',
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
'preset_mode': None,
|
||||
'preset_modes': list([
|
||||
'eco',
|
||||
'away',
|
||||
]),
|
||||
'status': dict({
|
||||
'activeSystemFaults': tuple(
|
||||
),
|
||||
'system_id': '3454856',
|
||||
'system_mode_status': dict({
|
||||
'is_permanent': True,
|
||||
'mode': 'Auto',
|
||||
}),
|
||||
}),
|
||||
'supported_features': <ClimateEntityFeature: 400>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.vr',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'heat',
|
||||
})
|
||||
# ---
|
||||
# name: test_setup_platform[minimal][climate.main_room-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
@@ -1312,6 +1466,13 @@
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_zone_set_hvac_mode[h139906]
|
||||
list([
|
||||
tuple(
|
||||
5.0,
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_zone_set_hvac_mode[minimal]
|
||||
list([
|
||||
tuple(
|
||||
@@ -1365,6 +1526,19 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_zone_set_preset_mode[h139906]
|
||||
list([
|
||||
tuple(
|
||||
5.0,
|
||||
),
|
||||
tuple(
|
||||
5.0,
|
||||
),
|
||||
dict({
|
||||
'until': None,
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_zone_set_preset_mode[minimal]
|
||||
list([
|
||||
tuple(
|
||||
@@ -1412,6 +1586,13 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_zone_set_temperature[h139906]
|
||||
list([
|
||||
dict({
|
||||
'until': None,
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_zone_set_temperature[minimal]
|
||||
list([
|
||||
dict({
|
||||
@@ -1447,6 +1628,13 @@
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_zone_turn_off[h139906]
|
||||
list([
|
||||
tuple(
|
||||
5.0,
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_zone_turn_off[minimal]
|
||||
list([
|
||||
tuple(
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
# name: test_setup[h099625]
|
||||
dict_keys(['refresh_system', 'set_system_mode', 'clear_zone_override', 'set_zone_override'])
|
||||
# ---
|
||||
# name: test_setup[h139906]
|
||||
dict_keys(['refresh_system', 'set_system_mode', 'clear_zone_override', 'set_zone_override'])
|
||||
# ---
|
||||
# name: test_setup[minimal]
|
||||
dict_keys(['refresh_system', 'reset_system', 'set_system_mode', 'clear_zone_override', 'set_zone_override'])
|
||||
# ---
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
# serializer version: 1
|
||||
# name: test_set_operation_mode[botched]
|
||||
list([
|
||||
dict({
|
||||
'until': HAFakeDatetime(2024, 7, 10, 12, 30, tzinfo=datetime.timezone.utc),
|
||||
}),
|
||||
dict({
|
||||
'until': HAFakeDatetime(2024, 7, 10, 12, 30, tzinfo=datetime.timezone.utc),
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_set_operation_mode[default]
|
||||
list([
|
||||
dict({
|
||||
@@ -29,9 +39,9 @@
|
||||
),
|
||||
'dhw_id': '3933910',
|
||||
'setpoints': dict({
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 13, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'next_sp_from': HAFakeDatetime(2024, 7, 10, 8, 30, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'next_sp_state': 'Off',
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/London')),
|
||||
'this_sp_from': HAFakeDatetime(2024, 7, 10, 6, 30, tzinfo=zoneinfo.ZoneInfo(key='America/Santiago')),
|
||||
'this_sp_state': 'On',
|
||||
}),
|
||||
'state_status': dict({
|
||||
|
||||
@@ -33,7 +33,7 @@ from .const import TEST_INSTALLS_WITH_DHW
|
||||
DHW_ENTITY_ID = "water_heater.domestic_hot_water"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("install", [*TEST_INSTALLS_WITH_DHW, "botched"])
|
||||
@pytest.mark.parametrize("install", TEST_INSTALLS_WITH_DHW)
|
||||
async def test_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: dict[str, str],
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
tuple(
|
||||
),
|
||||
dict({
|
||||
'config': GenerateContentConfig(http_options=None, system_instruction="Current time is 05:00:00. Today's date is 2024-05-24.\nYou are a voice assistant for Home Assistant.\nAnswer questions about the world truthfully.\nAnswer in plain text. Keep it simple and to the point.\nOnly if the user wants to control a device, tell them to expose entities to their voice assistant in Home Assistant.", temperature=1.0, top_p=0.95, top_k=64.0, candidate_count=None, max_output_tokens=150, stop_sequences=None, response_logprobs=None, logprobs=None, presence_penalty=None, frequency_penalty=None, seed=None, response_mime_type=None, response_schema=None, routing_config=None, safety_settings=[SafetySetting(method=None, category=<HarmCategory.HARM_CATEGORY_HATE_SPEECH: 'HARM_CATEGORY_HATE_SPEECH'>, threshold=<HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE: 'BLOCK_MEDIUM_AND_ABOVE'>), SafetySetting(method=None, category=<HarmCategory.HARM_CATEGORY_HARASSMENT: 'HARM_CATEGORY_HARASSMENT'>, threshold=<HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE: 'BLOCK_MEDIUM_AND_ABOVE'>), SafetySetting(method=None, category=<HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: 'HARM_CATEGORY_DANGEROUS_CONTENT'>, threshold=<HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE: 'BLOCK_MEDIUM_AND_ABOVE'>), SafetySetting(method=None, category=<HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: 'HARM_CATEGORY_SEXUALLY_EXPLICIT'>, threshold=<HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE: 'BLOCK_MEDIUM_AND_ABOVE'>)], tools=[Tool(function_declarations=[FunctionDeclaration(response=None, description='Test function', name='test_tool', parameters=Schema(min_items=None, example=None, property_ordering=None, pattern=None, minimum=None, default=None, any_of=None, max_length=None, title=None, min_length=None, min_properties=None, max_items=None, maximum=None, nullable=None, max_properties=None, type=<Type.OBJECT: 'OBJECT'>, description=None, enum=None, format=None, items=None, properties={'param1': Schema(min_items=None, example=None, property_ordering=None, pattern=None, minimum=None, default=None, any_of=None, max_length=None, title=None, min_length=None, min_properties=None, max_items=None, maximum=None, nullable=None, max_properties=None, type=<Type.ARRAY: 'ARRAY'>, description='Test parameters', enum=None, format=None, items=Schema(min_items=None, example=None, property_ordering=None, pattern=None, minimum=None, default=None, any_of=None, max_length=None, title=None, min_length=None, min_properties=None, max_items=None, maximum=None, nullable=None, max_properties=None, type=<Type.STRING: 'STRING'>, description=None, enum=None, format=None, items=None, properties=None, required=None), properties=None, required=None), 'param2': Schema(min_items=None, example=None, property_ordering=None, pattern=None, minimum=None, default=None, any_of=[Schema(min_items=None, example=None, property_ordering=None, pattern=None, minimum=None, default=None, any_of=None, max_length=None, title=None, min_length=None, min_properties=None, max_items=None, maximum=None, nullable=None, max_properties=None, type=<Type.NUMBER: 'NUMBER'>, description=None, enum=None, format=None, items=None, properties=None, required=None), Schema(min_items=None, example=None, property_ordering=None, pattern=None, minimum=None, default=None, any_of=None, max_length=None, title=None, min_length=None, min_properties=None, max_items=None, maximum=None, nullable=None, max_properties=None, type=<Type.INTEGER: 'INTEGER'>, description=None, enum=None, format=None, items=None, properties=None, required=None)], max_length=None, title=None, min_length=None, min_properties=None, max_items=None, maximum=None, nullable=None, max_properties=None, type=None, description=None, enum=None, format=None, items=None, properties=None, required=None), 'param3': Schema(min_items=None, example=None, property_ordering=None, pattern=None, minimum=None, default=None, any_of=None, max_length=None, title=None, min_length=None, min_properties=None, max_items=None, maximum=None, nullable=None, max_properties=None, type=<Type.OBJECT: 'OBJECT'>, description=None, enum=None, format=None, items=None, properties={'json': Schema(min_items=None, example=None, property_ordering=None, pattern=None, minimum=None, default=None, any_of=None, max_length=None, title=None, min_length=None, min_properties=None, max_items=None, maximum=None, nullable=None, max_properties=None, type=<Type.STRING: 'STRING'>, description=None, enum=None, format=None, items=None, properties=None, required=None)}, required=[])}, required=[]))], retrieval=None, google_search=None, google_search_retrieval=None, code_execution=None)], tool_config=None, labels=None, cached_content=None, response_modalities=None, media_resolution=None, speech_config=None, audio_timestamp=None, automatic_function_calling=AutomaticFunctionCallingConfig(disable=True, maximum_remote_calls=None, ignore_call_history=None), thinking_config=None),
|
||||
'config': GenerateContentConfig(http_options=None, system_instruction="Current time is 05:00:00. Today's date is 2024-05-24.\nYou are a voice assistant for Home Assistant.\nAnswer questions about the world truthfully.\nAnswer in plain text. Keep it simple and to the point.\nOnly if the user wants to control a device, tell them to expose entities to their voice assistant in Home Assistant.", temperature=1.0, top_p=0.95, top_k=64.0, candidate_count=None, max_output_tokens=150, stop_sequences=None, response_logprobs=None, logprobs=None, presence_penalty=None, frequency_penalty=None, seed=None, response_mime_type=None, response_schema=None, routing_config=None, safety_settings=[SafetySetting(method=None, category=<HarmCategory.HARM_CATEGORY_HATE_SPEECH: 'HARM_CATEGORY_HATE_SPEECH'>, threshold=<HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE: 'BLOCK_MEDIUM_AND_ABOVE'>), SafetySetting(method=None, category=<HarmCategory.HARM_CATEGORY_HARASSMENT: 'HARM_CATEGORY_HARASSMENT'>, threshold=<HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE: 'BLOCK_MEDIUM_AND_ABOVE'>), SafetySetting(method=None, category=<HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: 'HARM_CATEGORY_DANGEROUS_CONTENT'>, threshold=<HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE: 'BLOCK_MEDIUM_AND_ABOVE'>), SafetySetting(method=None, category=<HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: 'HARM_CATEGORY_SEXUALLY_EXPLICIT'>, threshold=<HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE: 'BLOCK_MEDIUM_AND_ABOVE'>)], tools=[Tool(function_declarations=[FunctionDeclaration(response=None, description='Test function', name='test_tool', parameters=Schema(min_items=None, example=None, property_ordering=None, pattern=None, minimum=None, default=None, any_of=None, max_length=None, title=None, min_length=None, min_properties=None, max_items=None, maximum=None, nullable=None, max_properties=None, type=<Type.OBJECT: 'OBJECT'>, description=None, enum=None, format=None, items=None, properties={'param1': Schema(min_items=None, example=None, property_ordering=None, pattern=None, minimum=None, default=None, any_of=None, max_length=None, title=None, min_length=None, min_properties=None, max_items=None, maximum=None, nullable=None, max_properties=None, type=<Type.ARRAY: 'ARRAY'>, description='Test parameters', enum=None, format=None, items=Schema(min_items=None, example=None, property_ordering=None, pattern=None, minimum=None, default=None, any_of=None, max_length=None, title=None, min_length=None, min_properties=None, max_items=None, maximum=None, nullable=None, max_properties=None, type=<Type.STRING: 'STRING'>, description=None, enum=None, format=None, items=None, properties=None, required=None), properties=None, required=None), 'param2': Schema(min_items=None, example=None, property_ordering=None, pattern=None, minimum=None, default=None, any_of=None, max_length=None, title=None, min_length=None, min_properties=None, max_items=None, maximum=None, nullable=None, max_properties=None, type=None, description=None, enum=None, format=None, items=None, properties=None, required=None), 'param3': Schema(min_items=None, example=None, property_ordering=None, pattern=None, minimum=None, default=None, any_of=None, max_length=None, title=None, min_length=None, min_properties=None, max_items=None, maximum=None, nullable=None, max_properties=None, type=<Type.OBJECT: 'OBJECT'>, description=None, enum=None, format=None, items=None, properties={'json': Schema(min_items=None, example=None, property_ordering=None, pattern=None, minimum=None, default=None, any_of=None, max_length=None, title=None, min_length=None, min_properties=None, max_items=None, maximum=None, nullable=None, max_properties=None, type=<Type.STRING: 'STRING'>, description=None, enum=None, format=None, items=None, properties=None, required=None)}, required=[])}, required=[]))], retrieval=None, google_search=None, google_search_retrieval=None, code_execution=None)], tool_config=None, labels=None, cached_content=None, response_modalities=None, media_resolution=None, speech_config=None, audio_timestamp=None, automatic_function_calling=AutomaticFunctionCallingConfig(disable=True, maximum_remote_calls=None, ignore_call_history=None), thinking_config=None),
|
||||
'history': list([
|
||||
]),
|
||||
'model': 'models/gemini-2.0-flash',
|
||||
|
||||
@@ -31,3 +31,18 @@
|
||||
),
|
||||
])
|
||||
# ---
|
||||
# name: test_load_entry_with_unloaded_entries
|
||||
list([
|
||||
tuple(
|
||||
'',
|
||||
tuple(
|
||||
),
|
||||
dict({
|
||||
'contents': list([
|
||||
'Write an opening speech for a Home Assistant release party',
|
||||
]),
|
||||
'model': 'models/gemini-2.0-flash',
|
||||
}),
|
||||
),
|
||||
])
|
||||
# ---
|
||||
|
||||
@@ -493,6 +493,26 @@ async def test_escape_decode() -> None:
|
||||
{"type": "string", "enum": ["a", "b", "c"]},
|
||||
{"type": "STRING", "enum": ["a", "b", "c"]},
|
||||
),
|
||||
(
|
||||
{"type": "string", "default": "default"},
|
||||
{"type": "STRING"},
|
||||
),
|
||||
(
|
||||
{"type": "string", "pattern": "default"},
|
||||
{"type": "STRING"},
|
||||
),
|
||||
(
|
||||
{"type": "string", "maxLength": 10},
|
||||
{"type": "STRING"},
|
||||
),
|
||||
(
|
||||
{"type": "string", "minLength": 10},
|
||||
{"type": "STRING"},
|
||||
),
|
||||
(
|
||||
{"type": "string", "title": "title"},
|
||||
{"type": "STRING"},
|
||||
),
|
||||
(
|
||||
{"type": "string", "format": "enum", "enum": ["a", "b", "c"]},
|
||||
{"type": "STRING", "format": "enum", "enum": ["a", "b", "c"]},
|
||||
@@ -517,6 +537,10 @@ async def test_escape_decode() -> None:
|
||||
{"type": "number", "format": "hex"},
|
||||
{"type": "NUMBER"},
|
||||
),
|
||||
(
|
||||
{"type": "number", "minimum": 1},
|
||||
{"type": "NUMBER"},
|
||||
),
|
||||
(
|
||||
{"type": "integer", "format": "int32"},
|
||||
{"type": "INTEGER", "format": "int32"},
|
||||
@@ -535,21 +559,7 @@ async def test_escape_decode() -> None:
|
||||
),
|
||||
(
|
||||
{"anyOf": [{"type": "integer"}, {"type": "number"}]},
|
||||
{"any_of": [{"type": "INTEGER"}, {"type": "NUMBER"}]},
|
||||
),
|
||||
(
|
||||
{
|
||||
"any_of": [
|
||||
{"any_of": [{"type": "integer"}, {"type": "number"}]},
|
||||
{"any_of": [{"type": "integer"}, {"type": "number"}]},
|
||||
]
|
||||
},
|
||||
{
|
||||
"any_of": [
|
||||
{"any_of": [{"type": "INTEGER"}, {"type": "NUMBER"}]},
|
||||
{"any_of": [{"type": "INTEGER"}, {"type": "NUMBER"}]},
|
||||
]
|
||||
},
|
||||
{},
|
||||
),
|
||||
({"type": "string", "format": "lower"}, {"type": "STRING"}),
|
||||
({"type": "boolean", "format": "bool"}, {"type": "BOOLEAN"}),
|
||||
@@ -570,7 +580,15 @@ async def test_escape_decode() -> None:
|
||||
},
|
||||
),
|
||||
(
|
||||
{"type": "object", "additionalProperties": True},
|
||||
{"type": "object", "additionalProperties": True, "minProperties": 1},
|
||||
{
|
||||
"type": "OBJECT",
|
||||
"properties": {"json": {"type": "STRING"}},
|
||||
"required": [],
|
||||
},
|
||||
),
|
||||
(
|
||||
{"type": "object", "additionalProperties": True, "maxProperties": 1},
|
||||
{
|
||||
"type": "OBJECT",
|
||||
"properties": {"json": {"type": "STRING"}},
|
||||
@@ -581,6 +599,20 @@ async def test_escape_decode() -> None:
|
||||
{"type": "array", "items": {"type": "string"}},
|
||||
{"type": "ARRAY", "items": {"type": "STRING"}},
|
||||
),
|
||||
(
|
||||
{
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"minItems": 1,
|
||||
"maxItems": 2,
|
||||
},
|
||||
{
|
||||
"type": "ARRAY",
|
||||
"items": {"type": "STRING"},
|
||||
"min_items": 1,
|
||||
"max_items": 2,
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_format_schema(openapi, genai_schema) -> None:
|
||||
|
||||
@@ -224,3 +224,52 @@ async def test_config_entry_error(
|
||||
await hass.async_block_till_done()
|
||||
assert mock_config_entry.state == state
|
||||
assert any(mock_config_entry.async_get_active_flows(hass, {"reauth"})) == reauth
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_load_entry_with_unloaded_entries(
|
||||
hass: HomeAssistant, snapshot: SnapshotAssertion
|
||||
) -> None:
|
||||
"""Test loading an entry with unloaded entries."""
|
||||
config_entries = hass.config_entries.async_entries(
|
||||
"google_generative_ai_conversation"
|
||||
)
|
||||
runtime_data = config_entries[0].runtime_data
|
||||
await hass.config_entries.async_unload(config_entries[0].entry_id)
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain="google_generative_ai_conversation",
|
||||
title="Google Generative AI Conversation",
|
||||
data={
|
||||
"api_key": "bla",
|
||||
},
|
||||
state=ConfigEntryState.LOADED,
|
||||
)
|
||||
entry.runtime_data = runtime_data
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
stubbed_generated_content = (
|
||||
"I'm thrilled to welcome you all to the release "
|
||||
"party for the latest version of Home Assistant!"
|
||||
)
|
||||
|
||||
with patch(
|
||||
"google.genai.models.AsyncModels.generate_content",
|
||||
return_value=Mock(
|
||||
text=stubbed_generated_content,
|
||||
prompt_feedback=None,
|
||||
candidates=[Mock()],
|
||||
),
|
||||
) as mock_generate:
|
||||
response = await hass.services.async_call(
|
||||
"google_generative_ai_conversation",
|
||||
"generate_content",
|
||||
{"prompt": "Write an opening speech for a Home Assistant release party"},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
|
||||
assert response == {
|
||||
"text": stubbed_generated_content,
|
||||
}
|
||||
assert [tuple(mock_call) for mock_call in mock_generate.mock_calls] == snapshot
|
||||
|
||||
@@ -11,7 +11,7 @@ import pytest
|
||||
|
||||
from homeassistant.auth.models import RefreshToken
|
||||
from homeassistant.components.hassio.handler import HassIO, HassioAPIError
|
||||
from homeassistant.core import CoreState, HomeAssistant
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
@@ -75,7 +75,6 @@ def hassio_stubs(
|
||||
"homeassistant.components.hassio.issues.SupervisorIssues.setup",
|
||||
),
|
||||
):
|
||||
hass.set_state(CoreState.starting)
|
||||
hass.loop.run_until_complete(async_setup_component(hass, "hassio", {}))
|
||||
|
||||
return hass_api.call_args[0][1]
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
'model': 'HEOS Drive HS2',
|
||||
'name': 'Test Player',
|
||||
'network': 'wired',
|
||||
'preferred_host': True,
|
||||
'serial': '**REDACTED**',
|
||||
'supported_version': True,
|
||||
'version': '1.0.0',
|
||||
@@ -116,6 +117,7 @@
|
||||
'model': 'HEOS Drive HS2',
|
||||
'name': 'Test Player',
|
||||
'network': 'wired',
|
||||
'preferred_host': True,
|
||||
'serial': '**REDACTED**',
|
||||
'supported_version': True,
|
||||
'version': '1.0.0',
|
||||
@@ -125,6 +127,7 @@
|
||||
'model': 'Speaker',
|
||||
'name': 'Test Player 2',
|
||||
'network': 'wifi',
|
||||
'preferred_host': False,
|
||||
'serial': '**REDACTED**',
|
||||
'supported_version': True,
|
||||
'version': '1.0.0',
|
||||
@@ -137,6 +140,7 @@
|
||||
'model': 'HEOS Drive HS2',
|
||||
'name': 'Test Player',
|
||||
'network': 'wired',
|
||||
'preferred_host': True,
|
||||
'serial': '**REDACTED**',
|
||||
'supported_version': True,
|
||||
'version': '1.0.0',
|
||||
|
||||
@@ -14,7 +14,12 @@ from pyheos import (
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.heos.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_SSDP, SOURCE_USER, ConfigEntryState
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_IGNORE,
|
||||
SOURCE_SSDP,
|
||||
SOURCE_USER,
|
||||
ConfigEntryState,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
@@ -83,6 +88,35 @@ async def test_create_entry_when_host_valid(
|
||||
assert controller.disconnect.call_count == 1
|
||||
|
||||
|
||||
async def test_manual_setup_with_discovery_in_progress(
|
||||
hass: HomeAssistant,
|
||||
discovery_data: SsdpServiceInfo,
|
||||
controller: MockHeos,
|
||||
system: HeosSystem,
|
||||
) -> None:
|
||||
"""Test user can manually set up when discovery is in progress."""
|
||||
# Single discovered, selects preferred host, shows confirm
|
||||
controller.get_system_info.return_value = system
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_SSDP}, data=discovery_data
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "confirm_discovery"
|
||||
|
||||
# Setup manually
|
||||
user_result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
assert user_result["type"] is FlowResultType.FORM
|
||||
user_result = await hass.config_entries.flow.async_configure(
|
||||
user_result["flow_id"], user_input={CONF_HOST: "127.0.0.1"}
|
||||
)
|
||||
assert user_result["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
# Discovery flow is removed
|
||||
assert not hass.config_entries.flow.async_progress_by_handler(DOMAIN)
|
||||
|
||||
|
||||
async def test_discovery(
|
||||
hass: HomeAssistant,
|
||||
discovery_data: SsdpServiceInfo,
|
||||
@@ -160,6 +194,22 @@ async def test_discovery_aborts_same_system(
|
||||
assert config_entry.data[CONF_HOST] == "127.0.0.1"
|
||||
|
||||
|
||||
async def test_discovery_ignored_aborts(
|
||||
hass: HomeAssistant,
|
||||
discovery_data: SsdpServiceInfo,
|
||||
) -> None:
|
||||
"""Test discovery aborts when ignored."""
|
||||
MockConfigEntry(domain=DOMAIN, unique_id=DOMAIN, source=SOURCE_IGNORE).add_to_hass(
|
||||
hass
|
||||
)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_SSDP}, data=discovery_data
|
||||
)
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "single_instance_allowed"
|
||||
|
||||
|
||||
async def test_discovery_fails_to_connect_aborts(
|
||||
hass: HomeAssistant, discovery_data: SsdpServiceInfo, controller: MockHeos
|
||||
) -> None:
|
||||
|
||||
@@ -285,11 +285,11 @@ async def test_reconnected_new_entities_created(
|
||||
players = controller.players.copy()
|
||||
players[3] = player_factory(3, "Test Player 3", "HEOS Link")
|
||||
controller.mock_set_players(players)
|
||||
controller.load_players.return_value = PlayerUpdateResult([3], [], {})
|
||||
update = PlayerUpdateResult([3], [], {})
|
||||
|
||||
# Simulate reconnection
|
||||
await controller.dispatcher.wait_send(
|
||||
SignalType.HEOS_EVENT, SignalHeosEvent.CONNECTED
|
||||
SignalType.CONTROLLER_EVENT, const.EVENT_PLAYERS_CHANGED, update
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
@@ -158,7 +158,6 @@ async def test_updates_from_connection_event(
|
||||
state = hass.states.get("media_player.test_player")
|
||||
assert state is not None
|
||||
assert state.state == STATE_IDLE
|
||||
assert controller.load_players.call_count == 1
|
||||
|
||||
# Disconnected
|
||||
controller.load_players.reset_mock()
|
||||
@@ -170,11 +169,8 @@ async def test_updates_from_connection_event(
|
||||
state = hass.states.get("media_player.test_player")
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
assert controller.load_players.call_count == 0
|
||||
|
||||
# Connected handles refresh failure
|
||||
controller.load_players.reset_mock()
|
||||
controller.load_players.side_effect = CommandFailedError("", "Failure", 1)
|
||||
# Reconnect and state updates
|
||||
player.available = True
|
||||
await controller.dispatcher.wait_send(
|
||||
SignalType.HEOS_EVENT, SignalHeosEvent.CONNECTED
|
||||
@@ -183,38 +179,6 @@ async def test_updates_from_connection_event(
|
||||
state = hass.states.get("media_player.test_player")
|
||||
assert state is not None
|
||||
assert state.state == STATE_IDLE
|
||||
assert controller.load_players.call_count == 1
|
||||
assert "Unable to refresh players" in caplog.text
|
||||
|
||||
|
||||
async def test_updates_from_connection_event_new_player_ids(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
config_entry: MockConfigEntry,
|
||||
controller: MockHeos,
|
||||
change_data_mapped_ids: PlayerUpdateResult,
|
||||
) -> None:
|
||||
"""Test player ids changed after reconnection updates ids."""
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
|
||||
# Assert current IDs
|
||||
assert device_registry.async_get_device(identifiers={(DOMAIN, "1")})
|
||||
assert entity_registry.async_get_entity_id(MEDIA_PLAYER_DOMAIN, DOMAIN, "1")
|
||||
|
||||
# Send event which will result in updated IDs.
|
||||
controller.load_players.return_value = change_data_mapped_ids
|
||||
await controller.dispatcher.wait_send(
|
||||
SignalType.HEOS_EVENT, SignalHeosEvent.CONNECTED
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Assert updated IDs and previous don't exist
|
||||
assert not device_registry.async_get_device(identifiers={(DOMAIN, "1")})
|
||||
assert device_registry.async_get_device(identifiers={(DOMAIN, "101")})
|
||||
assert not entity_registry.async_get_entity_id(MEDIA_PLAYER_DOMAIN, DOMAIN, "1")
|
||||
assert entity_registry.async_get_entity_id(MEDIA_PLAYER_DOMAIN, DOMAIN, "101")
|
||||
|
||||
|
||||
async def test_updates_from_sources_updated(
|
||||
|
||||
@@ -8,9 +8,8 @@ from unittest.mock import MagicMock, patch
|
||||
from aiohomeconnect.const import OAUTH2_TOKEN
|
||||
from aiohomeconnect.model import OptionKey, ProgramKey, SettingKey, StatusKey
|
||||
from aiohomeconnect.model.error import HomeConnectError, UnauthorizedError
|
||||
import aiohttp
|
||||
import pytest
|
||||
import requests_mock
|
||||
import respx
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
@@ -221,14 +220,12 @@ async def test_exception_handling(
|
||||
|
||||
|
||||
@pytest.mark.parametrize("token_expiration_time", [12345])
|
||||
@respx.mock
|
||||
async def test_token_refresh_success(
|
||||
hass: HomeAssistant,
|
||||
platforms: list[Platform],
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
config_entry: MockConfigEntry,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
requests_mock: requests_mock.Mocker,
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
) -> None:
|
||||
@@ -236,7 +233,6 @@ async def test_token_refresh_success(
|
||||
|
||||
assert config_entry.data["token"]["access_token"] == FAKE_ACCESS_TOKEN
|
||||
|
||||
requests_mock.post(OAUTH2_TOKEN, json=SERVER_ACCESS_TOKEN)
|
||||
aioclient_mock.post(
|
||||
OAUTH2_TOKEN,
|
||||
json=SERVER_ACCESS_TOKEN,
|
||||
@@ -280,6 +276,61 @@ async def test_token_refresh_success(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("token_expiration_time", [12345])
|
||||
@pytest.mark.parametrize(
|
||||
("aioclient_mock_args", "expected_config_entry_state"),
|
||||
[
|
||||
(
|
||||
{
|
||||
"status": 400,
|
||||
"json": {"error": "invalid_grant"},
|
||||
},
|
||||
ConfigEntryState.SETUP_ERROR,
|
||||
),
|
||||
(
|
||||
{
|
||||
"status": 500,
|
||||
},
|
||||
ConfigEntryState.SETUP_RETRY,
|
||||
),
|
||||
(
|
||||
{
|
||||
"exc": aiohttp.ClientError,
|
||||
},
|
||||
ConfigEntryState.SETUP_RETRY,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_token_refresh_error(
|
||||
aioclient_mock_args: dict[str, Any],
|
||||
expected_config_entry_state: ConfigEntryState,
|
||||
hass: HomeAssistant,
|
||||
platforms: list[Platform],
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
config_entry: MockConfigEntry,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
) -> None:
|
||||
"""Test where token is expired and the refresh attempt fails."""
|
||||
|
||||
config_entry.data["token"]["access_token"] = FAKE_ACCESS_TOKEN
|
||||
|
||||
aioclient_mock.post(
|
||||
OAUTH2_TOKEN,
|
||||
**aioclient_mock_args,
|
||||
)
|
||||
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
with patch(
|
||||
"homeassistant.components.home_connect.HomeConnectClient", return_value=client
|
||||
):
|
||||
assert not await integration_setup(client)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state == expected_config_entry_state
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("exception", "expected_state"),
|
||||
[
|
||||
|
||||
@@ -27,7 +27,7 @@ from homeassistant.components.home_connect.const import (
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
@@ -302,7 +302,7 @@ ENTITY_ID_STATES = {
|
||||
)
|
||||
),
|
||||
)
|
||||
async def test_event_sensors(
|
||||
async def test_program_sensors(
|
||||
client: MagicMock,
|
||||
appliance_ha_id: str,
|
||||
states: tuple,
|
||||
@@ -313,7 +313,7 @@ async def test_event_sensors(
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
) -> None:
|
||||
"""Test sequence for sensors that are only available after an event happens."""
|
||||
"""Test sequence for sensors that expose information about a program."""
|
||||
entity_ids = ENTITY_ID_STATES.keys()
|
||||
|
||||
time_to_freeze = "2021-01-09 12:00:00+00:00"
|
||||
@@ -358,6 +358,82 @@ async def test_event_sensors(
|
||||
assert hass.states.is_state(entity_id, state)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("appliance_ha_id", [TEST_HC_APP], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
("initial_operation_state", "initial_state", "event_order", "entity_states"),
|
||||
[
|
||||
(
|
||||
"BSH.Common.EnumType.OperationState.Ready",
|
||||
STATE_UNAVAILABLE,
|
||||
(EventType.STATUS, EventType.EVENT),
|
||||
(STATE_UNKNOWN, "60"),
|
||||
),
|
||||
(
|
||||
"BSH.Common.EnumType.OperationState.Run",
|
||||
STATE_UNKNOWN,
|
||||
(EventType.EVENT, EventType.STATUS),
|
||||
("60", "60"),
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_program_sensor_edge_case(
|
||||
initial_operation_state: str,
|
||||
initial_state: str,
|
||||
event_order: tuple[EventType, EventType],
|
||||
entity_states: tuple[str, str],
|
||||
appliance_ha_id: str,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[MagicMock], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
client: MagicMock,
|
||||
) -> None:
|
||||
"""Test edge case for the program related entities."""
|
||||
entity_id = "sensor.dishwasher_program_progress"
|
||||
client.get_status = AsyncMock(
|
||||
return_value=ArrayOfStatus(
|
||||
[
|
||||
Status(
|
||||
StatusKey.BSH_COMMON_OPERATION_STATE,
|
||||
StatusKey.BSH_COMMON_OPERATION_STATE.value,
|
||||
initial_operation_state,
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup(client)
|
||||
assert config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
assert hass.states.is_state(entity_id, initial_state)
|
||||
|
||||
for event_type, state in zip(event_order, entity_states, strict=True):
|
||||
await client.add_events(
|
||||
[
|
||||
EventMessage(
|
||||
appliance_ha_id,
|
||||
event_type,
|
||||
ArrayOfEvents(
|
||||
[
|
||||
Event(
|
||||
key=event_key,
|
||||
raw_key=event_key.value,
|
||||
timestamp=0,
|
||||
level="",
|
||||
handling="",
|
||||
value=value,
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
for event_key, value in EVENT_PROG_RUN[event_type].items()
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert hass.states.is_state(entity_id, state)
|
||||
|
||||
|
||||
# Program sequence for SensorDeviceClass.TIMESTAMP edge cases.
|
||||
PROGRAM_SEQUENCE_EDGE_CASE = [
|
||||
EVENT_PROG_DELAYED_START,
|
||||
|
||||
@@ -1556,6 +1556,42 @@ async def test_setup_uses_certificate_on_certificate_set_to_auto_and_insecure(
|
||||
assert insecure_check["insecure"] == insecure_param
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("mqtt_config_entry_data", "client_id"),
|
||||
[
|
||||
(
|
||||
{
|
||||
mqtt.CONF_BROKER: "mock-broker",
|
||||
"client_id": "random01234random0124",
|
||||
},
|
||||
"random01234random0124",
|
||||
),
|
||||
(
|
||||
{
|
||||
mqtt.CONF_BROKER: "mock-broker",
|
||||
},
|
||||
None,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_client_id_is_set(
|
||||
hass: HomeAssistant,
|
||||
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||
client_id: str | None,
|
||||
) -> None:
|
||||
"""Test setup defaults for tls."""
|
||||
with patch(
|
||||
"homeassistant.components.mqtt.async_client.AsyncMQTTClient"
|
||||
) as async_client_mock:
|
||||
await mqtt_mock_entry()
|
||||
await hass.async_block_till_done()
|
||||
assert async_client_mock.call_count == 1
|
||||
call_params: dict[str, Any] = async_client_mock.call_args[1]
|
||||
assert "client_id" in call_params
|
||||
assert client_id is None or client_id == call_params["client_id"]
|
||||
assert call_params["client_id"] is not None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mqtt_config_entry_data",
|
||||
[
|
||||
|
||||
@@ -432,6 +432,65 @@ async def test_brightness_only(
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"hass_config",
|
||||
[
|
||||
{
|
||||
mqtt.DOMAIN: {
|
||||
light.DOMAIN: {
|
||||
"schema": "json",
|
||||
"name": "test",
|
||||
"state_topic": "test_light_rgb",
|
||||
"command_topic": "test_light_rgb/set",
|
||||
"supported_color_modes": ["color_temp"],
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
)
|
||||
async def test_color_temp_only(
|
||||
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
|
||||
) -> None:
|
||||
"""Test a light that only support color_temp as supported color mode."""
|
||||
await mqtt_mock_entry()
|
||||
|
||||
state = hass.states.get("light.test")
|
||||
assert state.state == STATE_UNKNOWN
|
||||
assert state.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES) == [
|
||||
light.ColorMode.COLOR_TEMP
|
||||
]
|
||||
expected_features = (
|
||||
light.LightEntityFeature.FLASH | light.LightEntityFeature.TRANSITION
|
||||
)
|
||||
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
|
||||
assert state.attributes.get("rgb_color") is None
|
||||
assert state.attributes.get("brightness") is None
|
||||
assert state.attributes.get("color_temp_kelvin") is None
|
||||
assert state.attributes.get("effect") is None
|
||||
assert state.attributes.get("xy_color") is None
|
||||
assert state.attributes.get("hs_color") is None
|
||||
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
"test_light_rgb",
|
||||
'{"state":"ON", "color_mode": "color_temp", "color_temp": 250, "brightness": 50}',
|
||||
)
|
||||
|
||||
state = hass.states.get("light.test")
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes.get("rgb_color") == (255, 206, 166)
|
||||
assert state.attributes.get("brightness") == 50
|
||||
assert state.attributes.get("color_temp_kelvin") == 4000
|
||||
assert state.attributes.get("effect") is None
|
||||
assert state.attributes.get("xy_color") == (0.42, 0.365)
|
||||
assert state.attributes.get("hs_color") == (26.812, 34.87)
|
||||
|
||||
async_fire_mqtt_message(hass, "test_light_rgb", '{"state":"OFF"}')
|
||||
|
||||
state = hass.states.get("light.test")
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"hass_config",
|
||||
[
|
||||
|
||||
@@ -109,8 +109,8 @@
|
||||
'entity_picture_local': None,
|
||||
'friendly_name': 'Test Group Player 1',
|
||||
'group_members': list([
|
||||
'media_player.my_super_test_player_2',
|
||||
'media_player.test_player_1',
|
||||
'media_player.my_super_test_player_2',
|
||||
]),
|
||||
'icon': 'mdi:speaker-multiple',
|
||||
'is_volume_muted': False,
|
||||
|
||||
@@ -68,6 +68,8 @@ async def trigger_update(
|
||||
value,
|
||||
data,
|
||||
)
|
||||
for call in mock.add_unspecified_device_event_listener.call_args_list:
|
||||
call[0][0](event)
|
||||
for call in mock.add_device_event_listener.call_args_list:
|
||||
if call[0][0] == device_id:
|
||||
call[0][3](event)
|
||||
|
||||
@@ -9,6 +9,7 @@ from pysmartthings.models import (
|
||||
DeviceStatus,
|
||||
LocationResponse,
|
||||
SceneResponse,
|
||||
Subscription,
|
||||
)
|
||||
import pytest
|
||||
|
||||
@@ -78,12 +79,16 @@ def mock_smartthings() -> Generator[AsyncMock]:
|
||||
client.get_locations.return_value = LocationResponse.from_json(
|
||||
load_fixture("locations.json", DOMAIN)
|
||||
).items
|
||||
client.create_subscription.return_value = Subscription.from_json(
|
||||
load_fixture("subscription.json", DOMAIN)
|
||||
)
|
||||
yield client
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
params=[
|
||||
"da_ac_rac_000001",
|
||||
"da_ac_rac_100001",
|
||||
"da_ac_rac_01001",
|
||||
"multipurpose_sensor",
|
||||
"contact_sensor",
|
||||
@@ -99,7 +104,9 @@ def mock_smartthings() -> Generator[AsyncMock]:
|
||||
"iphone",
|
||||
"da_wm_dw_000001",
|
||||
"da_wm_wd_000001",
|
||||
"da_wm_wd_000001_1",
|
||||
"da_wm_wm_000001",
|
||||
"da_wm_wm_000001_1",
|
||||
"da_rvc_normal_000001",
|
||||
"da_ks_microwave_0101x",
|
||||
"hue_color_temperature_bulb",
|
||||
@@ -113,7 +120,15 @@ def mock_smartthings() -> Generator[AsyncMock]:
|
||||
"sensibo_airconditioner_1",
|
||||
"ecobee_sensor",
|
||||
"ecobee_thermostat",
|
||||
"ecobee_thermostat_offline",
|
||||
"fake_fan",
|
||||
"generic_fan_3_speed",
|
||||
"heatit_ztrm3_thermostat",
|
||||
"generic_ef00_v1",
|
||||
"bosch_radiator_thermostat_ii",
|
||||
"im_speaker_ai_0001",
|
||||
"abl_light_b_001",
|
||||
"tplink_p110",
|
||||
]
|
||||
)
|
||||
def device_fixture(
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"components": {
|
||||
"main": {
|
||||
"switchLevel": {
|
||||
"levelRange": {
|
||||
"value": null
|
||||
},
|
||||
"level": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"colorTemperature": {
|
||||
"colorTemperatureRange": {
|
||||
"value": null
|
||||
},
|
||||
"colorTemperature": {
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"components": {
|
||||
"main": {
|
||||
"temperatureMeasurement": {
|
||||
"temperatureRange": {
|
||||
"value": null
|
||||
},
|
||||
"temperature": {
|
||||
"value": 23.9,
|
||||
"unit": "C",
|
||||
"timestamp": "2025-03-07T19:55:13.328Z"
|
||||
}
|
||||
},
|
||||
"thermostatHeatingSetpoint": {
|
||||
"heatingSetpoint": {
|
||||
"value": 22.0,
|
||||
"unit": "C",
|
||||
"timestamp": "2025-03-05T03:05:26.510Z"
|
||||
},
|
||||
"heatingSetpointRange": {
|
||||
"value": {
|
||||
"minimum": 5.0,
|
||||
"maximum": 40.0,
|
||||
"step": 0.1
|
||||
},
|
||||
"unit": "C",
|
||||
"timestamp": "2025-03-05T03:05:26.510Z"
|
||||
}
|
||||
},
|
||||
"refresh": {},
|
||||
"thermostatMode": {
|
||||
"thermostatMode": {
|
||||
"value": "heat",
|
||||
"data": {
|
||||
"supportedThermostatModes": ["off", "heat"]
|
||||
},
|
||||
"timestamp": "2025-03-05T03:05:26.489Z"
|
||||
},
|
||||
"supportedThermostatModes": {
|
||||
"value": ["off", "heat"],
|
||||
"timestamp": "2025-03-05T03:05:26.509Z"
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"quantity": {
|
||||
"value": null
|
||||
},
|
||||
"battery": {
|
||||
"value": 94,
|
||||
"unit": "%",
|
||||
"timestamp": "2025-03-07T20:47:27.362Z"
|
||||
},
|
||||
"type": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"firmwareUpdate": {
|
||||
"lastUpdateStatusReason": {
|
||||
"value": null
|
||||
},
|
||||
"availableVersion": {
|
||||
"value": "2.00.09 (20009)",
|
||||
"timestamp": "2024-11-29T19:55:02.005Z"
|
||||
},
|
||||
"lastUpdateStatus": {
|
||||
"value": null
|
||||
},
|
||||
"supportedCommands": {
|
||||
"value": null
|
||||
},
|
||||
"state": {
|
||||
"value": "normalOperation",
|
||||
"timestamp": "2024-11-29T19:55:02.009Z"
|
||||
},
|
||||
"updateAvailable": {
|
||||
"value": false,
|
||||
"timestamp": "2024-11-29T19:55:02.004Z"
|
||||
},
|
||||
"currentVersion": {
|
||||
"value": "2.00.09 (20009)",
|
||||
"timestamp": "2024-11-29T19:55:02.037Z"
|
||||
},
|
||||
"lastUpdateTime": {
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"components": {
|
||||
"main": {
|
||||
"button": {
|
||||
"button": {
|
||||
"value": "pushed",
|
||||
"timestamp": "2025-03-07T12:20:43.363Z"
|
||||
},
|
||||
"numberOfButtons": {
|
||||
"value": 1,
|
||||
"timestamp": "2025-03-07T12:20:43.363Z"
|
||||
},
|
||||
"supportedButtonValues": {
|
||||
"value": ["pushed", "held", "pushed_2x"],
|
||||
"timestamp": "2025-03-07T12:20:43.363Z"
|
||||
}
|
||||
},
|
||||
"refresh": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,15 @@
|
||||
"timestamp": "2025-02-08T21:56:09.761Z"
|
||||
}
|
||||
},
|
||||
"powerConsumptionReport": {
|
||||
"powerConsumption": {
|
||||
"value": {
|
||||
"start": "2025-02-09T15:45:29Z",
|
||||
"end": "2025-02-09T16:15:33Z"
|
||||
},
|
||||
"timestamp": "2025-02-09T16:15:33.639Z"
|
||||
}
|
||||
},
|
||||
"battery": {
|
||||
"quantity": {
|
||||
"value": null
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"timestamp": "2025-02-09T14:35:56.800Z"
|
||||
},
|
||||
"supportedAcModes": {
|
||||
"value": ["auto", "cool", "dry", "wind", "heat"],
|
||||
"value": ["auto", "cool", "dry", "wind", "heat", "dryClean"],
|
||||
"timestamp": "2025-02-09T15:42:13.444Z"
|
||||
},
|
||||
"airConditionerMode": {
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
{
|
||||
"components": {
|
||||
"main": {
|
||||
"refresh": {},
|
||||
"custom.thermostatSetpointControl": {
|
||||
"minimumSetpoint": {
|
||||
"value": 16,
|
||||
"timestamp": "2024-11-25T22:17:38.251Z"
|
||||
},
|
||||
"maximumSetpoint": {
|
||||
"value": 30,
|
||||
"timestamp": "2024-11-25T22:17:38.251Z"
|
||||
}
|
||||
},
|
||||
"airConditionerMode": {
|
||||
"availableAcModes": {
|
||||
"value": null
|
||||
},
|
||||
"supportedAcModes": {
|
||||
"value": ["cool", "dry", "wind", "auto"],
|
||||
"timestamp": "2025-03-02T10:16:19.519Z"
|
||||
},
|
||||
"airConditionerMode": {
|
||||
"value": "cool",
|
||||
"timestamp": "2025-03-02T10:16:19.519Z"
|
||||
}
|
||||
},
|
||||
"execute": {
|
||||
"data": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"airQualitySensor": {
|
||||
"airQuality": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"value": "off",
|
||||
"timestamp": "2025-03-02T06:54:52.852Z"
|
||||
}
|
||||
},
|
||||
"ocf": {
|
||||
"st": {
|
||||
"value": null
|
||||
},
|
||||
"mndt": {
|
||||
"value": null
|
||||
},
|
||||
"mnfv": {
|
||||
"value": null
|
||||
},
|
||||
"mnhw": {
|
||||
"value": null
|
||||
},
|
||||
"di": {
|
||||
"value": "F8042E25-0E53-0000-0000-000000000000",
|
||||
"timestamp": "2025-02-28T21:15:28.920Z"
|
||||
},
|
||||
"mnsl": {
|
||||
"value": null
|
||||
},
|
||||
"dmv": {
|
||||
"value": "res.1.1.0,sh.1.1.0",
|
||||
"timestamp": "2025-02-28T21:15:28.920Z"
|
||||
},
|
||||
"n": {
|
||||
"value": "Room A/C",
|
||||
"timestamp": "2025-02-28T21:15:28.920Z"
|
||||
},
|
||||
"mnmo": {
|
||||
"value": "TP6X_RAC_15K",
|
||||
"timestamp": "2025-02-28T21:15:28.920Z"
|
||||
},
|
||||
"vid": {
|
||||
"value": "DA-AC-RAC-100001",
|
||||
"timestamp": "2025-02-28T21:15:28.920Z"
|
||||
},
|
||||
"mnmn": {
|
||||
"value": "Samsung Electronics",
|
||||
"timestamp": "2025-02-28T21:15:28.920Z"
|
||||
},
|
||||
"mnml": {
|
||||
"value": null
|
||||
},
|
||||
"mnpv": {
|
||||
"value": null
|
||||
},
|
||||
"mnos": {
|
||||
"value": null
|
||||
},
|
||||
"pi": {
|
||||
"value": "shp",
|
||||
"timestamp": "2025-02-28T21:15:28.920Z"
|
||||
},
|
||||
"icv": {
|
||||
"value": "core.1.1.0",
|
||||
"timestamp": "2025-02-28T21:15:28.920Z"
|
||||
}
|
||||
},
|
||||
"odorSensor": {
|
||||
"odorLevel": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"airConditionerFanMode": {
|
||||
"fanMode": {
|
||||
"value": "auto",
|
||||
"timestamp": "2025-02-28T21:15:28.941Z"
|
||||
},
|
||||
"supportedAcFanModes": {
|
||||
"value": ["auto", "low", "medium", "high", "turbo"],
|
||||
"timestamp": "2025-02-28T21:15:28.941Z"
|
||||
},
|
||||
"availableAcFanModes": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungce.driverState": {
|
||||
"driverState": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"custom.disabledCapabilities": {
|
||||
"disabledCapabilities": {
|
||||
"value": ["odorSensor"],
|
||||
"timestamp": "2024-11-25T22:17:38.251Z"
|
||||
}
|
||||
},
|
||||
"samsungce.driverVersion": {
|
||||
"versionNumber": {
|
||||
"value": 22090101,
|
||||
"timestamp": "2024-11-25T22:17:38.251Z"
|
||||
}
|
||||
},
|
||||
"temperatureMeasurement": {
|
||||
"temperatureRange": {
|
||||
"value": null
|
||||
},
|
||||
"temperature": {
|
||||
"value": 27,
|
||||
"unit": "C",
|
||||
"timestamp": "2025-03-02T08:28:39.409Z"
|
||||
}
|
||||
},
|
||||
"dustSensor": {
|
||||
"dustLevel": {
|
||||
"value": 46,
|
||||
"unit": "\u03bcg/m^3",
|
||||
"timestamp": "2025-03-06T16:01:49.656000+00:00"
|
||||
},
|
||||
"fineDustLevel": {
|
||||
"value": 10,
|
||||
"unit": "\u03bcg/m^3",
|
||||
"timestamp": "2025-03-06T16:01:49.656000+00:00"
|
||||
}
|
||||
},
|
||||
"thermostatCoolingSetpoint": {
|
||||
"coolingSetpointRange": {
|
||||
"value": null
|
||||
},
|
||||
"coolingSetpoint": {
|
||||
"value": 18,
|
||||
"unit": "C",
|
||||
"timestamp": "2025-03-02T06:54:23.887Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,692 @@
|
||||
{
|
||||
"components": {
|
||||
"hca.main": {
|
||||
"hca.dryerMode": {
|
||||
"mode": {
|
||||
"value": "normal",
|
||||
"timestamp": "2025-03-09T16:31:41.247Z"
|
||||
},
|
||||
"supportedModes": {
|
||||
"value": ["normal", "quickDry", "mix", "timeDry"],
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"main": {
|
||||
"custom.dryerWrinklePrevent": {
|
||||
"operatingState": {
|
||||
"value": "ready",
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
},
|
||||
"dryerWrinklePrevent": {
|
||||
"value": "off",
|
||||
"timestamp": "2025-03-09T16:31:41.077Z"
|
||||
}
|
||||
},
|
||||
"samsungce.dryerDryingTemperature": {
|
||||
"dryingTemperature": {
|
||||
"value": null,
|
||||
"timestamp": "2021-04-02T18:31:36.756Z"
|
||||
},
|
||||
"supportedDryingTemperature": {
|
||||
"value": null,
|
||||
"timestamp": "2021-04-02T18:29:52.258Z"
|
||||
}
|
||||
},
|
||||
"samsungce.welcomeMessage": {
|
||||
"welcomeMessage": {
|
||||
"value": null,
|
||||
"timestamp": "2021-04-02T18:32:37.913Z"
|
||||
}
|
||||
},
|
||||
"samsungce.dongleSoftwareInstallation": {
|
||||
"status": {
|
||||
"value": "completed",
|
||||
"timestamp": "2022-06-17T17:07:35.734Z"
|
||||
}
|
||||
},
|
||||
"samsungce.dryerCyclePreset": {
|
||||
"maxNumberOfPresets": {
|
||||
"value": 10,
|
||||
"timestamp": "2025-03-09T16:31:41.229Z"
|
||||
},
|
||||
"presets": {
|
||||
"value": null,
|
||||
"timestamp": "2021-04-02T18:30:36.772Z"
|
||||
}
|
||||
},
|
||||
"samsungce.deviceIdentification": {
|
||||
"micomAssayCode": {
|
||||
"value": "20221341",
|
||||
"timestamp": "2025-03-09T16:31:40.834Z"
|
||||
},
|
||||
"modelName": {
|
||||
"value": null,
|
||||
"timestamp": "2021-04-02T18:29:53.622Z"
|
||||
},
|
||||
"serialNumber": {
|
||||
"value": null,
|
||||
"timestamp": "2021-04-02T18:29:52.641Z"
|
||||
},
|
||||
"serialNumberExtra": {
|
||||
"value": null,
|
||||
"timestamp": "2021-04-02T18:29:51.653Z"
|
||||
},
|
||||
"modelClassificationCode": {
|
||||
"value": "30010102001211000103000000000000",
|
||||
"timestamp": "2025-03-09T16:31:40.834Z"
|
||||
},
|
||||
"description": {
|
||||
"value": "DA_WM_A51_20_COMMON_DV6800N/DC92-01967B_0404",
|
||||
"timestamp": "2025-03-09T16:31:40.834Z"
|
||||
},
|
||||
"releaseYear": {
|
||||
"value": null
|
||||
},
|
||||
"binaryId": {
|
||||
"value": "DA_WM_A51_20_COMMON",
|
||||
"timestamp": "2025-03-09T19:07:40.295Z"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"value": "off",
|
||||
"timestamp": "2025-03-09T19:47:36.549Z"
|
||||
}
|
||||
},
|
||||
"samsungce.quickControl": {
|
||||
"version": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungce.dryerFreezePrevent": {
|
||||
"operatingState": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"ocf": {
|
||||
"st": {
|
||||
"value": null,
|
||||
"timestamp": "2020-06-20T10:01:02.741Z"
|
||||
},
|
||||
"mndt": {
|
||||
"value": null,
|
||||
"timestamp": "2020-06-25T01:53:25.278Z"
|
||||
},
|
||||
"mnfv": {
|
||||
"value": "DA_WM_A51_20_COMMON_30230708",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"mnhw": {
|
||||
"value": "ARTIK051",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"di": {
|
||||
"value": "3a6c4e05-811d-5041-e956-3d04c424cbcd",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"mnsl": {
|
||||
"value": "http://www.samsung.com",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"dmv": {
|
||||
"value": "res.1.1.0,sh.1.1.0",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"n": {
|
||||
"value": "[dryer] Samsung",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"mnmo": {
|
||||
"value": "DA_WM_A51_20_COMMON|20221341|30010102001211000103000000000000",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"vid": {
|
||||
"value": "DA-WM-WD-000001",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"mnmn": {
|
||||
"value": "Samsung Electronics",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"mnml": {
|
||||
"value": "http://www.samsung.com",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"mnpv": {
|
||||
"value": "DAWIT 2.0",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"mnos": {
|
||||
"value": "TizenRT 1.0 + IPv6",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"pi": {
|
||||
"value": "3a6c4e05-811d-5041-e956-3d04c424cbcd",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
},
|
||||
"icv": {
|
||||
"value": "core.1.1.0",
|
||||
"timestamp": "2024-12-15T10:53:49.561Z"
|
||||
}
|
||||
},
|
||||
"custom.dryerDryLevel": {
|
||||
"dryerDryLevel": {
|
||||
"value": "2",
|
||||
"timestamp": "2025-03-09T19:47:36.806Z"
|
||||
},
|
||||
"supportedDryerDryLevel": {
|
||||
"value": ["none", "1", "2", "3"],
|
||||
"timestamp": "2020-11-18T20:16:43.428Z"
|
||||
}
|
||||
},
|
||||
"samsungce.dryerAutoCycleLink": {
|
||||
"dryerAutoCycleLink": {
|
||||
"value": null,
|
||||
"timestamp": "2020-08-11T12:41:38.646Z"
|
||||
}
|
||||
},
|
||||
"samsungce.dryerCycle": {
|
||||
"dryerCycle": {
|
||||
"value": "Table_00_Course_9A",
|
||||
"timestamp": "2025-03-09T16:31:41.247Z"
|
||||
},
|
||||
"supportedCycles": {
|
||||
"value": [
|
||||
{
|
||||
"cycle": "9A",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D20E",
|
||||
"default": "2",
|
||||
"options": ["1", "2", "3"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "CA",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D10E",
|
||||
"default": "1",
|
||||
"options": ["1", "2", "3"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "DB",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D204",
|
||||
"default": "2",
|
||||
"options": ["2"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "99",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D20E",
|
||||
"default": "2",
|
||||
"options": ["1", "2", "3"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "93",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D102",
|
||||
"default": "1",
|
||||
"options": ["1"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "B5",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D102",
|
||||
"default": "1",
|
||||
"options": ["1"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "D7",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D204",
|
||||
"default": "2",
|
||||
"options": ["2"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "A5",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D204",
|
||||
"default": "2",
|
||||
"options": ["2"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "96",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D000",
|
||||
"default": "none",
|
||||
"options": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "97",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D000",
|
||||
"default": "none",
|
||||
"options": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "7F",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D000",
|
||||
"default": "none",
|
||||
"options": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "98",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D000",
|
||||
"default": "none",
|
||||
"options": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "EB",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D204",
|
||||
"default": "2",
|
||||
"options": ["2"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"cycle": "B6",
|
||||
"supportedOptions": {
|
||||
"dryingLevel": {
|
||||
"raw": "D20E",
|
||||
"default": "2",
|
||||
"options": ["1", "2", "3"]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"timestamp": "2025-02-10T02:24:03.524Z"
|
||||
},
|
||||
"referenceTable": {
|
||||
"value": {
|
||||
"id": "Table_00"
|
||||
},
|
||||
"timestamp": "2025-03-09T16:31:41.247Z"
|
||||
},
|
||||
"specializedFunctionClassification": {
|
||||
"value": 4,
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
}
|
||||
},
|
||||
"custom.disabledCapabilities": {
|
||||
"disabledCapabilities": {
|
||||
"value": [
|
||||
"samsungce.dryerDelayEnd",
|
||||
"dryerOperatingState",
|
||||
"samsungce.dryerCyclePreset",
|
||||
"samsungce.welcomeMessage",
|
||||
"samsungce.dongleSoftwareInstallation",
|
||||
"sec.wifiConfiguration",
|
||||
"samsungce.quickControl",
|
||||
"samsungce.deviceInfoPrivate",
|
||||
"demandResponseLoadControl",
|
||||
"samsungce.dryerFreezePrevent",
|
||||
"samsungce.dryerDryingTemperature",
|
||||
"sec.diagnosticsInformation"
|
||||
],
|
||||
"timestamp": "2024-07-02T14:42:38.334Z"
|
||||
}
|
||||
},
|
||||
"samsungce.driverVersion": {
|
||||
"versionNumber": {
|
||||
"value": 24110101,
|
||||
"timestamp": "2024-12-02T07:43:41.263Z"
|
||||
}
|
||||
},
|
||||
"sec.diagnosticsInformation": {
|
||||
"logType": {
|
||||
"value": null
|
||||
},
|
||||
"endpoint": {
|
||||
"value": null
|
||||
},
|
||||
"minVersion": {
|
||||
"value": null
|
||||
},
|
||||
"signinPermission": {
|
||||
"value": null
|
||||
},
|
||||
"setupId": {
|
||||
"value": null
|
||||
},
|
||||
"protocolType": {
|
||||
"value": null
|
||||
},
|
||||
"tsId": {
|
||||
"value": null
|
||||
},
|
||||
"mnId": {
|
||||
"value": null
|
||||
},
|
||||
"dumpType": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungce.kidsLock": {
|
||||
"lockState": {
|
||||
"value": "unlocked",
|
||||
"timestamp": "2025-03-09T16:31:40.882Z"
|
||||
}
|
||||
},
|
||||
"demandResponseLoadControl": {
|
||||
"drlcStatus": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungce.detergentOrder": {
|
||||
"alarmEnabled": {
|
||||
"value": false,
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
},
|
||||
"orderThreshold": {
|
||||
"value": 0,
|
||||
"unit": "cc",
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
}
|
||||
},
|
||||
"powerConsumptionReport": {
|
||||
"powerConsumption": {
|
||||
"value": {
|
||||
"energy": 796400,
|
||||
"deltaEnergy": 0,
|
||||
"power": 0,
|
||||
"powerEnergy": 0.0,
|
||||
"persistedEnergy": 0,
|
||||
"energySaved": 0,
|
||||
"start": "2025-03-09T19:47:26Z",
|
||||
"end": "2025-03-09T19:47:37Z"
|
||||
},
|
||||
"timestamp": "2025-03-09T19:47:37.283Z"
|
||||
}
|
||||
},
|
||||
"dryerOperatingState": {
|
||||
"completionTime": {
|
||||
"value": "2025-03-09T22:55:37Z",
|
||||
"timestamp": "2025-03-09T19:47:37.015Z"
|
||||
},
|
||||
"machineState": {
|
||||
"value": "stop",
|
||||
"timestamp": "2025-03-09T19:47:37.015Z"
|
||||
},
|
||||
"supportedMachineStates": {
|
||||
"value": ["stop", "run", "pause"],
|
||||
"timestamp": "2025-03-09T16:31:41.172Z"
|
||||
},
|
||||
"dryerJobState": {
|
||||
"value": "none",
|
||||
"timestamp": "2025-03-09T19:47:37.015Z"
|
||||
}
|
||||
},
|
||||
"samsungce.detergentState": {
|
||||
"remainingAmount": {
|
||||
"value": 0,
|
||||
"unit": "cc",
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
},
|
||||
"dosage": {
|
||||
"value": 0,
|
||||
"unit": "cc",
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
},
|
||||
"initialAmount": {
|
||||
"value": 0,
|
||||
"unit": "cc",
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
},
|
||||
"detergentType": {
|
||||
"value": "none",
|
||||
"timestamp": "2021-04-02T18:29:51.428Z"
|
||||
}
|
||||
},
|
||||
"samsungce.dryerDelayEnd": {
|
||||
"remainingTime": {
|
||||
"value": 0,
|
||||
"unit": "min",
|
||||
"timestamp": "2025-03-09T16:31:41.172Z"
|
||||
}
|
||||
},
|
||||
"refresh": {},
|
||||
"custom.jobBeginningStatus": {
|
||||
"jobBeginningStatus": {
|
||||
"value": null,
|
||||
"timestamp": "2020-06-25T01:53:34.974Z"
|
||||
}
|
||||
},
|
||||
"execute": {
|
||||
"data": {
|
||||
"value": {
|
||||
"payload": {
|
||||
"rt": ["x.com.samsung.da.information"],
|
||||
"if": ["oic.if.baseline", "oic.if.a"],
|
||||
"x.com.samsung.da.modelNum": "DA_WM_A51_20_COMMON|20221341|30010102001211000103000000000000",
|
||||
"x.com.samsung.da.description": "DA_WM_A51_20_COMMON_DV6800N/DC92-01967B_0404",
|
||||
"x.com.samsung.da.serialNum": "0T625AEN100200N",
|
||||
"x.com.samsung.da.otnDUID": "SHCDM6YAPCCXC",
|
||||
"x.com.samsung.da.items": [
|
||||
{
|
||||
"x.com.samsung.da.id": "0",
|
||||
"x.com.samsung.da.description": "DA_WM_A51_20_COMMON|20221341|30010102001211000103000000000000",
|
||||
"x.com.samsung.da.type": "Software",
|
||||
"x.com.samsung.da.number": "02198A220728(E256)",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
},
|
||||
{
|
||||
"x.com.samsung.da.id": "1",
|
||||
"x.com.samsung.da.description": "DA_WM_A51_20_COMMON",
|
||||
"x.com.samsung.da.type": "Firmware",
|
||||
"x.com.samsung.da.number": "17111305,19060420",
|
||||
"x.com.samsung.da.newVersionAvailable": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"href": "/information/vs/0"
|
||||
},
|
||||
"timestamp": "2023-08-07T00:06:05.984Z"
|
||||
}
|
||||
},
|
||||
"sec.wifiConfiguration": {
|
||||
"autoReconnection": {
|
||||
"value": null
|
||||
},
|
||||
"minVersion": {
|
||||
"value": null
|
||||
},
|
||||
"supportedWiFiFreq": {
|
||||
"value": null
|
||||
},
|
||||
"supportedAuthType": {
|
||||
"value": null
|
||||
},
|
||||
"protocolType": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"remoteControlStatus": {
|
||||
"remoteControlEnabled": {
|
||||
"value": "false",
|
||||
"timestamp": "2025-03-09T16:31:41.180Z"
|
||||
}
|
||||
},
|
||||
"custom.supportedOptions": {
|
||||
"course": {
|
||||
"value": null
|
||||
},
|
||||
"referenceTable": {
|
||||
"value": {
|
||||
"id": "Table_00"
|
||||
},
|
||||
"timestamp": "2025-03-09T16:31:41.247Z"
|
||||
},
|
||||
"supportedCourses": {
|
||||
"value": [
|
||||
"9A",
|
||||
"CA",
|
||||
"DB",
|
||||
"99",
|
||||
"93",
|
||||
"B5",
|
||||
"D7",
|
||||
"A5",
|
||||
"96",
|
||||
"97",
|
||||
"7F",
|
||||
"98",
|
||||
"EB",
|
||||
"B6"
|
||||
],
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
}
|
||||
},
|
||||
"custom.energyType": {
|
||||
"energyType": {
|
||||
"value": "2.0",
|
||||
"timestamp": "2022-06-17T17:07:35.734Z"
|
||||
},
|
||||
"energySavingSupport": {
|
||||
"value": false,
|
||||
"timestamp": "2022-06-17T17:07:35.734Z"
|
||||
},
|
||||
"drMaxDuration": {
|
||||
"value": null
|
||||
},
|
||||
"energySavingLevel": {
|
||||
"value": null
|
||||
},
|
||||
"energySavingInfo": {
|
||||
"value": null
|
||||
},
|
||||
"supportedEnergySavingLevels": {
|
||||
"value": null
|
||||
},
|
||||
"energySavingOperation": {
|
||||
"value": null
|
||||
},
|
||||
"notificationTemplateID": {
|
||||
"value": null
|
||||
},
|
||||
"energySavingOperationSupport": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungce.dryerOperatingState": {
|
||||
"operatingState": {
|
||||
"value": "ready",
|
||||
"timestamp": "2025-03-09T19:47:37.015Z"
|
||||
},
|
||||
"supportedOperatingStates": {
|
||||
"value": ["ready", "running", "paused"],
|
||||
"timestamp": "2022-11-01T12:48:22.390Z"
|
||||
},
|
||||
"scheduledJobs": {
|
||||
"value": [
|
||||
{
|
||||
"jobName": "drying",
|
||||
"timeInMin": 192
|
||||
},
|
||||
{
|
||||
"jobName": "cooling",
|
||||
"timeInMin": 1
|
||||
}
|
||||
],
|
||||
"timestamp": "2025-03-09T16:31:40.486Z"
|
||||
},
|
||||
"progress": {
|
||||
"value": 1,
|
||||
"unit": "%",
|
||||
"timestamp": "2025-03-09T19:47:37.015Z"
|
||||
},
|
||||
"remainingTimeStr": {
|
||||
"value": "03:08",
|
||||
"timestamp": "2025-03-09T19:47:37.015Z"
|
||||
},
|
||||
"dryerJobState": {
|
||||
"value": "none",
|
||||
"timestamp": "2025-03-09T19:47:37.015Z"
|
||||
},
|
||||
"remainingTime": {
|
||||
"value": 188,
|
||||
"unit": "min",
|
||||
"timestamp": "2025-03-09T19:47:37.015Z"
|
||||
}
|
||||
},
|
||||
"samsungce.softwareUpdate": {
|
||||
"targetModule": {
|
||||
"value": null
|
||||
},
|
||||
"otnDUID": {
|
||||
"value": "SHCDM6YAPCCXC",
|
||||
"timestamp": "2025-03-09T16:31:40.834Z"
|
||||
},
|
||||
"lastUpdatedDate": {
|
||||
"value": null
|
||||
},
|
||||
"availableModules": {
|
||||
"value": [],
|
||||
"timestamp": "2024-12-01T21:16:50.598Z"
|
||||
},
|
||||
"newVersionAvailable": {
|
||||
"value": false,
|
||||
"timestamp": "2024-12-01T21:16:50.598Z"
|
||||
},
|
||||
"operatingState": {
|
||||
"value": null
|
||||
},
|
||||
"progress": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungce.dryerDryingTime": {
|
||||
"supportedDryingTime": {
|
||||
"value": ["0", "30", "60", "90", "120", "150"],
|
||||
"timestamp": "2021-04-02T18:29:51.428Z"
|
||||
},
|
||||
"dryingTime": {
|
||||
"value": "0",
|
||||
"unit": "min",
|
||||
"timestamp": "2025-03-09T16:31:41.077Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"components": {
|
||||
"main": {
|
||||
"relativeHumidityMeasurement": {
|
||||
"humidity": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"thermostatOperatingState": {
|
||||
"thermostatOperatingState": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"healthCheck": {
|
||||
"checkInterval": {
|
||||
"value": 60,
|
||||
"unit": "s",
|
||||
"data": {
|
||||
"deviceScheme": "UNTRACKED",
|
||||
"protocol": "cloud"
|
||||
},
|
||||
"timestamp": "2025-03-10T00:57:26.866Z"
|
||||
},
|
||||
"healthStatus": {
|
||||
"value": null
|
||||
},
|
||||
"DeviceWatch-Enroll": {
|
||||
"value": null
|
||||
},
|
||||
"DeviceWatch-DeviceStatus": {
|
||||
"value": "offline",
|
||||
"data": {
|
||||
"reason": "DEVICE-OFFLINE"
|
||||
},
|
||||
"timestamp": "2025-03-11T10:22:17.013Z"
|
||||
}
|
||||
},
|
||||
"temperatureMeasurement": {
|
||||
"temperatureRange": {
|
||||
"value": null
|
||||
},
|
||||
"temperature": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"thermostatHeatingSetpoint": {
|
||||
"heatingSetpoint": {
|
||||
"value": null
|
||||
},
|
||||
"heatingSetpointRange": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"thermostatFanMode": {
|
||||
"thermostatFanMode": {
|
||||
"value": null
|
||||
},
|
||||
"supportedThermostatFanModes": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"refresh": {},
|
||||
"thermostatMode": {
|
||||
"thermostatMode": {
|
||||
"value": null
|
||||
},
|
||||
"supportedThermostatModes": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"thermostatCoolingSetpoint": {
|
||||
"coolingSetpointRange": {
|
||||
"value": null
|
||||
},
|
||||
"coolingSetpoint": {
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"components": {
|
||||
"main02": {
|
||||
"temperatureMeasurement": {
|
||||
"temperatureRange": {
|
||||
"value": null
|
||||
},
|
||||
"temperature": {
|
||||
"value": 200.0,
|
||||
"unit": "C",
|
||||
"timestamp": "2024-12-02T20:18:52.095Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"main": {
|
||||
"thermostatOperatingState": {
|
||||
"thermostatOperatingState": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"signalStrength": {
|
||||
"rssi": {
|
||||
"value": -84,
|
||||
"unit": "dBm",
|
||||
"timestamp": "2025-03-07T20:53:55.346Z"
|
||||
},
|
||||
"lqi": {
|
||||
"value": 255,
|
||||
"timestamp": "2025-03-07T20:53:55.387Z"
|
||||
}
|
||||
},
|
||||
"temperatureMeasurement": {
|
||||
"temperatureRange": {
|
||||
"value": null
|
||||
},
|
||||
"temperature": {
|
||||
"value": 21.0,
|
||||
"unit": "C",
|
||||
"timestamp": "2025-03-07T16:58:23.773Z"
|
||||
}
|
||||
},
|
||||
"thermostatHeatingSetpoint": {
|
||||
"heatingSetpoint": {
|
||||
"value": 23.0,
|
||||
"unit": "C",
|
||||
"timestamp": "2025-02-10T17:48:38.299Z"
|
||||
},
|
||||
"heatingSetpointRange": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"refresh": {},
|
||||
"valleyboard16460.debug": {
|
||||
"value": {
|
||||
"value": "<table style=\"font-size:0.6em;min-width:100%\"><tbody>\n <tr><th align=\"left\" style=\"width:35%\">Actual</th><td style=\"width:65%\">_TZE200_rxntag7i</td></tr>\n <tr><th align=\"left\">Expected</th><td>_TZE200_4hbx5cvx</td></tr>\n <tr><th align=\"left\">Profile</th><td>normal-thermostat-v3</td></tr>\n <tr><th align=\"left\">Mode</th><td>Similarity</td></tr>\n <tr><th align=\"left\">Preferences</th><td>Modified</td></tr>\n <tr><th align=\"left\">Exposes EF00</th><td>Yes</td></tr>\n <tr><th align=\"left\">Default DP</th><td>No</td></tr>\n </tbody></table>",
|
||||
"timestamp": "2025-03-05T03:04:54.025Z"
|
||||
}
|
||||
},
|
||||
"thermostatMode": {
|
||||
"thermostatMode": {
|
||||
"value": "heat",
|
||||
"data": {},
|
||||
"timestamp": "2024-12-30T08:22:19.273Z"
|
||||
},
|
||||
"supportedThermostatModes": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user