diff --git a/.coveragerc b/.coveragerc index 49cfbb3acc7..14773947be1 100644 --- a/.coveragerc +++ b/.coveragerc @@ -640,7 +640,10 @@ omit = homeassistant/components/kwb/sensor.py homeassistant/components/lacrosse/sensor.py homeassistant/components/lametric/__init__.py + homeassistant/components/lametric/coordinator.py + homeassistant/components/lametric/entity.py homeassistant/components/lametric/notify.py + homeassistant/components/lametric/number.py homeassistant/components/lannouncer/notify.py homeassistant/components/lastfm/sensor.py homeassistant/components/launch_library/__init__.py diff --git a/homeassistant/components/lametric/__init__.py b/homeassistant/components/lametric/__init__.py index 2f89d88d79d..04f02a7f8f9 100644 --- a/homeassistant/components/lametric/__init__.py +++ b/homeassistant/components/lametric/__init__.py @@ -1,25 +1,17 @@ """Support for LaMetric time.""" -from demetriek import LaMetricConnectionError, LaMetricDevice import voluptuous as vol +from homeassistant.components import notify as hass_notify from homeassistant.components.repairs import IssueSeverity, async_create_issue from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - CONF_API_KEY, - CONF_CLIENT_ID, - CONF_CLIENT_SECRET, - CONF_HOST, - CONF_NAME, - Platform, -) +from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_NAME, Platform from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import discovery -from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType -from .const import DOMAIN +from .const import DOMAIN, PLATFORMS +from .coordinator import LaMetricDataUpdateCoordinator CONFIG_SCHEMA = vol.Schema( vol.All( @@ -56,18 +48,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up LaMetric from a config entry.""" - lametric = LaMetricDevice( - host=entry.data[CONF_HOST], - api_key=entry.data[CONF_API_KEY], - session=async_get_clientsession(hass), - ) + coordinator = LaMetricDataUpdateCoordinator(hass, entry) + await coordinator.async_config_entry_first_refresh() - try: - device = await lametric.device() - except LaMetricConnectionError as ex: - raise ConfigEntryNotReady("Cannot connect to LaMetric device") from ex - - hass.data.setdefault(DOMAIN, {})[entry.entry_id] = lametric + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) # Set up notify platform, no entry support for notify component yet, # have to use discovery to load platform. @@ -76,8 +61,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass, Platform.NOTIFY, DOMAIN, - {CONF_NAME: device.name, "entry_id": entry.entry_id}, + {CONF_NAME: coordinator.data.name, "entry_id": entry.entry_id}, hass.data[DOMAIN]["hass_config"], ) ) return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload LaMetric config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + del hass.data[DOMAIN][entry.entry_id] + await hass_notify.async_reload(hass, DOMAIN) + return unload_ok diff --git a/homeassistant/components/lametric/const.py b/homeassistant/components/lametric/const.py index d357f678d9d..0fe4b3f21d8 100644 --- a/homeassistant/components/lametric/const.py +++ b/homeassistant/components/lametric/const.py @@ -1,11 +1,16 @@ """Constants for the LaMetric integration.""" +from datetime import timedelta import logging from typing import Final +from homeassistant.const import Platform + DOMAIN: Final = "lametric" +PLATFORMS = [Platform.NUMBER] LOGGER = logging.getLogger(__package__) +SCAN_INTERVAL = timedelta(seconds=30) CONF_CYCLES: Final = "cycles" CONF_ICON_TYPE: Final = "icon_type" diff --git a/homeassistant/components/lametric/coordinator.py b/homeassistant/components/lametric/coordinator.py new file mode 100644 index 00000000000..0a5e99e5668 --- /dev/null +++ b/homeassistant/components/lametric/coordinator.py @@ -0,0 +1,38 @@ +"""DataUpdateCoordinator for the LaMatric integration.""" +from __future__ import annotations + +from demetriek import Device, LaMetricDevice, LaMetricError + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_API_KEY, CONF_HOST +from homeassistant.core import HomeAssistant +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DOMAIN, LOGGER, SCAN_INTERVAL + + +class LaMetricDataUpdateCoordinator(DataUpdateCoordinator[Device]): + """The LaMetric Data Update Coordinator.""" + + config_entry: ConfigEntry + + def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: + """Initialize the LaMatric coordinator.""" + self.config_entry = entry + self.lametric = LaMetricDevice( + host=entry.data[CONF_HOST], + api_key=entry.data[CONF_API_KEY], + session=async_get_clientsession(hass), + ) + + super().__init__(hass, LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL) + + async def _async_update_data(self) -> Device: + """Fetch device information of the LaMetric device.""" + try: + return await self.lametric.device() + except LaMetricError as ex: + raise UpdateFailed( + "Could not fetch device information from LaMetric device" + ) from ex diff --git a/homeassistant/components/lametric/entity.py b/homeassistant/components/lametric/entity.py new file mode 100644 index 00000000000..1e31b2968af --- /dev/null +++ b/homeassistant/components/lametric/entity.py @@ -0,0 +1,29 @@ +"""Base entity for the LaMetric integration.""" +from __future__ import annotations + +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, format_mac +from homeassistant.helpers.entity import DeviceInfo, Entity +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN +from .coordinator import LaMetricDataUpdateCoordinator + + +class LaMetricEntity(CoordinatorEntity[LaMetricDataUpdateCoordinator], Entity): + """Defines a LaMetric entity.""" + + _attr_has_entity_name = True + + def __init__(self, coordinator: LaMetricDataUpdateCoordinator) -> None: + """Initialize the LaMetric entity.""" + super().__init__(coordinator=coordinator) + self._attr_device_info = DeviceInfo( + connections={ + (CONNECTION_NETWORK_MAC, format_mac(coordinator.data.wifi.mac)) + }, + identifiers={(DOMAIN, coordinator.data.serial_number)}, + manufacturer="LaMetric Inc.", + model=coordinator.data.model, + name=coordinator.data.name, + sw_version=coordinator.data.os_version, + ) diff --git a/homeassistant/components/lametric/manifest.json b/homeassistant/components/lametric/manifest.json index 1a40f962156..578906483fa 100644 --- a/homeassistant/components/lametric/manifest.json +++ b/homeassistant/components/lametric/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/lametric", "requirements": ["demetriek==0.2.2"], "codeowners": ["@robbiet480", "@frenck"], - "iot_class": "local_push", + "iot_class": "local_polling", "dependencies": ["application_credentials", "repairs"], "loggers": ["demetriek"], "config_flow": true, diff --git a/homeassistant/components/lametric/number.py b/homeassistant/components/lametric/number.py new file mode 100644 index 00000000000..c788eb3255e --- /dev/null +++ b/homeassistant/components/lametric/number.py @@ -0,0 +1,90 @@ +"""Support for LaMetric numbers.""" +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from typing import Any + +from demetriek import Device, LaMetricDevice + +from homeassistant.components.number import NumberEntity, NumberEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import LaMetricDataUpdateCoordinator +from .entity import LaMetricEntity + + +@dataclass +class LaMetricEntityDescriptionMixin: + """Mixin values for LaMetric entities.""" + + value_fn: Callable[[Device], int | None] + set_value_fn: Callable[[LaMetricDevice, float], Awaitable[Any]] + + +@dataclass +class LaMetricNumberEntityDescription( + NumberEntityDescription, LaMetricEntityDescriptionMixin +): + """Class describing LaMetric number entities.""" + + +NUMBERS = [ + LaMetricNumberEntityDescription( + key="volume", + name="Volume", + icon="mdi:volume-high", + entity_category=EntityCategory.CONFIG, + native_step=1, + native_min_value=0, + native_max_value=100, + value_fn=lambda device: device.audio.volume, + set_value_fn=lambda api, volume: api.audio(volume=int(volume)), + ), +] + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up LaMetric number based on a config entry.""" + coordinator: LaMetricDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + LaMetricNumberEntity( + coordinator=coordinator, + description=description, + ) + for description in NUMBERS + ) + + +class LaMetricNumberEntity(LaMetricEntity, NumberEntity): + """Representation of a LaMetric number.""" + + entity_description: LaMetricNumberEntityDescription + + def __init__( + self, + coordinator: LaMetricDataUpdateCoordinator, + description: LaMetricNumberEntityDescription, + ) -> None: + """Initiate LaMetric Number.""" + super().__init__(coordinator) + self.entity_description = description + self._attr_unique_id = f"{coordinator.data.serial_number}-{description.key}" + + @property + def native_value(self) -> int | None: + """Return the number value.""" + return self.entity_description.value_fn(self.coordinator.data) + + async def async_set_native_value(self, value: float) -> None: + """Change to new number value.""" + await self.entity_description.set_value_fn(self.coordinator.lametric, value) + await self.coordinator.async_request_refresh()