Compare commits

..

10 Commits

Author SHA1 Message Date
Jan Čermák
09546525b6 fixup! Bump base image to 2026.02.0 with Python 3.14.3, use 3.14.3 in CI 2026-03-02 13:17:40 +01:00
Jan Čermák
37a59bd23c Sleep twice as suggested in PR 2026-03-02 12:29:29 +01:00
Jan Čermák
6793a98c08 Sleep 1e-99s in async_block_till_done to workaround unfinished tasks 2026-03-02 12:29:28 +01:00
Jan Čermák
0ee4589ba3 Revert "Try reverting changes from Python's gh-105836 patch"
This reverts commit 5de171f714.
2026-03-02 12:29:27 +01:00
Jan Čermák
55bd4b00b4 Try reverting changes from Python's gh-105836 patch 2026-03-02 12:29:27 +01:00
Jan Čermák
43f5f922e3 Bump base image to 2026.02.0 with Python 3.14.3, use 3.14.3 in CI
This also bumps libcec used in the base image to 7.1.1, full changelog:
* https://github.com/home-assistant/docker/releases/tag/2026.02.0

Python changelog:
* https://docs.python.org/release/3.14.3/whatsnew/changelog.html
2026-03-02 12:29:25 +01:00
Joost Lekkerkerker
5dd6dcc215 Add select for SmartThings Water spray level (#164520) 2026-03-02 12:17:31 +01:00
epenet
8bf894a514 Migrate microbees to runtime_data (#164564) 2026-03-02 12:04:34 +01:00
epenet
d3c67f2ae1 Migrate medcom_ble to runtime_data (#164557) 2026-03-02 12:03:35 +01:00
epenet
b60a282b60 Move motioneye coordinator to separate module (#164568) 2026-03-02 11:57:19 +01:00
37 changed files with 233 additions and 128 deletions

View File

@@ -10,12 +10,12 @@ on:
env:
BUILD_TYPE: core
DEFAULT_PYTHON: "3.14.2"
DEFAULT_PYTHON: "3.14.3"
PIP_TIMEOUT: 60
UV_HTTP_TIMEOUT: 60
UV_SYSTEM_PYTHON: "true"
# Base image version from https://github.com/home-assistant/docker
BASE_IMAGE_VERSION: "2026.01.0"
BASE_IMAGE_VERSION: "2026.02.0"
ARCHITECTURES: '["amd64", "aarch64"]'
permissions: {}

View File

@@ -41,8 +41,8 @@ env:
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 1
HA_SHORT_VERSION: "2026.4"
DEFAULT_PYTHON: "3.14.2"
ALL_PYTHON_VERSIONS: "['3.14.2']"
DEFAULT_PYTHON: "3.14.3"
ALL_PYTHON_VERSIONS: "['3.14.3']"
# 10.3 is the oldest supported version
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
# 10.6 is the current long-term-support

View File

@@ -16,7 +16,7 @@ concurrency:
cancel-in-progress: true
env:
DEFAULT_PYTHON: "3.14.2"
DEFAULT_PYTHON: "3.14.3"
jobs:
upload:

View File

@@ -17,7 +17,7 @@ on:
- "script/gen_requirements_all.py"
env:
DEFAULT_PYTHON: "3.14.2"
DEFAULT_PYTHON: "3.14.3"
permissions: {}

View File

@@ -3,19 +3,17 @@
from __future__ import annotations
from homeassistant.components import bluetooth
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN
from .coordinator import MedcomBleUpdateCoordinator
from .coordinator import MedcomBleConfigEntry, MedcomBleUpdateCoordinator
# Supported platforms
PLATFORMS: list[Platform] = [Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: MedcomBleConfigEntry) -> bool:
"""Set up Medcom BLE radiation monitor from a config entry."""
address = entry.unique_id
@@ -31,16 +29,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: MedcomBleConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -18,13 +18,17 @@ from .const import DEFAULT_SCAN_INTERVAL, DOMAIN
_LOGGER = logging.getLogger(__name__)
type MedcomBleConfigEntry = ConfigEntry[MedcomBleUpdateCoordinator]
class MedcomBleUpdateCoordinator(DataUpdateCoordinator[MedcomBleDevice]):
"""Coordinator for Medcom BLE radiation monitor data."""
config_entry: ConfigEntry
config_entry: MedcomBleConfigEntry
def __init__(self, hass: HomeAssistant, entry: ConfigEntry, address: str) -> None:
def __init__(
self, hass: HomeAssistant, entry: MedcomBleConfigEntry, address: str
) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,

View File

@@ -4,7 +4,6 @@ from __future__ import annotations
import logging
from homeassistant import config_entries
from homeassistant.components.sensor import (
SensorEntity,
SensorEntityDescription,
@@ -15,8 +14,8 @@ from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH, DeviceIn
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, UNIT_CPM
from .coordinator import MedcomBleUpdateCoordinator
from .const import UNIT_CPM
from .coordinator import MedcomBleConfigEntry, MedcomBleUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
@@ -32,12 +31,12 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
async def async_setup_entry(
hass: HomeAssistant,
entry: config_entries.ConfigEntry,
entry: MedcomBleConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Medcom BLE radiation monitor sensors."""
coordinator: MedcomBleUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator = entry.runtime_data
entities = []
_LOGGER.debug("got sensors: %s", coordinator.data.sensors)

View File

@@ -13,22 +13,25 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_entry_oauth2_flow
from .const import DOMAIN, PLATFORMS
from .const import PLATFORMS
from .coordinator import MicroBeesUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
type MicroBeesConfigEntry = ConfigEntry[HomeAssistantMicroBeesData]
@dataclass(frozen=True, kw_only=True)
class HomeAssistantMicroBeesData:
"""Microbees data stored in the Home Assistant data object."""
"""Microbees data stored in the config entry runtime_data."""
connector: MicroBees
coordinator: MicroBeesUpdateCoordinator
session: config_entry_oauth2_flow.OAuth2Session
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_migrate_entry(hass: HomeAssistant, entry: MicroBeesConfigEntry) -> bool:
"""Migrate entry."""
_LOGGER.debug("Migrating from version %s.%s", entry.version, entry.minor_version)
@@ -45,7 +48,7 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: MicroBeesConfigEntry) -> bool:
"""Set up microBees from a config entry."""
implementation = (
await config_entry_oauth2_flow.async_get_config_entry_implementation(
@@ -67,7 +70,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
microbees = MicroBees(token=session.token[CONF_ACCESS_TOKEN])
coordinator = MicroBeesUpdateCoordinator(hass, entry, microbees)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = HomeAssistantMicroBeesData(
entry.runtime_data = HomeAssistantMicroBeesData(
connector=microbees,
coordinator=coordinator,
session=session,
@@ -76,9 +79,6 @@ 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: MicroBeesConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -7,11 +7,10 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from . import MicroBeesConfigEntry
from .coordinator import MicroBeesUpdateCoordinator
from .entity import MicroBeesEntity
@@ -37,13 +36,11 @@ BINARYSENSOR_TYPES = {
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: MicroBeesConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the microBees binary sensor platform."""
coordinator: MicroBeesUpdateCoordinator = hass.data[DOMAIN][
entry.entry_id
].coordinator
coordinator = entry.runtime_data.coordinator
async_add_entities(
MBBinarySensor(coordinator, entity_description, bee_id, binary_sensor.id)
for bee_id, bee in coordinator.data.bees.items()

View File

@@ -3,11 +3,10 @@
from typing import Any
from homeassistant.components.button import ButtonEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from . import MicroBeesConfigEntry
from .coordinator import MicroBeesUpdateCoordinator
from .entity import MicroBeesActuatorEntity
@@ -16,13 +15,11 @@ BUTTON_TRANSLATIONS = {51: "button_gate", 91: "button_panic"}
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: MicroBeesConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the microBees button platform."""
coordinator: MicroBeesUpdateCoordinator = hass.data[DOMAIN][
entry.entry_id
].coordinator
coordinator = entry.runtime_data.coordinator
async_add_entities(
MBButton(coordinator, bee_id, button.id)
for bee_id, bee in coordinator.data.bees.items()

View File

@@ -7,13 +7,12 @@ from homeassistant.components.climate import (
ClimateEntityFeature,
HVACMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from . import MicroBeesConfigEntry
from .coordinator import MicroBeesUpdateCoordinator
from .entity import MicroBeesActuatorEntity
@@ -27,13 +26,11 @@ THERMOVALVE_SENSOR_ID = 782
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: MicroBeesConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the microBees climate platform."""
coordinator: MicroBeesUpdateCoordinator = hass.data[DOMAIN][
entry.entry_id
].coordinator
coordinator = entry.runtime_data.coordinator
async_add_entities(
MBClimate(
coordinator,

View File

@@ -1,19 +1,24 @@
"""The microBees Coordinator."""
from __future__ import annotations
import asyncio
from dataclasses import dataclass
from datetime import timedelta
from http import HTTPStatus
import logging
from typing import TYPE_CHECKING
import aiohttp
from microBeesPy import Actuator, Bee, MicroBees, MicroBeesException, Sensor
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
if TYPE_CHECKING:
from . import MicroBeesConfigEntry
_LOGGER = logging.getLogger(__name__)
@@ -29,10 +34,13 @@ class MicroBeesCoordinatorData:
class MicroBeesUpdateCoordinator(DataUpdateCoordinator[MicroBeesCoordinatorData]):
"""MicroBees coordinator."""
config_entry: ConfigEntry
config_entry: MicroBeesConfigEntry
def __init__(
self, hass: HomeAssistant, config_entry: ConfigEntry, microbees: MicroBees
self,
hass: HomeAssistant,
config_entry: MicroBeesConfigEntry,
microbees: MicroBees,
) -> None:
"""Initialize microBees coordinator."""
super().__init__(

View File

@@ -9,14 +9,12 @@ from homeassistant.components.cover import (
CoverEntity,
CoverEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.event import async_call_later
from .const import DOMAIN
from .coordinator import MicroBeesUpdateCoordinator
from . import MicroBeesConfigEntry
from .entity import MicroBeesEntity
COVER_IDS = {47: "roller_shutter"}
@@ -24,13 +22,11 @@ COVER_IDS = {47: "roller_shutter"}
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: MicroBeesConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the microBees cover platform."""
coordinator: MicroBeesUpdateCoordinator = hass.data[DOMAIN][
entry.entry_id
].coordinator
coordinator = entry.runtime_data.coordinator
async_add_entities(
MBCover(

View File

@@ -3,25 +3,22 @@
from typing import Any
from homeassistant.components.light import ATTR_RGBW_COLOR, ColorMode, LightEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from . import MicroBeesConfigEntry
from .coordinator import MicroBeesUpdateCoordinator
from .entity import MicroBeesActuatorEntity
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: MicroBeesConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Config entry."""
coordinator: MicroBeesUpdateCoordinator = hass.data[DOMAIN][
entry.entry_id
].coordinator
coordinator = entry.runtime_data.coordinator
async_add_entities(
MBLight(coordinator, bee_id, light.id)
for bee_id, bee in coordinator.data.bees.items()

View File

@@ -8,7 +8,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
@@ -19,7 +18,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from . import MicroBeesConfigEntry
from .coordinator import MicroBeesUpdateCoordinator
from .entity import MicroBeesEntity
@@ -64,11 +63,11 @@ SENSOR_TYPES = {
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: MicroBeesConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Config entry."""
coordinator = hass.data[DOMAIN][entry.entry_id].coordinator
coordinator = entry.runtime_data.coordinator
async_add_entities(
MBSensor(coordinator, desc, bee_id, sensor.id)

View File

@@ -3,12 +3,11 @@
from typing import Any
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from . import MicroBeesConfigEntry
from .coordinator import MicroBeesUpdateCoordinator
from .entity import MicroBeesActuatorEntity
@@ -18,11 +17,11 @@ SWITCH_PRODUCT_IDS = {25, 26, 27, 35, 38, 46, 63, 64, 65, 86}
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: MicroBeesConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Config entry."""
coordinator = hass.data[DOMAIN][entry.entry_id].coordinator
coordinator = entry.runtime_data.coordinator
async_add_entities(
MBSwitch(coordinator, bee_id, switch.id)

View File

@@ -56,7 +56,6 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_send,
)
from homeassistant.helpers.network import NoURLAvailableError, get_url
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import (
ATTR_EVENT_TYPE,
@@ -69,7 +68,6 @@ from .const import (
CONF_SURVEILLANCE_USERNAME,
CONF_WEBHOOK_SET,
CONF_WEBHOOK_SET_OVERWRITE,
DEFAULT_SCAN_INTERVAL,
DEFAULT_WEBHOOK_SET,
DEFAULT_WEBHOOK_SET_OVERWRITE,
DOMAIN,
@@ -84,6 +82,7 @@ from .const import (
WEB_HOOK_SENTINEL_KEY,
WEB_HOOK_SENTINEL_VALUE,
)
from .coordinator import MotionEyeUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [CAMERA_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN]
@@ -308,20 +307,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass, DOMAIN, "motionEye", entry.data[CONF_WEBHOOK_ID], handle_webhook
)
async def async_update_data() -> dict[str, Any] | None:
try:
return await client.async_get_cameras()
except MotionEyeClientError as exc:
raise UpdateFailed("Error communicating with API") from exc
coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
config_entry=entry,
name=DOMAIN,
update_method=async_update_data,
update_interval=DEFAULT_SCAN_INTERVAL,
)
coordinator = MotionEyeUpdateCoordinator(hass, entry, client)
hass.data[DOMAIN][entry.entry_id] = {
CONF_CLIENT: client,
CONF_COORDINATOR: coordinator,

View File

@@ -43,7 +43,6 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from . import get_camera_from_cameras, is_acceptable_camera, listen_for_new_cameras
from .const import (
@@ -60,6 +59,7 @@ from .const import (
SERVICE_SNAPSHOT,
TYPE_MOTIONEYE_MJPEG_CAMERA,
)
from .coordinator import MotionEyeUpdateCoordinator
from .entity import MotionEyeEntity
PLATFORMS = [Platform.CAMERA]
@@ -153,7 +153,7 @@ class MotionEyeMjpegCamera(MotionEyeEntity, MjpegCamera):
password: str,
camera: dict[str, Any],
client: MotionEyeClient,
coordinator: DataUpdateCoordinator,
coordinator: MotionEyeUpdateCoordinator,
options: Mapping[str, str],
) -> None:
"""Initialize a MJPEG camera."""

View File

@@ -0,0 +1,41 @@
"""Coordinator for the motionEye integration."""
from __future__ import annotations
import logging
from typing import Any
from motioneye_client.client import MotionEyeClient, MotionEyeClientError
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN
_LOGGER = logging.getLogger(__name__)
class MotionEyeUpdateCoordinator(DataUpdateCoordinator[dict[str, Any] | None]):
"""Coordinator for motionEye data."""
config_entry: ConfigEntry
def __init__(
self, hass: HomeAssistant, entry: ConfigEntry, client: MotionEyeClient
) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,
_LOGGER,
name=DOMAIN,
config_entry=entry,
update_interval=DEFAULT_SCAN_INTERVAL,
)
self.client = client
async def _async_update_data(self) -> dict[str, Any] | None:
try:
return await self.client.async_get_cameras()
except MotionEyeClientError as exc:
raise UpdateFailed("Error communicating with API") from exc

View File

@@ -10,12 +10,10 @@ from motioneye_client.const import KEY_ID
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import get_motioneye_device_identifier
from .coordinator import MotionEyeUpdateCoordinator
def get_motioneye_entity_unique_id(
@@ -25,7 +23,7 @@ def get_motioneye_entity_unique_id(
return f"{config_entry_id}_{camera_id}_{entity_type}"
class MotionEyeEntity(CoordinatorEntity):
class MotionEyeEntity(CoordinatorEntity[MotionEyeUpdateCoordinator]):
"""Base class for motionEye entities."""
_attr_has_entity_name = True
@@ -36,7 +34,7 @@ class MotionEyeEntity(CoordinatorEntity):
type_name: str,
camera: dict[str, Any],
client: MotionEyeClient,
coordinator: DataUpdateCoordinator,
coordinator: MotionEyeUpdateCoordinator,
options: Mapping[str, Any],
entity_description: EntityDescription | None = None,
) -> None:

View File

@@ -3,7 +3,6 @@
from __future__ import annotations
from collections.abc import Mapping
import logging
from typing import Any
from motioneye_client.client import MotionEyeClient
@@ -14,14 +13,12 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from . import get_camera_from_cameras, listen_for_new_cameras
from .const import CONF_CLIENT, CONF_COORDINATOR, DOMAIN, TYPE_MOTIONEYE_ACTION_SENSOR
from .coordinator import MotionEyeUpdateCoordinator
from .entity import MotionEyeEntity
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
@@ -59,7 +56,7 @@ class MotionEyeActionSensor(MotionEyeEntity, SensorEntity):
config_entry_id: str,
camera: dict[str, Any],
client: MotionEyeClient,
coordinator: DataUpdateCoordinator,
coordinator: MotionEyeUpdateCoordinator,
options: Mapping[str, str],
) -> None:
"""Initialize an action sensor."""

View File

@@ -20,10 +20,10 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from . import get_camera_from_cameras, listen_for_new_cameras
from .const import CONF_CLIENT, CONF_COORDINATOR, DOMAIN, TYPE_MOTIONEYE_SWITCH_BASE
from .coordinator import MotionEyeUpdateCoordinator
from .entity import MotionEyeEntity
MOTIONEYE_SWITCHES = [
@@ -102,7 +102,7 @@ class MotionEyeSwitch(MotionEyeEntity, SwitchEntity):
config_entry_id: str,
camera: dict[str, Any],
client: MotionEyeClient,
coordinator: DataUpdateCoordinator,
coordinator: MotionEyeUpdateCoordinator,
options: Mapping[str, str],
entity_description: SwitchEntityDescription,
) -> None:

View File

@@ -102,6 +102,9 @@
"robot_cleaner_driving_mode": {
"default": "mdi:car-cog"
},
"robot_cleaner_water_spray_level": {
"default": "mdi:spray-bottle"
},
"selected_zone": {
"state": {
"all": "mdi:card",

View File

@@ -43,6 +43,14 @@ WASHER_SOIL_LEVEL_TO_HA = {
"down": "down",
}
WATER_SPRAY_LEVEL_TO_HA = {
"high": "high",
"mediumHigh": "moderate_high",
"medium": "medium",
"mediumLow": "moderate_low",
"low": "low",
}
WASHER_SPIN_LEVEL_TO_HA = {
"none": "none",
"rinseHold": "rinse_hold",
@@ -202,6 +210,15 @@ CAPABILITIES_TO_SELECT: dict[Capability | str, SmartThingsSelectDescription] = {
options_map=WASHER_WATER_TEMPERATURE_TO_HA,
entity_category=EntityCategory.CONFIG,
),
Capability.SAMSUNG_CE_ROBOT_CLEANER_WATER_SPRAY_LEVEL: SmartThingsSelectDescription(
key=Capability.SAMSUNG_CE_ROBOT_CLEANER_WATER_SPRAY_LEVEL,
translation_key="robot_cleaner_water_spray_level",
options_attribute=Attribute.SUPPORTED_WATER_SPRAY_LEVELS,
status_attribute=Attribute.WATER_SPRAY_LEVEL,
command=Command.SET_WATER_SPRAY_LEVEL,
options_map=WATER_SPRAY_LEVEL_TO_HA,
entity_category=EntityCategory.CONFIG,
),
Capability.SAMSUNG_CE_ROBOT_CLEANER_DRIVING_MODE: SmartThingsSelectDescription(
key=Capability.SAMSUNG_CE_ROBOT_CLEANER_DRIVING_MODE,
translation_key="robot_cleaner_driving_mode",

View File

@@ -237,6 +237,16 @@
"walls_first": "Walls first"
}
},
"robot_cleaner_water_spray_level": {
"name": "Water level",
"state": {
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
"medium": "Medium",
"moderate_high": "Moderate high",
"moderate_low": "Moderate low"
}
},
"selected_zone": {
"name": "Selected zone",
"state": {

View File

@@ -139,10 +139,10 @@ async def async_setup_entry(
action_wrapper=_AlarmActionWrapper(
master_mode.dpcode, master_mode
),
changed_by_wrapper=_AlarmChangedByWrapper.find_dpcode( # type: ignore[arg-type]
changed_by_wrapper=_AlarmChangedByWrapper.find_dpcode(
device, DPCode.ALARM_MSG
),
state_wrapper=_AlarmStateWrapper( # type: ignore[arg-type]
state_wrapper=_AlarmStateWrapper(
master_mode.dpcode, master_mode
),
)

View File

@@ -177,7 +177,7 @@ class _HvacModeWrapper(DPCodeEnumWrapper):
return None
return TUYA_HVAC_TO_HA[raw]
def _convert_value_to_raw_value(
def _convert_value_to_raw_value( # type: ignore[override]
self,
device: CustomerDevice,
value: HVACMode,
@@ -358,7 +358,7 @@ async def async_setup_entry(
device,
manager,
CLIMATE_DESCRIPTIONS[device.category],
current_humidity_wrapper=_RoundedIntegerWrapper.find_dpcode( # type: ignore[arg-type]
current_humidity_wrapper=_RoundedIntegerWrapper.find_dpcode(
device, DPCode.HUMIDITY_CURRENT
),
current_temperature_wrapper=temperature_wrappers[0],
@@ -367,7 +367,7 @@ async def async_setup_entry(
(DPCode.FAN_SPEED_ENUM, DPCode.LEVEL, DPCode.WINDSPEED),
prefer_function=True,
),
hvac_mode_wrapper=_HvacModeWrapper.find_dpcode( # type: ignore[arg-type]
hvac_mode_wrapper=_HvacModeWrapper.find_dpcode(
device, DPCode.MODE, prefer_function=True
),
preset_wrapper=_PresetWrapper.find_dpcode(
@@ -378,7 +378,7 @@ async def async_setup_entry(
switch_wrapper=DPCodeBooleanWrapper.find_dpcode(
device, DPCode.SWITCH, prefer_function=True
),
target_humidity_wrapper=_RoundedIntegerWrapper.find_dpcode( # type: ignore[arg-type]
target_humidity_wrapper=_RoundedIntegerWrapper.find_dpcode(
device, DPCode.HUMIDITY_SET, prefer_function=True
),
temperature_unit=temperature_wrappers[2],

View File

@@ -87,7 +87,7 @@ class _InstructionBooleanWrapper(DPCodeBooleanWrapper):
options = ["open", "close"]
_ACTION_MAPPINGS = {"open": True, "close": False}
def _convert_value_to_raw_value(self, device: CustomerDevice, value: str) -> bool:
def _convert_value_to_raw_value(self, device: CustomerDevice, value: str) -> bool: # type: ignore[override]
return self._ACTION_MAPPINGS[value]
@@ -291,19 +291,19 @@ async def async_setup_entry(
device,
manager,
description,
current_position=description.position_wrapper.find_dpcode( # type: ignore[arg-type]
current_position=description.position_wrapper.find_dpcode(
device, description.current_position
),
current_state_wrapper=description.current_state_wrapper.find_dpcode( # type: ignore[arg-type]
current_state_wrapper=description.current_state_wrapper.find_dpcode(
device, description.current_state
),
instruction_wrapper=_get_instruction_wrapper(
device, description
),
set_position=description.position_wrapper.find_dpcode( # type: ignore[arg-type]
set_position=description.position_wrapper.find_dpcode(
device, description.set_position, prefer_function=True
),
tilt_position=description.position_wrapper.find_dpcode( # type: ignore[arg-type]
tilt_position=description.position_wrapper.find_dpcode(
device,
(DPCode.ANGLE_HORIZONTAL, DPCode.ANGLE_VERTICAL),
prefer_function=True,

View File

@@ -49,7 +49,7 @@ class _AlarmMessageWrapper(DPCodeStringWrapper):
super().__init__(dpcode, type_information)
self.options = ["triggered"]
def read_device_status( # type: ignore[override]
def read_device_status(
self, device: CustomerDevice
) -> tuple[str, dict[str, Any]] | None:
"""Return the event attributes for the alarm message."""

View File

@@ -154,7 +154,7 @@ async def async_setup_entry(
oscillate_wrapper=DPCodeBooleanWrapper.find_dpcode(
device, _OSCILLATE_DPCODES, prefer_function=True
),
speed_wrapper=_get_speed_wrapper(device), # type: ignore[arg-type]
speed_wrapper=_get_speed_wrapper(device),
switch_wrapper=DPCodeBooleanWrapper.find_dpcode(
device, _SWITCH_DPCODES, prefer_function=True
),

View File

@@ -104,7 +104,7 @@ async def async_setup_entry(
device,
manager,
description,
current_humidity_wrapper=_RoundedIntegerWrapper.find_dpcode( # type: ignore[arg-type]
current_humidity_wrapper=_RoundedIntegerWrapper.find_dpcode(
device, description.current_humidity
),
mode_wrapper=DPCodeEnumWrapper.find_dpcode(
@@ -115,7 +115,7 @@ async def async_setup_entry(
description.dpcode or description.key,
prefer_function=True,
),
target_humidity_wrapper=_RoundedIntegerWrapper.find_dpcode( # type: ignore[arg-type]
target_humidity_wrapper=_RoundedIntegerWrapper.find_dpcode(
device, description.humidity, prefer_function=True
),
)

View File

@@ -633,17 +633,17 @@ async def async_setup_entry(
manager,
description,
brightness_wrapper=(
brightness_wrapper := _get_brightness_wrapper( # type: ignore[arg-type]
brightness_wrapper := _get_brightness_wrapper(
device, description
)
),
color_data_wrapper=_get_color_data_wrapper( # type: ignore[arg-type]
color_data_wrapper=_get_color_data_wrapper(
device, description, brightness_wrapper
),
color_mode_wrapper=DPCodeEnumWrapper.find_dpcode(
device, description.color_mode, prefer_function=True
),
color_temp_wrapper=_ColorTempWrapper.find_dpcode( # type: ignore[arg-type]
color_temp_wrapper=_ColorTempWrapper.find_dpcode(
device, description.color_temp, prefer_function=True
),
switch_wrapper=switch_wrapper,

View File

@@ -44,7 +44,7 @@
"iot_class": "cloud_push",
"loggers": ["tuya_sharing"],
"requirements": [
"tuya-device-handlers==0.0.11",
"tuya-device-handlers==0.0.10",
"tuya-device-sharing-sdk==0.2.8"
]
}

View File

@@ -961,7 +961,8 @@ class HomeAssistant:
async def async_block_till_done(self, wait_background_tasks: bool = False) -> None:
"""Block until all pending work is done."""
# To flush out any call_soon_threadsafe
# Sleep twice to flush out any call_soon_threadsafe
await asyncio.sleep(0)
await asyncio.sleep(0)
start_time: float | None = None
current_task = asyncio.current_task()

2
requirements_all.txt generated
View File

@@ -3121,7 +3121,7 @@ ttls==1.8.3
ttn_client==1.2.3
# homeassistant.components.tuya
tuya-device-handlers==0.0.11
tuya-device-handlers==0.0.10
# homeassistant.components.tuya
tuya-device-sharing-sdk==0.2.8

View File

@@ -2624,7 +2624,7 @@ ttls==1.8.3
ttn_client==1.2.3
# homeassistant.components.tuya
tuya-device-handlers==0.0.11
tuya-device-handlers==0.0.10
# homeassistant.components.tuya
tuya-device-sharing-sdk==0.2.8

View File

@@ -593,6 +593,70 @@
'state': 'medium',
})
# ---
# name: test_all_entities[da_rvc_map_01011][select.robot_vacuum_water_level-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'high',
'moderate_high',
'medium',
'moderate_low',
'low',
]),
}),
'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_water_level',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Water level',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Water level',
'platform': 'smartthings',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'robot_cleaner_water_spray_level',
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_samsungce.robotCleanerWaterSprayLevel_waterSprayLevel_waterSprayLevel',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[da_rvc_map_01011][select.robot_vacuum_water_level-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Robot Vacuum Water level',
'options': list([
'high',
'moderate_high',
'medium',
'moderate_low',
'low',
]),
}),
'context': <ANY>,
'entity_id': 'select.robot_vacuum_water_level',
'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({