forked from home-assistant/core
Compare commits
10 Commits
2025.3.0b7
...
2025.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 97cc3984c5 | |||
| 98e317dd55 | |||
| ed088aa72f | |||
| 51162320cb | |||
| b88eab8ba3 | |||
| 6c080ee650 | |||
| 8056b0df2b | |||
| 3f94b7a61c | |||
| 1484e46317 | |||
| 2812c8a993 |
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/conversation",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["hassil==2.2.3", "home-assistant-intents==2025.2.26"]
|
||||
"requirements": ["hassil==2.2.3", "home-assistant-intents==2025.3.5"]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"dependencies": ["webhook"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/ecowitt",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["aioecowitt==2024.2.1"]
|
||||
"requirements": ["aioecowitt==2025.3.1"]
|
||||
}
|
||||
|
||||
@@ -110,7 +110,9 @@ class ThinQClimateEntity(ThinQEntity, ClimateEntity):
|
||||
self._attr_hvac_modes = [HVACMode.OFF]
|
||||
self._attr_hvac_mode = HVACMode.OFF
|
||||
self._attr_preset_modes = []
|
||||
self._attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
self._attr_temperature_unit = (
|
||||
self._get_unit_of_measurement(self.data.unit) or UnitOfTemperature.CELSIUS
|
||||
)
|
||||
self._requested_hvac_mode: str | None = None
|
||||
|
||||
# Set up HVAC modes.
|
||||
@@ -182,6 +184,11 @@ class ThinQClimateEntity(ThinQEntity, ClimateEntity):
|
||||
self._attr_target_temperature_high = self.data.target_temp_high
|
||||
self._attr_target_temperature_low = self.data.target_temp_low
|
||||
|
||||
# Update unit.
|
||||
self._attr_temperature_unit = (
|
||||
self._get_unit_of_measurement(self.data.unit) or UnitOfTemperature.CELSIUS
|
||||
)
|
||||
|
||||
_LOGGER.debug(
|
||||
"[%s:%s] update status: c:%s, t:%s, l:%s, h:%s, hvac:%s, unit:%s, step:%s",
|
||||
self.coordinator.device_name,
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
from datetime import timedelta
|
||||
from typing import Final
|
||||
|
||||
from homeassistant.const import UnitOfTemperature
|
||||
|
||||
# Config flow
|
||||
DOMAIN = "lg_thinq"
|
||||
COMPANY = "LGE"
|
||||
@@ -18,3 +20,10 @@ MQTT_SUBSCRIPTION_INTERVAL: Final = timedelta(days=1)
|
||||
# MQTT: Message types
|
||||
DEVICE_PUSH_MESSAGE: Final = "DEVICE_PUSH"
|
||||
DEVICE_STATUS_MESSAGE: Final = "DEVICE_STATUS"
|
||||
|
||||
# Unit conversion map
|
||||
DEVICE_UNIT_TO_HA: dict[str, str] = {
|
||||
"F": UnitOfTemperature.FAHRENHEIT,
|
||||
"C": UnitOfTemperature.CELSIUS,
|
||||
}
|
||||
REVERSE_DEVICE_UNIT_TO_HA = {v: k for k, v in DEVICE_UNIT_TO_HA.items()}
|
||||
|
||||
@@ -2,19 +2,21 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from thinqconnect import ThinQAPIException
|
||||
from thinqconnect.integration import HABridge
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.const import EVENT_CORE_CONFIG_UPDATE
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ThinqConfigEntry
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import DOMAIN, REVERSE_DEVICE_UNIT_TO_HA
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -54,6 +56,40 @@ class DeviceDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
f"{self.device_id}_{self.sub_id}" if self.sub_id else self.device_id
|
||||
)
|
||||
|
||||
# Set your preferred temperature unit. This will allow us to retrieve
|
||||
# temperature values from the API in a converted value corresponding to
|
||||
# preferred unit.
|
||||
self._update_preferred_temperature_unit()
|
||||
|
||||
# Add a callback to handle core config update.
|
||||
self.unit_system: str | None = None
|
||||
self.hass.bus.async_listen(
|
||||
event_type=EVENT_CORE_CONFIG_UPDATE,
|
||||
listener=self._handle_update_config,
|
||||
event_filter=self.async_config_update_filter,
|
||||
)
|
||||
|
||||
async def _handle_update_config(self, _: Event) -> None:
|
||||
"""Handle update core config."""
|
||||
self._update_preferred_temperature_unit()
|
||||
|
||||
await self.async_refresh()
|
||||
|
||||
@callback
|
||||
def async_config_update_filter(self, event_data: Mapping[str, Any]) -> bool:
|
||||
"""Filter out unwanted events."""
|
||||
if (unit_system := event_data.get("unit_system")) != self.unit_system:
|
||||
self.unit_system = unit_system
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _update_preferred_temperature_unit(self) -> None:
|
||||
"""Update preferred temperature unit."""
|
||||
self.api.set_preferred_temperature_unit(
|
||||
REVERSE_DEVICE_UNIT_TO_HA.get(self.hass.config.units.temperature_unit)
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
"""Request to the server to update the status from full response data."""
|
||||
try:
|
||||
|
||||
@@ -10,25 +10,19 @@ from thinqconnect import ThinQAPIException
|
||||
from thinqconnect.devices.const import Location
|
||||
from thinqconnect.integration import PropertyState
|
||||
|
||||
from homeassistant.const import UnitOfTemperature
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import COMPANY, DOMAIN
|
||||
from .const import COMPANY, DEVICE_UNIT_TO_HA, DOMAIN
|
||||
from .coordinator import DeviceDataUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
EMPTY_STATE = PropertyState()
|
||||
|
||||
UNIT_CONVERSION_MAP: dict[str, str] = {
|
||||
"F": UnitOfTemperature.FAHRENHEIT,
|
||||
"C": UnitOfTemperature.CELSIUS,
|
||||
}
|
||||
|
||||
|
||||
class ThinQEntity(CoordinatorEntity[DeviceDataUpdateCoordinator]):
|
||||
"""The base implementation of all lg thinq entities."""
|
||||
@@ -75,7 +69,7 @@ class ThinQEntity(CoordinatorEntity[DeviceDataUpdateCoordinator]):
|
||||
if unit is None:
|
||||
return None
|
||||
|
||||
return UNIT_CONVERSION_MAP.get(unit)
|
||||
return DEVICE_UNIT_TO_HA.get(unit)
|
||||
|
||||
def _update_status(self) -> None:
|
||||
"""Update status itself.
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/nexia",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["nexia"],
|
||||
"requirements": ["nexia==2.1.1"]
|
||||
"requirements": ["nexia==2.2.1"]
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["onedrive_personal_sdk"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["onedrive-personal-sdk==0.0.12"]
|
||||
"requirements": ["onedrive-personal-sdk==0.0.13"]
|
||||
}
|
||||
|
||||
@@ -83,13 +83,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: RoborockConfigEntry) ->
|
||||
# Get a Coordinator if the device is available or if we have connected to the device before
|
||||
coordinators = await asyncio.gather(
|
||||
*build_setup_functions(
|
||||
hass,
|
||||
entry,
|
||||
device_map,
|
||||
user_data,
|
||||
product_info,
|
||||
home_data.rooms,
|
||||
api_client,
|
||||
hass, entry, device_map, user_data, product_info, home_data.rooms
|
||||
),
|
||||
return_exceptions=True,
|
||||
)
|
||||
@@ -141,7 +135,6 @@ def build_setup_functions(
|
||||
user_data: UserData,
|
||||
product_info: dict[str, HomeDataProduct],
|
||||
home_data_rooms: list[HomeDataRoom],
|
||||
api_client: RoborockApiClient,
|
||||
) -> list[
|
||||
Coroutine[
|
||||
Any,
|
||||
@@ -158,7 +151,6 @@ def build_setup_functions(
|
||||
device,
|
||||
product_info[device.product_id],
|
||||
home_data_rooms,
|
||||
api_client,
|
||||
)
|
||||
for device in device_map.values()
|
||||
]
|
||||
@@ -171,12 +163,11 @@ async def setup_device(
|
||||
device: HomeDataDevice,
|
||||
product_info: HomeDataProduct,
|
||||
home_data_rooms: list[HomeDataRoom],
|
||||
api_client: RoborockApiClient,
|
||||
) -> RoborockDataUpdateCoordinator | RoborockDataUpdateCoordinatorA01 | None:
|
||||
"""Set up a coordinator for a given device."""
|
||||
if device.pv == "1.0":
|
||||
return await setup_device_v1(
|
||||
hass, entry, user_data, device, product_info, home_data_rooms, api_client
|
||||
hass, entry, user_data, device, product_info, home_data_rooms
|
||||
)
|
||||
if device.pv == "A01":
|
||||
return await setup_device_a01(hass, entry, user_data, device, product_info)
|
||||
@@ -196,7 +187,6 @@ async def setup_device_v1(
|
||||
device: HomeDataDevice,
|
||||
product_info: HomeDataProduct,
|
||||
home_data_rooms: list[HomeDataRoom],
|
||||
api_client: RoborockApiClient,
|
||||
) -> RoborockDataUpdateCoordinator | None:
|
||||
"""Set up a device Coordinator."""
|
||||
mqtt_client = await hass.async_add_executor_job(
|
||||
@@ -218,15 +208,7 @@ async def setup_device_v1(
|
||||
await mqtt_client.async_release()
|
||||
raise
|
||||
coordinator = RoborockDataUpdateCoordinator(
|
||||
hass,
|
||||
entry,
|
||||
device,
|
||||
networking,
|
||||
product_info,
|
||||
mqtt_client,
|
||||
home_data_rooms,
|
||||
api_client,
|
||||
user_data,
|
||||
hass, entry, device, networking, product_info, mqtt_client, home_data_rooms
|
||||
)
|
||||
try:
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
@@ -36,7 +36,6 @@ PLATFORMS = [
|
||||
Platform.BUTTON,
|
||||
Platform.IMAGE,
|
||||
Platform.NUMBER,
|
||||
Platform.SCENE,
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
|
||||
@@ -10,26 +10,17 @@ import logging
|
||||
from propcache.api import cached_property
|
||||
from roborock import HomeDataRoom
|
||||
from roborock.code_mappings import RoborockCategory
|
||||
from roborock.containers import (
|
||||
DeviceData,
|
||||
HomeDataDevice,
|
||||
HomeDataProduct,
|
||||
HomeDataScene,
|
||||
NetworkInfo,
|
||||
UserData,
|
||||
)
|
||||
from roborock.containers import DeviceData, HomeDataDevice, HomeDataProduct, NetworkInfo
|
||||
from roborock.exceptions import RoborockException
|
||||
from roborock.roborock_message import RoborockDyadDataProtocol, RoborockZeoProtocol
|
||||
from roborock.roborock_typing import DeviceProp
|
||||
from roborock.version_1_apis.roborock_local_client_v1 import RoborockLocalClientV1
|
||||
from roborock.version_1_apis.roborock_mqtt_client_v1 import RoborockMqttClientV1
|
||||
from roborock.version_a01_apis import RoborockClientA01
|
||||
from roborock.web_api import RoborockApiClient
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_CONNECTIONS
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.typing import StateType
|
||||
@@ -76,8 +67,6 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
|
||||
product_info: HomeDataProduct,
|
||||
cloud_api: RoborockMqttClientV1,
|
||||
home_data_rooms: list[HomeDataRoom],
|
||||
api_client: RoborockApiClient,
|
||||
user_data: UserData,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(
|
||||
@@ -100,7 +89,7 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
|
||||
self.cloud_api = cloud_api
|
||||
self.device_info = DeviceInfo(
|
||||
name=self.roborock_device_info.device.name,
|
||||
identifiers={(DOMAIN, self.duid)},
|
||||
identifiers={(DOMAIN, self.roborock_device_info.device.duid)},
|
||||
manufacturer="Roborock",
|
||||
model=self.roborock_device_info.product.model,
|
||||
model_id=self.roborock_device_info.product.model,
|
||||
@@ -114,10 +103,8 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
|
||||
self.maps: dict[int, RoborockMapInfo] = {}
|
||||
self._home_data_rooms = {str(room.id): room.name for room in home_data_rooms}
|
||||
self.map_storage = RoborockMapStorage(
|
||||
hass, self.config_entry.entry_id, self.duid_slug
|
||||
hass, self.config_entry.entry_id, slugify(self.duid)
|
||||
)
|
||||
self._user_data = user_data
|
||||
self._api_client = api_client
|
||||
|
||||
async def _async_setup(self) -> None:
|
||||
"""Set up the coordinator."""
|
||||
@@ -147,7 +134,7 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
|
||||
except RoborockException:
|
||||
_LOGGER.warning(
|
||||
"Using the cloud API for device %s. This is not recommended as it can lead to rate limiting. We recommend making your vacuum accessible by your Home Assistant instance",
|
||||
self.duid,
|
||||
self.roborock_device_info.device.duid,
|
||||
)
|
||||
await self.api.async_disconnect()
|
||||
# We use the cloud api if the local api fails to connect.
|
||||
@@ -207,34 +194,6 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
|
||||
for room in room_mapping or ()
|
||||
}
|
||||
|
||||
async def get_scenes(self) -> list[HomeDataScene]:
|
||||
"""Get scenes."""
|
||||
try:
|
||||
return await self._api_client.get_scenes(self._user_data, self.duid)
|
||||
except RoborockException as err:
|
||||
_LOGGER.error("Failed to get scenes %s", err)
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="command_failed",
|
||||
translation_placeholders={
|
||||
"command": "get_scenes",
|
||||
},
|
||||
) from err
|
||||
|
||||
async def execute_scene(self, scene_id: int) -> None:
|
||||
"""Execute scene."""
|
||||
try:
|
||||
await self._api_client.execute_scene(self._user_data, scene_id)
|
||||
except RoborockException as err:
|
||||
_LOGGER.error("Failed to execute scene %s %s", scene_id, err)
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="command_failed",
|
||||
translation_placeholders={
|
||||
"command": "execute_scene",
|
||||
},
|
||||
) from err
|
||||
|
||||
@cached_property
|
||||
def duid(self) -> str:
|
||||
"""Get the unique id of the device as specified by Roborock."""
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
"""Support for Roborock scene."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.scene import Scene as SceneEntity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import RoborockConfigEntry
|
||||
from .coordinator import RoborockDataUpdateCoordinator
|
||||
from .entity import RoborockEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: RoborockConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up scene platform."""
|
||||
scene_lists = await asyncio.gather(
|
||||
*[coordinator.get_scenes() for coordinator in config_entry.runtime_data.v1],
|
||||
)
|
||||
async_add_entities(
|
||||
RoborockSceneEntity(
|
||||
coordinator,
|
||||
EntityDescription(
|
||||
key=str(scene.id),
|
||||
name=scene.name,
|
||||
),
|
||||
)
|
||||
for coordinator, scenes in zip(
|
||||
config_entry.runtime_data.v1, scene_lists, strict=True
|
||||
)
|
||||
for scene in scenes
|
||||
)
|
||||
|
||||
|
||||
class RoborockSceneEntity(RoborockEntity, SceneEntity):
|
||||
"""A class to define Roborock scene entities."""
|
||||
|
||||
entity_description: EntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: RoborockDataUpdateCoordinator,
|
||||
entity_description: EntityDescription,
|
||||
) -> None:
|
||||
"""Create a scene entity."""
|
||||
super().__init__(
|
||||
f"{entity_description.key}_{coordinator.duid_slug}",
|
||||
coordinator.device_info,
|
||||
coordinator.api,
|
||||
)
|
||||
self._scene_id = int(entity_description.key)
|
||||
self._coordinator = coordinator
|
||||
self.entity_description = entity_description
|
||||
|
||||
async def async_activate(self, **kwargs: Any) -> None:
|
||||
"""Activate the scene."""
|
||||
await self._coordinator.execute_scene(self._scene_id)
|
||||
@@ -174,11 +174,12 @@ def process_status(
|
||||
list[Capability | str],
|
||||
disabled_capabilities_capability[Attribute.DISABLED_CAPABILITIES].value,
|
||||
)
|
||||
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]
|
||||
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]
|
||||
return status
|
||||
|
||||
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2025
|
||||
MINOR_VERSION: Final = 3
|
||||
PATCH_VERSION: Final = "0b7"
|
||||
PATCH_VERSION: Final = "0"
|
||||
__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)
|
||||
|
||||
@@ -38,7 +38,7 @@ hass-nabucasa==0.92.0
|
||||
hassil==2.2.3
|
||||
home-assistant-bluetooth==1.13.1
|
||||
home-assistant-frontend==20250305.0
|
||||
home-assistant-intents==2025.2.26
|
||||
home-assistant-intents==2025.3.5
|
||||
httpx==0.28.1
|
||||
ifaddr==0.2.0
|
||||
Jinja2==3.1.5
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2025.3.0b7"
|
||||
version = "2025.3.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
|
||||
Generated
+4
-4
@@ -234,7 +234,7 @@ aioeafm==0.1.2
|
||||
aioeagle==1.1.0
|
||||
|
||||
# homeassistant.components.ecowitt
|
||||
aioecowitt==2024.2.1
|
||||
aioecowitt==2025.3.1
|
||||
|
||||
# homeassistant.components.co2signal
|
||||
aioelectricitymaps==0.4.0
|
||||
@@ -1155,7 +1155,7 @@ holidays==0.68
|
||||
home-assistant-frontend==20250305.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2025.2.26
|
||||
home-assistant-intents==2025.3.5
|
||||
|
||||
# homeassistant.components.homematicip_cloud
|
||||
homematicip==1.1.7
|
||||
@@ -1483,7 +1483,7 @@ nettigo-air-monitor==4.0.0
|
||||
neurio==0.3.1
|
||||
|
||||
# homeassistant.components.nexia
|
||||
nexia==2.1.1
|
||||
nexia==2.2.1
|
||||
|
||||
# homeassistant.components.nextcloud
|
||||
nextcloudmonitor==1.5.1
|
||||
@@ -1565,7 +1565,7 @@ omnilogic==0.4.5
|
||||
ondilo==0.5.0
|
||||
|
||||
# homeassistant.components.onedrive
|
||||
onedrive-personal-sdk==0.0.12
|
||||
onedrive-personal-sdk==0.0.13
|
||||
|
||||
# homeassistant.components.onvif
|
||||
onvif-zeep-async==3.2.5
|
||||
|
||||
Generated
+4
-4
@@ -222,7 +222,7 @@ aioeafm==0.1.2
|
||||
aioeagle==1.1.0
|
||||
|
||||
# homeassistant.components.ecowitt
|
||||
aioecowitt==2024.2.1
|
||||
aioecowitt==2025.3.1
|
||||
|
||||
# homeassistant.components.co2signal
|
||||
aioelectricitymaps==0.4.0
|
||||
@@ -984,7 +984,7 @@ holidays==0.68
|
||||
home-assistant-frontend==20250305.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2025.2.26
|
||||
home-assistant-intents==2025.3.5
|
||||
|
||||
# homeassistant.components.homematicip_cloud
|
||||
homematicip==1.1.7
|
||||
@@ -1246,7 +1246,7 @@ netmap==0.7.0.2
|
||||
nettigo-air-monitor==4.0.0
|
||||
|
||||
# homeassistant.components.nexia
|
||||
nexia==2.1.1
|
||||
nexia==2.2.1
|
||||
|
||||
# homeassistant.components.nextcloud
|
||||
nextcloudmonitor==1.5.1
|
||||
@@ -1313,7 +1313,7 @@ omnilogic==0.4.5
|
||||
ondilo==0.5.0
|
||||
|
||||
# homeassistant.components.onedrive
|
||||
onedrive-personal-sdk==0.0.12
|
||||
onedrive-personal-sdk==0.0.13
|
||||
|
||||
# homeassistant.components.onvif
|
||||
onvif-zeep-async==3.2.5
|
||||
|
||||
Generated
+1
-1
@@ -25,7 +25,7 @@ RUN --mount=from=ghcr.io/astral-sh/uv:0.6.1,source=/uv,target=/bin/uv \
|
||||
-c /usr/src/homeassistant/homeassistant/package_constraints.txt \
|
||||
-r /usr/src/homeassistant/requirements.txt \
|
||||
stdlib-list==0.10.0 pipdeptree==2.25.0 tqdm==4.67.1 ruff==0.9.7 \
|
||||
PyTurboJPEG==1.7.5 go2rtc-client==0.1.2 ha-ffmpeg==3.2.2 hassil==2.2.3 home-assistant-intents==2025.2.26 mutagen==1.47.0 pymicro-vad==1.0.1 pyspeex-noise==1.0.2
|
||||
PyTurboJPEG==1.7.5 go2rtc-client==0.1.2 ha-ffmpeg==3.2.2 hassil==2.2.3 home-assistant-intents==2025.3.5 mutagen==1.47.0 pymicro-vad==1.0.1 pyspeex-noise==1.0.2
|
||||
|
||||
LABEL "name"="hassfest"
|
||||
LABEL "maintainer"="Home Assistant <hello@home-assistant.io>"
|
||||
|
||||
@@ -180,7 +180,6 @@ EXCEPTIONS = {
|
||||
"PyMicroBot", # https://github.com/spycle/pyMicroBot/pull/3
|
||||
"PySwitchmate", # https://github.com/Danielhiversen/pySwitchmate/pull/16
|
||||
"PyXiaomiGateway", # https://github.com/Danielhiversen/PyXiaomiGateway/pull/201
|
||||
"aioecowitt", # https://github.com/home-assistant-libs/aioecowitt/pull/180
|
||||
"chacha20poly1305", # LGPL
|
||||
"commentjson", # https://github.com/vaidik/commentjson/pull/55
|
||||
"crownstone-cloud", # https://github.com/crownstone/crownstone-lib-python-cloud/pull/5
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.DRY: 'dry'>,
|
||||
]),
|
||||
'max_temp': 30,
|
||||
'min_temp': 18,
|
||||
'max_temp': 86,
|
||||
'min_temp': 64,
|
||||
'preset_modes': list([
|
||||
'air_clean',
|
||||
]),
|
||||
@@ -28,7 +28,7 @@
|
||||
'on',
|
||||
'off',
|
||||
]),
|
||||
'target_temp_step': 1,
|
||||
'target_temp_step': 2,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -62,7 +62,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_humidity': 40,
|
||||
'current_temperature': 25,
|
||||
'current_temperature': 77,
|
||||
'fan_mode': 'mid',
|
||||
'fan_modes': list([
|
||||
'low',
|
||||
@@ -75,8 +75,8 @@
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.DRY: 'dry'>,
|
||||
]),
|
||||
'max_temp': 30,
|
||||
'min_temp': 18,
|
||||
'max_temp': 86,
|
||||
'min_temp': 64,
|
||||
'preset_mode': None,
|
||||
'preset_modes': list([
|
||||
'air_clean',
|
||||
@@ -94,8 +94,8 @@
|
||||
]),
|
||||
'target_temp_high': None,
|
||||
'target_temp_low': None,
|
||||
'target_temp_step': 1,
|
||||
'temperature': 19,
|
||||
'target_temp_step': 2,
|
||||
'temperature': 66,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.test_air_conditioner',
|
||||
|
||||
@@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, patch
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.const import Platform, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
@@ -23,6 +23,7 @@ async def test_all_entities(
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test all entities."""
|
||||
hass.config.units.temperature_unit = UnitOfTemperature.FAHRENHEIT
|
||||
with patch("homeassistant.components.lg_thinq.PLATFORMS", [Platform.CLIMATE]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ from .mock_data import (
|
||||
MULTI_MAP_LIST,
|
||||
NETWORK_INFO,
|
||||
PROP,
|
||||
SCENES,
|
||||
USER_DATA,
|
||||
USER_EMAIL,
|
||||
)
|
||||
@@ -68,24 +67,8 @@ class A01Mock(RoborockMqttClientA01):
|
||||
return {prot: self.protocol_responses[prot] for prot in dyad_data_protocols}
|
||||
|
||||
|
||||
@pytest.fixture(name="bypass_api_client_fixture")
|
||||
def bypass_api_client_fixture() -> None:
|
||||
"""Skip calls to the API client."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.roborock.RoborockApiClient.get_home_data_v2",
|
||||
return_value=HOME_DATA,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.roborock.RoborockApiClient.get_scenes",
|
||||
return_value=SCENES,
|
||||
),
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(name="bypass_api_fixture")
|
||||
def bypass_api_fixture(bypass_api_client_fixture: Any) -> None:
|
||||
def bypass_api_fixture() -> None:
|
||||
"""Skip calls to the API."""
|
||||
with (
|
||||
patch("homeassistant.components.roborock.RoborockMqttClientV1.async_connect"),
|
||||
@@ -93,6 +76,10 @@ def bypass_api_fixture(bypass_api_client_fixture: Any) -> None:
|
||||
patch(
|
||||
"homeassistant.components.roborock.coordinator.RoborockMqttClientV1._send_command"
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.roborock.RoborockApiClient.get_home_data_v2",
|
||||
return_value=HOME_DATA,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.roborock.RoborockMqttClientV1.get_networking",
|
||||
return_value=NETWORK_INFO,
|
||||
|
||||
@@ -9,7 +9,6 @@ from roborock.containers import (
|
||||
Consumable,
|
||||
DnDTimer,
|
||||
HomeData,
|
||||
HomeDataScene,
|
||||
MultiMapsList,
|
||||
NetworkInfo,
|
||||
S7Status,
|
||||
@@ -1151,19 +1150,3 @@ MAP_DATA = MapData(0, 0)
|
||||
MAP_DATA.image = ImageData(
|
||||
100, 10, 10, 10, 10, ImageConfig(), Image.new("RGB", (1, 1)), lambda p: p
|
||||
)
|
||||
|
||||
|
||||
SCENES = [
|
||||
HomeDataScene.from_dict(
|
||||
{
|
||||
"name": "sc1",
|
||||
"id": 12,
|
||||
},
|
||||
),
|
||||
HomeDataScene.from_dict(
|
||||
{
|
||||
"name": "sc2",
|
||||
"id": 24,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
"""Test Roborock Scene platform."""
|
||||
|
||||
from unittest.mock import ANY, patch
|
||||
|
||||
import pytest
|
||||
from roborock import RoborockException
|
||||
|
||||
from homeassistant.const import SERVICE_TURN_ON, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bypass_api_client_get_scenes_fixture(bypass_api_fixture) -> None:
|
||||
"""Fixture to raise when getting scenes."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.roborock.RoborockApiClient.get_scenes",
|
||||
side_effect=RoborockException(),
|
||||
),
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id"),
|
||||
[
|
||||
("scene.roborock_s7_maxv_sc1"),
|
||||
("scene.roborock_s7_maxv_sc2"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_get_scenes_failure(
|
||||
hass: HomeAssistant,
|
||||
bypass_api_client_get_scenes_fixture,
|
||||
setup_entry: MockConfigEntry,
|
||||
entity_id: str,
|
||||
) -> None:
|
||||
"""Test that if scene retrieval fails, no entity is being created."""
|
||||
# Ensure that the entity does not exist
|
||||
assert hass.states.get(entity_id) is None
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> list[Platform]:
|
||||
"""Fixture to set platforms used in the test."""
|
||||
return [Platform.SCENE]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "scene_id"),
|
||||
[
|
||||
("scene.roborock_s7_maxv_sc1", 12),
|
||||
("scene.roborock_s7_maxv_sc2", 24),
|
||||
],
|
||||
)
|
||||
@pytest.mark.freeze_time("2023-10-30 08:50:00")
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_execute_success(
|
||||
hass: HomeAssistant,
|
||||
bypass_api_fixture,
|
||||
setup_entry: MockConfigEntry,
|
||||
entity_id: str,
|
||||
scene_id: int,
|
||||
) -> None:
|
||||
"""Test activating the scene entities."""
|
||||
with patch(
|
||||
"homeassistant.components.roborock.RoborockApiClient.execute_scene"
|
||||
) as mock_execute_scene:
|
||||
await hass.services.async_call(
|
||||
"scene",
|
||||
SERVICE_TURN_ON,
|
||||
blocking=True,
|
||||
target={"entity_id": entity_id},
|
||||
)
|
||||
mock_execute_scene.assert_called_once_with(ANY, scene_id)
|
||||
assert hass.states.get(entity_id).state == "2023-10-30T08:50:00+00:00"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "scene_id"),
|
||||
[
|
||||
("scene.roborock_s7_maxv_sc1", 12),
|
||||
],
|
||||
)
|
||||
@pytest.mark.freeze_time("2023-10-30 08:50:00")
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_execute_failure(
|
||||
hass: HomeAssistant,
|
||||
bypass_api_fixture,
|
||||
setup_entry: MockConfigEntry,
|
||||
entity_id: str,
|
||||
scene_id: int,
|
||||
) -> None:
|
||||
"""Test failure while activating the scene entity."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.roborock.RoborockApiClient.execute_scene",
|
||||
side_effect=RoborockException,
|
||||
) as mock_execute_scene,
|
||||
pytest.raises(HomeAssistantError, match="Error while calling execute_scene"),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
"scene",
|
||||
SERVICE_TURN_ON,
|
||||
blocking=True,
|
||||
target={"entity_id": entity_id},
|
||||
)
|
||||
mock_execute_scene.assert_called_once_with(ANY, scene_id)
|
||||
assert hass.states.get(entity_id).state == "2023-10-30T08:50:00+00:00"
|
||||
Reference in New Issue
Block a user