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