core/homeassistant/components/thermopro/button.py

158 lines
5.2 KiB
Python

"""Thermopro button platform."""
from __future__ import annotations
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from typing import Any
from thermopro_ble import SensorUpdate, ThermoProBluetoothDeviceData, ThermoProDevice
from homeassistant.components.bluetooth import (
BluetoothServiceInfoBleak,
async_ble_device_from_address,
async_track_unavailable,
)
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
)
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util.dt import now
from .const import DOMAIN, SIGNAL_AVAILABILITY_UPDATED, SIGNAL_DATA_UPDATED
PARALLEL_UPDATES = 1 # one connection at a time
@dataclass(kw_only=True, frozen=True)
class ThermoProButtonEntityDescription(ButtonEntityDescription):
"""Describe a ThermoPro button entity."""
press_action_fn: Callable[[HomeAssistant, str], Coroutine[None, Any, Any]]
async def _async_set_datetime(hass: HomeAssistant, address: str) -> None:
"""Set Date&Time for a given device."""
ble_device = async_ble_device_from_address(hass, address, connectable=True)
assert ble_device is not None
await ThermoProDevice(ble_device).set_datetime(now(), am_pm=False)
BUTTON_ENTITIES: tuple[ThermoProButtonEntityDescription, ...] = (
ThermoProButtonEntityDescription(
key="datetime",
translation_key="set_datetime",
icon="mdi:calendar-clock",
entity_category=EntityCategory.CONFIG,
press_action_fn=_async_set_datetime,
),
)
MODELS_THAT_SUPPORT_BUTTONS = {"TP358", "TP393"}
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the thermopro button platform."""
address = entry.unique_id
assert address is not None
availability_signal = f"{SIGNAL_AVAILABILITY_UPDATED}_{entry.entry_id}"
entity_added = False
@callback
def _async_on_data_updated(
data: ThermoProBluetoothDeviceData,
service_info: BluetoothServiceInfoBleak,
update: SensorUpdate,
) -> None:
nonlocal entity_added
sensor_device_info = update.devices[data.primary_device_id]
if sensor_device_info.model not in MODELS_THAT_SUPPORT_BUTTONS:
return
if not entity_added:
name = sensor_device_info.name
assert name is not None
entity_added = True
async_add_entities(
ThermoProButtonEntity(
description=description,
data=data,
availability_signal=availability_signal,
address=address,
)
for description in BUTTON_ENTITIES
)
if service_info.connectable:
async_dispatcher_send(hass, availability_signal, True)
entry.async_on_unload(
async_dispatcher_connect(
hass, f"{SIGNAL_DATA_UPDATED}_{entry.entry_id}", _async_on_data_updated
)
)
class ThermoProButtonEntity(ButtonEntity):
"""Representation of a ThermoPro button entity."""
_attr_has_entity_name = True
entity_description: ThermoProButtonEntityDescription
def __init__(
self,
description: ThermoProButtonEntityDescription,
data: ThermoProBluetoothDeviceData,
availability_signal: str,
address: str,
) -> None:
"""Initialize the thermopro button entity."""
self.entity_description = description
self._address = address
self._availability_signal = availability_signal
self._attr_unique_id = f"{address}-{description.key}"
self._attr_device_info = dr.DeviceInfo(
name=data.get_device_name(),
identifiers={(DOMAIN, address)},
connections={(dr.CONNECTION_BLUETOOTH, address)},
)
async def async_added_to_hass(self) -> None:
"""Connect availability dispatcher."""
await super().async_added_to_hass()
self.async_on_remove(
async_dispatcher_connect(
self.hass,
self._availability_signal,
self._async_on_availability_changed,
)
)
self.async_on_remove(
async_track_unavailable(
self.hass, self._async_on_unavailable, self._address, connectable=True
)
)
@callback
def _async_on_unavailable(self, _: BluetoothServiceInfoBleak) -> None:
self._async_on_availability_changed(False)
@callback
def _async_on_availability_changed(self, available: bool) -> None:
self._attr_available = available
self.async_write_ha_state()
async def async_press(self) -> None:
"""Execute the press action for the entity."""
await self.entity_description.press_action_fn(self.hass, self._address)