mirror of
https://github.com/home-assistant/core.git
synced 2025-08-02 10:08:23 +00:00
Merge branch 'dev' into block_pyserial_asyncio
This commit is contained in:
commit
1428ce4084
@ -519,6 +519,7 @@ omit =
|
||||
homeassistant/components/guardian/util.py
|
||||
homeassistant/components/guardian/valve.py
|
||||
homeassistant/components/habitica/__init__.py
|
||||
homeassistant/components/habitica/coordinator.py
|
||||
homeassistant/components/habitica/sensor.py
|
||||
homeassistant/components/harman_kardon_avr/media_player.py
|
||||
homeassistant/components/harmony/data.py
|
||||
|
@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.4.2
|
||||
rev: v0.4.3
|
||||
hooks:
|
||||
- id: ruff
|
||||
args:
|
||||
|
@ -31,7 +31,6 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import TimestampDataUpdateCoordinator
|
||||
from homeassistant.util.dt import utc_from_timestamp
|
||||
|
||||
from . import AccuWeatherData
|
||||
@ -65,8 +64,6 @@ class AccuWeatherEntity(
|
||||
CoordinatorWeatherEntity[
|
||||
AccuWeatherObservationDataUpdateCoordinator,
|
||||
AccuWeatherDailyForecastDataUpdateCoordinator,
|
||||
TimestampDataUpdateCoordinator,
|
||||
TimestampDataUpdateCoordinator,
|
||||
]
|
||||
):
|
||||
"""Define an AccuWeather entity."""
|
||||
|
@ -17,15 +17,18 @@ from homeassistant.const import CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STOP,
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
|
||||
from .const import DOMAIN
|
||||
from .helpers import create_api, get_enable_ime
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.MEDIA_PLAYER, Platform.REMOTE]
|
||||
|
||||
AndroidTVRemoteConfigEntry = ConfigEntry[AndroidTVRemote]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: AndroidTVRemoteConfigEntry
|
||||
) -> bool:
|
||||
"""Set up Android TV Remote from a config entry."""
|
||||
api = create_api(hass, entry.data[CONF_HOST], get_enable_ime(entry))
|
||||
|
||||
@ -64,7 +67,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
# update the config entry data and reload the config entry.
|
||||
api.keep_reconnecting(reauth_needed)
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = api
|
||||
entry.runtime_data = api
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
@ -77,17 +80,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop)
|
||||
)
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
entry.async_on_unload(api.disconnect)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
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):
|
||||
api: AndroidTVRemote = hass.data[DOMAIN].pop(entry.entry_id)
|
||||
api.disconnect()
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
|
@ -4,23 +4,20 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from androidtvremote2 import AndroidTVRemote
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, CONF_MAC
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from . import AndroidTVRemoteConfigEntry
|
||||
|
||||
TO_REDACT = {CONF_HOST, CONF_MAC}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
hass: HomeAssistant, entry: AndroidTVRemoteConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
api: AndroidTVRemote = hass.data[DOMAIN].pop(entry.entry_id)
|
||||
api = entry.runtime_data
|
||||
return async_redact_data(
|
||||
{
|
||||
"api_device_info": api.device_info,
|
||||
|
@ -8,6 +8,6 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["androidtvremote2"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["androidtvremote2==0.0.14"],
|
||||
"requirements": ["androidtvremote2==0.0.15"],
|
||||
"zeroconf": ["_androidtvremote2._tcp.local."]
|
||||
}
|
||||
|
@ -14,12 +14,11 @@ from homeassistant.components.media_player import (
|
||||
MediaPlayerState,
|
||||
MediaType,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from . import AndroidTVRemoteConfigEntry
|
||||
from .entity import AndroidTVRemoteBaseEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
@ -27,11 +26,11 @@ PARALLEL_UPDATES = 0
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: AndroidTVRemoteConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Android TV media player entity based on a config entry."""
|
||||
api: AndroidTVRemote = hass.data[DOMAIN][config_entry.entry_id]
|
||||
api = config_entry.runtime_data
|
||||
async_add_entities([AndroidTVRemoteMediaPlayerEntity(api, config_entry)])
|
||||
|
||||
|
||||
@ -53,7 +52,9 @@ class AndroidTVRemoteMediaPlayerEntity(AndroidTVRemoteBaseEntity, MediaPlayerEnt
|
||||
| MediaPlayerEntityFeature.PLAY_MEDIA
|
||||
)
|
||||
|
||||
def __init__(self, api: AndroidTVRemote, config_entry: ConfigEntry) -> None:
|
||||
def __init__(
|
||||
self, api: AndroidTVRemote, config_entry: AndroidTVRemoteConfigEntry
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(api, config_entry)
|
||||
|
||||
|
@ -6,8 +6,6 @@ import asyncio
|
||||
from collections.abc import Iterable
|
||||
from typing import Any
|
||||
|
||||
from androidtvremote2 import AndroidTVRemote
|
||||
|
||||
from homeassistant.components.remote import (
|
||||
ATTR_ACTIVITY,
|
||||
ATTR_DELAY_SECS,
|
||||
@ -19,11 +17,10 @@ from homeassistant.components.remote import (
|
||||
RemoteEntity,
|
||||
RemoteEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from . import AndroidTVRemoteConfigEntry
|
||||
from .entity import AndroidTVRemoteBaseEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
@ -31,11 +28,11 @@ PARALLEL_UPDATES = 0
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: AndroidTVRemoteConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Android TV remote entity based on a config entry."""
|
||||
api: AndroidTVRemote = hass.data[DOMAIN][config_entry.entry_id]
|
||||
api = config_entry.runtime_data
|
||||
async_add_entities([AndroidTVRemoteEntity(api, config_entry)])
|
||||
|
||||
|
||||
|
@ -651,11 +651,8 @@ def websocket_delete_all_refresh_tokens(
|
||||
continue
|
||||
try:
|
||||
hass.auth.async_remove_refresh_token(token)
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
getLogger(__name__).exception(
|
||||
"During refresh token removal, the following error occurred: %s",
|
||||
err,
|
||||
)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
getLogger(__name__).exception("Error during refresh token removal")
|
||||
remove_failed = True
|
||||
|
||||
if remove_failed:
|
||||
|
@ -13,8 +13,10 @@ from .hub import AxisHub, get_axis_api
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
AxisConfigEntry = ConfigEntry[AxisHub]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: AxisConfigEntry) -> bool:
|
||||
"""Set up the Axis integration."""
|
||||
hass.data.setdefault(AXIS_DOMAIN, {})
|
||||
|
||||
@ -25,8 +27,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
except AuthenticationRequired as err:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
|
||||
hub = AxisHub(hass, config_entry, api)
|
||||
hass.data[AXIS_DOMAIN][config_entry.entry_id] = hub
|
||||
hub = config_entry.runtime_data = AxisHub(hass, config_entry, api)
|
||||
await hub.async_update_device_registry()
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||
hub.setup()
|
||||
@ -42,7 +43,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Unload Axis device config entry."""
|
||||
hass.data[AXIS_DOMAIN].pop(config_entry.entry_id)
|
||||
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
|
||||
|
||||
|
||||
|
@ -17,11 +17,11 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
|
||||
from . import AxisConfigEntry
|
||||
from .entity import AxisEventDescription, AxisEventEntity
|
||||
from .hub import AxisHub
|
||||
|
||||
@ -177,11 +177,11 @@ ENTITY_DESCRIPTIONS = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: AxisConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up a Axis binary sensor."""
|
||||
AxisHub.get_hub(hass, config_entry).entity_loader.register_platform(
|
||||
config_entry.runtime_data.entity_loader.register_platform(
|
||||
async_add_entities, AxisBinarySensor, ENTITY_DESCRIPTIONS
|
||||
)
|
||||
|
||||
|
@ -4,12 +4,12 @@ from urllib.parse import urlencode
|
||||
|
||||
from homeassistant.components.camera import CameraEntityFeature
|
||||
from homeassistant.components.mjpeg import MjpegCamera, filter_urllib3_logging
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import HTTP_DIGEST_AUTHENTICATION
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AxisConfigEntry
|
||||
from .const import DEFAULT_STREAM_PROFILE, DEFAULT_VIDEO_SOURCE
|
||||
from .entity import AxisEntity
|
||||
from .hub import AxisHub
|
||||
@ -17,13 +17,13 @@ from .hub import AxisHub
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: AxisConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Axis camera video stream."""
|
||||
filter_urllib3_logging()
|
||||
|
||||
hub = AxisHub.get_hub(hass, config_entry)
|
||||
hub = config_entry.runtime_data
|
||||
|
||||
if (
|
||||
not (prop := hub.api.vapix.params.property_handler.get("0"))
|
||||
|
@ -32,6 +32,7 @@ from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
from homeassistant.util.network import is_link_local
|
||||
|
||||
from . import AxisConfigEntry
|
||||
from .const import (
|
||||
CONF_STREAM_PROFILE,
|
||||
CONF_VIDEO_SOURCE,
|
||||
@ -260,13 +261,14 @@ class AxisFlowHandler(ConfigFlow, domain=AXIS_DOMAIN):
|
||||
class AxisOptionsFlowHandler(OptionsFlowWithConfigEntry):
|
||||
"""Handle Axis device options."""
|
||||
|
||||
config_entry: AxisConfigEntry
|
||||
hub: AxisHub
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Manage the Axis device options."""
|
||||
self.hub = AxisHub.get_hub(self.hass, self.config_entry)
|
||||
self.hub = self.config_entry.runtime_data
|
||||
return await self.async_step_configure_stream()
|
||||
|
||||
async def async_step_configure_stream(
|
||||
|
@ -5,11 +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_MAC, CONF_PASSWORD, CONF_UNIQUE_ID, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .hub import AxisHub
|
||||
from . import AxisConfigEntry
|
||||
|
||||
REDACT_CONFIG = {CONF_MAC, CONF_PASSWORD, CONF_UNIQUE_ID, CONF_USERNAME}
|
||||
REDACT_BASIC_DEVICE_INFO = {"SerialNumber", "SocSerialNumber"}
|
||||
@ -17,10 +16,10 @@ REDACT_VAPIX_PARAMS = {"root.Network", "System.SerialNumber"}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
hass: HomeAssistant, config_entry: AxisConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
hub = AxisHub.get_hub(hass, config_entry)
|
||||
hub = config_entry.runtime_data
|
||||
diag: dict[str, Any] = hub.additional_diagnostics.copy()
|
||||
|
||||
diag["config"] = async_redact_data(config_entry.as_dict(), REDACT_CONFIG)
|
||||
|
@ -2,11 +2,10 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import axis
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, format_mac
|
||||
@ -17,12 +16,15 @@ from .config import AxisConfig
|
||||
from .entity_loader import AxisEntityLoader
|
||||
from .event_source import AxisEventSource
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .. import AxisConfigEntry
|
||||
|
||||
|
||||
class AxisHub:
|
||||
"""Manages a Axis device."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, config_entry: ConfigEntry, api: axis.AxisDevice
|
||||
self, hass: HomeAssistant, config_entry: AxisConfigEntry, api: axis.AxisDevice
|
||||
) -> None:
|
||||
"""Initialize the device."""
|
||||
self.hass = hass
|
||||
@ -37,13 +39,6 @@ class AxisHub:
|
||||
|
||||
self.additional_diagnostics: dict[str, Any] = {}
|
||||
|
||||
@callback
|
||||
@staticmethod
|
||||
def get_hub(hass: HomeAssistant, config_entry: ConfigEntry) -> AxisHub:
|
||||
"""Get Axis hub from config entry."""
|
||||
hub: AxisHub = hass.data[AXIS_DOMAIN][config_entry.entry_id]
|
||||
return hub
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Connection state to the device."""
|
||||
@ -63,7 +58,7 @@ class AxisHub:
|
||||
|
||||
@staticmethod
|
||||
async def async_new_address_callback(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
hass: HomeAssistant, config_entry: AxisConfigEntry
|
||||
) -> None:
|
||||
"""Handle signals of device getting new address.
|
||||
|
||||
@ -71,7 +66,7 @@ class AxisHub:
|
||||
This is a static method because a class method (bound method),
|
||||
cannot be used with weak references.
|
||||
"""
|
||||
hub = AxisHub.get_hub(hass, config_entry)
|
||||
hub = config_entry.runtime_data
|
||||
hub.config = AxisConfig.from_config_entry(config_entry)
|
||||
hub.event_source.config_entry = config_entry
|
||||
hub.api.config.host = hub.config.host
|
||||
|
@ -11,10 +11,10 @@ from homeassistant.components.light import (
|
||||
LightEntity,
|
||||
LightEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AxisConfigEntry
|
||||
from .entity import TOPIC_TO_EVENT_TYPE, AxisEventDescription, AxisEventEntity
|
||||
from .hub import AxisHub
|
||||
|
||||
@ -45,11 +45,11 @@ ENTITY_DESCRIPTIONS = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: AxisConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Axis light platform."""
|
||||
AxisHub.get_hub(hass, config_entry).entity_loader.register_platform(
|
||||
config_entry.runtime_data.entity_loader.register_platform(
|
||||
async_add_entities, AxisLight, ENTITY_DESCRIPTIONS
|
||||
)
|
||||
|
||||
|
@ -10,11 +10,11 @@ from homeassistant.components.switch import (
|
||||
SwitchEntity,
|
||||
SwitchEntityDescription,
|
||||
)
|
||||
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 . import AxisConfigEntry
|
||||
from .entity import AxisEventDescription, AxisEventEntity
|
||||
from .hub import AxisHub
|
||||
|
||||
@ -38,11 +38,11 @@ ENTITY_DESCRIPTIONS = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: AxisConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Axis switch platform."""
|
||||
AxisHub.get_hub(hass, config_entry).entity_loader.register_platform(
|
||||
config_entry.runtime_data.entity_loader.register_platform(
|
||||
async_add_entities, AxisSwitch, ENTITY_DESCRIPTIONS
|
||||
)
|
||||
|
||||
|
@ -97,10 +97,9 @@ class HomeAssistantBluetoothManager(BluetoothManager):
|
||||
matched_domains = self._integration_matcher.match_domains(service_info)
|
||||
if self._debug:
|
||||
_LOGGER.debug(
|
||||
"%s: %s %s match: %s",
|
||||
"%s: %s match: %s",
|
||||
self._async_describe_source(service_info),
|
||||
service_info.address,
|
||||
service_info.advertisement,
|
||||
service_info,
|
||||
matched_domains,
|
||||
)
|
||||
|
||||
|
@ -16,10 +16,10 @@
|
||||
"requirements": [
|
||||
"bleak==0.21.1",
|
||||
"bleak-retry-connector==3.5.0",
|
||||
"bluetooth-adapters==0.19.1",
|
||||
"bluetooth-adapters==0.19.2",
|
||||
"bluetooth-auto-recovery==1.4.2",
|
||||
"bluetooth-data-tools==1.19.0",
|
||||
"dbus-fast==2.21.1",
|
||||
"habluetooth==2.8.1"
|
||||
"habluetooth==3.0.1"
|
||||
]
|
||||
}
|
||||
|
@ -2,12 +2,17 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from boschshcpy import SHCSession
|
||||
from boschshcpy.device import SHCDevice
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@ -20,341 +25,207 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
|
||||
from .const import DATA_SESSION, DOMAIN
|
||||
from .entity import SHCEntity
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class SHCSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes a SHC sensor."""
|
||||
|
||||
value_fn: Callable[[SHCDevice], StateType]
|
||||
attributes_fn: Callable[[SHCDevice], dict[str, Any]] | None = None
|
||||
|
||||
|
||||
TEMPERATURE_SENSOR = "temperature"
|
||||
HUMIDITY_SENSOR = "humidity"
|
||||
VALVE_TAPPET_SENSOR = "valvetappet"
|
||||
PURITY_SENSOR = "purity"
|
||||
AIR_QUALITY_SENSOR = "airquality"
|
||||
TEMPERATURE_RATING_SENSOR = "temperature_rating"
|
||||
HUMIDITY_RATING_SENSOR = "humidity_rating"
|
||||
PURITY_RATING_SENSOR = "purity_rating"
|
||||
POWER_SENSOR = "power"
|
||||
ENERGY_SENSOR = "energy"
|
||||
COMMUNICATION_QUALITY_SENSOR = "communication_quality"
|
||||
|
||||
SENSOR_DESCRIPTIONS: dict[str, SHCSensorEntityDescription] = {
|
||||
TEMPERATURE_SENSOR: SHCSensorEntityDescription(
|
||||
key=TEMPERATURE_SENSOR,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
value_fn=lambda device: device.temperature,
|
||||
),
|
||||
HUMIDITY_SENSOR: SHCSensorEntityDescription(
|
||||
key=HUMIDITY_SENSOR,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda device: device.humidity,
|
||||
),
|
||||
PURITY_SENSOR: SHCSensorEntityDescription(
|
||||
key=PURITY_SENSOR,
|
||||
translation_key=PURITY_SENSOR,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
value_fn=lambda device: device.purity,
|
||||
),
|
||||
AIR_QUALITY_SENSOR: SHCSensorEntityDescription(
|
||||
key=AIR_QUALITY_SENSOR,
|
||||
translation_key="air_quality",
|
||||
value_fn=lambda device: device.combined_rating.name,
|
||||
attributes_fn=lambda device: {
|
||||
"rating_description": device.description,
|
||||
},
|
||||
),
|
||||
TEMPERATURE_RATING_SENSOR: SHCSensorEntityDescription(
|
||||
key=TEMPERATURE_RATING_SENSOR,
|
||||
translation_key=TEMPERATURE_RATING_SENSOR,
|
||||
value_fn=lambda device: device.temperature_rating.name,
|
||||
),
|
||||
COMMUNICATION_QUALITY_SENSOR: SHCSensorEntityDescription(
|
||||
key=COMMUNICATION_QUALITY_SENSOR,
|
||||
translation_key=COMMUNICATION_QUALITY_SENSOR,
|
||||
value_fn=lambda device: device.communicationquality.name,
|
||||
),
|
||||
HUMIDITY_RATING_SENSOR: SHCSensorEntityDescription(
|
||||
key=HUMIDITY_RATING_SENSOR,
|
||||
translation_key=HUMIDITY_RATING_SENSOR,
|
||||
value_fn=lambda device: device.humidity_rating.name,
|
||||
),
|
||||
PURITY_RATING_SENSOR: SHCSensorEntityDescription(
|
||||
key=PURITY_RATING_SENSOR,
|
||||
translation_key=PURITY_RATING_SENSOR,
|
||||
value_fn=lambda device: device.purity_rating.name,
|
||||
),
|
||||
POWER_SENSOR: SHCSensorEntityDescription(
|
||||
key=POWER_SENSOR,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
value_fn=lambda device: device.powerconsumption,
|
||||
),
|
||||
ENERGY_SENSOR: SHCSensorEntityDescription(
|
||||
key=ENERGY_SENSOR,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
value_fn=lambda device: device.energyconsumption / 1000.0,
|
||||
),
|
||||
VALVE_TAPPET_SENSOR: SHCSensorEntityDescription(
|
||||
key=VALVE_TAPPET_SENSOR,
|
||||
translation_key=VALVE_TAPPET_SENSOR,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
value_fn=lambda device: device.position,
|
||||
attributes_fn=lambda device: {
|
||||
"valve_tappet_state": device.valvestate.name,
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the SHC sensor platform."""
|
||||
entities: list[SensorEntity] = []
|
||||
session: SHCSession = hass.data[DOMAIN][config_entry.entry_id][DATA_SESSION]
|
||||
|
||||
for sensor in session.device_helper.thermostats:
|
||||
entities.append(
|
||||
TemperatureSensor(
|
||||
device=sensor,
|
||||
parent_id=session.information.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
)
|
||||
)
|
||||
entities.append(
|
||||
ValveTappetSensor(
|
||||
device=sensor,
|
||||
parent_id=session.information.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
)
|
||||
entities: list[SensorEntity] = [
|
||||
SHCSensor(
|
||||
device,
|
||||
SENSOR_DESCRIPTIONS[sensor_type],
|
||||
session.information.unique_id,
|
||||
config_entry.entry_id,
|
||||
)
|
||||
for device in session.device_helper.thermostats
|
||||
for sensor_type in (TEMPERATURE_SENSOR, VALVE_TAPPET_SENSOR)
|
||||
]
|
||||
|
||||
for sensor in session.device_helper.wallthermostats:
|
||||
entities.append(
|
||||
TemperatureSensor(
|
||||
device=sensor,
|
||||
parent_id=session.information.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
)
|
||||
)
|
||||
entities.append(
|
||||
HumiditySensor(
|
||||
device=sensor,
|
||||
parent_id=session.information.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
)
|
||||
entities.extend(
|
||||
SHCSensor(
|
||||
device,
|
||||
SENSOR_DESCRIPTIONS[sensor_type],
|
||||
session.information.unique_id,
|
||||
config_entry.entry_id,
|
||||
)
|
||||
for device in session.device_helper.wallthermostats
|
||||
for sensor_type in (TEMPERATURE_SENSOR, HUMIDITY_SENSOR)
|
||||
)
|
||||
|
||||
for sensor in session.device_helper.twinguards:
|
||||
entities.append(
|
||||
TemperatureSensor(
|
||||
device=sensor,
|
||||
parent_id=session.information.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
)
|
||||
entities.extend(
|
||||
SHCSensor(
|
||||
device,
|
||||
SENSOR_DESCRIPTIONS[sensor_type],
|
||||
session.information.unique_id,
|
||||
config_entry.entry_id,
|
||||
)
|
||||
entities.append(
|
||||
HumiditySensor(
|
||||
device=sensor,
|
||||
parent_id=session.information.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
)
|
||||
)
|
||||
entities.append(
|
||||
PuritySensor(
|
||||
device=sensor,
|
||||
parent_id=session.information.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
)
|
||||
)
|
||||
entities.append(
|
||||
AirQualitySensor(
|
||||
device=sensor,
|
||||
parent_id=session.information.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
)
|
||||
)
|
||||
entities.append(
|
||||
TemperatureRatingSensor(
|
||||
device=sensor,
|
||||
parent_id=session.information.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
)
|
||||
)
|
||||
entities.append(
|
||||
HumidityRatingSensor(
|
||||
device=sensor,
|
||||
parent_id=session.information.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
)
|
||||
)
|
||||
entities.append(
|
||||
PurityRatingSensor(
|
||||
device=sensor,
|
||||
parent_id=session.information.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
)
|
||||
for device in session.device_helper.twinguards
|
||||
for sensor_type in (
|
||||
TEMPERATURE_SENSOR,
|
||||
HUMIDITY_SENSOR,
|
||||
PURITY_SENSOR,
|
||||
AIR_QUALITY_SENSOR,
|
||||
TEMPERATURE_RATING_SENSOR,
|
||||
HUMIDITY_RATING_SENSOR,
|
||||
PURITY_RATING_SENSOR,
|
||||
)
|
||||
)
|
||||
|
||||
for sensor in (
|
||||
session.device_helper.smart_plugs + session.device_helper.light_switches_bsm
|
||||
):
|
||||
entities.append(
|
||||
PowerSensor(
|
||||
device=sensor,
|
||||
parent_id=session.information.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
)
|
||||
entities.extend(
|
||||
SHCSensor(
|
||||
device,
|
||||
SENSOR_DESCRIPTIONS[sensor_type],
|
||||
session.information.unique_id,
|
||||
config_entry.entry_id,
|
||||
)
|
||||
entities.append(
|
||||
EnergySensor(
|
||||
device=sensor,
|
||||
parent_id=session.information.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
)
|
||||
for device in (
|
||||
session.device_helper.smart_plugs + session.device_helper.light_switches_bsm
|
||||
)
|
||||
for sensor_type in (POWER_SENSOR, ENERGY_SENSOR)
|
||||
)
|
||||
|
||||
for sensor in session.device_helper.smart_plugs_compact:
|
||||
entities.append(
|
||||
PowerSensor(
|
||||
device=sensor,
|
||||
parent_id=session.information.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
)
|
||||
)
|
||||
entities.append(
|
||||
EnergySensor(
|
||||
device=sensor,
|
||||
parent_id=session.information.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
)
|
||||
)
|
||||
entities.append(
|
||||
CommunicationQualitySensor(
|
||||
device=sensor,
|
||||
parent_id=session.information.unique_id,
|
||||
entry_id=config_entry.entry_id,
|
||||
)
|
||||
entities.extend(
|
||||
SHCSensor(
|
||||
device,
|
||||
SENSOR_DESCRIPTIONS[sensor_type],
|
||||
session.information.unique_id,
|
||||
config_entry.entry_id,
|
||||
)
|
||||
for device in session.device_helper.smart_plugs_compact
|
||||
for sensor_type in (POWER_SENSOR, ENERGY_SENSOR, COMMUNICATION_QUALITY_SENSOR)
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class TemperatureSensor(SHCEntity, SensorEntity):
|
||||
"""Representation of an SHC temperature reporting sensor."""
|
||||
class SHCSensor(SHCEntity, SensorEntity):
|
||||
"""Representation of a SHC sensor."""
|
||||
|
||||
_attr_device_class = SensorDeviceClass.TEMPERATURE
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
_attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
|
||||
entity_description: SHCSensorEntityDescription
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
"""Initialize an SHC temperature reporting sensor."""
|
||||
def __init__(
|
||||
self,
|
||||
device: SHCDevice,
|
||||
entity_description: SHCSensorEntityDescription,
|
||||
parent_id: str,
|
||||
entry_id: str,
|
||||
) -> None:
|
||||
"""Initialize sensor."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
self._attr_unique_id = f"{device.serial}_temperature"
|
||||
self.entity_description = entity_description
|
||||
self._attr_unique_id = f"{device.serial}_{entity_description.key}"
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the sensor."""
|
||||
return self._device.temperature
|
||||
|
||||
|
||||
class HumiditySensor(SHCEntity, SensorEntity):
|
||||
"""Representation of an SHC humidity reporting sensor."""
|
||||
|
||||
_attr_device_class = SensorDeviceClass.HUMIDITY
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
"""Initialize an SHC humidity reporting sensor."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
self._attr_unique_id = f"{device.serial}_humidity"
|
||||
return self.entity_description.value_fn(self._device)
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._device.humidity
|
||||
|
||||
|
||||
class PuritySensor(SHCEntity, SensorEntity):
|
||||
"""Representation of an SHC purity reporting sensor."""
|
||||
|
||||
_attr_translation_key = "purity"
|
||||
_attr_native_unit_of_measurement = CONCENTRATION_PARTS_PER_MILLION
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
"""Initialize an SHC purity reporting sensor."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
self._attr_unique_id = f"{device.serial}_purity"
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._device.purity
|
||||
|
||||
|
||||
class AirQualitySensor(SHCEntity, SensorEntity):
|
||||
"""Representation of an SHC airquality reporting sensor."""
|
||||
|
||||
_attr_translation_key = "air_quality"
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
"""Initialize an SHC airquality reporting sensor."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
self._attr_unique_id = f"{device.serial}_airquality"
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._device.combined_rating.name
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
def extra_state_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return the state attributes."""
|
||||
return {
|
||||
"rating_description": self._device.description,
|
||||
}
|
||||
|
||||
|
||||
class TemperatureRatingSensor(SHCEntity, SensorEntity):
|
||||
"""Representation of an SHC temperature rating sensor."""
|
||||
|
||||
_attr_translation_key = "temperature_rating"
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
"""Initialize an SHC temperature rating sensor."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
self._attr_unique_id = f"{device.serial}_temperature_rating"
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._device.temperature_rating.name
|
||||
|
||||
|
||||
class CommunicationQualitySensor(SHCEntity, SensorEntity):
|
||||
"""Representation of an SHC communication quality reporting sensor."""
|
||||
|
||||
_attr_translation_key = "communication_quality"
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
"""Initialize an SHC communication quality reporting sensor."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
self._attr_unique_id = f"{device.serial}_communication_quality"
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._device.communicationquality.name
|
||||
|
||||
|
||||
class HumidityRatingSensor(SHCEntity, SensorEntity):
|
||||
"""Representation of an SHC humidity rating sensor."""
|
||||
|
||||
_attr_translation_key = "humidity_rating"
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
"""Initialize an SHC humidity rating sensor."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
self._attr_unique_id = f"{device.serial}_humidity_rating"
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._device.humidity_rating.name
|
||||
|
||||
|
||||
class PurityRatingSensor(SHCEntity, SensorEntity):
|
||||
"""Representation of an SHC purity rating sensor."""
|
||||
|
||||
_attr_translation_key = "purity_rating"
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
"""Initialize an SHC purity rating sensor."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
self._attr_unique_id = f"{device.serial}_purity_rating"
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._device.purity_rating.name
|
||||
|
||||
|
||||
class PowerSensor(SHCEntity, SensorEntity):
|
||||
"""Representation of an SHC power reporting sensor."""
|
||||
|
||||
_attr_device_class = SensorDeviceClass.POWER
|
||||
_attr_native_unit_of_measurement = UnitOfPower.WATT
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
"""Initialize an SHC power reporting sensor."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
self._attr_unique_id = f"{device.serial}_power"
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._device.powerconsumption
|
||||
|
||||
|
||||
class EnergySensor(SHCEntity, SensorEntity):
|
||||
"""Representation of an SHC energy reporting sensor."""
|
||||
|
||||
_attr_device_class = SensorDeviceClass.ENERGY
|
||||
_attr_state_class = SensorStateClass.TOTAL_INCREASING
|
||||
_attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
"""Initialize an SHC energy reporting sensor."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
self._attr_unique_id = f"{self._device.serial}_energy"
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._device.energyconsumption / 1000.0
|
||||
|
||||
|
||||
class ValveTappetSensor(SHCEntity, SensorEntity):
|
||||
"""Representation of an SHC valve tappet reporting sensor."""
|
||||
|
||||
_attr_translation_key = "valvetappet"
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
_attr_native_unit_of_measurement = PERCENTAGE
|
||||
|
||||
def __init__(self, device: SHCDevice, parent_id: str, entry_id: str) -> None:
|
||||
"""Initialize an SHC valve tappet reporting sensor."""
|
||||
super().__init__(device, parent_id, entry_id)
|
||||
self._attr_unique_id = f"{device.serial}_valvetappet"
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._device.position
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
return {
|
||||
"valve_tappet_state": self._device.valvestate.name,
|
||||
}
|
||||
if self.entity_description.attributes_fn is not None:
|
||||
return self.entity_description.attributes_fn(self._device)
|
||||
return None
|
||||
|
@ -43,21 +43,21 @@ SWITCH_TYPES: dict[str, SHCSwitchEntityDescription] = {
|
||||
"smartplug": SHCSwitchEntityDescription(
|
||||
key="smartplug",
|
||||
device_class=SwitchDeviceClass.OUTLET,
|
||||
on_key="state",
|
||||
on_key="switchstate",
|
||||
on_value=SHCSmartPlug.PowerSwitchService.State.ON,
|
||||
should_poll=False,
|
||||
),
|
||||
"smartplugcompact": SHCSwitchEntityDescription(
|
||||
key="smartplugcompact",
|
||||
device_class=SwitchDeviceClass.OUTLET,
|
||||
on_key="state",
|
||||
on_key="switchstate",
|
||||
on_value=SHCSmartPlugCompact.PowerSwitchService.State.ON,
|
||||
should_poll=False,
|
||||
),
|
||||
"lightswitch": SHCSwitchEntityDescription(
|
||||
key="lightswitch",
|
||||
device_class=SwitchDeviceClass.SWITCH,
|
||||
on_key="state",
|
||||
on_key="switchstate",
|
||||
on_value=SHCLightSwitch.PowerSwitchService.State.ON,
|
||||
should_poll=False,
|
||||
),
|
||||
|
@ -12,9 +12,10 @@ from homeassistant.const import CONF_HOST, CONF_MAC, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_create_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import BraviaTVCoordinator
|
||||
|
||||
BraviaTVConfigEntry = ConfigEntry[BraviaTVCoordinator]
|
||||
|
||||
PLATFORMS: Final[list[Platform]] = [
|
||||
Platform.BUTTON,
|
||||
Platform.MEDIA_PLAYER,
|
||||
@ -22,7 +23,9 @@ PLATFORMS: Final[list[Platform]] = [
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, config_entry: BraviaTVConfigEntry
|
||||
) -> bool:
|
||||
"""Set up a config entry."""
|
||||
host = config_entry.data[CONF_HOST]
|
||||
mac = config_entry.data[CONF_MAC]
|
||||
@ -40,26 +43,22 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][config_entry.entry_id] = coordinator
|
||||
config_entry.runtime_data = coordinator
|
||||
|
||||
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: BraviaTVConfigEntry
|
||||
) -> bool:
|
||||
"""Unload a config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(
|
||||
config_entry, PLATFORMS
|
||||
)
|
||||
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(config_entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||
async def update_listener(
|
||||
hass: HomeAssistant, config_entry: BraviaTVConfigEntry
|
||||
) -> None:
|
||||
"""Handle options update."""
|
||||
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
|
@ -10,12 +10,11 @@ from homeassistant.components.button import (
|
||||
ButtonEntity,
|
||||
ButtonEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from . import BraviaTVConfigEntry
|
||||
from .coordinator import BraviaTVCoordinator
|
||||
from .entity import BraviaTVEntity
|
||||
|
||||
@ -45,12 +44,12 @@ BUTTONS: tuple[BraviaTVButtonDescription, ...] = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: BraviaTVConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Bravia TV Button entities."""
|
||||
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
coordinator = config_entry.runtime_data
|
||||
unique_id = config_entry.unique_id
|
||||
assert unique_id is not None
|
||||
|
||||
|
@ -3,21 +3,19 @@
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_MAC, CONF_PIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import BraviaTVCoordinator
|
||||
from . import BraviaTVConfigEntry
|
||||
|
||||
TO_REDACT = {CONF_MAC, CONF_PIN, "macAddr"}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
hass: HomeAssistant, config_entry: BraviaTVConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
coordinator: BraviaTVCoordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
device_info = await coordinator.client.get_system_info()
|
||||
|
||||
|
@ -15,22 +15,22 @@ from homeassistant.components.media_player import (
|
||||
MediaType,
|
||||
)
|
||||
from homeassistant.components.media_player.browse_media import BrowseMedia
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN, SourceType
|
||||
from . import BraviaTVConfigEntry
|
||||
from .const import SourceType
|
||||
from .entity import BraviaTVEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: BraviaTVConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Bravia TV Media Player from a config_entry."""
|
||||
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
coordinator = config_entry.runtime_data
|
||||
unique_id = config_entry.unique_id
|
||||
assert unique_id is not None
|
||||
|
||||
|
@ -6,22 +6,21 @@ from collections.abc import Iterable
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.remote import ATTR_NUM_REPEATS, RemoteEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from . import BraviaTVConfigEntry
|
||||
from .entity import BraviaTVEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: BraviaTVConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Bravia TV Remote from a config entry."""
|
||||
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
coordinator = config_entry.runtime_data
|
||||
unique_id = config_entry.unique_id
|
||||
assert unique_id is not None
|
||||
|
||||
|
@ -24,8 +24,10 @@ PLATFORMS: list[Platform] = [Platform.TODO]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
BringConfigEntry = ConfigEntry[BringDataUpdateCoordinator]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: BringConfigEntry) -> bool:
|
||||
"""Set up Bring! from a config entry."""
|
||||
|
||||
email = entry.data[CONF_EMAIL]
|
||||
@ -57,7 +59,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
coordinator = BringDataUpdateCoordinator(hass, bring)
|
||||
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)
|
||||
|
||||
@ -66,7 +68,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)
|
||||
|
@ -15,7 +15,6 @@ from homeassistant.components.todo import (
|
||||
TodoListEntity,
|
||||
TodoListEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||
@ -23,6 +22,7 @@ from homeassistant.helpers.config_validation import make_entity_service_schema
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import BringConfigEntry
|
||||
from .const import (
|
||||
ATTR_ITEM_NAME,
|
||||
ATTR_NOTIFICATION_TYPE,
|
||||
@ -34,11 +34,11 @@ from .coordinator import BringData, BringDataUpdateCoordinator
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: BringConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the sensor from a config entry created in the integrations UI."""
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
unique_id = config_entry.unique_id
|
||||
|
||||
|
@ -373,8 +373,11 @@ class BroadlinkRemote(BroadlinkEntity, RemoteEntity, RestoreEntity):
|
||||
start_time = dt_util.utcnow()
|
||||
while (dt_util.utcnow() - start_time) < LEARNING_TIMEOUT:
|
||||
await asyncio.sleep(1)
|
||||
found = await device.async_request(device.api.check_frequency)[0]
|
||||
if found:
|
||||
is_found, frequency = await device.async_request(
|
||||
device.api.check_frequency
|
||||
)
|
||||
if is_found:
|
||||
_LOGGER.info("Radiofrequency detected: %s MHz", frequency)
|
||||
break
|
||||
else:
|
||||
await device.async_request(device.api.cancel_sweep_frequency)
|
||||
|
@ -2,29 +2,23 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from asyncio import timeout
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from brother import Brother, SnmpError
|
||||
|
||||
from brother import Brother, BrotherSensors, SnmpError, UnsupportedModelError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||
from homeassistant.const import CONF_HOST, CONF_TYPE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DATA_CONFIG_ENTRY, DOMAIN, SNMP
|
||||
from .const import DOMAIN, SNMP
|
||||
from .coordinator import BrotherDataUpdateCoordinator
|
||||
from .utils import get_snmp_engine
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
BrotherConfigEntry = ConfigEntry[BrotherDataUpdateCoordinator]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: BrotherConfigEntry) -> bool:
|
||||
"""Set up Brother from a config entry."""
|
||||
host = entry.data[CONF_HOST]
|
||||
printer_type = entry.data[CONF_TYPE]
|
||||
@ -40,48 +34,25 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
coordinator = BrotherDataUpdateCoordinator(hass, brother)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN].setdefault(DATA_CONFIG_ENTRY, {})
|
||||
hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id] = coordinator
|
||||
hass.data[DOMAIN][SNMP] = snmp_engine
|
||||
entry.runtime_data = coordinator
|
||||
hass.data.setdefault(DOMAIN, {SNMP: snmp_engine})
|
||||
|
||||
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: BrotherConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN][DATA_CONFIG_ENTRY].pop(entry.entry_id)
|
||||
if not hass.data[DOMAIN][DATA_CONFIG_ENTRY]:
|
||||
hass.data[DOMAIN].pop(SNMP)
|
||||
hass.data[DOMAIN].pop(DATA_CONFIG_ENTRY)
|
||||
loaded_entries = [
|
||||
entry
|
||||
for entry in hass.config_entries.async_entries(DOMAIN)
|
||||
if entry.state == ConfigEntryState.LOADED
|
||||
]
|
||||
# We only want to remove the SNMP engine when unloading the last config entry
|
||||
if unload_ok and len(loaded_entries) == 1:
|
||||
hass.data[DOMAIN].pop(SNMP)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
class BrotherDataUpdateCoordinator(DataUpdateCoordinator[BrotherSensors]): # pylint: disable=hass-enforce-coordinator-module
|
||||
"""Class to manage fetching Brother data from the printer."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, brother: Brother) -> None:
|
||||
"""Initialize."""
|
||||
self.brother = brother
|
||||
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=SCAN_INTERVAL,
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> BrotherSensors:
|
||||
"""Update data via library."""
|
||||
try:
|
||||
async with timeout(20):
|
||||
data = await self.brother.async_update()
|
||||
except (ConnectionError, SnmpError, UnsupportedModelError) as error:
|
||||
raise UpdateFailed(error) from error
|
||||
return data
|
||||
|
@ -2,12 +2,13 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from typing import Final
|
||||
|
||||
DATA_CONFIG_ENTRY: Final = "config_entry"
|
||||
|
||||
DOMAIN: Final = "brother"
|
||||
|
||||
PRINTER_TYPES: Final = ["laser", "ink"]
|
||||
|
||||
SNMP: Final = "snmp"
|
||||
|
||||
UPDATE_INTERVAL = timedelta(seconds=30)
|
||||
|
37
homeassistant/components/brother/coordinator.py
Normal file
37
homeassistant/components/brother/coordinator.py
Normal file
@ -0,0 +1,37 @@
|
||||
"""Coordinator for Brother integration."""
|
||||
|
||||
from asyncio import timeout
|
||||
import logging
|
||||
|
||||
from brother import Brother, BrotherSensors, SnmpError, UnsupportedModelError
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN, UPDATE_INTERVAL
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BrotherDataUpdateCoordinator(DataUpdateCoordinator[BrotherSensors]):
|
||||
"""Class to manage fetching Brother data from the printer."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, brother: Brother) -> None:
|
||||
"""Initialize."""
|
||||
self.brother = brother
|
||||
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=UPDATE_INTERVAL,
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> BrotherSensors:
|
||||
"""Update data via library."""
|
||||
try:
|
||||
async with timeout(20):
|
||||
data = await self.brother.async_update()
|
||||
except (ConnectionError, SnmpError, UnsupportedModelError) as error:
|
||||
raise UpdateFailed(error) from error
|
||||
return data
|
@ -5,20 +5,16 @@ from __future__ import annotations
|
||||
from dataclasses import asdict
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import BrotherDataUpdateCoordinator
|
||||
from .const import DATA_CONFIG_ENTRY, DOMAIN
|
||||
from . import BrotherConfigEntry
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
hass: HomeAssistant, config_entry: BrotherConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
coordinator: BrotherDataUpdateCoordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
|
||||
config_entry.entry_id
|
||||
]
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
return {
|
||||
"info": dict(config_entry.data),
|
||||
|
@ -16,7 +16,6 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
@ -25,8 +24,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import BrotherDataUpdateCoordinator
|
||||
from .const import DATA_CONFIG_ENTRY, DOMAIN
|
||||
from . import BrotherConfigEntry, BrotherDataUpdateCoordinator
|
||||
from .const import DOMAIN
|
||||
|
||||
ATTR_COUNTER = "counter"
|
||||
ATTR_REMAINING_PAGES = "remaining_pages"
|
||||
@ -318,11 +317,12 @@ SENSOR_TYPES: tuple[BrotherSensorEntityDescription, ...] = (
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant,
|
||||
entry: BrotherConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add Brother entities from a config_entry."""
|
||||
coordinator = hass.data[DOMAIN][DATA_CONFIG_ENTRY][entry.entry_id]
|
||||
|
||||
coordinator = entry.runtime_data
|
||||
# Due to the change of the attribute name of one sensor, it is necessary to migrate
|
||||
# the unique_id to the new one.
|
||||
entity_registry = er.async_get(hass)
|
||||
|
@ -7,21 +7,21 @@ from homeassistant.const import CONF_HOST, CONF_PORT, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.start import async_at_started
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import CertExpiryDataUpdateCoordinator
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
CertExpiryConfigEntry = ConfigEntry[CertExpiryDataUpdateCoordinator]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: CertExpiryConfigEntry) -> bool:
|
||||
"""Load the saved entities."""
|
||||
host: str = entry.data[CONF_HOST]
|
||||
port: int = entry.data[CONF_PORT]
|
||||
|
||||
coordinator = CertExpiryDataUpdateCoordinator(hass, host, port)
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
if entry.unique_id is None:
|
||||
hass.config_entries.async_update_entry(entry, unique_id=f"{host}:{port}")
|
||||
|
@ -12,7 +12,7 @@ from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_START
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
@ -22,7 +22,7 @@ from homeassistant.helpers.event import async_call_later
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import CertExpiryDataUpdateCoordinator
|
||||
from . import CertExpiryConfigEntry, CertExpiryDataUpdateCoordinator
|
||||
from .const import DEFAULT_PORT, DOMAIN
|
||||
|
||||
SCAN_INTERVAL = timedelta(hours=12)
|
||||
@ -62,15 +62,13 @@ async def async_setup_platform(
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: CertExpiryConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add cert-expiry entry."""
|
||||
coordinator: CertExpiryDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
sensors = [
|
||||
SSLCertificateTimestamp(coordinator),
|
||||
]
|
||||
sensors = [SSLCertificateTimestamp(coordinator)]
|
||||
|
||||
async_add_entities(sensors, True)
|
||||
|
||||
|
@ -142,6 +142,9 @@ async def websocket_list_agents(
|
||||
agent = manager.async_get_agent(agent_info.id)
|
||||
assert agent is not None
|
||||
|
||||
if isinstance(agent, ConversationEntity):
|
||||
continue
|
||||
|
||||
supported_languages = agent.supported_languages
|
||||
if language and supported_languages != MATCH_ALL:
|
||||
supported_languages = language_util.matches(
|
||||
|
@ -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)
|
||||
|
@ -18,19 +18,15 @@ from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
|
||||
from .const import (
|
||||
CONF_MYDEVOLO,
|
||||
DEFAULT_MYDEVOLO,
|
||||
DOMAIN,
|
||||
GATEWAY_SERIAL_PATTERN,
|
||||
PLATFORMS,
|
||||
)
|
||||
from .const import CONF_MYDEVOLO, DEFAULT_MYDEVOLO, GATEWAY_SERIAL_PATTERN, PLATFORMS
|
||||
|
||||
DevoloHomeControlConfigEntry = ConfigEntry[list[HomeControl]]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: DevoloHomeControlConfigEntry
|
||||
) -> bool:
|
||||
"""Set up the devolo account from a config entry."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
mydevolo = configure_mydevolo(entry.data)
|
||||
|
||||
credentials_valid = await hass.async_add_executor_job(mydevolo.credentials_valid)
|
||||
@ -47,11 +43,22 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
uuid = await hass.async_add_executor_job(mydevolo.uuid)
|
||||
hass.config_entries.async_update_entry(entry, unique_id=uuid)
|
||||
|
||||
def shutdown(event: Event) -> None:
|
||||
for gateway in entry.runtime_data:
|
||||
gateway.websocket_disconnect(
|
||||
f"websocket disconnect requested by {EVENT_HOMEASSISTANT_STOP}"
|
||||
)
|
||||
|
||||
# Listen when EVENT_HOMEASSISTANT_STOP is fired
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown)
|
||||
)
|
||||
|
||||
try:
|
||||
zeroconf_instance = await zeroconf.async_get_instance(hass)
|
||||
hass.data[DOMAIN][entry.entry_id] = {"gateways": [], "listener": None}
|
||||
entry.runtime_data = []
|
||||
for gateway_id in gateway_ids:
|
||||
hass.data[DOMAIN][entry.entry_id]["gateways"].append(
|
||||
entry.runtime_data.append(
|
||||
await hass.async_add_executor_job(
|
||||
partial(
|
||||
HomeControl,
|
||||
@ -66,31 +73,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
def shutdown(event: Event) -> None:
|
||||
for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"]:
|
||||
gateway.websocket_disconnect(
|
||||
f"websocket disconnect requested by {EVENT_HOMEASSISTANT_STOP}"
|
||||
)
|
||||
|
||||
# Listen when EVENT_HOMEASSISTANT_STOP is fired
|
||||
hass.data[DOMAIN][entry.entry_id]["listener"] = hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_STOP, shutdown
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(
|
||||
hass: HomeAssistant, entry: DevoloHomeControlConfigEntry
|
||||
) -> bool:
|
||||
"""Unload a config entry."""
|
||||
unload = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
await asyncio.gather(
|
||||
*(
|
||||
hass.async_add_executor_job(gateway.websocket_disconnect)
|
||||
for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"]
|
||||
for gateway in entry.runtime_data
|
||||
)
|
||||
)
|
||||
hass.data[DOMAIN][entry.entry_id]["listener"]()
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
return unload
|
||||
|
||||
|
||||
|
@ -9,12 +9,11 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
BinarySensorEntity,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from . import DevoloHomeControlConfigEntry
|
||||
from .devolo_device import DevoloDeviceEntity
|
||||
|
||||
DEVICE_CLASS_MAPPING = {
|
||||
@ -28,12 +27,14 @@ DEVICE_CLASS_MAPPING = {
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant,
|
||||
entry: DevoloHomeControlConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Get all binary sensor and multi level sensor devices and setup them via config entry."""
|
||||
entities: list[BinarySensorEntity] = []
|
||||
|
||||
for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"]:
|
||||
for gateway in entry.runtime_data:
|
||||
entities.extend(
|
||||
DevoloBinaryDeviceEntity(
|
||||
homecontrol=gateway,
|
||||
|
@ -13,17 +13,18 @@ from homeassistant.components.climate import (
|
||||
ClimateEntityFeature,
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import PRECISION_HALVES, PRECISION_TENTHS, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from . import DevoloHomeControlConfigEntry
|
||||
from .devolo_multi_level_switch import DevoloMultiLevelSwitchDeviceEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant,
|
||||
entry: DevoloHomeControlConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Get all cover devices and setup them via config entry."""
|
||||
|
||||
@ -33,7 +34,7 @@ async def async_setup_entry(
|
||||
device_instance=device,
|
||||
element_uid=multi_level_switch,
|
||||
)
|
||||
for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"]
|
||||
for gateway in entry.runtime_data
|
||||
for device in gateway.multi_level_switch_devices
|
||||
for multi_level_switch in device.multi_level_switch_property
|
||||
if device.device_model_uid
|
||||
|
@ -9,16 +9,17 @@ 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 DevoloHomeControlConfigEntry
|
||||
from .devolo_multi_level_switch import DevoloMultiLevelSwitchDeviceEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant,
|
||||
entry: DevoloHomeControlConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Get all cover devices and setup them via config entry."""
|
||||
|
||||
@ -28,7 +29,7 @@ async def async_setup_entry(
|
||||
device_instance=device,
|
||||
element_uid=multi_level_switch,
|
||||
)
|
||||
for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"]
|
||||
for gateway in entry.runtime_data
|
||||
for device in gateway.multi_level_switch_devices
|
||||
for multi_level_switch in device.multi_level_switch_property
|
||||
if multi_level_switch.startswith("devolo.Blinds")
|
||||
|
@ -4,24 +4,19 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from devolo_home_control_api.homecontrol import HomeControl
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN
|
||||
from . import DevoloHomeControlConfigEntry
|
||||
|
||||
TO_REDACT = {CONF_PASSWORD, CONF_USERNAME}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
hass: HomeAssistant, entry: DevoloHomeControlConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
gateways: list[HomeControl] = hass.data[DOMAIN][entry.entry_id]["gateways"]
|
||||
|
||||
device_info = [
|
||||
{
|
||||
"gateway": {
|
||||
@ -38,7 +33,7 @@ async def async_get_config_entry_diagnostics(
|
||||
for device_id, properties in gateway.devices.items()
|
||||
],
|
||||
}
|
||||
for gateway in gateways
|
||||
for gateway in entry.runtime_data
|
||||
]
|
||||
|
||||
return {
|
||||
|
@ -8,16 +8,17 @@ from devolo_home_control_api.devices.zwave import Zwave
|
||||
from devolo_home_control_api.homecontrol import HomeControl
|
||||
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from . import DevoloHomeControlConfigEntry
|
||||
from .devolo_multi_level_switch import DevoloMultiLevelSwitchDeviceEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant,
|
||||
entry: DevoloHomeControlConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Get all light devices and setup them via config entry."""
|
||||
|
||||
@ -27,7 +28,7 @@ async def async_setup_entry(
|
||||
device_instance=device,
|
||||
element_uid=multi_level_switch.element_uid,
|
||||
)
|
||||
for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"]
|
||||
for gateway in entry.runtime_data
|
||||
for device in gateway.multi_level_switch_devices
|
||||
for multi_level_switch in device.multi_level_switch_property.values()
|
||||
if multi_level_switch.switch_type == "dimmer"
|
||||
|
@ -10,12 +10,11 @@ from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from . import DevoloHomeControlConfigEntry
|
||||
from .devolo_device import DevoloDeviceEntity
|
||||
|
||||
DEVICE_CLASS_MAPPING = {
|
||||
@ -39,12 +38,14 @@ STATE_CLASS_MAPPING = {
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant,
|
||||
entry: DevoloHomeControlConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Get all sensor devices and setup them via config entry."""
|
||||
entities: list[SensorEntity] = []
|
||||
|
||||
for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"]:
|
||||
for gateway in entry.runtime_data:
|
||||
entities.extend(
|
||||
DevoloGenericMultiLevelDeviceEntity(
|
||||
homecontrol=gateway,
|
||||
|
@ -6,16 +6,17 @@ from devolo_home_control_api.devices.zwave import Zwave
|
||||
from devolo_home_control_api.homecontrol import HomeControl
|
||||
|
||||
from homeassistant.components.siren import ATTR_TONE, SirenEntity, SirenEntityFeature
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from . import DevoloHomeControlConfigEntry
|
||||
from .devolo_multi_level_switch import DevoloMultiLevelSwitchDeviceEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant,
|
||||
entry: DevoloHomeControlConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Get all binary sensor and multi level sensor devices and setup them via config entry."""
|
||||
|
||||
@ -25,7 +26,7 @@ async def async_setup_entry(
|
||||
device_instance=device,
|
||||
element_uid=multi_level_switch,
|
||||
)
|
||||
for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"]
|
||||
for gateway in entry.runtime_data
|
||||
for device in gateway.multi_level_switch_devices
|
||||
for multi_level_switch in device.multi_level_switch_property
|
||||
if multi_level_switch.startswith("devolo.SirenMultiLevelSwitch")
|
||||
|
@ -8,16 +8,17 @@ from devolo_home_control_api.devices.zwave import Zwave
|
||||
from devolo_home_control_api.homecontrol import HomeControl
|
||||
|
||||
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 .const import DOMAIN
|
||||
from . import DevoloHomeControlConfigEntry
|
||||
from .devolo_device import DevoloDeviceEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant,
|
||||
entry: DevoloHomeControlConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Get all devices and setup the switch devices via config entry."""
|
||||
|
||||
@ -27,7 +28,7 @@ async def async_setup_entry(
|
||||
device_instance=device,
|
||||
element_uid=binary_switch,
|
||||
)
|
||||
for gateway in hass.data[DOMAIN][entry.entry_id]["gateways"]
|
||||
for gateway in entry.runtime_data
|
||||
for device in gateway.binary_switch_devices
|
||||
for binary_switch in device.binary_switch_property
|
||||
# Exclude the binary switch which also has multi_level_switches here,
|
||||
|
@ -12,16 +12,15 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers.httpx_client import get_async_client
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import DiscovergyUpdateCoordinator
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
DiscovergyConfigEntry = ConfigEntry[list[DiscovergyUpdateCoordinator]]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: DiscovergyConfigEntry) -> bool:
|
||||
"""Set up Discovergy from a config entry."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
client = Discovergy(
|
||||
email=entry.data[CONF_EMAIL],
|
||||
password=entry.data[CONF_PASSWORD],
|
||||
@ -53,7 +52,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
coordinators.append(coordinator)
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinators
|
||||
entry.runtime_data = coordinators
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
|
||||
@ -63,11 +62,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> 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 async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
|
@ -6,11 +6,9 @@ from dataclasses import asdict
|
||||
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 .coordinator import DiscovergyUpdateCoordinator
|
||||
from . import DiscovergyConfigEntry
|
||||
|
||||
TO_REDACT_METER = {
|
||||
"serial_number",
|
||||
@ -22,14 +20,13 @@ TO_REDACT_METER = {
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
hass: HomeAssistant, entry: DiscovergyConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
flattened_meter: list[dict] = []
|
||||
last_readings: dict[str, dict] = {}
|
||||
coordinators: list[DiscovergyUpdateCoordinator] = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
for coordinator in coordinators:
|
||||
for coordinator in entry.runtime_data:
|
||||
# make a dict of meter data and redact some data
|
||||
flattened_meter.append(
|
||||
async_redact_data(asdict(coordinator.meter), TO_REDACT_METER)
|
||||
|
@ -12,7 +12,6 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
EntityCategory,
|
||||
UnitOfElectricPotential,
|
||||
@ -25,6 +24,7 @@ from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import DiscovergyConfigEntry
|
||||
from .const import DOMAIN, MANUFACTURER
|
||||
from .coordinator import DiscovergyUpdateCoordinator
|
||||
|
||||
@ -163,13 +163,13 @@ ADDITIONAL_SENSORS: tuple[DiscovergySensorEntityDescription, ...] = (
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant,
|
||||
entry: DiscovergyConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Discovergy sensors."""
|
||||
coordinators: list[DiscovergyUpdateCoordinator] = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
entities: list[DiscovergySensor] = []
|
||||
for coordinator in coordinators:
|
||||
for coordinator in entry.runtime_data:
|
||||
sensors: tuple[DiscovergySensorEntityDescription, ...] = ()
|
||||
|
||||
# select sensor descriptions based on meter type and combine with additional sensors
|
||||
|
@ -2,27 +2,27 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import DOMAIN, PLATFORMS
|
||||
from .coordinator import DwdWeatherWarningsCoordinator
|
||||
from .const import PLATFORMS
|
||||
from .coordinator import DwdWeatherWarningsConfigEntry, DwdWeatherWarningsCoordinator
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: DwdWeatherWarningsConfigEntry
|
||||
) -> bool:
|
||||
"""Set up a config entry."""
|
||||
coordinator = DwdWeatherWarningsCoordinator(hass)
|
||||
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: DwdWeatherWarningsConfigEntry
|
||||
) -> 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)
|
||||
|
@ -19,11 +19,13 @@ from .const import (
|
||||
from .exceptions import EntityNotFoundError
|
||||
from .util import get_position_data
|
||||
|
||||
DwdWeatherWarningsConfigEntry = ConfigEntry["DwdWeatherWarningsCoordinator"]
|
||||
|
||||
|
||||
class DwdWeatherWarningsCoordinator(DataUpdateCoordinator[None]):
|
||||
"""Custom coordinator for the dwd_weather_warnings integration."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
config_entry: DwdWeatherWarningsConfigEntry
|
||||
api: DwdWeatherWarningsAPI
|
||||
|
||||
def __init__(self, hass: HomeAssistant) -> None:
|
||||
|
@ -14,7 +14,6 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
@ -40,7 +39,7 @@ from .const import (
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
)
|
||||
from .coordinator import DwdWeatherWarningsCoordinator
|
||||
from .coordinator import DwdWeatherWarningsConfigEntry, DwdWeatherWarningsCoordinator
|
||||
|
||||
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
SensorEntityDescription(
|
||||
@ -55,10 +54,12 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant,
|
||||
entry: DwdWeatherWarningsConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up entities from config entry."""
|
||||
coordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
@ -80,7 +81,7 @@ class DwdWeatherWarningsSensor(
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: DwdWeatherWarningsCoordinator,
|
||||
entry: ConfigEntry,
|
||||
entry: DwdWeatherWarningsConfigEntry,
|
||||
description: SensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a DWD-Weather-Warnings sensor."""
|
||||
|
@ -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)
|
||||
|
@ -17,7 +17,7 @@ from homeassistant.components.light import (
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
@ -94,7 +94,7 @@ class GoveeLight(CoordinatorEntity[GoveeLocalApiCoordinator], LightEntity):
|
||||
name=device.sku,
|
||||
manufacturer=MANUFACTURER,
|
||||
model=device.sku,
|
||||
connections={(CONNECTION_NETWORK_MAC, device.fingerprint)},
|
||||
serial_number=device.fingerprint,
|
||||
)
|
||||
|
||||
@property
|
||||
|
@ -6,5 +6,5 @@
|
||||
"dependencies": ["network"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/govee_light_local",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["govee-local-api==1.4.4"]
|
||||
"requirements": ["govee-local-api==1.4.5"]
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
"""The habitica integration."""
|
||||
|
||||
from http import HTTPStatus
|
||||
import logging
|
||||
|
||||
from aiohttp import ClientResponseError
|
||||
from habitipy.aio import HabitipyAsync
|
||||
import voluptuous as vol
|
||||
|
||||
@ -16,6 +18,7 @@ from homeassistant.const import (
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
@ -30,9 +33,12 @@ from .const import (
|
||||
EVENT_API_CALL_SUCCESS,
|
||||
SERVICE_API_CALL,
|
||||
)
|
||||
from .coordinator import HabiticaDataUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
HabiticaConfigEntry = ConfigEntry[HabiticaDataUpdateCoordinator]
|
||||
|
||||
SENSORS_TYPES = ["name", "hp", "maxHealth", "mp", "maxMP", "exp", "toNextLevel", "lvl"]
|
||||
|
||||
INSTANCE_SCHEMA = vol.All(
|
||||
@ -104,7 +110,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: HabiticaConfigEntry) -> bool:
|
||||
"""Set up habitica from a config entry."""
|
||||
|
||||
class HAHabitipyAsync(HabitipyAsync):
|
||||
@ -120,7 +126,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
api = None
|
||||
for entry in entries:
|
||||
if entry.data[CONF_NAME] == name:
|
||||
api = hass.data[DOMAIN].get(entry.entry_id)
|
||||
api = entry.runtime_data.api
|
||||
break
|
||||
if api is None:
|
||||
_LOGGER.error("API_CALL: User '%s' not configured", name)
|
||||
@ -139,24 +145,40 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
EVENT_API_CALL_SUCCESS, {ATTR_NAME: name, ATTR_PATH: path, ATTR_DATA: data}
|
||||
)
|
||||
|
||||
data = hass.data.setdefault(DOMAIN, {})
|
||||
config = entry.data
|
||||
websession = async_get_clientsession(hass)
|
||||
url = config[CONF_URL]
|
||||
username = config[CONF_API_USER]
|
||||
password = config[CONF_API_KEY]
|
||||
name = config.get(CONF_NAME)
|
||||
config_dict = {"url": url, "login": username, "password": password}
|
||||
api = HAHabitipyAsync(config_dict)
|
||||
user = await api.user.get()
|
||||
if name is None:
|
||||
|
||||
url = entry.data[CONF_URL]
|
||||
username = entry.data[CONF_API_USER]
|
||||
password = entry.data[CONF_API_KEY]
|
||||
|
||||
api = HAHabitipyAsync(
|
||||
{
|
||||
"url": url,
|
||||
"login": username,
|
||||
"password": password,
|
||||
}
|
||||
)
|
||||
try:
|
||||
user = await api.user.get(userFields="profile")
|
||||
except ClientResponseError as e:
|
||||
if e.status == HTTPStatus.TOO_MANY_REQUESTS:
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="setup_rate_limit_exception",
|
||||
) from e
|
||||
raise ConfigEntryNotReady(e) from e
|
||||
|
||||
if not entry.data.get(CONF_NAME):
|
||||
name = user["profile"]["name"]
|
||||
hass.config_entries.async_update_entry(
|
||||
entry,
|
||||
data={**entry.data, CONF_NAME: name},
|
||||
)
|
||||
data[entry.entry_id] = api
|
||||
|
||||
coordinator = HabiticaDataUpdateCoordinator(hass, api)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
entry.runtime_data = coordinator
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
if not hass.services.has_service(DOMAIN, SERVICE_API_CALL):
|
||||
@ -169,10 +191,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> 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)
|
||||
|
||||
if len(hass.config_entries.async_entries(DOMAIN)) == 1:
|
||||
hass.services.async_remove(DOMAIN, SERVICE_API_CALL)
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
56
homeassistant/components/habitica/coordinator.py
Normal file
56
homeassistant/components/habitica/coordinator.py
Normal file
@ -0,0 +1,56 @@
|
||||
"""DataUpdateCoordinator for the Habitica integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientResponseError
|
||||
from habitipy.aio import HabitipyAsync
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class HabiticaData:
|
||||
"""Coordinator data class."""
|
||||
|
||||
user: dict[str, Any]
|
||||
tasks: list[dict]
|
||||
|
||||
|
||||
class HabiticaDataUpdateCoordinator(DataUpdateCoordinator[HabiticaData]):
|
||||
"""Habitica Data Update Coordinator."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
|
||||
def __init__(self, hass: HomeAssistant, habitipy: HabitipyAsync) -> None:
|
||||
"""Initialize the Habitica data coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=timedelta(seconds=30),
|
||||
)
|
||||
self.api = habitipy
|
||||
|
||||
async def _async_update_data(self) -> HabiticaData:
|
||||
user_fields = set(self.async_contexts())
|
||||
|
||||
try:
|
||||
user_response = await self.api.user.get(userFields=",".join(user_fields))
|
||||
tasks_response = []
|
||||
for task_type in ("todos", "dailys", "habits", "rewards"):
|
||||
tasks_response.extend(await self.api.tasks.user.get(type=task_type))
|
||||
except ClientResponseError as error:
|
||||
raise UpdateFailed(f"Error communicating with API: {error}") from error
|
||||
|
||||
return HabiticaData(user=user_response, tasks=tasks_response)
|
@ -4,13 +4,9 @@ from __future__ import annotations
|
||||
|
||||
from collections import namedtuple
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
from enum import StrEnum
|
||||
from http import HTTPStatus
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from aiohttp import ClientResponseError
|
||||
from typing import TYPE_CHECKING, cast
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
@ -22,14 +18,15 @@ from homeassistant.const import CONF_NAME, CONF_URL
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import HabiticaConfigEntry
|
||||
from .const import DOMAIN, MANUFACTURER, NAME
|
||||
from .coordinator import HabiticaDataUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15)
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class HabitipySensorEntityDescription(SensorEntityDescription):
|
||||
@ -122,14 +119,14 @@ SENSOR_DESCRIPTIONS: dict[str, HabitipySensorEntityDescription] = {
|
||||
SensorType = namedtuple("SensorType", ["name", "icon", "unit", "path"])
|
||||
TASKS_TYPES = {
|
||||
"habits": SensorType(
|
||||
"Habits", "mdi:clipboard-list-outline", "n_of_tasks", ["habits"]
|
||||
"Habits", "mdi:clipboard-list-outline", "n_of_tasks", ["habit"]
|
||||
),
|
||||
"dailys": SensorType(
|
||||
"Dailys", "mdi:clipboard-list-outline", "n_of_tasks", ["dailys"]
|
||||
"Dailys", "mdi:clipboard-list-outline", "n_of_tasks", ["daily"]
|
||||
),
|
||||
"todos": SensorType("TODOs", "mdi:clipboard-list-outline", "n_of_tasks", ["todos"]),
|
||||
"todos": SensorType("TODOs", "mdi:clipboard-list-outline", "n_of_tasks", ["todo"]),
|
||||
"rewards": SensorType(
|
||||
"Rewards", "mdi:clipboard-list-outline", "n_of_tasks", ["rewards"]
|
||||
"Rewards", "mdi:clipboard-list-outline", "n_of_tasks", ["reward"]
|
||||
),
|
||||
}
|
||||
|
||||
@ -163,79 +160,26 @@ TASKS_MAP = {
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: HabiticaConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the habitica sensors."""
|
||||
|
||||
name = config_entry.data[CONF_NAME]
|
||||
sensor_data = HabitipyData(hass.data[DOMAIN][config_entry.entry_id])
|
||||
await sensor_data.update()
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
entities: list[SensorEntity] = [
|
||||
HabitipySensor(sensor_data, description, config_entry)
|
||||
HabitipySensor(coordinator, description, config_entry)
|
||||
for description in SENSOR_DESCRIPTIONS.values()
|
||||
]
|
||||
entities.extend(
|
||||
HabitipyTaskSensor(name, task_type, sensor_data, config_entry)
|
||||
HabitipyTaskSensor(name, task_type, coordinator, config_entry)
|
||||
for task_type in TASKS_TYPES
|
||||
)
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
class HabitipyData:
|
||||
"""Habitica API user data cache."""
|
||||
|
||||
tasks: dict[str, Any]
|
||||
|
||||
def __init__(self, api) -> None:
|
||||
"""Habitica API user data cache."""
|
||||
self.api = api
|
||||
self.data = None
|
||||
self.tasks = {}
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
async def update(self):
|
||||
"""Get a new fix from Habitica servers."""
|
||||
try:
|
||||
self.data = await self.api.user.get()
|
||||
except ClientResponseError as error:
|
||||
if error.status == HTTPStatus.TOO_MANY_REQUESTS:
|
||||
_LOGGER.warning(
|
||||
(
|
||||
"Sensor data update for %s has too many API requests;"
|
||||
" Skipping the update"
|
||||
),
|
||||
DOMAIN,
|
||||
)
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"Count not update sensor data for %s (%s)",
|
||||
DOMAIN,
|
||||
error,
|
||||
)
|
||||
|
||||
for task_type in TASKS_TYPES:
|
||||
try:
|
||||
self.tasks[task_type] = await self.api.tasks.user.get(type=task_type)
|
||||
except ClientResponseError as error:
|
||||
if error.status == HTTPStatus.TOO_MANY_REQUESTS:
|
||||
_LOGGER.warning(
|
||||
(
|
||||
"Sensor data update for %s has too many API requests;"
|
||||
" Skipping the update"
|
||||
),
|
||||
DOMAIN,
|
||||
)
|
||||
else:
|
||||
_LOGGER.error(
|
||||
"Count not update sensor data for %s (%s)",
|
||||
DOMAIN,
|
||||
error,
|
||||
)
|
||||
|
||||
|
||||
class HabitipySensor(SensorEntity):
|
||||
class HabitipySensor(CoordinatorEntity[HabiticaDataUpdateCoordinator], SensorEntity):
|
||||
"""A generic Habitica sensor."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
@ -243,15 +187,14 @@ class HabitipySensor(SensorEntity):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator,
|
||||
coordinator: HabiticaDataUpdateCoordinator,
|
||||
entity_description: HabitipySensorEntityDescription,
|
||||
entry: ConfigEntry,
|
||||
) -> None:
|
||||
"""Initialize a generic Habitica sensor."""
|
||||
super().__init__()
|
||||
super().__init__(coordinator, context=entity_description.value_path[0])
|
||||
if TYPE_CHECKING:
|
||||
assert entry.unique_id
|
||||
self.coordinator = coordinator
|
||||
self.entity_description = entity_description
|
||||
self._attr_unique_id = f"{entry.unique_id}_{entity_description.key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
@ -263,25 +206,27 @@ class HabitipySensor(SensorEntity):
|
||||
identifiers={(DOMAIN, entry.unique_id)},
|
||||
)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update Sensor state."""
|
||||
await self.coordinator.update()
|
||||
data = self.coordinator.data
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the device."""
|
||||
data = self.coordinator.data.user
|
||||
for element in self.entity_description.value_path:
|
||||
data = data[element]
|
||||
self._attr_native_value = data
|
||||
return cast(StateType, data)
|
||||
|
||||
|
||||
class HabitipyTaskSensor(SensorEntity):
|
||||
class HabitipyTaskSensor(
|
||||
CoordinatorEntity[HabiticaDataUpdateCoordinator], SensorEntity
|
||||
):
|
||||
"""A Habitica task sensor."""
|
||||
|
||||
def __init__(self, name, task_name, updater, entry):
|
||||
def __init__(self, name, task_name, coordinator, entry):
|
||||
"""Initialize a generic Habitica task."""
|
||||
super().__init__(coordinator)
|
||||
self._name = name
|
||||
self._task_name = task_name
|
||||
self._task_type = TASKS_TYPES[task_name]
|
||||
self._state = None
|
||||
self._updater = updater
|
||||
self._attr_unique_id = f"{entry.unique_id}_{task_name}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
@ -292,14 +237,6 @@ class HabitipyTaskSensor(SensorEntity):
|
||||
identifiers={(DOMAIN, entry.unique_id)},
|
||||
)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Update Condition and Forecast."""
|
||||
await self._updater.update()
|
||||
all_tasks = self._updater.tasks
|
||||
for element in self._task_type.path:
|
||||
tasks_length = len(all_tasks[element])
|
||||
self._state = tasks_length
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use in the frontend, if any."""
|
||||
@ -313,26 +250,29 @@ class HabitipyTaskSensor(SensorEntity):
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the device."""
|
||||
return self._state
|
||||
return len(
|
||||
[
|
||||
task
|
||||
for task in self.coordinator.data.tasks
|
||||
if task.get("type") in self._task_type.path
|
||||
]
|
||||
)
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return the state attributes of all user tasks."""
|
||||
if self._updater.tasks is not None:
|
||||
all_received_tasks = self._updater.tasks
|
||||
for element in self._task_type.path:
|
||||
received_tasks = all_received_tasks[element]
|
||||
attrs = {}
|
||||
attrs = {}
|
||||
|
||||
# Map tasks to TASKS_MAP
|
||||
for received_task in received_tasks:
|
||||
# Map tasks to TASKS_MAP
|
||||
for received_task in self.coordinator.data.tasks:
|
||||
if received_task.get("type") in self._task_type.path:
|
||||
task_id = received_task[TASKS_MAP_ID]
|
||||
task = {}
|
||||
for map_key, map_value in TASKS_MAP.items():
|
||||
if value := received_task.get(map_value):
|
||||
task[map_key] = value
|
||||
attrs[task_id] = task
|
||||
return attrs
|
||||
return attrs
|
||||
|
||||
@property
|
||||
def native_unit_of_measurement(self):
|
||||
|
@ -59,6 +59,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"setup_rate_limit_exception": {
|
||||
"message": "Currently rate limited, try again later"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"api_call": {
|
||||
"name": "API name",
|
||||
|
@ -35,7 +35,11 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import device_registry as dr, issue_registry as ir
|
||||
from homeassistant.helpers import (
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
issue_registry as ir,
|
||||
)
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
@ -99,7 +103,7 @@ async def async_setup_entry(
|
||||
heat_away_temp = entry.options.get(CONF_HEAT_AWAY_TEMPERATURE)
|
||||
|
||||
data: HoneywellData = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
_async_migrate_unique_id(hass, data.devices)
|
||||
async_add_entities(
|
||||
[
|
||||
HoneywellUSThermostat(data, device, cool_away_temp, heat_away_temp)
|
||||
@ -109,6 +113,21 @@ async def async_setup_entry(
|
||||
remove_stale_devices(hass, entry, data.devices)
|
||||
|
||||
|
||||
def _async_migrate_unique_id(
|
||||
hass: HomeAssistant, devices: dict[str, SomeComfortDevice]
|
||||
) -> None:
|
||||
"""Migrate entities to string."""
|
||||
entity_registry = er.async_get(hass)
|
||||
for device in devices.values():
|
||||
entity_id = entity_registry.async_get_entity_id(
|
||||
"climate", DOMAIN, device.deviceid
|
||||
)
|
||||
if entity_id is not None:
|
||||
entity_registry.async_update_entity(
|
||||
entity_id, new_unique_id=str(device.deviceid)
|
||||
)
|
||||
|
||||
|
||||
def remove_stale_devices(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
@ -161,7 +180,7 @@ class HoneywellUSThermostat(ClimateEntity):
|
||||
self._away = False
|
||||
self._retry = 0
|
||||
|
||||
self._attr_unique_id = device.deviceid
|
||||
self._attr_unique_id = str(device.deviceid)
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, device.deviceid)},
|
||||
|
@ -75,7 +75,7 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
vol.Optional(CONF_FOLDER, default="INBOX"): str,
|
||||
vol.Optional(CONF_SEARCH, default="UnSeen UnDeleted"): str,
|
||||
# The default for new entries is to not include text and headers
|
||||
vol.Optional(CONF_EVENT_MESSAGE_DATA, default=[]): cv.ensure_list,
|
||||
vol.Optional(CONF_EVENT_MESSAGE_DATA, default=[]): EVENT_MESSAGE_DATA_SELECTOR,
|
||||
}
|
||||
)
|
||||
CONFIG_SCHEMA_ADVANCED = {
|
||||
|
@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/imgw_pib",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["imgw_pib==1.0.0"]
|
||||
"requirements": ["imgw_pib==1.0.1"]
|
||||
}
|
||||
|
@ -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"
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -10,19 +10,18 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.util import slugify
|
||||
|
||||
from .const import CONF_STORAGE_KEY, CONF_TODO_LIST_NAME, DOMAIN
|
||||
from .const import CONF_STORAGE_KEY, CONF_TODO_LIST_NAME
|
||||
from .store import LocalTodoListStore
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.TODO]
|
||||
|
||||
STORAGE_PATH = ".storage/local_todo.{key}.ics"
|
||||
|
||||
LocalTodoConfigEntry = ConfigEntry[LocalTodoListStore]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: LocalTodoConfigEntry) -> bool:
|
||||
"""Set up Local To-do from a config entry."""
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
|
||||
path = Path(hass.config.path(STORAGE_PATH.format(key=entry.data[CONF_STORAGE_KEY])))
|
||||
store = LocalTodoListStore(hass, path)
|
||||
try:
|
||||
@ -30,7 +29,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
except OSError as err:
|
||||
raise ConfigEntryNotReady("Failed to load file {path}: {err}") from err
|
||||
|
||||
hass.data[DOMAIN][entry.entry_id] = store
|
||||
entry.runtime_data = store
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
@ -39,10 +38,7 @@ 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)
|
||||
|
||||
|
||||
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
|
@ -14,14 +14,14 @@ from homeassistant.components.todo import (
|
||||
TodoListEntity,
|
||||
TodoListEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.setup import SetupPhases, async_pause_setup
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import CONF_TODO_LIST_NAME, DOMAIN
|
||||
from . import LocalTodoConfigEntry
|
||||
from .const import CONF_TODO_LIST_NAME
|
||||
from .store import LocalTodoListStore
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -63,12 +63,12 @@ def _migrate_calendar(calendar: Calendar) -> bool:
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: LocalTodoConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the local_todo todo platform."""
|
||||
|
||||
store: LocalTodoListStore = hass.data[DOMAIN][config_entry.entry_id]
|
||||
store = config_entry.runtime_data
|
||||
ics = await store.async_load()
|
||||
|
||||
with async_pause_setup(hass, SetupPhases.WAIT_IMPORT_PACKAGES):
|
||||
|
@ -106,4 +106,4 @@ class LutronEventEntity(LutronKeypad, EventEntity):
|
||||
}
|
||||
self.hass.bus.fire("lutron_event", data)
|
||||
self._trigger_event(action)
|
||||
self.async_write_ha_state()
|
||||
self.schedule_update_ha_state()
|
||||
|
@ -398,6 +398,8 @@ class MatterLight(MatterEntity, LightEntity):
|
||||
def _check_transition_blocklist(self) -> None:
|
||||
"""Check if this device is reported to have non working transitions."""
|
||||
device_info = self._endpoint.device_info
|
||||
if isinstance(device_info, clusters.BridgedDeviceBasicInformation):
|
||||
return
|
||||
if (
|
||||
device_info.vendorID,
|
||||
device_info.productID,
|
||||
|
@ -21,8 +21,12 @@ PLATFORMS = [Platform.WEATHER]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
MetWeatherConfigEntry = ConfigEntry[MetDataUpdateCoordinator]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, config_entry: MetWeatherConfigEntry
|
||||
) -> bool:
|
||||
"""Set up Met as config entry."""
|
||||
# Don't setup if tracking home location and latitude or longitude isn't set.
|
||||
# Also, filters out our onboarding default location.
|
||||
@ -44,10 +48,10 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
if config_entry.data.get(CONF_TRACK_HOME, False):
|
||||
coordinator.track_home()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][config_entry.entry_id] = coordinator
|
||||
config_entry.runtime_data = coordinator
|
||||
|
||||
config_entry.async_on_unload(config_entry.add_update_listener(async_update_entry))
|
||||
config_entry.async_on_unload(coordinator.untrack_home)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||
|
||||
@ -56,19 +60,14 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(
|
||||
hass: HomeAssistant, config_entry: MetWeatherConfigEntry
|
||||
) -> bool:
|
||||
"""Unload a config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(
|
||||
config_entry, PLATFORMS
|
||||
)
|
||||
|
||||
hass.data[DOMAIN][config_entry.entry_id].untrack_home()
|
||||
hass.data[DOMAIN].pop(config_entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_update_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
||||
async def async_update_entry(hass: HomeAssistant, config_entry: MetWeatherConfigEntry):
|
||||
"""Reload Met component when options changed."""
|
||||
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
|
||||
|
@ -21,7 +21,6 @@ from homeassistant.components.weather import (
|
||||
SingleCoordinatorWeatherEntity,
|
||||
WeatherEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONF_LATITUDE,
|
||||
CONF_LONGITUDE,
|
||||
@ -37,6 +36,7 @@ from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util.unit_system import METRIC_SYSTEM
|
||||
|
||||
from . import MetWeatherConfigEntry
|
||||
from .const import (
|
||||
ATTR_CONDITION_CLEAR_NIGHT,
|
||||
ATTR_CONDITION_SUNNY,
|
||||
@ -53,11 +53,11 @@ DEFAULT_NAME = "Met.no"
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: MetWeatherConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add a weather entity from a config_entry."""
|
||||
coordinator: MetDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
coordinator = config_entry.runtime_data
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
name: str | None
|
||||
@ -120,7 +120,7 @@ class MetWeather(SingleCoordinatorWeatherEntity[MetDataUpdateCoordinator]):
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: MetDataUpdateCoordinator,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: MetWeatherConfigEntry,
|
||||
name: str,
|
||||
is_metric: bool,
|
||||
) -> None:
|
||||
|
@ -91,8 +91,6 @@ class MetOfficeWeather(
|
||||
CoordinatorWeatherEntity[
|
||||
TimestampDataUpdateCoordinator[MetOfficeData],
|
||||
TimestampDataUpdateCoordinator[MetOfficeData],
|
||||
TimestampDataUpdateCoordinator[MetOfficeData],
|
||||
TimestampDataUpdateCoordinator[MetOfficeData], # Can be removed in Python 3.12
|
||||
]
|
||||
):
|
||||
"""Implementation of a Met Office weather condition."""
|
||||
|
@ -218,8 +218,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
async def async_step_reauth(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle re-authentication with Aladdin Connect."""
|
||||
|
||||
"""Handle re-authentication with MQTT broker."""
|
||||
self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
|
@ -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(
|
||||
|
@ -2,17 +2,14 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import cast
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from aiohttp.client_exceptions import ClientConnectorError, ClientError
|
||||
from nettigo_air_monitor import (
|
||||
ApiError,
|
||||
AuthFailedError,
|
||||
ConnectionOptions,
|
||||
InvalidSensorDataError,
|
||||
NAMSensors,
|
||||
NettigoAirMonitor,
|
||||
)
|
||||
|
||||
@ -21,25 +18,20 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import (
|
||||
ATTR_SDS011,
|
||||
ATTR_SPS30,
|
||||
DEFAULT_UPDATE_INTERVAL,
|
||||
DOMAIN,
|
||||
MANUFACTURER,
|
||||
)
|
||||
from .const import ATTR_SDS011, ATTR_SPS30, DOMAIN
|
||||
from .coordinator import NAMDataUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = [Platform.BUTTON, Platform.SENSOR]
|
||||
|
||||
NAMConfigEntry = ConfigEntry[NAMDataUpdateCoordinator]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: NAMConfigEntry) -> bool:
|
||||
"""Set up Nettigo as config entry."""
|
||||
host: str = entry.data[CONF_HOST]
|
||||
username: str | None = entry.data.get(CONF_USERNAME)
|
||||
@ -60,11 +52,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
except AuthFailedError as err:
|
||||
raise ConfigEntryAuthFailed from err
|
||||
|
||||
if TYPE_CHECKING:
|
||||
assert entry.unique_id
|
||||
|
||||
coordinator = NAMDataUpdateCoordinator(hass, nam, entry.unique_id)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
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)
|
||||
|
||||
@ -81,57 +75,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: NAMConfigEntry) -> 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
|
||||
|
||||
|
||||
class NAMDataUpdateCoordinator(DataUpdateCoordinator[NAMSensors]): # pylint: disable=hass-enforce-coordinator-module
|
||||
"""Class to manage fetching Nettigo Air Monitor data."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
nam: NettigoAirMonitor,
|
||||
unique_id: str | None,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
self._unique_id = unique_id
|
||||
self.nam = nam
|
||||
|
||||
super().__init__(
|
||||
hass, _LOGGER, name=DOMAIN, update_interval=DEFAULT_UPDATE_INTERVAL
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> NAMSensors:
|
||||
"""Update data via library."""
|
||||
try:
|
||||
async with asyncio.timeout(10):
|
||||
data = await self.nam.async_update()
|
||||
# We do not need to catch AuthFailed exception here because sensor data is
|
||||
# always available without authorization.
|
||||
except (ApiError, ClientConnectorError, InvalidSensorDataError) as error:
|
||||
raise UpdateFailed(error) from error
|
||||
|
||||
return data
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str | None:
|
||||
"""Return a unique_id."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device info."""
|
||||
return DeviceInfo(
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, cast(str, self._unique_id))},
|
||||
name="Nettigo Air Monitor",
|
||||
sw_version=self.nam.software_version,
|
||||
manufacturer=MANUFACTURER,
|
||||
configuration_url=f"http://{self.nam.host}/",
|
||||
)
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
@ -9,14 +9,12 @@ from homeassistant.components.button import (
|
||||
ButtonEntity,
|
||||
ButtonEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import NAMDataUpdateCoordinator
|
||||
from .const import DOMAIN
|
||||
from . import NAMConfigEntry, NAMDataUpdateCoordinator
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
@ -30,10 +28,10 @@ RESTART_BUTTON: ButtonEntityDescription = ButtonEntityDescription(
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant, entry: NAMConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Add a Nettigo Air Monitor entities from a config_entry."""
|
||||
coordinator: NAMDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
buttons: list[NAMButton] = []
|
||||
buttons.append(NAMButton(coordinator, RESTART_BUTTON))
|
||||
|
57
homeassistant/components/nam/coordinator.py
Normal file
57
homeassistant/components/nam/coordinator.py
Normal file
@ -0,0 +1,57 @@
|
||||
"""The Nettigo Air Monitor coordinator."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from nettigo_air_monitor import (
|
||||
ApiError,
|
||||
InvalidSensorDataError,
|
||||
NAMSensors,
|
||||
NettigoAirMonitor,
|
||||
)
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DEFAULT_UPDATE_INTERVAL, DOMAIN, MANUFACTURER
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NAMDataUpdateCoordinator(DataUpdateCoordinator[NAMSensors]):
|
||||
"""Class to manage fetching Nettigo Air Monitor data."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
nam: NettigoAirMonitor,
|
||||
unique_id: str,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
self.unique_id = unique_id
|
||||
self.device_info = DeviceInfo(
|
||||
connections={(CONNECTION_NETWORK_MAC, unique_id)},
|
||||
name="Nettigo Air Monitor",
|
||||
sw_version=nam.software_version,
|
||||
manufacturer=MANUFACTURER,
|
||||
configuration_url=f"http://{nam.host}/",
|
||||
)
|
||||
self.nam = nam
|
||||
|
||||
super().__init__(
|
||||
hass, _LOGGER, name=DOMAIN, update_interval=DEFAULT_UPDATE_INTERVAL
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> NAMSensors:
|
||||
"""Update data via library."""
|
||||
try:
|
||||
async with asyncio.timeout(10):
|
||||
data = await self.nam.async_update()
|
||||
# We do not need to catch AuthFailed exception here because sensor data is
|
||||
# always available without authorization.
|
||||
except (ApiError, ClientConnectorError, InvalidSensorDataError) as error:
|
||||
raise UpdateFailed(error) from error
|
||||
|
||||
return data
|
@ -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_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import NAMDataUpdateCoordinator
|
||||
from .const import DOMAIN
|
||||
from . import NAMConfigEntry
|
||||
|
||||
TO_REDACT = {CONF_PASSWORD, CONF_USERNAME}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
hass: HomeAssistant, config_entry: NAMConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
coordinator: NAMDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
return {
|
||||
"info": async_redact_data(config_entry.data, TO_REDACT),
|
||||
|
@ -8,7 +8,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["nettigo_air_monitor"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["nettigo-air-monitor==3.0.0"],
|
||||
"requirements": ["nettigo-air-monitor==3.0.1"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_http._tcp.local.",
|
||||
|
@ -16,7 +16,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,
|
||||
@ -33,7 +32,7 @@ from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from . import NAMDataUpdateCoordinator
|
||||
from . import NAMConfigEntry, NAMDataUpdateCoordinator
|
||||
from .const import (
|
||||
ATTR_BME280_HUMIDITY,
|
||||
ATTR_BME280_PRESSURE,
|
||||
@ -347,10 +346,10 @@ SENSORS: tuple[NAMSensorEntityDescription, ...] = (
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant, entry: NAMConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Add a Nettigo Air Monitor entities from a config_entry."""
|
||||
coordinator: NAMDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
# Due to the change of the attribute name of two sensors, it is necessary to migrate
|
||||
# the unique_ids to the new names.
|
||||
|
@ -30,8 +30,10 @@ CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
NextcloudConfigEntry = ConfigEntry[NextcloudDataUpdateCoordinator]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: NextcloudConfigEntry) -> bool:
|
||||
"""Set up the Nextcloud integration."""
|
||||
|
||||
# migrate old entity unique ids
|
||||
@ -71,17 +73,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: NextcloudConfigEntry) -> bool:
|
||||
"""Unload Nextcloud integration."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
if not hass.data[DOMAIN]:
|
||||
hass.data.pop(DOMAIN)
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
@ -8,13 +8,11 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import NextcloudDataUpdateCoordinator
|
||||
from . import NextcloudConfigEntry
|
||||
from .entity import NextcloudEntity
|
||||
|
||||
BINARY_SENSORS: Final[list[BinarySensorEntityDescription]] = [
|
||||
@ -54,10 +52,12 @@ BINARY_SENSORS: Final[list[BinarySensorEntityDescription]] = [
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant,
|
||||
entry: NextcloudConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Nextcloud binary sensors."""
|
||||
coordinator: NextcloudDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
NextcloudBinarySensor(coordinator, entry, sensor)
|
||||
for sensor in BINARY_SENSORS
|
||||
|
@ -2,11 +2,11 @@
|
||||
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import NextcloudConfigEntry
|
||||
from .const import DOMAIN
|
||||
from .coordinator import NextcloudDataUpdateCoordinator
|
||||
|
||||
@ -19,7 +19,7 @@ class NextcloudEntity(CoordinatorEntity[NextcloudDataUpdateCoordinator]):
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: NextcloudDataUpdateCoordinator,
|
||||
entry: ConfigEntry,
|
||||
entry: NextcloudConfigEntry,
|
||||
description: EntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the Nextcloud sensor."""
|
||||
|
@ -13,7 +13,6 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
PERCENTAGE,
|
||||
EntityCategory,
|
||||
@ -24,8 +23,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.util.dt import utc_from_timestamp
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import NextcloudDataUpdateCoordinator
|
||||
from . import NextcloudConfigEntry
|
||||
from .entity import NextcloudEntity
|
||||
|
||||
UNIT_OF_LOAD: Final[str] = "load"
|
||||
@ -602,10 +600,12 @@ SENSORS: Final[list[NextcloudSensorEntityDescription]] = [
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant,
|
||||
entry: NextcloudConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Nextcloud sensors."""
|
||||
coordinator: NextcloudDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
NextcloudSensor(coordinator, entry, sensor)
|
||||
for sensor in SENSORS
|
||||
|
@ -3,20 +3,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.update import UpdateEntity, UpdateEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import NextcloudDataUpdateCoordinator
|
||||
from . import NextcloudConfigEntry
|
||||
from .entity import NextcloudEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant,
|
||||
entry: NextcloudConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Nextcloud update entity."""
|
||||
coordinator: NextcloudDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
if coordinator.data.get("update_available") is None:
|
||||
return
|
||||
async_add_entities(
|
||||
|
@ -3,10 +3,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from nextdns import ApiError, NextDns
|
||||
from nextdns import (
|
||||
AnalyticsDnssec,
|
||||
AnalyticsEncryption,
|
||||
AnalyticsIpVersions,
|
||||
AnalyticsProtocols,
|
||||
AnalyticsStatus,
|
||||
ApiError,
|
||||
ConnectionStatus,
|
||||
NextDns,
|
||||
Settings,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY, Platform
|
||||
@ -23,7 +34,6 @@ from .const import (
|
||||
ATTR_SETTINGS,
|
||||
ATTR_STATUS,
|
||||
CONF_PROFILE_ID,
|
||||
DOMAIN,
|
||||
UPDATE_INTERVAL_ANALYTICS,
|
||||
UPDATE_INTERVAL_CONNECTION,
|
||||
UPDATE_INTERVAL_SETTINGS,
|
||||
@ -39,6 +49,22 @@ from .coordinator import (
|
||||
NextDnsUpdateCoordinator,
|
||||
)
|
||||
|
||||
NextDnsConfigEntry = ConfigEntry["NextDnsData"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class NextDnsData:
|
||||
"""Data for the NextDNS integration."""
|
||||
|
||||
connection: NextDnsUpdateCoordinator[ConnectionStatus]
|
||||
dnssec: NextDnsUpdateCoordinator[AnalyticsDnssec]
|
||||
encryption: NextDnsUpdateCoordinator[AnalyticsEncryption]
|
||||
ip_versions: NextDnsUpdateCoordinator[AnalyticsIpVersions]
|
||||
protocols: NextDnsUpdateCoordinator[AnalyticsProtocols]
|
||||
settings: NextDnsUpdateCoordinator[Settings]
|
||||
status: NextDnsUpdateCoordinator[AnalyticsStatus]
|
||||
|
||||
|
||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR, Platform.SWITCH]
|
||||
COORDINATORS: list[tuple[str, type[NextDnsUpdateCoordinator], timedelta]] = [
|
||||
(ATTR_CONNECTION, NextDnsConnectionUpdateCoordinator, UPDATE_INTERVAL_CONNECTION),
|
||||
@ -51,7 +77,7 @@ COORDINATORS: list[tuple[str, type[NextDnsUpdateCoordinator], timedelta]] = [
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: NextDnsConfigEntry) -> bool:
|
||||
"""Set up NextDNS as config entry."""
|
||||
api_key = entry.data[CONF_API_KEY]
|
||||
profile_id = entry.data[CONF_PROFILE_ID]
|
||||
@ -75,18 +101,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinators
|
||||
entry.runtime_data = NextDnsData(**coordinators)
|
||||
|
||||
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: NextDnsConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
unload_ok: bool = 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)
|
||||
|
@ -4,7 +4,6 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Generic
|
||||
|
||||
from nextdns import ConnectionStatus
|
||||
|
||||
@ -13,36 +12,33 @@ 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.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import ATTR_CONNECTION, DOMAIN
|
||||
from .coordinator import CoordinatorDataT, NextDnsConnectionUpdateCoordinator
|
||||
from . import NextDnsConfigEntry
|
||||
from .coordinator import NextDnsUpdateCoordinator
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class NextDnsBinarySensorEntityDescription(
|
||||
BinarySensorEntityDescription, Generic[CoordinatorDataT]
|
||||
):
|
||||
class NextDnsBinarySensorEntityDescription(BinarySensorEntityDescription):
|
||||
"""NextDNS binary sensor entity description."""
|
||||
|
||||
state: Callable[[CoordinatorDataT, str], bool]
|
||||
state: Callable[[ConnectionStatus, str], bool]
|
||||
|
||||
|
||||
SENSORS = (
|
||||
NextDnsBinarySensorEntityDescription[ConnectionStatus](
|
||||
NextDnsBinarySensorEntityDescription(
|
||||
key="this_device_nextdns_connection_status",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
translation_key="device_connection_status",
|
||||
device_class=BinarySensorDeviceClass.CONNECTIVITY,
|
||||
state=lambda data, _: data.connected,
|
||||
),
|
||||
NextDnsBinarySensorEntityDescription[ConnectionStatus](
|
||||
NextDnsBinarySensorEntityDescription(
|
||||
key="this_device_profile_connection_status",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
translation_key="device_profile_connection_status",
|
||||
@ -54,13 +50,11 @@ SENSORS = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: NextDnsConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add NextDNS entities from a config_entry."""
|
||||
coordinator: NextDnsConnectionUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][
|
||||
ATTR_CONNECTION
|
||||
]
|
||||
coordinator = entry.runtime_data.connection
|
||||
|
||||
async_add_entities(
|
||||
NextDnsBinarySensor(coordinator, description) for description in SENSORS
|
||||
@ -68,7 +62,7 @@ async def async_setup_entry(
|
||||
|
||||
|
||||
class NextDnsBinarySensor(
|
||||
CoordinatorEntity[NextDnsConnectionUpdateCoordinator], BinarySensorEntity
|
||||
CoordinatorEntity[NextDnsUpdateCoordinator[ConnectionStatus]], BinarySensorEntity
|
||||
):
|
||||
"""Define an NextDNS binary sensor."""
|
||||
|
||||
@ -77,7 +71,7 @@ class NextDnsBinarySensor(
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: NextDnsConnectionUpdateCoordinator,
|
||||
coordinator: NextDnsUpdateCoordinator[ConnectionStatus],
|
||||
description: NextDnsBinarySensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
|
@ -2,15 +2,16 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from nextdns import AnalyticsStatus
|
||||
|
||||
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import ATTR_STATUS, DOMAIN
|
||||
from .coordinator import NextDnsStatusUpdateCoordinator
|
||||
from . import NextDnsConfigEntry
|
||||
from .coordinator import NextDnsUpdateCoordinator
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
@ -22,27 +23,26 @@ CLEAR_LOGS_BUTTON = ButtonEntityDescription(
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant,
|
||||
entry: NextDnsConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add aNextDNS entities from a config_entry."""
|
||||
coordinator: NextDnsStatusUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][
|
||||
ATTR_STATUS
|
||||
]
|
||||
coordinator = entry.runtime_data.status
|
||||
|
||||
buttons: list[NextDnsButton] = []
|
||||
buttons.append(NextDnsButton(coordinator, CLEAR_LOGS_BUTTON))
|
||||
|
||||
async_add_entities(buttons)
|
||||
async_add_entities([NextDnsButton(coordinator, CLEAR_LOGS_BUTTON)])
|
||||
|
||||
|
||||
class NextDnsButton(CoordinatorEntity[NextDnsStatusUpdateCoordinator], ButtonEntity):
|
||||
class NextDnsButton(
|
||||
CoordinatorEntity[NextDnsUpdateCoordinator[AnalyticsStatus]], ButtonEntity
|
||||
):
|
||||
"""Define an NextDNS button."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: NextDnsStatusUpdateCoordinator,
|
||||
coordinator: NextDnsUpdateCoordinator[AnalyticsStatus],
|
||||
description: ButtonEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
|
@ -6,36 +6,25 @@ 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, CONF_UNIQUE_ID
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import (
|
||||
ATTR_DNSSEC,
|
||||
ATTR_ENCRYPTION,
|
||||
ATTR_IP_VERSIONS,
|
||||
ATTR_PROTOCOLS,
|
||||
ATTR_SETTINGS,
|
||||
ATTR_STATUS,
|
||||
CONF_PROFILE_ID,
|
||||
DOMAIN,
|
||||
)
|
||||
from . import NextDnsConfigEntry
|
||||
from .const import CONF_PROFILE_ID
|
||||
|
||||
TO_REDACT = {CONF_API_KEY, CONF_PROFILE_ID, CONF_UNIQUE_ID}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
hass: HomeAssistant, config_entry: NextDnsConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
coordinators = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
dnssec_coordinator = coordinators[ATTR_DNSSEC]
|
||||
encryption_coordinator = coordinators[ATTR_ENCRYPTION]
|
||||
ip_versions_coordinator = coordinators[ATTR_IP_VERSIONS]
|
||||
protocols_coordinator = coordinators[ATTR_PROTOCOLS]
|
||||
settings_coordinator = coordinators[ATTR_SETTINGS]
|
||||
status_coordinator = coordinators[ATTR_STATUS]
|
||||
dnssec_coordinator = config_entry.runtime_data.dnssec
|
||||
encryption_coordinator = config_entry.runtime_data.encryption
|
||||
ip_versions_coordinator = config_entry.runtime_data.ip_versions
|
||||
protocols_coordinator = config_entry.runtime_data.protocols
|
||||
settings_coordinator = config_entry.runtime_data.settings
|
||||
status_coordinator = config_entry.runtime_data.status
|
||||
|
||||
return {
|
||||
"config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT),
|
||||
|
@ -19,20 +19,19 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import NextDnsConfigEntry
|
||||
from .const import (
|
||||
ATTR_DNSSEC,
|
||||
ATTR_ENCRYPTION,
|
||||
ATTR_IP_VERSIONS,
|
||||
ATTR_PROTOCOLS,
|
||||
ATTR_STATUS,
|
||||
DOMAIN,
|
||||
)
|
||||
from .coordinator import CoordinatorDataT, NextDnsUpdateCoordinator
|
||||
|
||||
@ -301,14 +300,14 @@ SENSORS: tuple[NextDnsSensorEntityDescription, ...] = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: NextDnsConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add a NextDNS entities from a config_entry."""
|
||||
coordinators = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
async_add_entities(
|
||||
NextDnsSensor(coordinators[description.coordinator_type], description)
|
||||
NextDnsSensor(
|
||||
getattr(entry.runtime_data, description.coordinator_type), description
|
||||
)
|
||||
for description in SENSORS
|
||||
)
|
||||
|
||||
|
@ -4,518 +4,515 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Generic
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientError
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from nextdns import ApiError, Settings
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import ATTR_SETTINGS, DOMAIN
|
||||
from .coordinator import CoordinatorDataT, NextDnsSettingsUpdateCoordinator
|
||||
from . import NextDnsConfigEntry
|
||||
from .coordinator import NextDnsUpdateCoordinator
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class NextDnsSwitchEntityDescription(
|
||||
SwitchEntityDescription, Generic[CoordinatorDataT]
|
||||
):
|
||||
class NextDnsSwitchEntityDescription(SwitchEntityDescription):
|
||||
"""NextDNS switch entity description."""
|
||||
|
||||
state: Callable[[CoordinatorDataT], bool]
|
||||
state: Callable[[Settings], bool]
|
||||
|
||||
|
||||
SWITCHES = (
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_page",
|
||||
translation_key="block_page",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.block_page,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="cache_boost",
|
||||
translation_key="cache_boost",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.cache_boost,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="cname_flattening",
|
||||
translation_key="cname_flattening",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.cname_flattening,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="anonymized_ecs",
|
||||
translation_key="anonymized_ecs",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.anonymized_ecs,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="logs",
|
||||
translation_key="logs",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.logs,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="web3",
|
||||
translation_key="web3",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.web3,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="allow_affiliate",
|
||||
translation_key="allow_affiliate",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.allow_affiliate,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_disguised_trackers",
|
||||
translation_key="block_disguised_trackers",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.block_disguised_trackers,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="ai_threat_detection",
|
||||
translation_key="ai_threat_detection",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.ai_threat_detection,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_csam",
|
||||
translation_key="block_csam",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.block_csam,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_ddns",
|
||||
translation_key="block_ddns",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.block_ddns,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_nrd",
|
||||
translation_key="block_nrd",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.block_nrd,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_parked_domains",
|
||||
translation_key="block_parked_domains",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.block_parked_domains,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="cryptojacking_protection",
|
||||
translation_key="cryptojacking_protection",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.cryptojacking_protection,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="dga_protection",
|
||||
translation_key="dga_protection",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.dga_protection,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="dns_rebinding_protection",
|
||||
translation_key="dns_rebinding_protection",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.dns_rebinding_protection,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="google_safe_browsing",
|
||||
translation_key="google_safe_browsing",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.google_safe_browsing,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="idn_homograph_attacks_protection",
|
||||
translation_key="idn_homograph_attacks_protection",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.idn_homograph_attacks_protection,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="threat_intelligence_feeds",
|
||||
translation_key="threat_intelligence_feeds",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.threat_intelligence_feeds,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="typosquatting_protection",
|
||||
translation_key="typosquatting_protection",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.typosquatting_protection,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_bypass_methods",
|
||||
translation_key="block_bypass_methods",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.block_bypass_methods,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="safesearch",
|
||||
translation_key="safesearch",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.safesearch,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="youtube_restricted_mode",
|
||||
translation_key="youtube_restricted_mode",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
state=lambda data: data.youtube_restricted_mode,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_9gag",
|
||||
translation_key="block_9gag",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_9gag,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_amazon",
|
||||
translation_key="block_amazon",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_amazon,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_bereal",
|
||||
translation_key="block_bereal",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_bereal,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_blizzard",
|
||||
translation_key="block_blizzard",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_blizzard,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_chatgpt",
|
||||
translation_key="block_chatgpt",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_chatgpt,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_dailymotion",
|
||||
translation_key="block_dailymotion",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_dailymotion,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_discord",
|
||||
translation_key="block_discord",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_discord,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_disneyplus",
|
||||
translation_key="block_disneyplus",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_disneyplus,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_ebay",
|
||||
translation_key="block_ebay",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_ebay,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_facebook",
|
||||
translation_key="block_facebook",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_facebook,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_fortnite",
|
||||
translation_key="block_fortnite",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_fortnite,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_google_chat",
|
||||
translation_key="block_google_chat",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_google_chat,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_hbomax",
|
||||
translation_key="block_hbomax",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_hbomax,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_hulu",
|
||||
name="Block Hulu",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_hulu,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_imgur",
|
||||
translation_key="block_imgur",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_imgur,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_instagram",
|
||||
translation_key="block_instagram",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_instagram,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_leagueoflegends",
|
||||
translation_key="block_leagueoflegends",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_leagueoflegends,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_mastodon",
|
||||
translation_key="block_mastodon",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_mastodon,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_messenger",
|
||||
translation_key="block_messenger",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_messenger,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_minecraft",
|
||||
translation_key="block_minecraft",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_minecraft,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_netflix",
|
||||
translation_key="block_netflix",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_netflix,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_pinterest",
|
||||
translation_key="block_pinterest",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_pinterest,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_playstation_network",
|
||||
translation_key="block_playstation_network",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_playstation_network,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_primevideo",
|
||||
translation_key="block_primevideo",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_primevideo,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_reddit",
|
||||
translation_key="block_reddit",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_reddit,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_roblox",
|
||||
translation_key="block_roblox",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_roblox,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_signal",
|
||||
translation_key="block_signal",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_signal,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_skype",
|
||||
translation_key="block_skype",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_skype,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_snapchat",
|
||||
translation_key="block_snapchat",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_snapchat,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_spotify",
|
||||
translation_key="block_spotify",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_spotify,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_steam",
|
||||
translation_key="block_steam",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_steam,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_telegram",
|
||||
translation_key="block_telegram",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_telegram,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_tiktok",
|
||||
translation_key="block_tiktok",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_tiktok,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_tinder",
|
||||
translation_key="block_tinder",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_tinder,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_tumblr",
|
||||
translation_key="block_tumblr",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_tumblr,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_twitch",
|
||||
translation_key="block_twitch",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_twitch,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_twitter",
|
||||
translation_key="block_twitter",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_twitter,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_vimeo",
|
||||
translation_key="block_vimeo",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_vimeo,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_vk",
|
||||
translation_key="block_vk",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_vk,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_whatsapp",
|
||||
translation_key="block_whatsapp",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_whatsapp,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_xboxlive",
|
||||
translation_key="block_xboxlive",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_xboxlive,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_youtube",
|
||||
translation_key="block_youtube",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_youtube,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_zoom",
|
||||
translation_key="block_zoom",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_zoom,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_dating",
|
||||
translation_key="block_dating",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_dating,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_gambling",
|
||||
translation_key="block_gambling",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_gambling,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_online_gaming",
|
||||
translation_key="block_online_gaming",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_online_gaming,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_piracy",
|
||||
translation_key="block_piracy",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_piracy,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_porn",
|
||||
translation_key="block_porn",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_porn,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_social_networks",
|
||||
translation_key="block_social_networks",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
state=lambda data: data.block_social_networks,
|
||||
),
|
||||
NextDnsSwitchEntityDescription[Settings](
|
||||
NextDnsSwitchEntityDescription(
|
||||
key="block_video_streaming",
|
||||
translation_key="block_video_streaming",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
@ -526,19 +523,21 @@ SWITCHES = (
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant,
|
||||
entry: NextDnsConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Add NextDNS entities from a config_entry."""
|
||||
coordinator: NextDnsSettingsUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][
|
||||
ATTR_SETTINGS
|
||||
]
|
||||
coordinator = entry.runtime_data.settings
|
||||
|
||||
async_add_entities(
|
||||
NextDnsSwitch(coordinator, description) for description in SWITCHES
|
||||
)
|
||||
|
||||
|
||||
class NextDnsSwitch(CoordinatorEntity[NextDnsSettingsUpdateCoordinator], SwitchEntity):
|
||||
class NextDnsSwitch(
|
||||
CoordinatorEntity[NextDnsUpdateCoordinator[Settings]], SwitchEntity
|
||||
):
|
||||
"""Define an NextDNS switch."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
@ -546,7 +545,7 @@ class NextDnsSwitch(CoordinatorEntity[NextDnsSettingsUpdateCoordinator], SwitchE
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: NextDnsSettingsUpdateCoordinator,
|
||||
coordinator: NextDnsUpdateCoordinator[Settings],
|
||||
description: NextDnsSwitchEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
|
@ -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)
|
||||
)
|
||||
|
@ -29,6 +29,13 @@ send_message:
|
||||
required: true
|
||||
selector:
|
||||
text:
|
||||
title:
|
||||
required: false
|
||||
selector:
|
||||
text:
|
||||
filter:
|
||||
supported_features:
|
||||
- notify.NotifyEntityFeature.TITLE
|
||||
|
||||
persistent_notification:
|
||||
fields:
|
||||
|
@ -35,6 +35,10 @@
|
||||
"message": {
|
||||
"name": "Message",
|
||||
"description": "Your notification message."
|
||||
},
|
||||
"title": {
|
||||
"name": "Title",
|
||||
"description": "Title for your notification message."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -26,22 +26,30 @@ from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import (
|
||||
COORDINATOR,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DOMAIN,
|
||||
INTEGRATION_SUPPORTED_COMMANDS,
|
||||
PLATFORMS,
|
||||
PYNUT_DATA,
|
||||
PYNUT_UNIQUE_ID,
|
||||
USER_AVAILABLE_COMMANDS,
|
||||
)
|
||||
|
||||
NUT_FAKE_SERIAL = ["unknown", "blank"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
NutConfigEntry = ConfigEntry["NutRuntimeData"]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
@dataclass
|
||||
class NutRuntimeData:
|
||||
"""Runtime data definition."""
|
||||
|
||||
coordinator: DataUpdateCoordinator
|
||||
data: PyNUTData
|
||||
unique_id: str
|
||||
user_available_commands: set[str]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: NutConfigEntry) -> bool:
|
||||
"""Set up Network UPS Tools (NUT) from a config entry."""
|
||||
|
||||
# strip out the stale options CONF_RESOURCES,
|
||||
@ -110,13 +118,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
else:
|
||||
user_available_commands = set()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = {
|
||||
COORDINATOR: coordinator,
|
||||
PYNUT_DATA: data,
|
||||
PYNUT_UNIQUE_ID: unique_id,
|
||||
USER_AVAILABLE_COMMANDS: user_available_commands,
|
||||
}
|
||||
entry.runtime_data = NutRuntimeData(
|
||||
coordinator, data, unique_id, user_available_commands
|
||||
)
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
device_registry.async_get_or_create(
|
||||
@ -135,9 +139,7 @@ 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)
|
||||
|
||||
|
||||
async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
|
@ -15,15 +15,8 @@ DEFAULT_PORT = 3493
|
||||
KEY_STATUS = "ups.status"
|
||||
KEY_STATUS_DISPLAY = "ups.status.display"
|
||||
|
||||
COORDINATOR = "coordinator"
|
||||
DEFAULT_SCAN_INTERVAL = 60
|
||||
|
||||
PYNUT_DATA = "data"
|
||||
PYNUT_UNIQUE_ID = "unique_id"
|
||||
|
||||
|
||||
USER_AVAILABLE_COMMANDS = "user_available_commands"
|
||||
|
||||
STATE_TYPES = {
|
||||
"OL": "Online",
|
||||
"OB": "On Battery",
|
||||
|
@ -4,19 +4,15 @@ from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.device_automation import InvalidDeviceAutomationConfig
|
||||
from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_TYPE
|
||||
from homeassistant.core import Context, HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.typing import ConfigType, TemplateVarsType
|
||||
|
||||
from . import PyNUTData
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
INTEGRATION_SUPPORTED_COMMANDS,
|
||||
PYNUT_DATA,
|
||||
USER_AVAILABLE_COMMANDS,
|
||||
)
|
||||
from . import NutRuntimeData
|
||||
from .const import DOMAIN, INTEGRATION_SUPPORTED_COMMANDS
|
||||
|
||||
ACTION_TYPES = {cmd.replace(".", "_") for cmd in INTEGRATION_SUPPORTED_COMMANDS}
|
||||
|
||||
@ -31,18 +27,15 @@ async def async_get_actions(
|
||||
hass: HomeAssistant, device_id: str
|
||||
) -> list[dict[str, str]]:
|
||||
"""List device actions for Network UPS Tools (NUT) devices."""
|
||||
if (entry_id := _get_entry_id_from_device_id(hass, device_id)) is None:
|
||||
if (runtime_data := _get_runtime_data_from_device_id(hass, device_id)) is None:
|
||||
return []
|
||||
base_action = {
|
||||
CONF_DEVICE_ID: device_id,
|
||||
CONF_DOMAIN: DOMAIN,
|
||||
}
|
||||
user_available_commands: set[str] = hass.data[DOMAIN][entry_id][
|
||||
USER_AVAILABLE_COMMANDS
|
||||
]
|
||||
return [
|
||||
{CONF_TYPE: _get_device_action_name(command_name)} | base_action
|
||||
for command_name in user_available_commands
|
||||
for command_name in runtime_data.user_available_commands
|
||||
]
|
||||
|
||||
|
||||
@ -56,9 +49,12 @@ async def async_call_action_from_config(
|
||||
device_action_name: str = config[CONF_TYPE]
|
||||
command_name = _get_command_name(device_action_name)
|
||||
device_id: str = config[CONF_DEVICE_ID]
|
||||
entry_id = _get_entry_id_from_device_id(hass, device_id)
|
||||
data: PyNUTData = hass.data[DOMAIN][entry_id][PYNUT_DATA]
|
||||
await data.async_run_command(command_name)
|
||||
runtime_data = _get_runtime_data_from_device_id(hass, device_id)
|
||||
if not runtime_data:
|
||||
raise InvalidDeviceAutomationConfig(
|
||||
f"Unable to find a NUT device with id {device_id}"
|
||||
)
|
||||
await runtime_data.data.async_run_command(command_name)
|
||||
|
||||
|
||||
def _get_device_action_name(command_name: str) -> str:
|
||||
@ -69,8 +65,14 @@ def _get_command_name(device_action_name: str) -> str:
|
||||
return device_action_name.replace("_", ".")
|
||||
|
||||
|
||||
def _get_entry_id_from_device_id(hass: HomeAssistant, device_id: str) -> str | None:
|
||||
def _get_runtime_data_from_device_id(
|
||||
hass: HomeAssistant, device_id: str
|
||||
) -> NutRuntimeData | None:
|
||||
device_registry = dr.async_get(hass)
|
||||
if (device := device_registry.async_get(device_id)) is None:
|
||||
return None
|
||||
return next(entry for entry in device.config_entries)
|
||||
entry = hass.config_entries.async_get_entry(
|
||||
next(entry_id for entry_id in device.config_entries)
|
||||
)
|
||||
assert entry and isinstance(entry.runtime_data, NutRuntimeData)
|
||||
return entry.runtime_data
|
||||
|
@ -7,27 +7,26 @@ from typing import Any
|
||||
import attr
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from . import PyNUTData
|
||||
from .const import DOMAIN, PYNUT_DATA, PYNUT_UNIQUE_ID, USER_AVAILABLE_COMMANDS
|
||||
from . import NutConfigEntry
|
||||
from .const import DOMAIN
|
||||
|
||||
TO_REDACT = {CONF_PASSWORD, CONF_USERNAME}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: ConfigEntry
|
||||
hass: HomeAssistant, entry: NutConfigEntry
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
data = {"entry": async_redact_data(entry.as_dict(), TO_REDACT)}
|
||||
hass_data = hass.data[DOMAIN][entry.entry_id]
|
||||
hass_data = entry.runtime_data
|
||||
|
||||
# Get information from Nut library
|
||||
nut_data: PyNUTData = hass_data[PYNUT_DATA]
|
||||
nut_cmd: set[str] = hass_data[USER_AVAILABLE_COMMANDS]
|
||||
nut_data = hass_data.data
|
||||
nut_cmd = hass_data.user_available_commands
|
||||
data["nut_data"] = {
|
||||
"ups_list": nut_data.ups_list,
|
||||
"status": nut_data.status,
|
||||
@ -38,7 +37,7 @@ async def async_get_config_entry_diagnostics(
|
||||
device_registry = dr.async_get(hass)
|
||||
entity_registry = er.async_get(hass)
|
||||
hass_device = device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, hass_data[PYNUT_UNIQUE_ID])}
|
||||
identifiers={(DOMAIN, hass_data.unique_id)}
|
||||
)
|
||||
if not hass_device:
|
||||
return data
|
||||
|
@ -12,7 +12,6 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_MANUFACTURER,
|
||||
ATTR_MODEL,
|
||||
@ -36,16 +35,8 @@ from homeassistant.helpers.update_coordinator import (
|
||||
DataUpdateCoordinator,
|
||||
)
|
||||
|
||||
from . import PyNUTData
|
||||
from .const import (
|
||||
COORDINATOR,
|
||||
DOMAIN,
|
||||
KEY_STATUS,
|
||||
KEY_STATUS_DISPLAY,
|
||||
PYNUT_DATA,
|
||||
PYNUT_UNIQUE_ID,
|
||||
STATE_TYPES,
|
||||
)
|
||||
from . import NutConfigEntry, PyNUTData
|
||||
from .const import DOMAIN, KEY_STATUS, KEY_STATUS_DISPLAY, STATE_TYPES
|
||||
|
||||
NUT_DEV_INFO_TO_DEV_INFO: dict[str, str] = {
|
||||
"manufacturer": ATTR_MANUFACTURER,
|
||||
@ -968,15 +959,15 @@ def _get_nut_device_info(data: PyNUTData) -> DeviceInfo:
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
config_entry: NutConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the NUT sensors."""
|
||||
|
||||
pynut_data = hass.data[DOMAIN][config_entry.entry_id]
|
||||
coordinator = pynut_data[COORDINATOR]
|
||||
data = pynut_data[PYNUT_DATA]
|
||||
unique_id = pynut_data[PYNUT_UNIQUE_ID]
|
||||
pynut_data = config_entry.runtime_data
|
||||
coordinator = pynut_data.coordinator
|
||||
data = pynut_data.data
|
||||
unique_id = pynut_data.unique_id
|
||||
status = coordinator.data
|
||||
|
||||
resources = [sensor_id for sensor_id in SENSOR_TYPES if sensor_id in status]
|
||||
|
@ -2,21 +2,18 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
import datetime
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pynws import SimpleNWS
|
||||
from pynws import SimpleNWS, call_with_retry
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import debounce
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||
from homeassistant.helpers.update_coordinator import TimestampDataUpdateCoordinator
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
@ -27,8 +24,10 @@ _LOGGER = logging.getLogger(__name__)
|
||||
PLATFORMS = [Platform.SENSOR, Platform.WEATHER]
|
||||
|
||||
DEFAULT_SCAN_INTERVAL = datetime.timedelta(minutes=10)
|
||||
FAILED_SCAN_INTERVAL = datetime.timedelta(minutes=1)
|
||||
DEBOUNCE_TIME = 60 # in seconds
|
||||
RETRY_INTERVAL = datetime.timedelta(minutes=1)
|
||||
RETRY_STOP = datetime.timedelta(minutes=10)
|
||||
|
||||
DEBOUNCE_TIME = 10 * 60 # in seconds
|
||||
|
||||
|
||||
def base_unique_id(latitude: float, longitude: float) -> str:
|
||||
@ -41,62 +40,9 @@ class NWSData:
|
||||
"""Data for the National Weather Service integration."""
|
||||
|
||||
api: SimpleNWS
|
||||
coordinator_observation: NwsDataUpdateCoordinator
|
||||
coordinator_forecast: NwsDataUpdateCoordinator
|
||||
coordinator_forecast_hourly: NwsDataUpdateCoordinator
|
||||
|
||||
|
||||
class NwsDataUpdateCoordinator(TimestampDataUpdateCoordinator[None]): # pylint: disable=hass-enforce-coordinator-module
|
||||
"""NWS data update coordinator.
|
||||
|
||||
Implements faster data update intervals for failed updates and exposes a last successful update time.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
logger: logging.Logger,
|
||||
*,
|
||||
name: str,
|
||||
update_interval: datetime.timedelta,
|
||||
failed_update_interval: datetime.timedelta,
|
||||
update_method: Callable[[], Awaitable[None]] | None = None,
|
||||
request_refresh_debouncer: debounce.Debouncer | None = None,
|
||||
) -> None:
|
||||
"""Initialize NWS coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
logger,
|
||||
name=name,
|
||||
update_interval=update_interval,
|
||||
update_method=update_method,
|
||||
request_refresh_debouncer=request_refresh_debouncer,
|
||||
)
|
||||
self.failed_update_interval = failed_update_interval
|
||||
|
||||
@callback
|
||||
def _schedule_refresh(self) -> None:
|
||||
"""Schedule a refresh."""
|
||||
if self._unsub_refresh:
|
||||
self._unsub_refresh()
|
||||
self._unsub_refresh = None
|
||||
|
||||
# We _floor_ utcnow to create a schedule on a rounded second,
|
||||
# minimizing the time between the point and the real activation.
|
||||
# That way we obtain a constant update frequency,
|
||||
# as long as the update process takes less than a second
|
||||
if self.last_update_success:
|
||||
if TYPE_CHECKING:
|
||||
# the base class allows None, but this one doesn't
|
||||
assert self.update_interval is not None
|
||||
update_interval = self.update_interval
|
||||
else:
|
||||
update_interval = self.failed_update_interval
|
||||
self._unsub_refresh = async_track_point_in_utc_time(
|
||||
self.hass,
|
||||
self._handle_refresh_interval,
|
||||
utcnow().replace(microsecond=0) + update_interval,
|
||||
)
|
||||
coordinator_observation: TimestampDataUpdateCoordinator[None]
|
||||
coordinator_forecast: TimestampDataUpdateCoordinator[None]
|
||||
coordinator_forecast_hourly: TimestampDataUpdateCoordinator[None]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
@ -114,39 +60,57 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def update_observation() -> None:
|
||||
"""Retrieve recent observations."""
|
||||
await nws_data.update_observation(start_time=utcnow() - UPDATE_TIME_PERIOD)
|
||||
await call_with_retry(
|
||||
nws_data.update_observation,
|
||||
RETRY_INTERVAL,
|
||||
RETRY_STOP,
|
||||
start_time=utcnow() - UPDATE_TIME_PERIOD,
|
||||
)
|
||||
|
||||
coordinator_observation = NwsDataUpdateCoordinator(
|
||||
async def update_forecast() -> None:
|
||||
"""Retrieve twice-daily forecsat."""
|
||||
await call_with_retry(
|
||||
nws_data.update_forecast,
|
||||
RETRY_INTERVAL,
|
||||
RETRY_STOP,
|
||||
)
|
||||
|
||||
async def update_forecast_hourly() -> None:
|
||||
"""Retrieve hourly forecast."""
|
||||
await call_with_retry(
|
||||
nws_data.update_forecast_hourly,
|
||||
RETRY_INTERVAL,
|
||||
RETRY_STOP,
|
||||
)
|
||||
|
||||
coordinator_observation = TimestampDataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=f"NWS observation station {station}",
|
||||
update_method=update_observation,
|
||||
update_interval=DEFAULT_SCAN_INTERVAL,
|
||||
failed_update_interval=FAILED_SCAN_INTERVAL,
|
||||
request_refresh_debouncer=debounce.Debouncer(
|
||||
hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True
|
||||
),
|
||||
)
|
||||
|
||||
coordinator_forecast = NwsDataUpdateCoordinator(
|
||||
coordinator_forecast = TimestampDataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=f"NWS forecast station {station}",
|
||||
update_method=nws_data.update_forecast,
|
||||
update_method=update_forecast,
|
||||
update_interval=DEFAULT_SCAN_INTERVAL,
|
||||
failed_update_interval=FAILED_SCAN_INTERVAL,
|
||||
request_refresh_debouncer=debounce.Debouncer(
|
||||
hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True
|
||||
),
|
||||
)
|
||||
|
||||
coordinator_forecast_hourly = NwsDataUpdateCoordinator(
|
||||
coordinator_forecast_hourly = TimestampDataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=f"NWS forecast hourly station {station}",
|
||||
update_method=nws_data.update_forecast_hourly,
|
||||
update_method=update_forecast_hourly,
|
||||
update_interval=DEFAULT_SCAN_INTERVAL,
|
||||
failed_update_interval=FAILED_SCAN_INTERVAL,
|
||||
request_refresh_debouncer=debounce.Debouncer(
|
||||
hass, _LOGGER, cooldown=DEBOUNCE_TIME, immediate=True
|
||||
),
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user