mirror of
https://github.com/home-assistant/core.git
synced 2026-03-02 09:39:44 +00:00
Compare commits
10 Commits
yalexs_ble
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5dce4a8eda | ||
|
|
6fcc9da948 | ||
|
|
bf93580ff9 | ||
|
|
0c2fe045d5 | ||
|
|
e14a3a6b0e | ||
|
|
e032740e90 | ||
|
|
78ad1e102d | ||
|
|
4f97cc7b68 | ||
|
|
df8f135532 | ||
|
|
0066801b0f |
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -28,11 +28,11 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
with:
|
||||
languages: python
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||
uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
|
||||
with:
|
||||
category: "/language:python"
|
||||
|
||||
@@ -8,12 +8,10 @@ from typing import Any, Concatenate
|
||||
|
||||
from aiomodernforms import ModernFormsConnectionError, ModernFormsError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import ModernFormsDataUpdateCoordinator
|
||||
from .coordinator import ModernFormsConfigEntry, ModernFormsDataUpdateCoordinator
|
||||
from .entity import ModernFormsDeviceEntity
|
||||
|
||||
PLATFORMS = [
|
||||
@@ -26,15 +24,14 @@ PLATFORMS = [
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ModernFormsConfigEntry) -> bool:
|
||||
"""Set up a Modern Forms device from a config entry."""
|
||||
|
||||
# Create Modern Forms instance for this entry
|
||||
coordinator = ModernFormsDataUpdateCoordinator(hass, entry)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
# Set up all platforms for this device/entry.
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
@@ -42,17 +39,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(
|
||||
hass: HomeAssistant, entry: ModernFormsConfigEntry
|
||||
) -> bool:
|
||||
"""Unload Modern Forms config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
if unload_ok:
|
||||
del hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
if not hass.data[DOMAIN]:
|
||||
del hass.data[DOMAIN]
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
def modernforms_exception_handler[
|
||||
|
||||
@@ -3,23 +3,22 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import CLEAR_TIMER, DOMAIN
|
||||
from .coordinator import ModernFormsDataUpdateCoordinator
|
||||
from .const import CLEAR_TIMER
|
||||
from .coordinator import ModernFormsConfigEntry, ModernFormsDataUpdateCoordinator
|
||||
from .entity import ModernFormsDeviceEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: ModernFormsConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Modern Forms binary sensors."""
|
||||
coordinator: ModernFormsDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
binary_sensors: list[ModernFormsBinarySensor] = [
|
||||
ModernFormsFanSleepTimerActive(entry.entry_id, coordinator),
|
||||
|
||||
@@ -20,6 +20,9 @@ SCAN_INTERVAL = timedelta(seconds=5)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
type ModernFormsConfigEntry = ConfigEntry[ModernFormsDataUpdateCoordinator]
|
||||
|
||||
|
||||
class ModernFormsDataUpdateCoordinator(DataUpdateCoordinator[ModernFormsDeviceState]):
|
||||
"""Class to manage fetching Modern Forms data from single endpoint."""
|
||||
|
||||
|
||||
@@ -3,27 +3,23 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_MAC
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import ModernFormsDataUpdateCoordinator
|
||||
from .coordinator import ModernFormsConfigEntry
|
||||
|
||||
REDACT_CONFIG = {CONF_MAC}
|
||||
REDACT_DEVICE_INFO = {"mac_address", "owner"}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
hass: HomeAssistant, entry: ModernFormsConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
coordinator: ModernFormsDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
if TYPE_CHECKING:
|
||||
assert coordinator is not None
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
return {
|
||||
"config_entry": async_redact_data(entry.as_dict(), REDACT_CONFIG),
|
||||
|
||||
@@ -8,7 +8,6 @@ from aiomodernforms.const import FAN_POWER_OFF, FAN_POWER_ON
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_platform
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -22,26 +21,23 @@ from . import modernforms_exception_handler
|
||||
from .const import (
|
||||
ATTR_SLEEP_TIME,
|
||||
CLEAR_TIMER,
|
||||
DOMAIN,
|
||||
OPT_ON,
|
||||
OPT_SPEED,
|
||||
SERVICE_CLEAR_FAN_SLEEP_TIMER,
|
||||
SERVICE_SET_FAN_SLEEP_TIMER,
|
||||
)
|
||||
from .coordinator import ModernFormsDataUpdateCoordinator
|
||||
from .coordinator import ModernFormsConfigEntry, ModernFormsDataUpdateCoordinator
|
||||
from .entity import ModernFormsDeviceEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: ModernFormsConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up a Modern Forms platform from config entry."""
|
||||
|
||||
coordinator: ModernFormsDataUpdateCoordinator = hass.data[DOMAIN][
|
||||
config_entry.entry_id
|
||||
]
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ from aiomodernforms.const import LIGHT_POWER_OFF, LIGHT_POWER_ON
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_platform
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -21,13 +20,12 @@ from . import modernforms_exception_handler
|
||||
from .const import (
|
||||
ATTR_SLEEP_TIME,
|
||||
CLEAR_TIMER,
|
||||
DOMAIN,
|
||||
OPT_BRIGHTNESS,
|
||||
OPT_ON,
|
||||
SERVICE_CLEAR_LIGHT_SLEEP_TIMER,
|
||||
SERVICE_SET_LIGHT_SLEEP_TIMER,
|
||||
)
|
||||
from .coordinator import ModernFormsDataUpdateCoordinator
|
||||
from .coordinator import ModernFormsConfigEntry, ModernFormsDataUpdateCoordinator
|
||||
from .entity import ModernFormsDeviceEntity
|
||||
|
||||
BRIGHTNESS_RANGE = (1, 255)
|
||||
@@ -35,14 +33,12 @@ BRIGHTNESS_RANGE = (1, 255)
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: ModernFormsConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up a Modern Forms platform from config entry."""
|
||||
|
||||
coordinator: ModernFormsDataUpdateCoordinator = hass.data[DOMAIN][
|
||||
config_entry.entry_id
|
||||
]
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
# if no light unit installed no light entity
|
||||
if not coordinator.data.info.light_type:
|
||||
|
||||
@@ -5,24 +5,23 @@ from __future__ import annotations
|
||||
from datetime import datetime
|
||||
|
||||
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import CLEAR_TIMER, DOMAIN
|
||||
from .coordinator import ModernFormsDataUpdateCoordinator
|
||||
from .const import CLEAR_TIMER
|
||||
from .coordinator import ModernFormsConfigEntry, ModernFormsDataUpdateCoordinator
|
||||
from .entity import ModernFormsDeviceEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: ModernFormsConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Modern Forms sensor based on a config entry."""
|
||||
coordinator: ModernFormsDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
sensors: list[ModernFormsSensor] = [
|
||||
ModernFormsFanTimerRemainingTimeSensor(entry.entry_id, coordinator),
|
||||
|
||||
@@ -5,23 +5,21 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import modernforms_exception_handler
|
||||
from .const import DOMAIN
|
||||
from .coordinator import ModernFormsDataUpdateCoordinator
|
||||
from .coordinator import ModernFormsConfigEntry, ModernFormsDataUpdateCoordinator
|
||||
from .entity import ModernFormsDeviceEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: ModernFormsConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Modern Forms switch based on a config entry."""
|
||||
coordinator: ModernFormsDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
switches = [
|
||||
ModernFormsAwaySwitch(entry.entry_id, coordinator),
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
"message": "[%key:common::config_flow::error::invalid_api_key%]"
|
||||
},
|
||||
"connection_error": {
|
||||
"message": "Error connecting to the Overseerr instance: {error}"
|
||||
"message": "Error connecting to the Seerr instance: {error}"
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
|
||||
@@ -107,6 +107,7 @@ PLATFORMS = [
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.TIME,
|
||||
Platform.UPDATE,
|
||||
Platform.VACUUM,
|
||||
Platform.VALVE,
|
||||
|
||||
@@ -36,6 +36,7 @@ class SmartThingsBinarySensorEntityDescription(BinarySensorEntityDescription):
|
||||
| None
|
||||
) = None
|
||||
component_translation_key: dict[str, str] | None = None
|
||||
supported_states_attributes: Attribute | None = None
|
||||
|
||||
|
||||
CAPABILITY_TO_SENSORS: dict[
|
||||
@@ -188,6 +189,17 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
},
|
||||
)
|
||||
},
|
||||
Capability.SAMSUNG_CE_ROBOT_CLEANER_DUST_BAG: {
|
||||
Attribute.STATUS: SmartThingsBinarySensorEntityDescription(
|
||||
key=Attribute.STATUS,
|
||||
is_on_key="full",
|
||||
component_translation_key={
|
||||
"station": "robot_cleaner_dust_bag",
|
||||
},
|
||||
exists_fn=lambda component, _: component == "station",
|
||||
supported_states_attributes=Attribute.SUPPORTED_STATUS,
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -237,6 +249,18 @@ async def async_setup_entry(
|
||||
not description.category
|
||||
or get_main_component_category(device) in description.category
|
||||
)
|
||||
and (
|
||||
not description.supported_states_attributes
|
||||
or (
|
||||
isinstance(
|
||||
options := device.status[component][capability][
|
||||
description.supported_states_attributes
|
||||
].value,
|
||||
list,
|
||||
)
|
||||
and len(options) == 2
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
"state": {
|
||||
"on": "mdi:remote"
|
||||
}
|
||||
},
|
||||
"robot_cleaner_dust_bag": {
|
||||
"default": "mdi:delete"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
@@ -110,6 +113,9 @@
|
||||
"soil_level": {
|
||||
"default": "mdi:liquid-spot"
|
||||
},
|
||||
"sound_detection_sensitivity": {
|
||||
"default": "mdi:home-sound-in"
|
||||
},
|
||||
"spin_level": {
|
||||
"default": "mdi:rotate-right"
|
||||
},
|
||||
@@ -250,6 +256,14 @@
|
||||
"off": "mdi:tumble-dryer-off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"do_not_disturb_end_time": {
|
||||
"default": "mdi:bell-ring"
|
||||
},
|
||||
"do_not_disturb_start_time": {
|
||||
"default": "mdi:bell-cancel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,6 +165,15 @@ CAPABILITIES_TO_SELECT: dict[Capability | str, SmartThingsSelectDescription] = {
|
||||
extra_components=["hood"],
|
||||
capability_ignore_list=[Capability.SAMSUNG_CE_CONNECTION_STATE],
|
||||
),
|
||||
Capability.SAMSUNG_CE_SOUND_DETECTION_SENSITIVITY: SmartThingsSelectDescription(
|
||||
key=Capability.SAMSUNG_CE_SOUND_DETECTION_SENSITIVITY,
|
||||
translation_key="sound_detection_sensitivity",
|
||||
options_attribute=Attribute.SUPPORTED_LEVELS,
|
||||
status_attribute=Attribute.LEVEL,
|
||||
command=Command.SET_LEVEL,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
Capability.CUSTOM_WASHER_SPIN_LEVEL: SmartThingsSelectDescription(
|
||||
key=Capability.CUSTOM_WASHER_SPIN_LEVEL,
|
||||
translation_key="spin_level",
|
||||
|
||||
@@ -162,6 +162,13 @@ class SmartThingsSensorEntityDescription(SensorEntityDescription):
|
||||
use_temperature_unit: bool = False
|
||||
deprecated: Callable[[ComponentStatus], tuple[str, str] | None] | None = None
|
||||
component_translation_key: dict[str, str] | None = None
|
||||
presentation_fn: (
|
||||
Callable[
|
||||
[str | None, str | float | int | datetime | None],
|
||||
str | float | int | datetime | None,
|
||||
]
|
||||
| None
|
||||
) = None
|
||||
|
||||
|
||||
CAPABILITY_TO_SENSORS: dict[
|
||||
@@ -763,6 +770,13 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
(value := cast(dict | None, status.value)) is not None
|
||||
and "power" in value
|
||||
),
|
||||
presentation_fn=lambda presentation_id, value: (
|
||||
value * 1000
|
||||
if presentation_id is not None
|
||||
and "EHS" in presentation_id
|
||||
and isinstance(value, (int, float))
|
||||
else value
|
||||
),
|
||||
),
|
||||
SmartThingsSensorEntityDescription(
|
||||
key="deltaEnergy_meter",
|
||||
@@ -1347,7 +1361,12 @@ class SmartThingsSensor(SmartThingsEntity, SensorEntity):
|
||||
res = self.get_attribute_value(self.capability, self._attribute)
|
||||
if options_map := self.entity_description.options_map:
|
||||
return options_map.get(res)
|
||||
return self.entity_description.value_fn(res)
|
||||
value = self.entity_description.value_fn(res)
|
||||
if self.entity_description.presentation_fn:
|
||||
value = self.entity_description.presentation_fn(
|
||||
self.device.device.presentation_id, value
|
||||
)
|
||||
return value
|
||||
|
||||
@property
|
||||
def native_unit_of_measurement(self) -> str | None:
|
||||
|
||||
@@ -76,6 +76,9 @@
|
||||
"remote_control": {
|
||||
"name": "Remote control"
|
||||
},
|
||||
"robot_cleaner_dust_bag": {
|
||||
"name": "Dust bag full"
|
||||
},
|
||||
"sub_remote_control": {
|
||||
"name": "Upper washer remote control"
|
||||
},
|
||||
@@ -256,6 +259,14 @@
|
||||
"up": "Up"
|
||||
}
|
||||
},
|
||||
"sound_detection_sensitivity": {
|
||||
"name": "Sound detection sensitivity",
|
||||
"state": {
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"medium": "[%key:common::state::medium%]"
|
||||
}
|
||||
},
|
||||
"spin_level": {
|
||||
"name": "Spin level",
|
||||
"state": {
|
||||
@@ -934,6 +945,14 @@
|
||||
"name": "Wrinkle prevent"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"do_not_disturb_end_time": {
|
||||
"name": "Do not disturb end time"
|
||||
},
|
||||
"do_not_disturb_start_time": {
|
||||
"name": "Do not disturb start time"
|
||||
}
|
||||
},
|
||||
"vacuum": {
|
||||
"vacuum": {
|
||||
"state_attributes": {
|
||||
|
||||
102
homeassistant/components/smartthings/time.py
Normal file
102
homeassistant/components/smartthings/time.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"""Time platform for SmartThings."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import time
|
||||
|
||||
from pysmartthings import Attribute, Capability, Command, SmartThings
|
||||
|
||||
from homeassistant.components.time import TimeEntity, TimeEntityDescription
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import FullDevice, SmartThingsConfigEntry
|
||||
from .const import MAIN
|
||||
from .entity import SmartThingsEntity
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class SmartThingsTimeEntityDescription(TimeEntityDescription):
|
||||
"""Describe a SmartThings time entity."""
|
||||
|
||||
attribute: Attribute
|
||||
|
||||
|
||||
DND_ENTITIES = [
|
||||
SmartThingsTimeEntityDescription(
|
||||
key=Attribute.START_TIME,
|
||||
translation_key="do_not_disturb_start_time",
|
||||
attribute=Attribute.START_TIME,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
SmartThingsTimeEntityDescription(
|
||||
key=Attribute.END_TIME,
|
||||
translation_key="do_not_disturb_end_time",
|
||||
attribute=Attribute.END_TIME,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: SmartThingsConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add time entities for a config entry."""
|
||||
entry_data = entry.runtime_data
|
||||
async_add_entities(
|
||||
SmartThingsDnDTime(entry_data.client, device, description)
|
||||
for device in entry_data.devices.values()
|
||||
if Capability.CUSTOM_DO_NOT_DISTURB_MODE in device.status.get(MAIN, {})
|
||||
for description in DND_ENTITIES
|
||||
)
|
||||
|
||||
|
||||
class SmartThingsDnDTime(SmartThingsEntity, TimeEntity):
|
||||
"""Define a SmartThings time entity."""
|
||||
|
||||
entity_description: SmartThingsTimeEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
client: SmartThings,
|
||||
device: FullDevice,
|
||||
entity_description: SmartThingsTimeEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the time entity."""
|
||||
super().__init__(client, device, {Capability.CUSTOM_DO_NOT_DISTURB_MODE})
|
||||
self.entity_description = entity_description
|
||||
self._attr_unique_id = f"{device.device.device_id}_{MAIN}_{Capability.CUSTOM_DO_NOT_DISTURB_MODE}_{entity_description.attribute}_{entity_description.attribute}"
|
||||
|
||||
async def async_set_value(self, value: time) -> None:
|
||||
"""Set the time value."""
|
||||
payload = {
|
||||
"mode": self.get_attribute_value(
|
||||
Capability.CUSTOM_DO_NOT_DISTURB_MODE, Attribute.DO_NOT_DISTURB
|
||||
),
|
||||
"startTime": self.get_attribute_value(
|
||||
Capability.CUSTOM_DO_NOT_DISTURB_MODE, Attribute.START_TIME
|
||||
),
|
||||
"endTime": self.get_attribute_value(
|
||||
Capability.CUSTOM_DO_NOT_DISTURB_MODE, Attribute.END_TIME
|
||||
),
|
||||
}
|
||||
await self.execute_device_command(
|
||||
Capability.CUSTOM_DO_NOT_DISTURB_MODE,
|
||||
Command.SET_DO_NOT_DISTURB_MODE,
|
||||
{
|
||||
**payload,
|
||||
self.entity_description.attribute: f"{value.hour:02d}{value.minute:02d}",
|
||||
},
|
||||
)
|
||||
|
||||
@property
|
||||
def native_value(self) -> time:
|
||||
"""Return the time value."""
|
||||
state = self.get_attribute_value(
|
||||
Capability.CUSTOM_DO_NOT_DISTURB_MODE, self.entity_description.attribute
|
||||
)
|
||||
return time(int(state[:2]), int(state[3:5]))
|
||||
@@ -222,8 +222,10 @@ class WebDavBackupAgent(BackupAgent):
|
||||
async def _download_metadata(path: str) -> AgentBackup:
|
||||
"""Download metadata file."""
|
||||
iterator = await self._client.download_iter(path)
|
||||
metadata = await anext(iterator)
|
||||
return AgentBackup.from_dict(json_loads_object(metadata))
|
||||
metadata_bytes = bytearray()
|
||||
async for chunk in iterator:
|
||||
metadata_bytes.extend(chunk)
|
||||
return AgentBackup.from_dict(json_loads_object(metadata_bytes))
|
||||
|
||||
async def _list_metadata_files() -> dict[str, AgentBackup]:
|
||||
"""List metadata files."""
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aiowebdav2"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["aiowebdav2==0.5.0"]
|
||||
"requirements": ["aiowebdav2==0.6.1"]
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ voluptuous-openapi==0.2.0
|
||||
voluptuous-serialize==2.7.0
|
||||
voluptuous==0.15.2
|
||||
webrtc-models==0.3.0
|
||||
yarl==1.22.0
|
||||
yarl==1.23.0
|
||||
zeroconf==0.148.0
|
||||
|
||||
# Constrain pycryptodome to avoid vulnerability
|
||||
|
||||
@@ -82,7 +82,7 @@ dependencies = [
|
||||
"voluptuous==0.15.2",
|
||||
"voluptuous-serialize==2.7.0",
|
||||
"voluptuous-openapi==0.2.0",
|
||||
"yarl==1.22.0",
|
||||
"yarl==1.23.0",
|
||||
"webrtc-models==0.3.0",
|
||||
"zeroconf==0.148.0",
|
||||
]
|
||||
|
||||
2
requirements.txt
generated
2
requirements.txt
generated
@@ -60,5 +60,5 @@ voluptuous-openapi==0.2.0
|
||||
voluptuous-serialize==2.7.0
|
||||
voluptuous==0.15.2
|
||||
webrtc-models==0.3.0
|
||||
yarl==1.22.0
|
||||
yarl==1.23.0
|
||||
zeroconf==0.148.0
|
||||
|
||||
2
requirements_all.txt
generated
2
requirements_all.txt
generated
@@ -443,7 +443,7 @@ aiowaqi==3.1.0
|
||||
aiowatttime==0.1.1
|
||||
|
||||
# homeassistant.components.webdav
|
||||
aiowebdav2==0.5.0
|
||||
aiowebdav2==0.6.1
|
||||
|
||||
# homeassistant.components.webostv
|
||||
aiowebostv==0.7.5
|
||||
|
||||
2
requirements_test_all.txt
generated
2
requirements_test_all.txt
generated
@@ -428,7 +428,7 @@ aiowaqi==3.1.0
|
||||
aiowatttime==0.1.1
|
||||
|
||||
# homeassistant.components.webdav
|
||||
aiowebdav2==0.5.0
|
||||
aiowebdav2==0.6.1
|
||||
|
||||
# homeassistant.components.webostv
|
||||
aiowebostv==0.7.5
|
||||
|
||||
@@ -4,7 +4,6 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
from aiomodernforms import ModernFormsConnectionError
|
||||
|
||||
from homeassistant.components.modern_forms.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
@@ -31,11 +30,11 @@ async def test_unload_config_entry(
|
||||
) -> None:
|
||||
"""Test the Modern Forms configuration entry unloading."""
|
||||
entry = await init_integration(hass, aioclient_mock)
|
||||
assert hass.data[DOMAIN]
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert not hass.data.get(DOMAIN)
|
||||
assert entry.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
async def test_fan_only_device(
|
||||
|
||||
@@ -84,7 +84,7 @@ async def test_service_get_requests_no_meta(
|
||||
"get_requests",
|
||||
OverseerrConnectionError("Timeout"),
|
||||
HomeAssistantError,
|
||||
"Error connecting to the Overseerr instance: Timeout",
|
||||
"Error connecting to the Seerr instance: Timeout",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1836,6 +1836,55 @@
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][binary_sensor.robot_vacuum_dust_bag_full-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'binary_sensor.robot_vacuum_dust_bag_full',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Dust bag full',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Dust bag full',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'robot_cleaner_dust_bag',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_station_samsungce.robotCleanerDustBag_status_status',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][binary_sensor.robot_vacuum_dust_bag_full-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Robot Vacuum Dust bag full',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.robot_vacuum_dust_bag_full',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_dw_000001][binary_sensor.dishwasher_child_lock-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -533,6 +533,66 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][select.robot_vacuum_sound_detection_sensitivity-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'select.robot_vacuum_sound_detection_sensitivity',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sound detection sensitivity',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sound detection sensitivity',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sound_detection_sensitivity',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_samsungce.soundDetectionSensitivity_level_level',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][select.robot_vacuum_sound_detection_sensitivity-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Robot Vacuum Sound detection sensitivity',
|
||||
'options': list([
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'select.robot_vacuum_sound_detection_sensitivity',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'medium',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_dw_000001][select.dishwasher-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -11504,7 +11504,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.015',
|
||||
'state': '15.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_sac_ehs_000001_sub][sensor.eco_heating_system_power_energy-entry]
|
||||
@@ -11850,7 +11850,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.015',
|
||||
'state': '15.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_sac_ehs_000001_sub_1][sensor.heat_pump_main_power_energy-entry]
|
||||
@@ -12196,7 +12196,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.015',
|
||||
'state': '15.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_sac_ehs_000002_sub][sensor.warmepumpe_power_energy-entry]
|
||||
|
||||
197
tests/components/smartthings/snapshots/test_time.ambr
Normal file
197
tests/components/smartthings/snapshots/test_time.ambr
Normal file
@@ -0,0 +1,197 @@
|
||||
# serializer version: 1
|
||||
# name: test_all_entities[da_ks_hood_01001][time.range_hood_do_not_disturb_end_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'time',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'time.range_hood_do_not_disturb_end_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Do not disturb end time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Do not disturb end time',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'do_not_disturb_end_time',
|
||||
'unique_id': 'fa5fca25-fa7a-1807-030a-2f72ee0f7bff_main_custom.doNotDisturbMode_endTime_endTime',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ks_hood_01001][time.range_hood_do_not_disturb_end_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Range hood Do not disturb end time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'time.range_hood_do_not_disturb_end_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '00:00:00',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ks_hood_01001][time.range_hood_do_not_disturb_start_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'time',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'time.range_hood_do_not_disturb_start_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Do not disturb start time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Do not disturb start time',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'do_not_disturb_start_time',
|
||||
'unique_id': 'fa5fca25-fa7a-1807-030a-2f72ee0f7bff_main_custom.doNotDisturbMode_startTime_startTime',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ks_hood_01001][time.range_hood_do_not_disturb_start_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Range hood Do not disturb start time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'time.range_hood_do_not_disturb_start_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '00:00:00',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][time.robot_vacuum_do_not_disturb_end_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'time',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'time.robot_vacuum_do_not_disturb_end_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Do not disturb end time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Do not disturb end time',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'do_not_disturb_end_time',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_custom.doNotDisturbMode_endTime_endTime',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][time.robot_vacuum_do_not_disturb_end_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Robot Vacuum Do not disturb end time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'time.robot_vacuum_do_not_disturb_end_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '06:00:00',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][time.robot_vacuum_do_not_disturb_start_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'time',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'time.robot_vacuum_do_not_disturb_start_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Do not disturb start time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Do not disturb start time',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'do_not_disturb_start_time',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_custom.doNotDisturbMode_startTime_startTime',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][time.robot_vacuum_do_not_disturb_start_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Robot Vacuum Do not disturb start time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'time.robot_vacuum_do_not_disturb_start_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '22:00:00',
|
||||
})
|
||||
# ---
|
||||
@@ -30,6 +30,7 @@ from . import (
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_all_entities(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
|
||||
128
tests/components/smartthings/test_time.py
Normal file
128
tests/components/smartthings/test_time.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""Test for the SmartThings time platform."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from pysmartthings import Attribute, Capability, Command
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.smartthings import MAIN
|
||||
from homeassistant.components.time import DOMAIN as TIME_DOMAIN, SERVICE_SET_VALUE
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TIME, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration, snapshot_smartthings_entities, trigger_update
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_all_entities(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test all entities."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
snapshot_smartthings_entities(hass, entity_registry, snapshot, Platform.TIME)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["da_rvc_map_01011"])
|
||||
async def test_state_update(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test state update."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert (
|
||||
hass.states.get("time.robot_vacuum_do_not_disturb_end_time").state == "06:00:00"
|
||||
)
|
||||
|
||||
await trigger_update(
|
||||
hass,
|
||||
devices,
|
||||
"01b28624-5907-c8bc-0325-8ad23f03a637",
|
||||
Capability.CUSTOM_DO_NOT_DISTURB_MODE,
|
||||
Attribute.END_TIME,
|
||||
"0800",
|
||||
)
|
||||
|
||||
assert (
|
||||
hass.states.get("time.robot_vacuum_do_not_disturb_end_time").state == "08:00:00"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["da_rvc_map_01011"])
|
||||
async def test_set_value(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test setting a value."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await hass.services.async_call(
|
||||
TIME_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{
|
||||
ATTR_ENTITY_ID: "time.robot_vacuum_do_not_disturb_end_time",
|
||||
ATTR_TIME: "09:00:00",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
devices.execute_device_command.assert_called_once_with(
|
||||
"01b28624-5907-c8bc-0325-8ad23f03a637",
|
||||
Capability.CUSTOM_DO_NOT_DISTURB_MODE,
|
||||
Command.SET_DO_NOT_DISTURB_MODE,
|
||||
MAIN,
|
||||
argument={
|
||||
"mode": "on",
|
||||
"startTime": "2200",
|
||||
"endTime": "0900",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["da_rvc_map_01011"])
|
||||
async def test_dnd_mode_updates(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test setting a value."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await trigger_update(
|
||||
hass,
|
||||
devices,
|
||||
"01b28624-5907-c8bc-0325-8ad23f03a637",
|
||||
Capability.CUSTOM_DO_NOT_DISTURB_MODE,
|
||||
Attribute.DO_NOT_DISTURB,
|
||||
"off",
|
||||
)
|
||||
|
||||
await hass.services.async_call(
|
||||
TIME_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{
|
||||
ATTR_ENTITY_ID: "time.robot_vacuum_do_not_disturb_end_time",
|
||||
ATTR_TIME: "09:00:00",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
devices.execute_device_command.assert_called_once_with(
|
||||
"01b28624-5907-c8bc-0325-8ad23f03a637",
|
||||
Capability.CUSTOM_DO_NOT_DISTURB_MODE,
|
||||
Command.SET_DO_NOT_DISTURB_MODE,
|
||||
MAIN,
|
||||
argument={
|
||||
"mode": "off",
|
||||
"startTime": "2200",
|
||||
"endTime": "0900",
|
||||
},
|
||||
)
|
||||
@@ -42,6 +42,7 @@ async def _download_mock(path: str, timeout=None) -> AsyncIterator[bytes]:
|
||||
"""Mock the download function."""
|
||||
if path.endswith(".json"):
|
||||
yield dumps(BACKUP_METADATA).encode()
|
||||
return
|
||||
|
||||
yield b"backup data"
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import AsyncGenerator
|
||||
from collections.abc import AsyncGenerator, AsyncIterator
|
||||
from io import StringIO
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
@@ -13,6 +13,7 @@ from homeassistant.components.backup import DOMAIN as BACKUP_DOMAIN, AgentBackup
|
||||
from homeassistant.components.webdav.backup import async_register_backup_agents_listener
|
||||
from homeassistant.components.webdav.const import DATA_BACKUP_AGENT_LISTENERS, DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.json import json_dumps
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .const import BACKUP_METADATA
|
||||
@@ -324,3 +325,44 @@ async def test_listeners_get_cleaned_up(hass: HomeAssistant) -> None:
|
||||
remove_listener()
|
||||
|
||||
assert hass.data.get(DATA_BACKUP_AGENT_LISTENERS) is None
|
||||
|
||||
|
||||
async def test_agents_list_backups_with_multi_chunk_metadata(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
webdav_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test listing backups when metadata is returned in multiple chunks."""
|
||||
metadata_json = json_dumps(BACKUP_METADATA).encode()
|
||||
mid = len(metadata_json) // 2
|
||||
chunk1 = metadata_json[:mid]
|
||||
chunk2 = metadata_json[mid:]
|
||||
|
||||
async def _multi_chunk_download(path: str, timeout=None) -> AsyncIterator[bytes]:
|
||||
"""Mock download returning metadata in multiple chunks."""
|
||||
if path.endswith(".json"):
|
||||
yield chunk1
|
||||
yield chunk2
|
||||
return
|
||||
yield b"backup data"
|
||||
|
||||
webdav_client.download_iter.side_effect = _multi_chunk_download
|
||||
|
||||
# Invalidate the metadata cache so the new mock is used
|
||||
hass.config_entries.async_update_entry(
|
||||
mock_config_entry, title=mock_config_entry.title
|
||||
)
|
||||
await hass.config_entries.async_reload(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
await client.send_json_auto_id({"type": "backup/info"})
|
||||
response = await client.receive_json()
|
||||
|
||||
assert response["success"]
|
||||
assert response["result"]["agent_errors"] == {}
|
||||
backups = response["result"]["backups"]
|
||||
assert len(backups) == 1
|
||||
assert backups[0]["backup_id"] == BACKUP_METADATA["backup_id"]
|
||||
assert backups[0]["name"] == BACKUP_METADATA["name"]
|
||||
|
||||
Reference in New Issue
Block a user