Add a coordinator to Point (#126775)

* Add a coordinator to Point

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix
This commit is contained in:
Joost Lekkerkerker 2025-03-30 20:58:40 +02:00 committed by GitHub
parent 5106548f2c
commit 9c869fa701
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 179 additions and 250 deletions

View File

@ -1,7 +1,5 @@
"""Support for Minut Point.""" """Support for Minut Point."""
import asyncio
from dataclasses import dataclass
from http import HTTPStatus from http import HTTPStatus
import logging import logging
@ -29,26 +27,18 @@ from homeassistant.helpers import (
config_validation as cv, config_validation as cv,
) )
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from . import api from . import api
from .const import ( from .const import CONF_WEBHOOK_URL, DOMAIN, EVENT_RECEIVED, SIGNAL_WEBHOOK
CONF_WEBHOOK_URL, from .coordinator import PointDataUpdateCoordinator
DOMAIN,
EVENT_RECEIVED,
POINT_DISCOVERY_NEW,
SCAN_INTERVAL,
SIGNAL_UPDATE_ENTITY,
SIGNAL_WEBHOOK,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
type PointConfigEntry = ConfigEntry[PointData] type PointConfigEntry = ConfigEntry[PointDataUpdateCoordinator]
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = vol.Schema(
{ {
@ -131,9 +121,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: PointConfigEntry) -> boo
point_session = PointSession(auth) point_session = PointSession(auth)
client = MinutPointClient(hass, entry, point_session) coordinator = PointDataUpdateCoordinator(hass, point_session)
hass.async_create_task(client.update())
entry.runtime_data = PointData(client) await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
await async_setup_webhook(hass, entry, point_session) await async_setup_webhook(hass, entry, point_session)
await hass.config_entries.async_forward_entry_setups( await hass.config_entries.async_forward_entry_setups(
@ -176,7 +168,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: PointConfigEntry) -> bo
if unload_ok := await hass.config_entries.async_unload_platforms( if unload_ok := await hass.config_entries.async_unload_platforms(
entry, [*PLATFORMS, Platform.ALARM_CONTROL_PANEL] entry, [*PLATFORMS, Platform.ALARM_CONTROL_PANEL]
): ):
session: PointSession = entry.runtime_data.client session = entry.runtime_data.point
if CONF_WEBHOOK_ID in entry.data: if CONF_WEBHOOK_ID in entry.data:
webhook.async_unregister(hass, entry.data[CONF_WEBHOOK_ID]) webhook.async_unregister(hass, entry.data[CONF_WEBHOOK_ID])
await session.remove_webhook() await session.remove_webhook()
@ -197,87 +189,3 @@ async def handle_webhook(
data["webhook_id"] = webhook_id data["webhook_id"] = webhook_id
async_dispatcher_send(hass, SIGNAL_WEBHOOK, data, data.get("hook_id")) async_dispatcher_send(hass, SIGNAL_WEBHOOK, data, data.get("hook_id"))
hass.bus.async_fire(EVENT_RECEIVED, data) hass.bus.async_fire(EVENT_RECEIVED, data)
class MinutPointClient:
"""Get the latest data and update the states."""
def __init__(
self, hass: HomeAssistant, config_entry: ConfigEntry, session: PointSession
) -> None:
"""Initialize the Minut data object."""
self._known_devices: set[str] = set()
self._known_homes: set[str] = set()
self._hass = hass
self._config_entry = config_entry
self._is_available = True
self._client = session
async_track_time_interval(self._hass, self.update, SCAN_INTERVAL)
async def update(self, *args):
"""Periodically poll the cloud for current state."""
await self._sync()
async def _sync(self):
"""Update local list of devices."""
if not await self._client.update():
self._is_available = False
_LOGGER.warning("Device is unavailable")
async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY)
return
self._is_available = True
for home_id in self._client.homes:
if home_id not in self._known_homes:
async_dispatcher_send(
self._hass,
POINT_DISCOVERY_NEW.format(Platform.ALARM_CONTROL_PANEL),
home_id,
)
self._known_homes.add(home_id)
for device in self._client.devices:
if device.device_id not in self._known_devices:
for platform in PLATFORMS:
async_dispatcher_send(
self._hass,
POINT_DISCOVERY_NEW.format(platform),
device.device_id,
)
self._known_devices.add(device.device_id)
async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY)
def device(self, device_id):
"""Return device representation."""
return self._client.device(device_id)
def is_available(self, device_id):
"""Return device availability."""
if not self._is_available:
return False
return device_id in self._client.device_ids
async def remove_webhook(self):
"""Remove the session webhook."""
return await self._client.remove_webhook()
@property
def homes(self):
"""Return known homes."""
return self._client.homes
async def async_alarm_disarm(self, home_id):
"""Send alarm disarm command."""
return await self._client.alarm_disarm(home_id)
async def async_alarm_arm(self, home_id):
"""Send alarm arm command."""
return await self._client.alarm_arm(home_id)
@dataclass
class PointData:
"""Point Data."""
client: MinutPointClient
entry_lock: asyncio.Lock = asyncio.Lock()

View File

@ -2,23 +2,22 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable
import logging import logging
from pypoint import PointSession
from homeassistant.components.alarm_control_panel import ( from homeassistant.components.alarm_control_panel import (
DOMAIN as ALARM_CONTROL_PANEL_DOMAIN,
AlarmControlPanelEntity, AlarmControlPanelEntity,
AlarmControlPanelEntityFeature, AlarmControlPanelEntityFeature,
AlarmControlPanelState, AlarmControlPanelState,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import MinutPointClient from . import PointConfigEntry
from .const import DOMAIN as POINT_DOMAIN, POINT_DISCOVERY_NEW, SIGNAL_WEBHOOK from .const import DOMAIN as POINT_DOMAIN, SIGNAL_WEBHOOK
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -32,21 +31,20 @@ EVENT_MAP = {
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: PointConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback, async_add_entities: AddConfigEntryEntitiesCallback,
) -> None: ) -> None:
"""Set up a Point's alarm_control_panel based on a config entry.""" """Set up a Point's alarm_control_panel based on a config entry."""
coordinator = config_entry.runtime_data
async def async_discover_home(home_id): def async_discover_home(home_id: str) -> None:
"""Discover and add a discovered home.""" """Discover and add a discovered home."""
client = config_entry.runtime_data.client async_add_entities([MinutPointAlarmControl(coordinator.point, home_id)])
async_add_entities([MinutPointAlarmControl(client, home_id)], True)
async_dispatcher_connect( coordinator.new_home_callback = async_discover_home
hass,
POINT_DISCOVERY_NEW.format(ALARM_CONTROL_PANEL_DOMAIN, POINT_DOMAIN), for home_id in coordinator.point.homes:
async_discover_home, async_discover_home(home_id)
)
class MinutPointAlarmControl(AlarmControlPanelEntity): class MinutPointAlarmControl(AlarmControlPanelEntity):
@ -55,12 +53,11 @@ class MinutPointAlarmControl(AlarmControlPanelEntity):
_attr_supported_features = AlarmControlPanelEntityFeature.ARM_AWAY _attr_supported_features = AlarmControlPanelEntityFeature.ARM_AWAY
_attr_code_arm_required = False _attr_code_arm_required = False
def __init__(self, point_client: MinutPointClient, home_id: str) -> None: def __init__(self, point: PointSession, home_id: str) -> None:
"""Initialize the entity.""" """Initialize the entity."""
self._client = point_client self._client = point
self._home_id = home_id self._home_id = home_id
self._async_unsub_hook_dispatcher_connect: Callable[[], None] | None = None self._home = point.homes[self._home_id]
self._home = point_client.homes[self._home_id]
self._attr_name = self._home["name"] self._attr_name = self._home["name"]
self._attr_unique_id = f"point.{home_id}" self._attr_unique_id = f"point.{home_id}"
@ -73,16 +70,10 @@ class MinutPointAlarmControl(AlarmControlPanelEntity):
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Call when entity is added to HOme Assistant.""" """Call when entity is added to HOme Assistant."""
await super().async_added_to_hass() await super().async_added_to_hass()
self._async_unsub_hook_dispatcher_connect = async_dispatcher_connect( self.async_on_remove(
self.hass, SIGNAL_WEBHOOK, self._webhook_event async_dispatcher_connect(self.hass, SIGNAL_WEBHOOK, self._webhook_event)
) )
async def async_will_remove_from_hass(self) -> None:
"""Disconnect dispatcher listener when removed."""
await super().async_will_remove_from_hass()
if self._async_unsub_hook_dispatcher_connect:
self._async_unsub_hook_dispatcher_connect()
@callback @callback
def _webhook_event(self, data, webhook): def _webhook_event(self, data, webhook):
"""Process new event from the webhook.""" """Process new event from the webhook."""
@ -107,12 +98,12 @@ class MinutPointAlarmControl(AlarmControlPanelEntity):
async def async_alarm_disarm(self, code: str | None = None) -> None: async def async_alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command.""" """Send disarm command."""
status = await self._client.async_alarm_disarm(self._home_id) status = await self._client.alarm_disarm(self._home_id)
if status: if status:
self._home["alarm_status"] = "off" self._home["alarm_status"] = "off"
async def async_alarm_arm_away(self, code: str | None = None) -> None: async def async_alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command.""" """Send arm away command."""
status = await self._client.async_alarm_arm(self._home_id) status = await self._client.alarm_arm(self._home_id)
if status: if status:
self._home["alarm_status"] = "on" self._home["alarm_status"] = "on"

View File

@ -3,26 +3,27 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from typing import Any
from pypoint import EVENTS from pypoint import EVENTS
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
DOMAIN as BINARY_SENSOR_DOMAIN,
BinarySensorDeviceClass, BinarySensorDeviceClass,
BinarySensorEntity, BinarySensorEntity,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN as POINT_DOMAIN, POINT_DISCOVERY_NEW, SIGNAL_WEBHOOK from . import PointConfigEntry
from .const import SIGNAL_WEBHOOK
from .coordinator import PointDataUpdateCoordinator
from .entity import MinutPointEntity from .entity import MinutPointEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEVICES = { DEVICES: dict[str, Any] = {
"alarm": {"icon": "mdi:alarm-bell"}, "alarm": {"icon": "mdi:alarm-bell"},
"battery": {"device_class": BinarySensorDeviceClass.BATTERY}, "battery": {"device_class": BinarySensorDeviceClass.BATTERY},
"button_press": {"icon": "mdi:gesture-tap-button"}, "button_press": {"icon": "mdi:gesture-tap-button"},
@ -42,69 +43,60 @@ DEVICES = {
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: PointConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback, async_add_entities: AddConfigEntryEntitiesCallback,
) -> None: ) -> None:
"""Set up a Point's binary sensors based on a config entry.""" """Set up a Point's binary sensors based on a config entry."""
async def async_discover_sensor(device_id): coordinator = config_entry.runtime_data
def async_discover_sensor(device_id: str) -> None:
"""Discover and add a discovered sensor.""" """Discover and add a discovered sensor."""
client = config_entry.runtime_data.client
async_add_entities( async_add_entities(
( MinutPointBinarySensor(coordinator, device_id, device_name)
MinutPointBinarySensor(client, device_id, device_name)
for device_name in DEVICES for device_name in DEVICES
if device_name in EVENTS if device_name in EVENTS
),
True,
) )
async_dispatcher_connect( coordinator.new_device_callbacks.append(async_discover_sensor)
hass,
POINT_DISCOVERY_NEW.format(BINARY_SENSOR_DOMAIN, POINT_DOMAIN), async_add_entities(
async_discover_sensor, MinutPointBinarySensor(coordinator, device_id, device_name)
for device_name in DEVICES
if device_name in EVENTS
for device_id in coordinator.point.device_ids
) )
class MinutPointBinarySensor(MinutPointEntity, BinarySensorEntity): class MinutPointBinarySensor(MinutPointEntity, BinarySensorEntity):
"""The platform class required by Home Assistant.""" """The platform class required by Home Assistant."""
def __init__(self, point_client, device_id, device_name): def __init__(
self, coordinator: PointDataUpdateCoordinator, device_id: str, key: str
) -> None:
"""Initialize the binary sensor.""" """Initialize the binary sensor."""
super().__init__( self._attr_device_class = DEVICES[key].get("device_class", key)
point_client, super().__init__(coordinator, device_id)
device_id, self._device_name = key
DEVICES[device_name].get("device_class", device_name), self._events = EVENTS[key]
) self._attr_unique_id = f"point.{device_id}-{key}"
self._device_name = device_name self._attr_icon = DEVICES[key].get("icon")
self._async_unsub_hook_dispatcher_connect = None
self._events = EVENTS[device_name]
self._attr_unique_id = f"point.{device_id}-{device_name}"
self._attr_icon = DEVICES[self._device_name].get("icon")
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Call when entity is added to HOme Assistant.""" """Call when entity is added to HOme Assistant."""
await super().async_added_to_hass() await super().async_added_to_hass()
self._async_unsub_hook_dispatcher_connect = async_dispatcher_connect( self.async_on_remove(
self.hass, SIGNAL_WEBHOOK, self._webhook_event async_dispatcher_connect(self.hass, SIGNAL_WEBHOOK, self._webhook_event)
) )
async def async_will_remove_from_hass(self) -> None: def _handle_coordinator_update(self) -> None:
"""Disconnect dispatcher listener when removed."""
await super().async_will_remove_from_hass()
if self._async_unsub_hook_dispatcher_connect:
self._async_unsub_hook_dispatcher_connect()
async def _update_callback(self):
"""Update the value of the sensor.""" """Update the value of the sensor."""
if not self.is_updated:
return
if self.device_class == BinarySensorDeviceClass.CONNECTIVITY: if self.device_class == BinarySensorDeviceClass.CONNECTIVITY:
# connectivity is the other way around. # connectivity is the other way around.
self._attr_is_on = self._events[0] not in self.device.ongoing_events self._attr_is_on = self._events[0] not in self.device.ongoing_events
else: else:
self._attr_is_on = self._events[0] in self.device.ongoing_events self._attr_is_on = self._events[0] in self.device.ongoing_events
self.async_write_ha_state() super()._handle_coordinator_update()
@callback @callback
def _webhook_event(self, data, webhook): def _webhook_event(self, data, webhook):

View File

@ -0,0 +1,70 @@
"""Define a data update coordinator for Point."""
from collections.abc import Callable
from datetime import datetime
import logging
from typing import Any
from pypoint import PointSession
from tempora.utc import fromtimestamp
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util.dt import parse_datetime
from .const import DOMAIN, SCAN_INTERVAL
_LOGGER = logging.getLogger(__name__)
class PointDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]):
"""Class to manage fetching Point data from the API."""
def __init__(self, hass: HomeAssistant, point: PointSession) -> None:
"""Initialize."""
super().__init__(
hass,
_LOGGER,
name=DOMAIN,
update_interval=SCAN_INTERVAL,
)
self.point = point
self.device_updates: dict[str, datetime] = {}
self._known_devices: set[str] = set()
self._known_homes: set[str] = set()
self.new_home_callback: Callable[[str], None] | None = None
self.new_device_callbacks: list[Callable[[str], None]] = []
self.data: dict[str, dict[str, Any]] = {}
async def _async_update_data(self) -> dict[str, dict[str, Any]]:
if not await self.point.update():
raise UpdateFailed("Failed to fetch data from Point")
if new_homes := set(self.point.homes) - self._known_homes:
_LOGGER.debug("Found new homes: %s", new_homes)
for home_id in new_homes:
if self.new_home_callback:
self.new_home_callback(home_id)
self._known_homes.update(new_homes)
device_ids = {device.device_id for device in self.point.devices}
if new_devices := device_ids - self._known_devices:
_LOGGER.debug("Found new devices: %s", new_devices)
for device_id in new_devices:
for callback in self.new_device_callbacks:
callback(device_id)
self._known_devices.update(new_devices)
for device in self.point.devices:
last_updated = parse_datetime(device.last_update)
if (
not last_updated
or device.device_id not in self.device_updates
or self.device_updates[device.device_id] < last_updated
):
self.device_updates[device.device_id] = last_updated or fromtimestamp(0)
self.data[device.device_id] = {
k: await device.sensor(k)
for k in ("temperature", "humidity", "sound_pressure")
}
return self.data

View File

@ -2,31 +2,27 @@
import logging import logging
from pypoint import Device, PointSession
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.helpers.entity import Entity from homeassistant.util.dt import as_local
from homeassistant.util.dt import as_local, parse_datetime, utc_from_timestamp
from .const import DOMAIN, SIGNAL_UPDATE_ENTITY from .const import DOMAIN
from .coordinator import PointDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class MinutPointEntity(Entity): class MinutPointEntity(CoordinatorEntity[PointDataUpdateCoordinator]):
"""Base Entity used by the sensors.""" """Base Entity used by the sensors."""
_attr_should_poll = False def __init__(self, coordinator: PointDataUpdateCoordinator, device_id: str) -> None:
def __init__(self, point_client, device_id, device_class) -> None:
"""Initialize the entity.""" """Initialize the entity."""
self._async_unsub_dispatcher_connect = None super().__init__(coordinator)
self._client = point_client self.device_id = device_id
self._id = device_id
self._name = self.device.name self._name = self.device.name
self._attr_device_class = device_class
self._updated = utc_from_timestamp(0)
self._attr_unique_id = f"point.{device_id}-{device_class}"
device = self.device.device device = self.device.device
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
connections={(dr.CONNECTION_NETWORK_MAC, device["device_mac"])}, connections={(dr.CONNECTION_NETWORK_MAC, device["device_mac"])},
@ -37,59 +33,32 @@ class MinutPointEntity(Entity):
sw_version=device["firmware"]["installed"], sw_version=device["firmware"]["installed"],
via_device=(DOMAIN, device["home"]), via_device=(DOMAIN, device["home"]),
) )
if device_class: if self.device_class:
self._attr_name = f"{self._name} {device_class.capitalize()}" self._attr_name = f"{self._name} {self.device_class.capitalize()}"
def __str__(self) -> str:
"""Return string representation of device."""
return f"MinutPoint {self.name}"
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
_LOGGER.debug("Created device %s", self)
self._async_unsub_dispatcher_connect = async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_ENTITY, self._update_callback
)
await self._update_callback()
async def async_will_remove_from_hass(self) -> None:
"""Disconnect dispatcher listener when removed."""
if self._async_unsub_dispatcher_connect:
self._async_unsub_dispatcher_connect()
async def _update_callback(self): async def _update_callback(self):
"""Update the value of the sensor.""" """Update the value of the sensor."""
@property
def client(self) -> PointSession:
"""Return the client object."""
return self.coordinator.point
@property @property
def available(self) -> bool: def available(self) -> bool:
"""Return true if device is not offline.""" """Return true if device is not offline."""
return self._client.is_available(self.device_id) return super().available and self.device_id in self.client.device_ids
@property @property
def device(self): def device(self) -> Device:
"""Return the representation of the device.""" """Return the representation of the device."""
return self._client.device(self.device_id) return self.client.device(self.device_id)
@property
def device_id(self):
"""Return the id of the device."""
return self._id
@property @property
def extra_state_attributes(self): def extra_state_attributes(self):
"""Return status of device.""" """Return status of device."""
attrs = self.device.device_status attrs = self.device.device_status
attrs["last_heard_from"] = as_local(self.last_update).strftime( attrs["last_heard_from"] = as_local(
"%Y-%m-%d %H:%M:%S" self.coordinator.device_updates[self.device_id]
) ).strftime("%Y-%m-%d %H:%M:%S")
return attrs return attrs
@property
def is_updated(self):
"""Return true if sensor have been updated."""
return self.last_update > self._updated
@property
def last_update(self):
"""Return the last_update time for the device."""
return parse_datetime(self.device.last_update)

View File

@ -5,19 +5,17 @@ from __future__ import annotations
import logging import logging
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
DOMAIN as SENSOR_DOMAIN,
SensorDeviceClass, SensorDeviceClass,
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE, UnitOfSoundPressure, UnitOfTemperature from homeassistant.const import PERCENTAGE, UnitOfSoundPressure, UnitOfTemperature
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util.dt import parse_datetime from homeassistant.helpers.typing import StateType
from .const import DOMAIN as POINT_DOMAIN, POINT_DISCOVERY_NEW from . import PointConfigEntry
from .coordinator import PointDataUpdateCoordinator
from .entity import MinutPointEntity from .entity import MinutPointEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -37,7 +35,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
), ),
SensorEntityDescription( SensorEntityDescription(
key="sound", key="sound_pressure",
suggested_display_precision=1, suggested_display_precision=1,
device_class=SensorDeviceClass.SOUND_PRESSURE, device_class=SensorDeviceClass.SOUND_PRESSURE,
native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A, native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A,
@ -47,26 +45,26 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: PointConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback, async_add_entities: AddConfigEntryEntitiesCallback,
) -> None: ) -> None:
"""Set up a Point's sensors based on a config entry.""" """Set up a Point's sensors based on a config entry."""
async def async_discover_sensor(device_id): coordinator = config_entry.runtime_data
def async_discover_sensor(device_id: str) -> None:
"""Discover and add a discovered sensor.""" """Discover and add a discovered sensor."""
client = config_entry.runtime_data.client
async_add_entities( async_add_entities(
[ MinutPointSensor(coordinator, device_id, description)
MinutPointSensor(client, device_id, description)
for description in SENSOR_TYPES for description in SENSOR_TYPES
],
True,
) )
async_dispatcher_connect( coordinator.new_device_callbacks.append(async_discover_sensor)
hass,
POINT_DISCOVERY_NEW.format(SENSOR_DOMAIN, POINT_DOMAIN), async_add_entities(
async_discover_sensor, MinutPointSensor(coordinator, device_id, description)
for device_id in coordinator.data
for description in SENSOR_TYPES
) )
@ -74,16 +72,17 @@ class MinutPointSensor(MinutPointEntity, SensorEntity):
"""The platform class required by Home Assistant.""" """The platform class required by Home Assistant."""
def __init__( def __init__(
self, point_client, device_id, description: SensorEntityDescription self,
coordinator: PointDataUpdateCoordinator,
device_id: str,
description: SensorEntityDescription,
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(point_client, device_id, description.device_class)
self.entity_description = description self.entity_description = description
super().__init__(coordinator, device_id)
self._attr_unique_id = f"point.{device_id}-{description.key}"
async def _update_callback(self): @property
"""Update the value of the sensor.""" def native_value(self) -> StateType:
_LOGGER.debug("Update sensor value for %s", self) """Return the state of the sensor."""
if self.is_updated: return self.coordinator.data[self.device_id].get(self.entity_description.key)
self._attr_native_value = await self.device.sensor(self.device_class)
self._updated = parse_datetime(self.device.last_update)
self.async_write_ha_state()