Merge branch 'dev' into pglab

This commit is contained in:
pglab-electronics
2024-05-03 11:40:05 +02:00
committed by GitHub
117 changed files with 1814 additions and 798 deletions

View File

@@ -939,6 +939,7 @@ omit =
homeassistant/components/omnilogic/switch.py
homeassistant/components/ondilo_ico/__init__.py
homeassistant/components/ondilo_ico/api.py
homeassistant/components/ondilo_ico/coordinator.py
homeassistant/components/ondilo_ico/sensor.py
homeassistant/components/onkyo/media_player.py
homeassistant/components/onvif/__init__.py

View File

@@ -4,30 +4,35 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .hub import PulseHub
CONF_HUBS = "hubs"
PLATFORMS = [Platform.COVER, Platform.SENSOR]
AcmedaConfigEntry = ConfigEntry[PulseHub]
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, config_entry: AcmedaConfigEntry
) -> bool:
"""Set up Rollease Acmeda Automate hub from a config entry."""
hub = PulseHub(hass, config_entry)
if not await hub.async_setup():
return False
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = hub
config_entry.runtime_data = hub
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, config_entry: AcmedaConfigEntry
) -> bool:
"""Unload a config entry."""
hub = hass.data[DOMAIN][config_entry.entry_id]
hub = config_entry.runtime_data
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
@@ -36,7 +41,4 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
if not await hub.async_reset():
return False
if unload_ok:
hass.data[DOMAIN].pop(config_entry.entry_id)
return unload_ok

View File

@@ -9,24 +9,23 @@ from homeassistant.components.cover import (
CoverEntity,
CoverEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AcmedaConfigEntry
from .base import AcmedaBase
from .const import ACMEDA_HUB_UPDATE, DOMAIN
from .const import ACMEDA_HUB_UPDATE
from .helpers import async_add_acmeda_entities
from .hub import PulseHub
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AcmedaConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Acmeda Rollers from a config entry."""
hub: PulseHub = hass.data[DOMAIN][config_entry.entry_id]
hub = config_entry.runtime_data
current: set[int] = set()

View File

@@ -2,6 +2,8 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from aiopulse import Roller
from homeassistant.config_entries import ConfigEntry
@@ -11,17 +13,20 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN, LOGGER
if TYPE_CHECKING:
from . import AcmedaConfigEntry
@callback
def async_add_acmeda_entities(
hass: HomeAssistant,
entity_class: type,
config_entry: ConfigEntry,
config_entry: AcmedaConfigEntry,
current: set[int],
async_add_entities: AddEntitiesCallback,
) -> None:
"""Add any new entities."""
hub = hass.data[DOMAIN][config_entry.entry_id]
hub = config_entry.runtime_data
LOGGER.debug("Looking for new %s on: %s", entity_class.__name__, hub.host)
api = hub.api.rollers

View File

@@ -3,25 +3,24 @@
from __future__ import annotations
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AcmedaConfigEntry
from .base import AcmedaBase
from .const import ACMEDA_HUB_UPDATE, DOMAIN
from .const import ACMEDA_HUB_UPDATE
from .helpers import async_add_acmeda_entities
from .hub import PulseHub
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AcmedaConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Acmeda Rollers from a config entry."""
hub: PulseHub = hass.data[DOMAIN][config_entry.entry_id]
hub = config_entry.runtime_data
current: set[int] = set()

View File

@@ -10,16 +10,14 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
PLATFORMS: list[Platform] = [Platform.SENSOR]
AfterShipConfigEntry = ConfigEntry[AfterShip]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: AfterShipConfigEntry) -> bool:
"""Set up AfterShip from a config entry."""
hass.data.setdefault(DOMAIN, {})
session = async_get_clientsession(hass)
aftership = AfterShip(api_key=entry.data[CONF_API_KEY], session=session)
@@ -28,7 +26,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except AfterShipException as err:
raise ConfigEntryNotReady from err
hass.data[DOMAIN][entry.entry_id] = aftership
entry.runtime_data = aftership
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@@ -37,7 +35,4 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> 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

@@ -8,7 +8,6 @@ from typing import Any, Final
from pyaftership import AfterShip, AfterShipException
from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, ServiceCall
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
@@ -18,6 +17,7 @@ from homeassistant.helpers.dispatcher import (
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import Throttle
from . import AfterShipConfigEntry
from .const import (
ADD_TRACKING_SERVICE_SCHEMA,
ATTR_TRACKINGS,
@@ -41,11 +41,11 @@ PLATFORM_SCHEMA: Final = cv.removed(DOMAIN, raise_if_present=False)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AfterShipConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AfterShip sensor entities based on a config entry."""
aftership: AfterShip = hass.data[DOMAIN][config_entry.entry_id]
aftership = config_entry.runtime_data
async_add_entities([AfterShipSensor(aftership, config_entry.title)], True)

View File

@@ -15,14 +15,16 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
from .const import DOMAIN # noqa: F401
from .coordinator import AirNowDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.SENSOR]
AirNowConfigEntry = ConfigEntry[AirNowDataUpdateCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: AirNowConfigEntry) -> bool:
"""Set up AirNow from a config entry."""
api_key = entry.data[CONF_API_KEY]
latitude = entry.data[CONF_LATITUDE]
@@ -44,8 +46,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.async_config_entry_first_refresh()
# Store Entity and Initialize Platforms
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = coordinator
entry.runtime_data = coordinator
# Listen for option changes
entry.async_on_unload(entry.add_update_listener(update_listener))
@@ -87,14 +88,9 @@ async def async_migrate_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: AirNowConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:

View File

@@ -5,7 +5,6 @@ from __future__ import annotations
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_API_KEY,
CONF_LATITUDE,
@@ -14,8 +13,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from . import AirNowDataUpdateCoordinator
from .const import DOMAIN
from . import AirNowConfigEntry
ATTR_LATITUDE_CAP = "Latitude"
ATTR_LONGITUDE_CAP = "Longitude"
@@ -40,10 +38,10 @@ TO_REDACT = {
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
hass: HomeAssistant, entry: AirNowConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator: AirNowDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator = entry.runtime_data
return async_redact_data(
{

View File

@@ -13,7 +13,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_TIME,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
@@ -26,7 +25,7 @@ from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.dt import get_time_zone
from . import AirNowDataUpdateCoordinator
from . import AirNowConfigEntry, AirNowDataUpdateCoordinator
from .const import (
ATTR_API_AQI,
ATTR_API_AQI_DESCRIPTION,
@@ -116,11 +115,11 @@ SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AirNowConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AirNow sensor entities based on a config entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
entities = [AirNowSensor(coordinator, description) for description in SENSOR_TYPES]

View File

@@ -6,13 +6,14 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import AirQCoordinator
PLATFORMS: list[Platform] = [Platform.SENSOR]
AirQConfigEntry = ConfigEntry[AirQCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: AirQConfigEntry) -> bool:
"""Set up air-Q from a config entry."""
coordinator = AirQCoordinator(hass, entry)
@@ -20,18 +21,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Query the device for the first time and initialise coordinator.data
await coordinator.async_config_entry_first_refresh()
# Record the coordinator in a global store
hass.data.setdefault(DOMAIN, {})
hass.data[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: AirQConfigEntry) -> 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

@@ -13,7 +13,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
@@ -28,11 +27,10 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import AirQCoordinator
from . import AirQConfigEntry, AirQCoordinator
from .const import (
ACTIVITY_BECQUEREL_PER_CUBIC_METER,
CONCENTRATION_GRAMS_PER_CUBIC_METER,
DOMAIN,
)
_LOGGER = logging.getLogger(__name__)
@@ -400,12 +398,12 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
async def async_setup_entry(
hass: HomeAssistant,
config: ConfigEntry,
entry: AirQConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up sensor entities based on a config entry."""
coordinator = hass.data[DOMAIN][config.entry_id]
coordinator = entry.runtime_data
entities: list[AirQSensor] = []

View File

@@ -22,11 +22,11 @@ SCAN_INTERVAL = timedelta(minutes=6)
AirthingsDataCoordinatorType = DataUpdateCoordinator[dict[str, AirthingsDevice]]
AirthingsConfigEntry = ConfigEntry[AirthingsDataCoordinatorType]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: AirthingsConfigEntry) -> bool:
"""Set up Airthings from a config entry."""
hass.data.setdefault(DOMAIN, {})
airthings = Airthings(
entry.data[CONF_ID],
entry.data[CONF_SECRET],
@@ -49,17 +49,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)
await coordinator.async_config_entry_first_refresh()
hass.data[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: AirthingsConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -10,7 +10,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
@@ -27,7 +26,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import AirthingsDataCoordinatorType
from . import AirthingsConfigEntry, AirthingsDataCoordinatorType
from .const import DOMAIN
SENSORS: dict[str, SensorEntityDescription] = {
@@ -102,12 +101,12 @@ SENSORS: dict[str, SensorEntityDescription] = {
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: AirthingsConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Airthings sensor."""
coordinator: AirthingsDataCoordinatorType = hass.data[DOMAIN][entry.entry_id]
coordinator = entry.runtime_data
entities = [
AirthingsHeaterEnergySensor(
coordinator,

View File

@@ -22,8 +22,13 @@ PLATFORMS: list[Platform] = [Platform.SENSOR]
_LOGGER = logging.getLogger(__name__)
AirthingsBLEDataUpdateCoordinator = DataUpdateCoordinator[AirthingsDevice]
AirthingsBLEConfigEntry = ConfigEntry[AirthingsBLEDataUpdateCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, entry: AirthingsBLEConfigEntry
) -> bool:
"""Set up Airthings BLE device from a config entry."""
hass.data.setdefault(DOMAIN, {})
address = entry.unique_id
@@ -51,7 +56,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return data
coordinator = DataUpdateCoordinator(
coordinator: AirthingsBLEDataUpdateCoordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name=DOMAIN,
@@ -61,16 +66,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await coordinator.async_config_entry_first_refresh()
hass.data[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: AirthingsBLEConfigEntry
) -> 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

@@ -24,5 +24,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/airthings_ble",
"iot_class": "local_polling",
"requirements": ["airthings-ble==0.7.1"]
"requirements": ["airthings-ble==0.8.0"]
}

View File

@@ -13,7 +13,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
@@ -36,12 +35,10 @@ from homeassistant.helpers.entity_registry import (
async_get as entity_async_get,
)
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.unit_system import METRIC_SYSTEM
from . import AirthingsBLEConfigEntry, AirthingsBLEDataUpdateCoordinator
from .const import DOMAIN, VOLUME_BECQUEREL, VOLUME_PICOCURIE
_LOGGER = logging.getLogger(__name__)
@@ -152,15 +149,13 @@ def async_migrate(hass: HomeAssistant, address: str, sensor_name: str) -> None:
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: AirthingsBLEConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Airthings BLE sensors."""
is_metric = hass.config.units is METRIC_SYSTEM
coordinator: DataUpdateCoordinator[AirthingsDevice] = hass.data[DOMAIN][
entry.entry_id
]
coordinator = entry.runtime_data
# we need to change some units
sensors_mapping = SENSORS_MAPPING_TEMPLATE.copy()
@@ -193,7 +188,7 @@ async def async_setup_entry(
class AirthingsSensor(
CoordinatorEntity[DataUpdateCoordinator[AirthingsDevice]], SensorEntity
CoordinatorEntity[AirthingsBLEDataUpdateCoordinator], SensorEntity
):
"""Airthings BLE sensors for the device."""
@@ -201,7 +196,7 @@ class AirthingsSensor(
def __init__(
self,
coordinator: DataUpdateCoordinator[AirthingsDevice],
coordinator: AirthingsBLEDataUpdateCoordinator,
airthings_device: AirthingsDevice,
entity_description: SensorEntityDescription,
) -> None:

View File

@@ -13,8 +13,10 @@ from .const import DOMAIN
PLATFORMS: list[Platform] = [Platform.CLIMATE]
Airtouch5ConfigEntry = ConfigEntry[Airtouch5SimpleClient]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: Airtouch5ConfigEntry) -> bool:
"""Set up Airtouch 5 from a config entry."""
hass.data.setdefault(DOMAIN, {})
@@ -30,22 +32,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
raise ConfigEntryNotReady from t
# Store an API object for your platforms to access
hass.data[DOMAIN][entry.entry_id] = client
entry.runtime_data = client
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: Airtouch5ConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
client: Airtouch5SimpleClient = hass.data[DOMAIN][entry.entry_id]
client = entry.runtime_data
await client.disconnect()
client.ac_status_callbacks.clear()
client.connection_state_callbacks.clear()
client.data_packet_callbacks.clear()
client.zone_status_callbacks.clear()
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@@ -34,12 +34,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, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import Airtouch5ConfigEntry
from .const import DOMAIN, FAN_INTELLIGENT_AUTO, FAN_TURBO
from .entity import Airtouch5Entity
@@ -92,11 +92,11 @@ FAN_MODE_TO_SET_AC_FAN_SPEED = {
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: Airtouch5ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Airtouch 5 Climate entities."""
client: Airtouch5SimpleClient = hass.data[DOMAIN][config_entry.entry_id]
client = config_entry.runtime_data
entities: list[ClimateEntity] = []

View File

@@ -38,6 +38,8 @@ PLATFORMS = [Platform.SENSOR]
UPDATE_INTERVAL = timedelta(minutes=1)
AirVisualProConfigEntry = ConfigEntry["AirVisualProData"]
@dataclass
class AirVisualProData:
@@ -47,7 +49,9 @@ class AirVisualProData:
node: NodeSamba
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, entry: AirVisualProConfigEntry
) -> bool:
"""Set up AirVisual Pro from a config entry."""
node = NodeSamba(entry.data[CONF_IP_ADDRESS], entry.data[CONF_PASSWORD])
@@ -89,9 +93,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = AirVisualProData(
coordinator=coordinator, node=node
)
entry.runtime_data = AirVisualProData(coordinator=coordinator, node=node)
async def async_shutdown(_: Event) -> None:
"""Define an event handler to disconnect from the websocket."""
@@ -110,11 +112,12 @@ 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: AirVisualProConfigEntry
) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
data = hass.data[DOMAIN].pop(entry.entry_id)
await data.node.async_disconnect()
await entry.runtime_data.node.async_disconnect()
return unload_ok

View File

@@ -5,12 +5,10 @@ from __future__ import annotations
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD
from homeassistant.core import HomeAssistant
from . import AirVisualProData
from .const import DOMAIN
from . import AirVisualProConfigEntry
CONF_MAC_ADDRESS = "mac_address"
CONF_SERIAL_NUMBER = "serial_number"
@@ -23,15 +21,13 @@ TO_REDACT = {
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
hass: HomeAssistant, entry: AirVisualProConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
data: AirVisualProData = hass.data[DOMAIN][entry.entry_id]
return async_redact_data(
{
"entry": entry.as_dict(),
"data": data.coordinator.data,
"data": entry.runtime_data.coordinator.data,
},
TO_REDACT,
)

View File

@@ -12,7 +12,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
@@ -23,8 +22,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AirVisualProData, AirVisualProEntity
from .const import DOMAIN
from . import AirVisualProConfigEntry, AirVisualProEntity
@dataclass(frozen=True, kw_only=True)
@@ -129,13 +127,13 @@ def async_get_aqi_locale(settings: dict[str, Any]) -> str:
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant,
entry: AirVisualProConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up AirVisual sensors based on a config entry."""
data: AirVisualProData = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
AirVisualProSensor(data.coordinator, description)
AirVisualProSensor(entry.runtime_data.coordinator, description)
for description in SENSOR_DESCRIPTIONS
)

View File

@@ -39,6 +39,8 @@ DEFAULT_SOCKET_MIN_RETRY = 15
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
AmbientStationConfigEntry = ConfigEntry["AmbientStation"]
@callback
def async_wm2_to_lx(value: float) -> int:
@@ -55,7 +57,9 @@ def async_hydrate_station_data(data: dict[str, Any]) -> dict[str, Any]:
return data
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, entry: AmbientStationConfigEntry
) -> bool:
"""Set up the Ambient PWS as config entry."""
if not entry.unique_id:
hass.config_entries.async_update_entry(
@@ -74,7 +78,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
LOGGER.error("Config entry failed: %s", err)
raise ConfigEntryNotReady from err
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ambient
entry.runtime_data = ambient
async def _async_disconnect_websocket(_: Event) -> None:
await ambient.websocket.disconnect()
@@ -88,12 +92,13 @@ 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: AmbientStationConfigEntry
) -> bool:
"""Unload an Ambient PWS config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
ambient = hass.data[DOMAIN].pop(entry.entry_id)
hass.async_create_task(ambient.ws_disconnect(), eager_start=True)
hass.async_create_task(entry.runtime_data.ws_disconnect(), eager_start=True)
return unload_ok

View File

@@ -10,12 +10,12 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_NAME, EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ATTR_LAST_DATA, DOMAIN
from . import AmbientStationConfigEntry
from .const import ATTR_LAST_DATA
from .entity import AmbientWeatherEntity
TYPE_BATT1 = "batt1"
@@ -379,10 +379,12 @@ BINARY_SENSOR_DESCRIPTIONS = (
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant,
entry: AmbientStationConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Ambient PWS binary sensors based on a config entry."""
ambient = hass.data[DOMAIN][entry.entry_id]
ambient = entry.runtime_data
async_add_entities(
AmbientWeatherBinarySensor(

View File

@@ -5,12 +5,11 @@ from __future__ import annotations
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, CONF_LOCATION, CONF_UNIQUE_ID
from homeassistant.core import HomeAssistant
from . import AmbientStation
from .const import CONF_APP_KEY, DOMAIN
from . import AmbientStationConfigEntry
from .const import CONF_APP_KEY
CONF_API_KEY_CAMEL = "apiKey"
CONF_APP_KEY_CAMEL = "appKey"
@@ -37,12 +36,10 @@ TO_REDACT = {
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
hass: HomeAssistant, entry: AmbientStationConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
ambient: AmbientStation = hass.data[DOMAIN][entry.entry_id]
return {
"entry": async_redact_data(entry.as_dict(), TO_REDACT),
"stations": async_redact_data(ambient.stations, TO_REDACT),
"stations": async_redact_data(entry.runtime_data.stations, TO_REDACT),
}

View File

@@ -10,7 +10,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_NAME,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
@@ -30,8 +29,8 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AmbientStation
from .const import ATTR_LAST_DATA, DOMAIN, TYPE_SOLARRADIATION, TYPE_SOLARRADIATION_LX
from . import AmbientStation, AmbientStationConfigEntry
from .const import ATTR_LAST_DATA, TYPE_SOLARRADIATION, TYPE_SOLARRADIATION_LX
from .entity import AmbientWeatherEntity
TYPE_24HOURRAININ = "24hourrainin"
@@ -661,10 +660,12 @@ SENSOR_DESCRIPTIONS = (
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant,
entry: AmbientStationConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Ambient PWS sensors based on a config entry."""
ambient = hass.data[DOMAIN][entry.entry_id]
ambient = entry.runtime_data
async_add_entities(
AmbientWeatherSensor(ambient, mac_address, station[ATTR_NAME], description)

View File

@@ -49,6 +49,8 @@ API_CACHED_ATTRS = {
}
YALEXS_BLE_DOMAIN = "yalexs_ble"
AugustConfigEntry = ConfigEntry["AugustData"]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up August from a config entry."""
@@ -66,22 +68,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
raise ConfigEntryNotReady from err
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: AugustConfigEntry) -> bool:
"""Unload a config entry."""
data: AugustData = hass.data[DOMAIN][entry.entry_id]
data.async_stop()
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
entry.runtime_data.async_stop()
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_setup_august(
hass: HomeAssistant, config_entry: ConfigEntry, august_gateway: AugustGateway
hass: HomeAssistant, config_entry: AugustConfigEntry, august_gateway: AugustGateway
) -> bool:
"""Set up the August component."""
@@ -95,10 +89,7 @@ async def async_setup_august(
await august_gateway.async_authenticate()
await august_gateway.async_refresh_access_token_if_needed()
hass.data.setdefault(DOMAIN, {})
data = hass.data[DOMAIN][config_entry.entry_id] = AugustData(
hass, config_entry, august_gateway
)
data = config_entry.runtime_data = AugustData(hass, config_entry, august_gateway)
await data.async_setup()
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
@@ -509,12 +500,12 @@ def _restore_live_attrs(
async def async_remove_config_entry_device(
hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry
hass: HomeAssistant, config_entry: AugustConfigEntry, device_entry: dr.DeviceEntry
) -> bool:
"""Remove august config entry from a device if its no longer present."""
data: AugustData = hass.data[DOMAIN][config_entry.entry_id]
return not any(
identifier
for identifier in device_entry.identifiers
if identifier[0] == DOMAIN and data.get_device(identifier[1])
if identifier[0] == DOMAIN
and config_entry.runtime_data.get_device(identifier[1])
)

View File

@@ -22,14 +22,13 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_call_later
from . import AugustData
from .const import ACTIVITY_UPDATE_INTERVAL, DOMAIN
from . import AugustConfigEntry, AugustData
from .const import ACTIVITY_UPDATE_INTERVAL
from .entity import AugustEntityMixin
_LOGGER = logging.getLogger(__name__)
@@ -154,11 +153,11 @@ SENSOR_TYPES_DOORBELL: tuple[AugustDoorbellBinarySensorEntityDescription, ...] =
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AugustConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the August binary sensors."""
data: AugustData = hass.data[DOMAIN][config_entry.entry_id]
data = config_entry.runtime_data
entities: list[BinarySensorEntity] = []
for door in data.locks:

View File

@@ -3,22 +3,20 @@
from yalexs.lock import Lock
from homeassistant.components.button import ButtonEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AugustData
from .const import DOMAIN
from . import AugustConfigEntry, AugustData
from .entity import AugustEntityMixin
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AugustConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up August lock wake buttons."""
data: AugustData = hass.data[DOMAIN][config_entry.entry_id]
data = config_entry.runtime_data
async_add_entities(AugustWakeLockButton(data, lock) for lock in data.locks)

View File

@@ -11,13 +11,12 @@ from yalexs.doorbell import ContentTokenExpired, Doorbell
from yalexs.util import update_doorbell_image_from_activity
from homeassistant.components.camera import Camera
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AugustData
from .const import DEFAULT_NAME, DEFAULT_TIMEOUT, DOMAIN
from . import AugustConfigEntry, AugustData
from .const import DEFAULT_NAME, DEFAULT_TIMEOUT
from .entity import AugustEntityMixin
_LOGGER = logging.getLogger(__name__)
@@ -25,11 +24,11 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AugustConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up August cameras."""
data: AugustData = hass.data[DOMAIN][config_entry.entry_id]
data = config_entry.runtime_data
# Create an aiohttp session instead of using the default one since the
# default one is likely to trigger august's WAF if another integration
# is also using Cloudflare

View File

@@ -7,11 +7,10 @@ from typing import Any
from yalexs.const import DEFAULT_BRAND
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from . import AugustData
from .const import CONF_BRAND, DOMAIN
from . import AugustConfigEntry
from .const import CONF_BRAND
TO_REDACT = {
"HouseID",
@@ -30,10 +29,10 @@ TO_REDACT = {
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
hass: HomeAssistant, entry: AugustConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
data: AugustData = hass.data[DOMAIN][entry.entry_id]
data = entry.runtime_data
return {
"locks": {

View File

@@ -12,15 +12,13 @@ from yalexs.lock import Lock, LockStatus
from yalexs.util import get_latest_activity, update_lock_detail_from_activity
from homeassistant.components.lock import ATTR_CHANGED_BY, LockEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_BATTERY_LEVEL
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
import homeassistant.util.dt as dt_util
from . import AugustData
from .const import DOMAIN
from . import AugustConfigEntry, AugustData
from .entity import AugustEntityMixin
_LOGGER = logging.getLogger(__name__)
@@ -30,11 +28,11 @@ LOCK_JAMMED_ERR = 531
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AugustConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up August locks."""
data: AugustData = hass.data[DOMAIN][config_entry.entry_id]
data = config_entry.runtime_data
async_add_entities(AugustLock(data, lock) for lock in data.locks)

View File

@@ -19,7 +19,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_ENTITY_PICTURE,
PERCENTAGE,
@@ -30,7 +29,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AugustData
from . import AugustConfigEntry, AugustData
from .const import (
ATTR_OPERATION_AUTORELOCK,
ATTR_OPERATION_KEYPAD,
@@ -95,11 +94,11 @@ SENSOR_TYPE_KEYPAD_BATTERY = AugustSensorEntityDescription[KeypadDetail](
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AugustConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the August sensors."""
data: AugustData = hass.data[DOMAIN][config_entry.entry_id]
data = config_entry.runtime_data
entities: list[SensorEntity] = []
migrate_unique_id_devices = []
operation_sensors = []

View File

@@ -51,8 +51,9 @@ from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.issue_registry import async_delete_issue
from homeassistant.loader import async_get_bluetooth
from . import models, passive_update_processor
from . import passive_update_processor
from .api import (
_get_manager,
async_address_present,
async_ble_device_from_address,
async_discovered_service_info,
@@ -76,7 +77,6 @@ from .const import (
CONF_ADAPTER,
CONF_DETAILS,
CONF_PASSIVE,
DATA_MANAGER,
DOMAIN,
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
LINUX_FIRMWARE_LOAD_FALLBACK_SECONDS,
@@ -230,10 +230,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
hass, integration_matcher, bluetooth_adapters, bluetooth_storage, slot_manager
)
set_manager(manager)
await storage_setup_task
await manager.async_setup()
hass.data[DATA_MANAGER] = models.MANAGER = manager
hass.async_create_background_task(
_async_start_adapter_discovery(hass, manager, bluetooth_adapters),
@@ -314,7 +312,7 @@ async def async_update_device(
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a config entry for a bluetooth scanner."""
manager: HomeAssistantBluetoothManager = hass.data[DATA_MANAGER]
manager = _get_manager(hass)
address = entry.unique_id
assert address is not None
adapter = await manager.async_get_adapter_from_address_or_recover(address)

View File

@@ -15,10 +15,12 @@ from habluetooth import (
BluetoothScannerDevice,
BluetoothScanningMode,
HaBleakScannerWrapper,
get_manager,
)
from home_assistant_bluetooth import BluetoothServiceInfoBleak
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback
from homeassistant.helpers.singleton import singleton
from .const import DATA_MANAGER
from .manager import HomeAssistantBluetoothManager
@@ -29,9 +31,10 @@ if TYPE_CHECKING:
from bleak.backends.device import BLEDevice
@singleton(DATA_MANAGER)
def _get_manager(hass: HomeAssistant) -> HomeAssistantBluetoothManager:
"""Get the bluetooth manager."""
return cast(HomeAssistantBluetoothManager, hass.data[DATA_MANAGER])
return cast(HomeAssistantBluetoothManager, get_manager())
@hass_callback
@@ -68,8 +71,6 @@ def async_discovered_service_info(
hass: HomeAssistant, connectable: bool = True
) -> Iterable[BluetoothServiceInfoBleak]:
"""Return the discovered devices list."""
if DATA_MANAGER not in hass.data:
return []
return _get_manager(hass).async_discovered_service_info(connectable)
@@ -78,8 +79,6 @@ def async_last_service_info(
hass: HomeAssistant, address: str, connectable: bool = True
) -> BluetoothServiceInfoBleak | None:
"""Return the last service info for an address."""
if DATA_MANAGER not in hass.data:
return None
return _get_manager(hass).async_last_service_info(address, connectable)
@@ -88,8 +87,6 @@ def async_ble_device_from_address(
hass: HomeAssistant, address: str, connectable: bool = True
) -> BLEDevice | None:
"""Return BLEDevice for an address if its present."""
if DATA_MANAGER not in hass.data:
return None
return _get_manager(hass).async_ble_device_from_address(address, connectable)
@@ -106,8 +103,6 @@ def async_address_present(
hass: HomeAssistant, address: str, connectable: bool = True
) -> bool:
"""Check if an address is present in the bluetooth device list."""
if DATA_MANAGER not in hass.data:
return False
return _get_manager(hass).async_address_present(address, connectable)

View File

@@ -14,6 +14,7 @@ from bluetooth_adapters import (
adapter_model,
get_adapters,
)
from habluetooth import get_manager
import voluptuous as vol
from homeassistant.components import onboarding
@@ -25,7 +26,6 @@ from homeassistant.helpers.schema_config_entry_flow import (
)
from homeassistant.helpers.typing import DiscoveryInfoType
from . import models
from .const import CONF_ADAPTER, CONF_DETAILS, CONF_PASSIVE, DOMAIN
from .util import adapter_title
@@ -185,4 +185,4 @@ class BluetoothConfigFlow(ConfigFlow, domain=DOMAIN):
@callback
def async_supports_options_flow(cls, config_entry: ConfigEntry) -> bool:
"""Return options flow support for this handler."""
return bool(models.MANAGER and models.MANAGER.supports_passive_scan)
return bool((manager := get_manager()) and manager.supports_passive_scan)

View File

@@ -20,6 +20,6 @@
"bluetooth-auto-recovery==1.4.2",
"bluetooth-data-tools==1.19.0",
"dbus-fast==2.21.1",
"habluetooth==2.8.0"
"habluetooth==2.8.1"
]
}

View File

@@ -4,17 +4,9 @@ from __future__ import annotations
from collections.abc import Callable
from enum import Enum
from typing import TYPE_CHECKING
from home_assistant_bluetooth import BluetoothServiceInfoBleak
if TYPE_CHECKING:
from .manager import HomeAssistantBluetoothManager
MANAGER: HomeAssistantBluetoothManager | None = None
BluetoothChange = Enum("BluetoothChange", "ADVERTISEMENT")
BluetoothCallback = Callable[[BluetoothServiceInfoBleak, BluetoothChange], None]
ProcessAdvertisementCallback = Callable[[BluetoothServiceInfoBleak], bool]

View File

@@ -35,8 +35,10 @@ _API_TIMEOUT = SLOW_UPDATE_WARNING - 1
_LOGGER = logging.getLogger(__name__)
BondConfigEntry = ConfigEntry[BondData]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: BondConfigEntry) -> bool:
"""Set up Bond from a config entry."""
host = entry.data[CONF_HOST]
token = entry.data[CONF_ACCESS_TOKEN]
@@ -70,7 +72,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
entry.async_on_unload(
hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, _async_stop_event)
)
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = BondData(hub, bpup_subs)
entry.runtime_data = BondData(hub, bpup_subs)
if not entry.unique_id:
hass.config_entries.async_update_entry(entry, unique_id=hub.bond_id)
@@ -97,11 +99,9 @@ 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: BondConfigEntry) -> 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)
@callback
@@ -118,10 +118,10 @@ def _async_remove_old_device_identifiers(
async def async_remove_config_entry_device(
hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry
hass: HomeAssistant, config_entry: BondConfigEntry, device_entry: dr.DeviceEntry
) -> bool:
"""Remove bond config entry from a device."""
data: BondData = hass.data[DOMAIN][config_entry.entry_id]
data = config_entry.runtime_data
hub = data.hub
for identifier in device_entry.identifiers:
if identifier[0] != DOMAIN or len(identifier) != 3:

View File

@@ -7,13 +7,11 @@ from dataclasses import dataclass
from bond_async import Action, BPUPSubscriptions
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from . import BondConfigEntry
from .entity import BondEntity
from .models import BondData
from .utils import BondDevice, BondHub
# The api requires a step size even though it does not
@@ -243,11 +241,11 @@ BUTTONS: tuple[BondButtonEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: BondConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Bond button devices."""
data: BondData = hass.data[DOMAIN][entry.entry_id]
data = entry.runtime_data
hub = data.hub
bpup_subs = data.bpup_subs
entities: list[BondButtonEntity] = []

View File

@@ -12,13 +12,11 @@ from homeassistant.components.cover import (
CoverEntity,
CoverEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from . import BondConfigEntry
from .entity import BondEntity
from .models import BondData
from .utils import BondDevice, BondHub
@@ -34,11 +32,11 @@ def _hass_to_bond_position(hass_position: int) -> int:
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: BondConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Bond cover devices."""
data: BondData = hass.data[DOMAIN][entry.entry_id]
data = entry.runtime_data
hub = data.hub
bpup_subs = data.bpup_subs

View File

@@ -5,20 +5,18 @@ from __future__ import annotations
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .models import BondData
from . import BondConfigEntry
TO_REDACT = {"access_token"}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
hass: HomeAssistant, entry: BondConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
data: BondData = hass.data[DOMAIN][entry.entry_id]
data = entry.runtime_data
hub = data.hub
return {
"entry": {

View File

@@ -16,7 +16,6 @@ from homeassistant.components.fan import (
FanEntity,
FanEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_platform
@@ -27,9 +26,9 @@ from homeassistant.util.percentage import (
)
from homeassistant.util.scaling import int_states_in_range
from .const import DOMAIN, SERVICE_SET_FAN_SPEED_TRACKED_STATE
from . import BondConfigEntry
from .const import SERVICE_SET_FAN_SPEED_TRACKED_STATE
from .entity import BondEntity
from .models import BondData
from .utils import BondDevice, BondHub
_LOGGER = logging.getLogger(__name__)
@@ -39,11 +38,11 @@ PRESET_MODE_BREEZE = "Breeze"
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: BondConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Bond fan devices."""
data: BondData = hass.data[DOMAIN][entry.entry_id]
data = entry.runtime_data
hub = data.hub
bpup_subs = data.bpup_subs
platform = entity_platform.async_get_current_platform()

View File

@@ -10,21 +10,19 @@ from bond_async import Action, BPUPSubscriptions, DeviceType
import voluptuous as vol
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import BondConfigEntry
from .const import (
ATTR_POWER_STATE,
DOMAIN,
SERVICE_SET_LIGHT_BRIGHTNESS_TRACKED_STATE,
SERVICE_SET_LIGHT_POWER_TRACKED_STATE,
)
from .entity import BondEntity
from .models import BondData
from .utils import BondDevice, BondHub
_LOGGER = logging.getLogger(__name__)
@@ -42,11 +40,11 @@ ENTITY_SERVICES = [
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: BondConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Bond light devices."""
data: BondData = hass.data[DOMAIN][entry.entry_id]
data = entry.runtime_data
hub = data.hub
bpup_subs = data.bpup_subs
platform = entity_platform.async_get_current_platform()

View File

@@ -9,24 +9,23 @@ from bond_async import Action, DeviceType
import voluptuous as vol
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 import config_validation as cv, entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ATTR_POWER_STATE, DOMAIN, SERVICE_SET_POWER_TRACKED_STATE
from . import BondConfigEntry
from .const import ATTR_POWER_STATE, SERVICE_SET_POWER_TRACKED_STATE
from .entity import BondEntity
from .models import BondData
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: BondConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Bond generic devices."""
data: BondData = hass.data[DOMAIN][entry.entry_id]
data = entry.runtime_data
hub = data.hub
bpup_subs = data.bpup_subs
platform = entity_platform.async_get_current_platform()

View File

@@ -9,13 +9,15 @@ from homeassistant.const import CONF_API_KEY, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
from .const import DOMAIN # noqa: F401
from .coordinator import CO2SignalCoordinator
PLATFORMS = [Platform.SENSOR]
CO2SignalConfigEntry = ConfigEntry[CO2SignalCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: CO2SignalConfigEntry) -> bool:
"""Set up CO2 Signal from a config entry."""
session = async_get_clientsession(hass)
coordinator = CO2SignalCoordinator(
@@ -23,11 +25,11 @@ 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: CO2SignalConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -6,21 +6,19 @@ from dataclasses import asdict
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import CO2SignalCoordinator
from . import CO2SignalConfigEntry
TO_REDACT = {CONF_API_KEY}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry
hass: HomeAssistant, config_entry: CO2SignalConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator: CO2SignalCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
return {
"config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT),

View File

@@ -12,13 +12,13 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import CO2SignalConfigEntry
from .const import ATTRIBUTION, DOMAIN
from .coordinator import CO2SignalCoordinator
@@ -53,10 +53,12 @@ SENSORS = (
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant,
entry: CO2SignalConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the CO2signal sensor."""
coordinator: CO2SignalCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator = entry.runtime_data
async_add_entities(
[CO2Sensor(coordinator, description) for description in SENSORS], False
)

View File

@@ -2,7 +2,7 @@
from __future__ import annotations
from homeassistant.components.notify import DOMAIN, NotifyEntity
from homeassistant.components.notify import DOMAIN, NotifyEntity, NotifyEntityFeature
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
@@ -33,12 +33,15 @@ class DemoNotifyEntity(NotifyEntity):
) -> None:
"""Initialize the Demo button entity."""
self._attr_unique_id = unique_id
self._attr_supported_features = NotifyEntityFeature.TITLE
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, unique_id)},
name=device_name,
)
async def async_send_message(self, message: str) -> None:
async def async_send_message(self, message: str, title: str | None = None) -> None:
"""Send a message to a user."""
event_notitifcation = {"message": message}
self.hass.bus.async_fire(EVENT_NOTIFY, event_notitifcation)
event_notification = {"message": message}
if title is not None:
event_notification["title"] = title
self.hass.bus.async_fire(EVENT_NOTIFY, event_notification)

View File

@@ -49,6 +49,7 @@ PLATFORMS = [
Platform.NOTIFY,
Platform.NUMBER,
Platform.SENSOR,
Platform.SWITCH,
Platform.WEATHER,
]

View File

@@ -10,7 +10,7 @@
},
"iot_class": "cloud_polling",
"loggers": ["pyecobee"],
"requirements": ["python-ecobee-api==0.2.17"],
"requirements": ["python-ecobee-api==0.2.18"],
"zeroconf": [
{
"type": "_ecobee._tcp.local."

View File

@@ -85,6 +85,6 @@ class EcobeeNotifyEntity(EcobeeBaseEntity, NotifyEntity):
f"{self.thermostat["identifier"]}_notify_{thermostat_index}"
)
def send_message(self, message: str) -> None:
def send_message(self, message: str, title: str | None = None) -> None:
"""Send a message."""
self.data.ecobee.send_message(self.thermostat_index, message)

View File

@@ -0,0 +1,90 @@
"""Support for using switch with ecobee thermostats."""
from __future__ import annotations
import logging
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 AddEntitiesCallback
from homeassistant.util import dt as dt_util
from . import EcobeeData
from .const import DOMAIN
from .entity import EcobeeBaseEntity
_LOGGER = logging.getLogger(__name__)
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the ecobee thermostat switch entity."""
data: EcobeeData = hass.data[DOMAIN]
async_add_entities(
(
EcobeeVentilator20MinSwitch(data, index)
for index, thermostat in enumerate(data.ecobee.thermostats)
if thermostat["settings"]["ventilatorType"] != "none"
),
True,
)
class EcobeeVentilator20MinSwitch(EcobeeBaseEntity, SwitchEntity):
"""A Switch class, representing 20 min timer for an ecobee thermostat with ventilator attached."""
_attr_has_entity_name = True
_attr_name = "Ventilator 20m Timer"
def __init__(
self,
data: EcobeeData,
thermostat_index: int,
) -> None:
"""Initialize ecobee ventilator platform."""
super().__init__(data, thermostat_index)
self._attr_unique_id = f"{self.base_unique_id}_ventilator_20m_timer"
self._attr_is_on = False
self.update_without_throttle = False
self._operating_timezone = dt_util.get_time_zone(
self.thermostat["location"]["timeZone"]
)
async def async_update(self) -> None:
"""Get the latest state from the thermostat."""
if self.update_without_throttle:
await self.data.update(no_throttle=True)
self.update_without_throttle = False
else:
await self.data.update()
ventilator_off_date_time = self.thermostat["settings"]["ventilatorOffDateTime"]
self._attr_is_on = ventilator_off_date_time and dt_util.parse_datetime(
ventilator_off_date_time, raise_on_error=True
).replace(tzinfo=self._operating_timezone) >= dt_util.now(
self._operating_timezone
)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Set ventilator 20 min timer on."""
await self.hass.async_add_executor_job(
self.data.ecobee.set_ventilator_timer, self.thermostat_index, True
)
self.update_without_throttle = True
async def async_turn_off(self, **kwargs: Any) -> None:
"""Set ventilator 20 min timer off."""
await self.hass.async_add_executor_job(
self.data.ecobee.set_ventilator_timer, self.thermostat_index, False
)
self.update_without_throttle = True

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/environment_canada",
"iot_class": "cloud_polling",
"loggers": ["env_canada"],
"requirements": ["env-canada==0.6.0"]
"requirements": ["env-canada==0.6.2"]
}

View File

@@ -3,7 +3,6 @@
from __future__ import annotations
import asyncio
from collections.abc import Callable
from datetime import datetime, timedelta
import logging
from typing import Final, TypeVar
@@ -42,28 +41,24 @@ PLATFORMS: Final = [Platform.SENSOR]
_FroniusCoordinatorT = TypeVar("_FroniusCoordinatorT", bound=FroniusCoordinatorBase)
FroniusConfigEntry = ConfigEntry["FroniusSolarNet"]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: FroniusConfigEntry) -> bool:
"""Set up fronius from a config entry."""
host = entry.data[CONF_HOST]
fronius = Fronius(async_get_clientsession(hass), host)
solar_net = FroniusSolarNet(hass, entry, fronius)
await solar_net.init_devices()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = solar_net
entry.runtime_data = solar_net
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: FroniusConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
solar_net = hass.data[DOMAIN].pop(entry.entry_id)
while solar_net.cleanup_callbacks:
solar_net.cleanup_callbacks.pop()()
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_remove_config_entry_device(
@@ -81,7 +76,6 @@ class FroniusSolarNet:
) -> None:
"""Initialize FroniusSolarNet class."""
self.hass = hass
self.cleanup_callbacks: list[Callable[[], None]] = []
self.config_entry = entry
self.coordinator_lock = asyncio.Lock()
self.fronius = fronius
@@ -151,7 +145,7 @@ class FroniusSolarNet:
)
# Setup periodic re-scan
self.cleanup_callbacks.append(
self.config_entry.async_on_unload(
async_track_time_interval(
self.hass,
self._init_devices_inverter,

View File

@@ -121,7 +121,7 @@ class FroniusCoordinatorBase(
async_add_entities(new_entities)
_add_entities_for_unregistered_descriptors()
self.solar_net.cleanup_callbacks.append(
self.solar_net.config_entry.async_on_unload(
self.async_add_listener(_add_entities_for_unregistered_descriptors)
)

View File

@@ -12,7 +12,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
PERCENTAGE,
POWER_VOLT_AMPERE_REACTIVE,
@@ -44,7 +43,7 @@ from .const import (
)
if TYPE_CHECKING:
from . import FroniusSolarNet
from . import FroniusConfigEntry
from .coordinator import (
FroniusCoordinatorBase,
FroniusInverterUpdateCoordinator,
@@ -60,11 +59,11 @@ ENERGY_VOLT_AMPERE_REACTIVE_HOUR: Final = "varh"
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: FroniusConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Fronius sensor entities based on a config entry."""
solar_net: FroniusSolarNet = hass.data[DOMAIN][config_entry.entry_id]
solar_net = config_entry.runtime_data
for inverter_coordinator in solar_net.inverter_coordinators:
inverter_coordinator.add_entities_for_seen_keys(

View File

@@ -8,7 +8,7 @@ from collections.abc import Callable, Collection, Mapping
import logging
from typing import Any
from homeassistant.const import ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, STATE_ON
from homeassistant.const import ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, STATE_OFF, STATE_ON
from homeassistant.core import (
CALLBACK_TYPE,
Event,
@@ -24,7 +24,7 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.event import async_track_state_change_event
from .const import ATTR_AUTO, ATTR_ORDER, DOMAIN, GROUP_ORDER, REG_KEY
from .registry import GroupIntegrationRegistry
from .registry import GroupIntegrationRegistry, SingleStateType
ENTITY_ID_FORMAT = DOMAIN + ".{}"
@@ -133,6 +133,7 @@ class Group(Entity):
_attr_should_poll = False
tracking: tuple[str, ...]
trackable: tuple[str, ...]
single_state_type_key: SingleStateType | None
def __init__(
self,
@@ -153,7 +154,7 @@ class Group(Entity):
self._attr_name = name
self._state: str | None = None
self._attr_icon = icon
self._set_tracked(entity_ids)
self._entity_ids = entity_ids
self._on_off: dict[str, bool] = {}
self._assumed: dict[str, bool] = {}
self._on_states: set[str] = set()
@@ -287,6 +288,7 @@ class Group(Entity):
if not entity_ids:
self.tracking = ()
self.trackable = ()
self.single_state_type_key = None
return
registry: GroupIntegrationRegistry = self.hass.data[REG_KEY]
@@ -294,16 +296,42 @@ class Group(Entity):
tracking: list[str] = []
trackable: list[str] = []
single_state_type_set: set[SingleStateType] = set()
for ent_id in entity_ids:
ent_id_lower = ent_id.lower()
domain = split_entity_id(ent_id_lower)[0]
tracking.append(ent_id_lower)
if domain not in excluded_domains:
trackable.append(ent_id_lower)
if domain in registry.state_group_mapping:
single_state_type_set.add(registry.state_group_mapping[domain])
elif domain == DOMAIN:
# If a group contains another group we check if that group
# has a specific single state type
if ent_id in registry.state_group_mapping:
single_state_type_set.add(registry.state_group_mapping[ent_id])
else:
single_state_type_set.add(SingleStateType(STATE_ON, STATE_OFF))
if len(single_state_type_set) == 1:
self.single_state_type_key = next(iter(single_state_type_set))
# To support groups with nested groups we store the state type
# per group entity_id if there is a single state type
registry.state_group_mapping[self.entity_id] = self.single_state_type_key
else:
self.single_state_type_key = None
self.async_on_remove(self._async_deregister)
self.trackable = tuple(trackable)
self.tracking = tuple(tracking)
@callback
def _async_deregister(self) -> None:
"""Deregister group entity from the registry."""
registry: GroupIntegrationRegistry = self.hass.data[REG_KEY]
if self.entity_id in registry.state_group_mapping:
registry.state_group_mapping.pop(self.entity_id)
@callback
def _async_start(self, _: HomeAssistant | None = None) -> None:
"""Start tracking members and write state."""
@@ -342,6 +370,7 @@ class Group(Entity):
async def async_added_to_hass(self) -> None:
"""Handle addition to Home Assistant."""
self._set_tracked(self._entity_ids)
self.async_on_remove(start.async_at_start(self.hass, self._async_start))
async def async_will_remove_from_hass(self) -> None:
@@ -430,12 +459,14 @@ class Group(Entity):
# have the same on state we use this state
# and its hass.data[REG_KEY].on_off_mapping to off
if num_on_states == 1:
on_state = list(self._on_states)[0]
on_state = next(iter(self._on_states))
# If we do not have an on state for any domains
# we use None (which will be STATE_UNKNOWN)
elif num_on_states == 0:
self._state = None
return
if self.single_state_type_key:
on_state = self.single_state_type_key.on_state
# If the entity domains have more than one
# on state, we use STATE_ON/STATE_OFF
else:
@@ -443,9 +474,10 @@ class Group(Entity):
group_is_on = self.mode(self._on_off.values())
if group_is_on:
self._state = on_state
elif self.single_state_type_key:
self._state = self.single_state_type_key.off_state
else:
registry: GroupIntegrationRegistry = self.hass.data[REG_KEY]
self._state = registry.on_off_mapping[on_state]
self._state = STATE_OFF
def async_get_component(hass: HomeAssistant) -> EntityComponent[Group]:

View File

@@ -1,8 +1,12 @@
"""Provide the functionality to group entities."""
"""Provide the functionality to group entities.
Legacy group support will not be extended for new domains.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Protocol
from dataclasses import dataclass
from typing import Protocol
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant, callback
@@ -12,9 +16,6 @@ from homeassistant.helpers.integration_platform import (
from .const import DOMAIN, REG_KEY
if TYPE_CHECKING:
from .entity import Group
async def async_setup(hass: HomeAssistant) -> None:
"""Set up the Group integration registry of integration platforms."""
@@ -43,6 +44,14 @@ def _process_group_platform(
platform.async_describe_on_off_states(hass, registry)
@dataclass(frozen=True, slots=True)
class SingleStateType:
"""Dataclass to store a single state type."""
on_state: str
off_state: str
class GroupIntegrationRegistry:
"""Class to hold a registry of integrations."""
@@ -53,8 +62,7 @@ class GroupIntegrationRegistry:
self.off_on_mapping: dict[str, str] = {STATE_OFF: STATE_ON}
self.on_states_by_domain: dict[str, set[str]] = {}
self.exclude_domains: set[str] = set()
self.state_group_mapping: dict[str, tuple[str, str]] = {}
self.group_entities: set[Group] = set()
self.state_group_mapping: dict[str, SingleStateType] = {}
@callback
def exclude_domain(self, domain: str) -> None:
@@ -65,12 +73,16 @@ class GroupIntegrationRegistry:
def on_off_states(
self, domain: str, on_states: set[str], default_on_state: str, off_state: str
) -> None:
"""Register on and off states for the current domain."""
"""Register on and off states for the current domain.
Legacy group support will not be extended for new domains.
"""
for on_state in on_states:
if on_state not in self.on_off_mapping:
self.on_off_mapping[on_state] = off_state
if len(on_states) == 1 and off_state not in self.off_on_mapping:
if off_state not in self.off_on_mapping:
self.off_on_mapping[off_state] = default_on_state
self.state_group_mapping[domain] = SingleStateType(default_on_state, off_state)
self.on_states_by_domain[domain] = on_states

View File

@@ -18,7 +18,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import CONF_STATION_ID
from .coordinator import ImgwPibDataUpdateCoordinator
PLATFORMS: list[Platform] = [Platform.SENSOR]
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
_LOGGER = logging.getLogger(__name__)

View File

@@ -0,0 +1,82 @@
"""IMGW-PIB binary sensor platform."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from imgw_pib.model import HydrologicalData
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import ImgwPibConfigEntry
from .coordinator import ImgwPibDataUpdateCoordinator
from .entity import ImgwPibEntity
PARALLEL_UPDATES = 1
@dataclass(frozen=True, kw_only=True)
class ImgwPibBinarySensorEntityDescription(BinarySensorEntityDescription):
"""IMGW-PIB sensor entity description."""
value: Callable[[HydrologicalData], bool | None]
BINARY_SENSOR_TYPES: tuple[ImgwPibBinarySensorEntityDescription, ...] = (
ImgwPibBinarySensorEntityDescription(
key="flood_warning",
translation_key="flood_warning",
device_class=BinarySensorDeviceClass.SAFETY,
value=lambda data: data.flood_warning,
),
ImgwPibBinarySensorEntityDescription(
key="flood_alarm",
translation_key="flood_alarm",
device_class=BinarySensorDeviceClass.SAFETY,
value=lambda data: data.flood_alarm,
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: ImgwPibConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Add a IMGW-PIB binary sensor entity from a config_entry."""
coordinator = entry.runtime_data.coordinator
async_add_entities(
ImgwPibBinarySensorEntity(coordinator, description)
for description in BINARY_SENSOR_TYPES
if getattr(coordinator.data, description.key) is not None
)
class ImgwPibBinarySensorEntity(ImgwPibEntity, BinarySensorEntity):
"""Define IMGW-PIB binary sensor entity."""
entity_description: ImgwPibBinarySensorEntityDescription
def __init__(
self,
coordinator: ImgwPibDataUpdateCoordinator,
description: ImgwPibBinarySensorEntityDescription,
) -> None:
"""Initialize."""
super().__init__(coordinator)
self._attr_unique_id = f"{coordinator.station_id}_{description.key}"
self.entity_description = description
@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
return self.entity_description.value(self.coordinator.data)

View File

@@ -0,0 +1,22 @@
"""Define the IMGW-PIB entity."""
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import ATTRIBUTION
from .coordinator import ImgwPibDataUpdateCoordinator
class ImgwPibEntity(CoordinatorEntity[ImgwPibDataUpdateCoordinator]):
"""Define IMGW-PIB entity."""
_attr_attribution = ATTRIBUTION
_attr_has_entity_name = True
def __init__(
self,
coordinator: ImgwPibDataUpdateCoordinator,
) -> None:
"""Initialize."""
super().__init__(coordinator)
self._attr_device_info = coordinator.device_info

View File

@@ -1,5 +1,19 @@
{
"entity": {
"binary_sensor": {
"flood_warning": {
"default": "mdi:check-circle",
"state": {
"on": "mdi:home-flood"
}
},
"flood_alarm": {
"default": "mdi:check-circle",
"state": {
"on": "mdi:home-flood"
}
}
},
"sensor": {
"water_level": {
"default": "mdi:waves"

View File

@@ -17,11 +17,10 @@ from homeassistant.const import UnitOfLength, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import ImgwPibConfigEntry
from .const import ATTRIBUTION
from .coordinator import ImgwPibDataUpdateCoordinator
from .entity import ImgwPibEntity
PARALLEL_UPDATES = 1
@@ -70,13 +69,9 @@ async def async_setup_entry(
)
class ImgwPibSensorEntity(
CoordinatorEntity[ImgwPibDataUpdateCoordinator], SensorEntity
):
class ImgwPibSensorEntity(ImgwPibEntity, SensorEntity):
"""Define IMGW-PIB sensor entity."""
_attr_attribution = ATTRIBUTION
_attr_has_entity_name = True
entity_description: ImgwPibSensorEntityDescription
def __init__(
@@ -88,7 +83,6 @@ class ImgwPibSensorEntity(
super().__init__(coordinator)
self._attr_unique_id = f"{coordinator.station_id}_{description.key}"
self._attr_device_info = coordinator.device_info
self.entity_description = description
@property

View File

@@ -17,6 +17,14 @@
}
},
"entity": {
"binary_sensor": {
"flood_alarm": {
"name": "Flood alarm"
},
"flood_warning": {
"name": "Flood warning"
}
},
"sensor": {
"water_level": {
"name": "Water level"

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
from homeassistant.components import persistent_notification
from homeassistant.components.notify import NotifyEntity
from homeassistant.components.notify import NotifyEntity, NotifyEntityFeature
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
@@ -25,6 +25,12 @@ async def async_setup_entry(
device_name="MyBox",
entity_name="Personal notifier",
),
DemoNotify(
unique_id="just_notify_me_title",
device_name="MyBox",
entity_name="Personal notifier with title",
supported_features=NotifyEntityFeature.TITLE,
),
]
)
@@ -40,15 +46,19 @@ class DemoNotify(NotifyEntity):
unique_id: str,
device_name: str,
entity_name: str | None,
supported_features: NotifyEntityFeature = NotifyEntityFeature(0),
) -> None:
"""Initialize the Demo button entity."""
self._attr_unique_id = unique_id
self._attr_supported_features = supported_features
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, unique_id)},
name=device_name,
)
self._attr_name = entity_name
async def async_send_message(self, message: str) -> None:
async def async_send_message(self, message: str, title: str | None = None) -> None:
"""Send out a persistent notification."""
persistent_notification.async_create(self.hass, message, "Demo notification")
persistent_notification.async_create(
self.hass, message, title or "Demo notification"
)

View File

@@ -108,6 +108,6 @@ class KNXNotify(KnxEntity, NotifyEntity):
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
self._attr_unique_id = str(self._device.remote_value.group_address)
async def async_send_message(self, message: str) -> None:
async def async_send_message(self, message: str, title: str | None = None) -> None:
"""Send a notification to knx bus."""
await self._device.set(message)

View File

@@ -13,16 +13,7 @@ import voluptuous as vol
from homeassistant import config as conf_util
from homeassistant.components import websocket_api
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_CLIENT_ID,
CONF_DISCOVERY,
CONF_PASSWORD,
CONF_PAYLOAD,
CONF_PORT,
CONF_PROTOCOL,
CONF_USERNAME,
SERVICE_RELOAD,
)
from homeassistant.const import CONF_DISCOVERY, CONF_PAYLOAD, SERVICE_RELOAD
from homeassistant.core import HassJob, HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import (
ConfigValidationError,
@@ -122,45 +113,6 @@ CONNECTION_SUCCESS = "connection_success"
CONNECTION_FAILED = "connection_failed"
CONNECTION_FAILED_RECOVERABLE = "connection_failed_recoverable"
CONFIG_ENTRY_CONFIG_KEYS = [
CONF_BIRTH_MESSAGE,
CONF_BROKER,
CONF_CERTIFICATE,
CONF_CLIENT_ID,
CONF_CLIENT_CERT,
CONF_CLIENT_KEY,
CONF_DISCOVERY,
CONF_DISCOVERY_PREFIX,
CONF_KEEPALIVE,
CONF_PASSWORD,
CONF_PORT,
CONF_PROTOCOL,
CONF_TLS_INSECURE,
CONF_TRANSPORT,
CONF_WS_PATH,
CONF_WS_HEADERS,
CONF_USERNAME,
CONF_WILL_MESSAGE,
]
REMOVED_OPTIONS = vol.All(
cv.removed(CONF_BIRTH_MESSAGE), # Removed in HA Core 2023.4
cv.removed(CONF_BROKER), # Removed in HA Core 2023.4
cv.removed(CONF_CERTIFICATE), # Removed in HA Core 2023.4
cv.removed(CONF_CLIENT_ID), # Removed in HA Core 2023.4
cv.removed(CONF_CLIENT_CERT), # Removed in HA Core 2023.4
cv.removed(CONF_CLIENT_KEY), # Removed in HA Core 2023.4
cv.removed(CONF_DISCOVERY), # Removed in HA Core 2022.3
cv.removed(CONF_DISCOVERY_PREFIX), # Removed in HA Core 2023.4
cv.removed(CONF_KEEPALIVE), # Removed in HA Core 2023.4
cv.removed(CONF_PASSWORD), # Removed in HA Core 2023.4
cv.removed(CONF_PORT), # Removed in HA Core 2023.4
cv.removed(CONF_PROTOCOL), # Removed in HA Core 2023.4
cv.removed(CONF_TLS_INSECURE), # Removed in HA Core 2023.4
cv.removed(CONF_USERNAME), # Removed in HA Core 2023.4
cv.removed(CONF_WILL_MESSAGE), # Removed in HA Core 2023.4
)
# We accept 2 schemes for configuring manual MQTT items
#
# Preferred style:
@@ -187,7 +139,6 @@ CONFIG_SCHEMA = vol.Schema(
DOMAIN: vol.All(
cv.ensure_list,
cv.remove_falsy,
[REMOVED_OPTIONS],
[CONFIG_SCHEMA_BASE],
)
},

View File

@@ -86,7 +86,6 @@ ABBREVIATIONS = {
"json_attr": "json_attributes",
"json_attr_t": "json_attributes_topic",
"json_attr_tpl": "json_attributes_template",
"lrst_t": "last_reset_topic",
"lrst_val_tpl": "last_reset_value_template",
"max": "max",
"min": "min",

View File

@@ -197,7 +197,6 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
entry: ConfigEntry | None
_hassio_discovery: dict[str, Any] | None = None
_reauth_config_entry: ConfigEntry | None = None
@staticmethod
@callback

View File

@@ -83,7 +83,7 @@ class MqttNotify(MqttEntity, NotifyEntity):
async def _subscribe_topics(self) -> None:
"""(Re)Subscribe to topics."""
async def async_send_message(self, message: str) -> None:
async def async_send_message(self, message: str, title: str | None = None) -> None:
"""Send a message."""
payload = self._command_template(message)
await self.async_publish(

View File

@@ -58,7 +58,6 @@ from .models import (
_LOGGER = logging.getLogger(__name__)
CONF_EXPIRE_AFTER = "expire_after"
CONF_LAST_RESET_TOPIC = "last_reset_topic"
CONF_LAST_RESET_VALUE_TEMPLATE = "last_reset_value_template"
CONF_SUGGESTED_DISPLAY_PRECISION = "suggested_display_precision"
@@ -101,17 +100,11 @@ def validate_sensor_state_class_config(config: ConfigType) -> ConfigType:
PLATFORM_SCHEMA_MODERN = vol.All(
# Deprecated in HA Core 2021.11.0 https://github.com/home-assistant/core/pull/54840
# Removed in HA Core 2023.6.0
cv.removed(CONF_LAST_RESET_TOPIC),
_PLATFORM_SCHEMA_BASE,
validate_sensor_state_class_config,
)
DISCOVERY_SCHEMA = vol.All(
# Deprecated in HA Core 2021.11.0 https://github.com/home-assistant/core/pull/54840
# Removed in HA Core 2023.6.0
cv.removed(CONF_LAST_RESET_TOPIC),
_PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA),
validate_sensor_state_class_config,
)

View File

@@ -3,6 +3,7 @@
from __future__ import annotations
from datetime import timedelta
from enum import IntFlag
from functools import cached_property, partial
import logging
from typing import Any, final, override
@@ -58,6 +59,12 @@ PLATFORM_SCHEMA = vol.Schema(
)
class NotifyEntityFeature(IntFlag):
"""Supported features of a notify entity."""
TITLE = 1
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the notify services."""
@@ -73,7 +80,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
component = hass.data[DOMAIN] = EntityComponent[NotifyEntity](_LOGGER, DOMAIN, hass)
component.async_register_entity_service(
SERVICE_SEND_MESSAGE,
{vol.Required(ATTR_MESSAGE): cv.string},
{
vol.Required(ATTR_MESSAGE): cv.string,
vol.Optional(ATTR_TITLE): cv.string,
},
"_async_send_message",
)
@@ -128,6 +138,7 @@ class NotifyEntity(RestoreEntity):
"""Representation of a notify entity."""
entity_description: NotifyEntityDescription
_attr_supported_features: NotifyEntityFeature = NotifyEntityFeature(0)
_attr_should_poll = False
_attr_device_class: None
_attr_state: None = None
@@ -162,10 +173,19 @@ class NotifyEntity(RestoreEntity):
self.async_write_ha_state()
await self.async_send_message(**kwargs)
def send_message(self, message: str) -> None:
def send_message(self, message: str, title: str | None = None) -> None:
"""Send a message."""
raise NotImplementedError
async def async_send_message(self, message: str) -> None:
async def async_send_message(self, message: str, title: str | None = None) -> None:
"""Send a message."""
await self.hass.async_add_executor_job(partial(self.send_message, message))
kwargs: dict[str, Any] = {}
if (
title is not None
and self.supported_features
and self.supported_features & NotifyEntityFeature.TITLE
):
kwargs[ATTR_TITLE] = title
await self.hass.async_add_executor_job(
partial(self.send_message, message, **kwargs)
)

View File

@@ -29,6 +29,13 @@ send_message:
required: true
selector:
text:
title:
required: false
selector:
text:
filter:
supported_features:
- notify.NotifyEntityFeature.TITLE
persistent_notification:
fields:

View File

@@ -35,6 +35,10 @@
"message": {
"name": "Message",
"description": "Your notification message."
},
"title": {
"name": "Title",
"description": "Title for your notification message."
}
}
},

View File

@@ -7,6 +7,7 @@ from homeassistant.helpers import config_entry_oauth2_flow
from . import api, config_flow
from .const import DOMAIN
from .coordinator import OndiloIcoCoordinator
from .oauth_impl import OndiloOauth2Implementation
PLATFORMS = [Platform.SENSOR]
@@ -26,8 +27,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)
)
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = api.OndiloClient(hass, entry, implementation)
coordinator = OndiloIcoCoordinator(
hass, api.OndiloClient(hass, entry, implementation)
)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

View File

@@ -0,0 +1,37 @@
"""Define an object to coordinate fetching Ondilo ICO data."""
from datetime import timedelta
import logging
from typing import Any
from ondilo import OndiloError
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from . import DOMAIN
from .api import OndiloClient
_LOGGER = logging.getLogger(__name__)
class OndiloIcoCoordinator(DataUpdateCoordinator[list[dict[str, Any]]]):
"""Class to manage fetching Ondilo ICO data from API."""
def __init__(self, hass: HomeAssistant, api: OndiloClient) -> None:
"""Initialize."""
super().__init__(
hass,
logger=_LOGGER,
name=DOMAIN,
update_interval=timedelta(minutes=5),
)
self.api = api
async def _async_update_data(self) -> list[dict[str, Any]]:
"""Fetch data from API endpoint."""
try:
return await self.hass.async_add_executor_job(self.api.get_all_pools_data)
except OndiloError as err:
raise UpdateFailed(f"Error communicating with API: {err}") from err

View File

@@ -2,12 +2,6 @@
from __future__ import annotations
from datetime import timedelta
import logging
from typing import Any
from ondilo import OndiloError
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
@@ -24,14 +18,10 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
UpdateFailed,
)
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .api import OndiloClient
from .const import DOMAIN
from .coordinator import OndiloIcoCoordinator
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
@@ -78,66 +68,30 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
)
SCAN_INTERVAL = timedelta(minutes=5)
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the Ondilo ICO sensors."""
api: OndiloClient = hass.data[DOMAIN][entry.entry_id]
coordinator: OndiloIcoCoordinator = hass.data[DOMAIN][entry.entry_id]
async def async_update_data() -> list[dict[str, Any]]:
"""Fetch data from API endpoint.
This is the place to pre-process the data to lookup tables
so entities can quickly look up their data.
"""
try:
return await hass.async_add_executor_job(api.get_all_pools_data)
except OndiloError as err:
raise UpdateFailed(f"Error communicating with API: {err}") from err
coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
# Name of the data. For logging purposes.
name="sensor",
update_method=async_update_data,
# Polling interval. Will only be polled if there are subscribers.
update_interval=SCAN_INTERVAL,
async_add_entities(
OndiloICO(coordinator, poolidx, description)
for poolidx, pool in enumerate(coordinator.data)
for sensor in pool["sensors"]
for description in SENSOR_TYPES
if description.key == sensor["data_type"]
)
# Fetch initial data so we have data when entities subscribe
await coordinator.async_refresh()
entities = []
for poolidx, pool in enumerate(coordinator.data):
entities.extend(
[
OndiloICO(coordinator, poolidx, description)
for sensor in pool["sensors"]
for description in SENSOR_TYPES
if description.key == sensor["data_type"]
]
)
async_add_entities(entities)
class OndiloICO(
CoordinatorEntity[DataUpdateCoordinator[list[dict[str, Any]]]], SensorEntity
):
class OndiloICO(CoordinatorEntity[OndiloIcoCoordinator], SensorEntity):
"""Representation of a Sensor."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: DataUpdateCoordinator[list[dict[str, Any]]],
coordinator: OndiloIcoCoordinator,
poolidx: int,
description: SensorEntityDescription,
) -> None:

View File

@@ -5,7 +5,6 @@ from __future__ import annotations
import openai
import voluptuous as vol
from homeassistant.components import conversation
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, Platform
from homeassistant.core import (
@@ -115,5 +114,4 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return False
hass.data[DOMAIN].pop(entry.entry_id)
conversation.async_unset_agent(hass, entry)
return True

View File

@@ -3,7 +3,7 @@
import logging
DOMAIN = "openai_conversation"
LOGGER = logging.getLogger(__name__)
LOGGER = logging.getLogger(__package__)
CONF_PROMPT = "prompt"
DEFAULT_PROMPT = """This smart home is controlled by Home Assistant.

View File

@@ -44,6 +44,8 @@ class OpenAIConversationEntity(
):
"""OpenAI conversation agent."""
_attr_has_entity_name = True
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Initialize the agent."""
self.hass = hass

View File

@@ -69,7 +69,9 @@ class RoborockMap(RoborockCoordinatedEntity, ImageEntity):
try:
self.cached_map = self._create_image(starting_map)
except HomeAssistantError:
# If we failed to update the image on init, we set cached_map to empty bytes so that we are unavailable and can try again later.
# If we failed to update the image on init,
# we set cached_map to empty bytes
# so that we are unavailable and can try again later.
self.cached_map = b""
self._attr_entity_category = EntityCategory.DIAGNOSTIC
@@ -84,7 +86,11 @@ class RoborockMap(RoborockCoordinatedEntity, ImageEntity):
return self.map_flag == self.coordinator.current_map
def is_map_valid(self) -> bool:
"""Update this map if it is the current active map, and the vacuum is cleaning or if it has never been set at all."""
"""Update the map if it is valid.
Update this map if it is the currently active map, and the
vacuum is cleaning, or if it has never been set at all.
"""
return self.cached_map == b"" or (
self.is_selected
and self.image_last_updated is not None
@@ -134,8 +140,9 @@ class RoborockMap(RoborockCoordinatedEntity, ImageEntity):
async def create_coordinator_maps(
coord: RoborockDataUpdateCoordinator,
) -> list[RoborockMap]:
"""Get the starting map information for all maps for this device. The following steps must be done synchronously.
"""Get the starting map information for all maps for this device.
The following steps must be done synchronously.
Only one map can be loaded at a time per device.
"""
entities = []
@@ -161,7 +168,8 @@ async def create_coordinator_maps(
map_update = await asyncio.gather(
*[coord.cloud_api.get_map_v1(), coord.get_rooms()], return_exceptions=True
)
# If we fail to get the map -> We should set it to empty byte, still create it, and set it as unavailable.
# If we fail to get the map, we should set it to empty byte,
# still create it, and set it as unavailable.
api_data: bytes = map_update[0] if isinstance(map_update[0], bytes) else b""
entities.append(
RoborockMap(

View File

@@ -4,5 +4,5 @@
"codeowners": ["@fabaff"],
"documentation": "https://www.home-assistant.io/integrations/serial",
"iot_class": "local_polling",
"requirements": ["pyserial-asyncio==0.6"]
"requirements": ["pyserial-asyncio-fast==0.11"]
}

View File

@@ -7,7 +7,7 @@ import json
import logging
from serial import SerialException
import serial_asyncio
import serial_asyncio_fast as serial_asyncio
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity

View File

@@ -8,7 +8,7 @@
"iot_class": "local_push",
"loggers": ["aiounifi"],
"quality_scale": "platinum",
"requirements": ["aiounifi==76"],
"requirements": ["aiounifi==77"],
"ssdp": [
{
"manufacturer": "Ubiquiti Networks",

View File

@@ -57,4 +57,6 @@ SKU_TO_BASE_DEVICE = {
"Vital100S": "Vital100S",
"LAP-V102S-WUS": "Vital100S", # Alt ID Model Vital100S
"LAP-V102S-AASR": "Vital100S", # Alt ID Model Vital100S
"LAP-V102S-WEU": "Vital100S", # Alt ID Model Vital100S
"LAP-V102S-WUK": "Vital100S", # Alt ID Model Vital100S
}

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/waze_travel_time",
"iot_class": "cloud_polling",
"loggers": ["pywaze", "homeassistant.helpers.location"],
"requirements": ["pywaze==1.0.0"]
"requirements": ["pywaze==1.0.1"]
}

View File

@@ -21,9 +21,8 @@
"universal_silabs_flasher"
],
"requirements": [
"bellows==0.38.3",
"bellows==0.38.4",
"pyserial==3.5",
"pyserial-asyncio==0.6",
"zha-quirks==0.0.115",
"zigpy-deconz==0.23.1",
"zigpy==0.64.0",

View File

@@ -98,6 +98,7 @@ def _entity_features() -> dict[str, type[IntFlag]]:
from homeassistant.components.light import LightEntityFeature
from homeassistant.components.lock import LockEntityFeature
from homeassistant.components.media_player import MediaPlayerEntityFeature
from homeassistant.components.notify import NotifyEntityFeature
from homeassistant.components.remote import RemoteEntityFeature
from homeassistant.components.siren import SirenEntityFeature
from homeassistant.components.todo import TodoListEntityFeature
@@ -119,6 +120,7 @@ def _entity_features() -> dict[str, type[IntFlag]]:
"LightEntityFeature": LightEntityFeature,
"LockEntityFeature": LockEntityFeature,
"MediaPlayerEntityFeature": MediaPlayerEntityFeature,
"NotifyEntityFeature": NotifyEntityFeature,
"RemoteEntityFeature": RemoteEntityFeature,
"SirenEntityFeature": SirenEntityFeature,
"TodoListEntityFeature": TodoListEntityFeature,

View File

@@ -28,7 +28,7 @@ dbus-fast==2.21.1
fnv-hash-fast==0.5.0
ha-av==10.1.1
ha-ffmpeg==3.2.0
habluetooth==2.8.0
habluetooth==2.8.1
hass-nabucasa==0.78.0
hassil==1.6.1
home-assistant-bluetooth==1.12.0
@@ -192,3 +192,8 @@ pycountry>=23.12.11
# scapy<2.5.0 will not work with python3.12
scapy>=2.5.0
# tuf isn't updated to deal with breaking changes in securesystemslib==1.0.
# Only tuf>=4 includes a constraint to <1.0.
# https://github.com/theupdateframework/python-tuf/releases/tag/v4.0.0
tuf>=4.0.0

View File

@@ -386,7 +386,7 @@ aiotankerkoenig==0.4.1
aiotractive==0.5.6
# homeassistant.components.unifi
aiounifi==76
aiounifi==77
# homeassistant.components.vlc_telnet
aiovlc==0.1.0
@@ -413,7 +413,7 @@ aioymaps==1.2.2
airly==1.1.0
# homeassistant.components.airthings_ble
airthings-ble==0.7.1
airthings-ble==0.8.0
# homeassistant.components.airthings
airthings-cloud==0.2.0
@@ -541,7 +541,7 @@ beautifulsoup4==4.12.3
# beewi-smartclim==0.0.10
# homeassistant.components.zha
bellows==0.38.3
bellows==0.38.4
# homeassistant.components.bmw_connected_drive
bimmer-connected[china]==0.15.2
@@ -804,7 +804,7 @@ enocean==0.50
enturclient==0.2.4
# homeassistant.components.environment_canada
env-canada==0.6.0
env-canada==0.6.2
# homeassistant.components.season
ephem==4.1.5
@@ -1035,7 +1035,7 @@ ha-philipsjs==3.1.1
habitipy==0.2.0
# homeassistant.components.bluetooth
habluetooth==2.8.0
habluetooth==2.8.1
# homeassistant.components.cloud
hass-nabucasa==0.78.0
@@ -2122,12 +2122,9 @@ pyschlage==2024.2.0
# homeassistant.components.sensibo
pysensibo==1.0.36
# homeassistant.components.zha
pyserial-asyncio-fast==0.11
# homeassistant.components.serial
# homeassistant.components.zha
pyserial-asyncio==0.6
pyserial-asyncio-fast==0.11
# homeassistant.components.acer_projector
# homeassistant.components.crownstone
@@ -2218,7 +2215,7 @@ python-clementine-remote==1.0.1
python-digitalocean==1.13.2
# homeassistant.components.ecobee
python-ecobee-api==0.2.17
python-ecobee-api==0.2.18
# homeassistant.components.etherscan
python-etherscan-api==0.0.3
@@ -2376,7 +2373,7 @@ pyvlx==0.2.21
pyvolumio==0.1.5
# homeassistant.components.waze_travel_time
pywaze==1.0.0
pywaze==1.0.1
# homeassistant.components.weatherflow
pyweatherflowudp==1.4.5

View File

@@ -359,7 +359,7 @@ aiotankerkoenig==0.4.1
aiotractive==0.5.6
# homeassistant.components.unifi
aiounifi==76
aiounifi==77
# homeassistant.components.vlc_telnet
aiovlc==0.1.0
@@ -386,7 +386,7 @@ aioymaps==1.2.2
airly==1.1.0
# homeassistant.components.airthings_ble
airthings-ble==0.7.1
airthings-ble==0.8.0
# homeassistant.components.airthings
airthings-cloud==0.2.0
@@ -466,7 +466,7 @@ base36==0.1.1
beautifulsoup4==4.12.3
# homeassistant.components.zha
bellows==0.38.3
bellows==0.38.4
# homeassistant.components.bmw_connected_drive
bimmer-connected[china]==0.15.2
@@ -658,7 +658,7 @@ energyzero==2.1.0
enocean==0.50
# homeassistant.components.environment_canada
env-canada==0.6.0
env-canada==0.6.2
# homeassistant.components.season
ephem==4.1.5
@@ -849,7 +849,7 @@ ha-philipsjs==3.1.1
habitipy==0.2.0
# homeassistant.components.bluetooth
habluetooth==2.8.0
habluetooth==2.8.1
# homeassistant.components.cloud
hass-nabucasa==0.78.0
@@ -1661,12 +1661,9 @@ pyschlage==2024.2.0
# homeassistant.components.sensibo
pysensibo==1.0.36
# homeassistant.components.zha
pyserial-asyncio-fast==0.11
# homeassistant.components.serial
# homeassistant.components.zha
pyserial-asyncio==0.6
pyserial-asyncio-fast==0.11
# homeassistant.components.acer_projector
# homeassistant.components.crownstone
@@ -1733,7 +1730,7 @@ python-awair==0.2.4
python-bsblan==0.5.18
# homeassistant.components.ecobee
python-ecobee-api==0.2.17
python-ecobee-api==0.2.18
# homeassistant.components.fully_kiosk
python-fullykiosk==0.0.12
@@ -1849,7 +1846,7 @@ pyvlx==0.2.21
pyvolumio==0.1.5
# homeassistant.components.waze_travel_time
pywaze==1.0.0
pywaze==1.0.1
# homeassistant.components.weatherflow
pyweatherflowudp==1.4.5

View File

@@ -214,6 +214,11 @@ pycountry>=23.12.11
# scapy<2.5.0 will not work with python3.12
scapy>=2.5.0
# tuf isn't updated to deal with breaking changes in securesystemslib==1.0.
# Only tuf>=4 includes a constraint to <1.0.
# https://github.com/theupdateframework/python-tuf/releases/tag/v4.0.0
tuf>=4.0.0
"""
GENERATED_MESSAGE = (

View File

@@ -81,7 +81,7 @@ async def setup_airvisual_pro_fixture(hass, config, pro):
return_value=pro,
),
patch("homeassistant.components.airvisual_pro.NodeSamba", return_value=pro),
patch("homeassistant.components.airvisual.PLATFORMS", []),
patch("homeassistant.components.airvisual_pro.PLATFORMS", []),
):
assert await async_setup_component(hass, DOMAIN, config)
await hass.async_block_till_done()

View File

@@ -2,7 +2,7 @@
from syrupy import SnapshotAssertion
from homeassistant.components.ambient_station import DOMAIN
from homeassistant.components.ambient_station import AmbientStationConfigEntry
from homeassistant.core import HomeAssistant
from tests.components.diagnostics import get_diagnostics_for_config_entry
@@ -11,14 +11,14 @@ from tests.typing import ClientSessionGenerator
async def test_entry_diagnostics(
hass: HomeAssistant,
config_entry,
config_entry: AmbientStationConfigEntry,
hass_client: ClientSessionGenerator,
data_station,
setup_config_entry,
snapshot: SnapshotAssertion,
) -> None:
"""Test config entry diagnostics."""
ambient = hass.data[DOMAIN][config_entry.entry_id]
ambient = config_entry.runtime_data
ambient.stations = data_station
assert (
await get_diagnostics_for_config_entry(hass, hass_client, config_entry)

View File

@@ -8,7 +8,7 @@ from unittest.mock import ANY, AsyncMock, MagicMock, Mock, patch
from bleak import BleakError
from bleak.backends.scanner import AdvertisementData, BLEDevice
from bluetooth_adapters import DEFAULT_ADDRESS
from habluetooth import scanner
from habluetooth import scanner, set_manager
from habluetooth.wrappers import HaBleakScannerWrapper
import pytest
@@ -1154,6 +1154,7 @@ async def test_async_discovered_device_api(
) -> None:
"""Test the async_discovered_device API."""
mock_bt = []
set_manager(None)
with (
patch(
"homeassistant.components.bluetooth.async_get_bluetooth",
@@ -1169,8 +1170,10 @@ async def test_async_discovered_device_api(
},
),
):
assert not bluetooth.async_discovered_service_info(hass)
assert not bluetooth.async_address_present(hass, "44:44:22:22:11:22")
with pytest.raises(RuntimeError, match="BluetoothManager has not been set"):
assert not bluetooth.async_discovered_service_info(hass)
with pytest.raises(RuntimeError, match="BluetoothManager has not been set"):
assert not bluetooth.async_address_present(hass, "44:44:22:22:11:22")
await async_setup_with_default_adapter(hass)
with patch.object(hass.config_entries.flow, "async_init"):
@@ -2744,6 +2747,7 @@ async def test_async_ble_device_from_address(
hass: HomeAssistant, mock_bleak_scanner_start: MagicMock, macos_adapter: None
) -> None:
"""Test the async_ble_device_from_address api."""
set_manager(None)
mock_bt = []
with (
patch(
@@ -2760,11 +2764,15 @@ async def test_async_ble_device_from_address(
},
),
):
assert not bluetooth.async_discovered_service_info(hass)
assert not bluetooth.async_address_present(hass, "44:44:22:22:11:22")
assert (
bluetooth.async_ble_device_from_address(hass, "44:44:33:11:23:45") is None
)
with pytest.raises(RuntimeError, match="BluetoothManager has not been set"):
assert not bluetooth.async_discovered_service_info(hass)
with pytest.raises(RuntimeError, match="BluetoothManager has not been set"):
assert not bluetooth.async_address_present(hass, "44:44:22:22:11:22")
with pytest.raises(RuntimeError, match="BluetoothManager has not been set"):
assert (
bluetooth.async_ble_device_from_address(hass, "44:44:33:11:23:45")
is None
)
await async_setup_with_default_adapter(hass)

View File

@@ -6,7 +6,7 @@ from aiohttp import ClientConnectionError, ClientResponseError
from bond_async import DeviceType
import pytest
from homeassistant.components.bond.const import DOMAIN
from homeassistant.components.bond import DOMAIN, BondData
from homeassistant.components.fan import DOMAIN as FAN_DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_ASSUMED_STATE, CONF_ACCESS_TOKEN, CONF_HOST
@@ -107,7 +107,7 @@ async def test_async_setup_entry_sets_up_hub_and_supported_domains(
assert result is True
await hass.async_block_till_done()
assert config_entry.entry_id in hass.data[DOMAIN]
assert isinstance(config_entry.runtime_data, BondData)
assert config_entry.state is ConfigEntryState.LOADED
assert config_entry.unique_id == "ZXXX12345"
@@ -148,7 +148,6 @@ async def test_unload_config_entry(hass: HomeAssistant) -> None:
await hass.config_entries.async_unload(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.entry_id not in hass.data[DOMAIN]
assert config_entry.state is ConfigEntryState.NOT_LOADED
@@ -194,7 +193,6 @@ async def test_old_identifiers_are_removed(
assert await hass.config_entries.async_setup(config_entry.entry_id) is True
await hass.async_block_till_done()
assert config_entry.entry_id in hass.data[DOMAIN]
assert config_entry.state is ConfigEntryState.LOADED
assert config_entry.unique_id == "ZXXX12345"
@@ -238,7 +236,6 @@ async def test_smart_by_bond_device_suggested_area(
assert await hass.config_entries.async_setup(config_entry.entry_id) is True
await hass.async_block_till_done()
assert config_entry.entry_id in hass.data[DOMAIN]
assert config_entry.state is ConfigEntryState.LOADED
assert config_entry.unique_id == "KXXX12345"
@@ -287,7 +284,6 @@ async def test_bridge_device_suggested_area(
assert await hass.config_entries.async_setup(config_entry.entry_id) is True
await hass.async_block_till_done()
assert config_entry.entry_id in hass.data[DOMAIN]
assert config_entry.state is ConfigEntryState.LOADED
assert config_entry.unique_id == "ZXXX12345"

View File

@@ -69,7 +69,17 @@ async def test_sending_message(hass: HomeAssistant, events: list[Event]) -> None
await hass.services.async_call(notify.DOMAIN, notify.SERVICE_SEND_MESSAGE, data)
await hass.async_block_till_done()
last_event = events[-1]
assert last_event.data[notify.ATTR_MESSAGE] == "Test message"
assert last_event.data == {notify.ATTR_MESSAGE: "Test message"}
data[notify.ATTR_TITLE] = "My title"
# Test with Title
await hass.services.async_call(notify.DOMAIN, notify.SERVICE_SEND_MESSAGE, data)
await hass.async_block_till_done()
last_event = events[-1]
assert last_event.data == {
notify.ATTR_MESSAGE: "Test message",
notify.ATTR_TITLE: "My title",
}
async def test_calling_notify_from_script_loaded_from_yaml(

View File

@@ -65,6 +65,9 @@ GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP = {
"identifier": 8675309,
"name": "ecobee",
"modelNumber": "athenaSmart",
"utcTime": "2022-01-01 10:00:00",
"thermostatTime": "2022-01-01 6:00:00",
"location": {"timeZone": "America/Toronto"},
"program": {
"climates": [
{"name": "Climate1", "climateRef": "c1"},
@@ -92,7 +95,8 @@ GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP = {
"humidifierMode": "manual",
"humidity": "30",
"hasHeatPump": True,
"ventilatorType": "none",
"ventilatorType": "hrv",
"ventilatorOffDateTime": "2022-01-01 6:00:00",
},
"equipmentStatus": "fan",
"events": [

View File

@@ -4,6 +4,11 @@
"identifier": 8675309,
"name": "ecobee",
"modelNumber": "athenaSmart",
"utcTime": "2022-01-01 10:00:00",
"thermostatTime": "2022-01-01 6:00:00",
"location": {
"timeZone": "America/Toronto"
},
"program": {
"climates": [
{ "name": "Climate1", "climateRef": "c1" },
@@ -30,6 +35,7 @@
"ventilatorType": "hrv",
"ventilatorMinOnTimeHome": 20,
"ventilatorMinOnTimeAway": 10,
"ventilatorOffDateTime": "2022-01-01 6:00:00",
"isVentilatorTimerOn": false,
"hasHumidifier": true,
"humidifierMode": "manual",

Some files were not shown because too many files have changed in this diff Show More