mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Add button to set date and time for thermopro TP358/TP393 (#135740)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
463d9617ac
commit
bf83f5a671
@ -2,25 +2,47 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from thermopro_ble import ThermoProBluetoothDeviceData
|
from thermopro_ble import SensorUpdate, ThermoProBluetoothDeviceData
|
||||||
|
|
||||||
from homeassistant.components.bluetooth import BluetoothScanningMode
|
from homeassistant.components.bluetooth import (
|
||||||
|
BluetoothScanningMode,
|
||||||
|
BluetoothServiceInfoBleak,
|
||||||
|
)
|
||||||
from homeassistant.components.bluetooth.passive_update_processor import (
|
from homeassistant.components.bluetooth.passive_update_processor import (
|
||||||
PassiveBluetoothProcessorCoordinator,
|
PassiveBluetoothProcessorCoordinator,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN, SIGNAL_DATA_UPDATED
|
||||||
|
|
||||||
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||||
|
|
||||||
|
PLATFORMS: list[Platform] = [Platform.BUTTON, Platform.SENSOR]
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def process_service_info(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
data: ThermoProBluetoothDeviceData,
|
||||||
|
service_info: BluetoothServiceInfoBleak,
|
||||||
|
) -> SensorUpdate:
|
||||||
|
"""Process a BluetoothServiceInfoBleak, running side effects and returning sensor data."""
|
||||||
|
update = data.update(service_info)
|
||||||
|
async_dispatcher_send(
|
||||||
|
hass, f"{SIGNAL_DATA_UPDATED}_{entry.entry_id}", data, service_info, update
|
||||||
|
)
|
||||||
|
return update
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up ThermoPro BLE device from a config entry."""
|
"""Set up ThermoPro BLE device from a config entry."""
|
||||||
address = entry.unique_id
|
address = entry.unique_id
|
||||||
@ -32,13 +54,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
_LOGGER,
|
_LOGGER,
|
||||||
address=address,
|
address=address,
|
||||||
mode=BluetoothScanningMode.ACTIVE,
|
mode=BluetoothScanningMode.ACTIVE,
|
||||||
update_method=data.update,
|
update_method=partial(process_service_info, hass, entry, data),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
entry.async_on_unload(
|
# only start after all platforms have had a chance to subscribe
|
||||||
coordinator.async_start()
|
entry.async_on_unload(coordinator.async_start())
|
||||||
) # only start after all platforms have had a chance to subscribe
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
157
homeassistant/components/thermopro/button.py
Normal file
157
homeassistant/components/thermopro/button.py
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
"""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)
|
@ -1,3 +1,6 @@
|
|||||||
"""Constants for the ThermoPro Bluetooth integration."""
|
"""Constants for the ThermoPro Bluetooth integration."""
|
||||||
|
|
||||||
DOMAIN = "thermopro"
|
DOMAIN = "thermopro"
|
||||||
|
|
||||||
|
SIGNAL_DATA_UPDATED = f"{DOMAIN}_service_info_updated"
|
||||||
|
SIGNAL_AVAILABILITY_UPDATED = f"{DOMAIN}_availability_updated"
|
||||||
|
@ -9,7 +9,6 @@ from thermopro_ble import (
|
|||||||
Units,
|
Units,
|
||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant import config_entries
|
|
||||||
from homeassistant.components.bluetooth.passive_update_processor import (
|
from homeassistant.components.bluetooth.passive_update_processor import (
|
||||||
PassiveBluetoothDataProcessor,
|
PassiveBluetoothDataProcessor,
|
||||||
PassiveBluetoothDataUpdate,
|
PassiveBluetoothDataUpdate,
|
||||||
@ -23,6 +22,7 @@ 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,
|
||||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||||
@ -110,7 +110,7 @@ def sensor_update_to_bluetooth_data_update(
|
|||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: config_entries.ConfigEntry,
|
entry: ConfigEntry,
|
||||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the ThermoPro BLE sensors."""
|
"""Set up the ThermoPro BLE sensors."""
|
||||||
|
@ -17,5 +17,12 @@
|
|||||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"button": {
|
||||||
|
"set_datetime": {
|
||||||
|
"name": "Set Date&Time"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,16 @@ TP357_SERVICE_INFO = BluetoothServiceInfo(
|
|||||||
source="local",
|
source="local",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
TP358_SERVICE_INFO = BluetoothServiceInfo(
|
||||||
|
name="TP358 (4221)",
|
||||||
|
manufacturer_data={61890: b"\x00\x1d\x02,"},
|
||||||
|
service_uuids=[],
|
||||||
|
address="aa:bb:cc:dd:ee:ff",
|
||||||
|
rssi=-65,
|
||||||
|
service_data={},
|
||||||
|
source="local",
|
||||||
|
)
|
||||||
|
|
||||||
TP962R_SERVICE_INFO = BluetoothServiceInfo(
|
TP962R_SERVICE_INFO = BluetoothServiceInfo(
|
||||||
name="TP962R (0000)",
|
name="TP962R (0000)",
|
||||||
manufacturer_data={14081: b"\x00;\x0b7\x00"},
|
manufacturer_data={14081: b"\x00;\x0b7\x00"},
|
||||||
|
@ -1,8 +1,64 @@
|
|||||||
"""ThermoPro session fixtures."""
|
"""ThermoPro session fixtures."""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from thermopro_ble import ThermoProDevice
|
||||||
|
|
||||||
|
from homeassistant.components.thermopro.const import DOMAIN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.util.dt import now
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def mock_bluetooth(enable_bluetooth: None) -> None:
|
def mock_bluetooth(enable_bluetooth: None) -> None:
|
||||||
"""Auto mock bluetooth."""
|
"""Auto mock bluetooth."""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def dummy_thermoprodevice(monkeypatch: pytest.MonkeyPatch) -> ThermoProDevice:
|
||||||
|
"""Mock for downstream library."""
|
||||||
|
client = ThermoProDevice("")
|
||||||
|
monkeypatch.setattr(client, "set_datetime", AsyncMock())
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_thermoprodevice(
|
||||||
|
monkeypatch: pytest.MonkeyPatch, dummy_thermoprodevice: ThermoProDevice
|
||||||
|
) -> ThermoProDevice:
|
||||||
|
"""Return downstream library mock."""
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"homeassistant.components.thermopro.button.ThermoProDevice",
|
||||||
|
MagicMock(return_value=dummy_thermoprodevice),
|
||||||
|
)
|
||||||
|
return dummy_thermoprodevice
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_now(monkeypatch: pytest.MonkeyPatch) -> datetime:
|
||||||
|
"""Return fixed datetime for comparison."""
|
||||||
|
fixed_now = now()
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"homeassistant.components.thermopro.button.now",
|
||||||
|
MagicMock(return_value=fixed_now),
|
||||||
|
)
|
||||||
|
return fixed_now
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def setup_thermopro(
|
||||||
|
hass: HomeAssistant, mock_thermoprodevice: ThermoProDevice
|
||||||
|
) -> None:
|
||||||
|
"""Set up the Thermopro integration."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id="aa:bb:cc:dd:ee:ff",
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
return entry
|
||||||
|
135
tests/components/thermopro/test_button.py
Normal file
135
tests/components/thermopro/test_button.py
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
"""Test the ThermoPro button platform."""
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import time
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from thermopro_ble import ThermoProDevice
|
||||||
|
|
||||||
|
from homeassistant.components.bluetooth import (
|
||||||
|
FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS,
|
||||||
|
)
|
||||||
|
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, STATE_UNKNOWN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
|
from . import TP357_SERVICE_INFO, TP358_SERVICE_INFO
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
|
from tests.components.bluetooth import (
|
||||||
|
inject_bluetooth_service_info,
|
||||||
|
patch_all_discovered_devices,
|
||||||
|
patch_bluetooth_time,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("setup_thermopro")
|
||||||
|
async def test_buttons_tp357(hass: HomeAssistant) -> None:
|
||||||
|
"""Test setting up creates the sensors."""
|
||||||
|
assert not hass.states.async_all()
|
||||||
|
assert not hass.states.get("button.tp358_4221_set_date_time")
|
||||||
|
inject_bluetooth_service_info(hass, TP357_SERVICE_INFO)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert not hass.states.get("button.tp358_4221_set_date_time")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("setup_thermopro")
|
||||||
|
async def test_buttons_tp358_discovery(hass: HomeAssistant) -> None:
|
||||||
|
"""Test discovery of device with button."""
|
||||||
|
assert not hass.states.async_all()
|
||||||
|
assert not hass.states.get("button.tp358_4221_set_date_time")
|
||||||
|
inject_bluetooth_service_info(hass, TP358_SERVICE_INFO)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
button = hass.states.get("button.tp358_4221_set_date_time")
|
||||||
|
assert button is not None
|
||||||
|
assert button.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("setup_thermopro")
|
||||||
|
async def test_buttons_tp358_unavailable(hass: HomeAssistant) -> None:
|
||||||
|
"""Test tp358 set date&time button goes to unavailability."""
|
||||||
|
start_monotonic = time.monotonic()
|
||||||
|
assert not hass.states.async_all()
|
||||||
|
assert not hass.states.get("button.tp358_4221_set_date_time")
|
||||||
|
inject_bluetooth_service_info(hass, TP358_SERVICE_INFO)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
button = hass.states.get("button.tp358_4221_set_date_time")
|
||||||
|
assert button is not None
|
||||||
|
assert button.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
# Fast-forward time without BLE advertisements
|
||||||
|
monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 15
|
||||||
|
|
||||||
|
with patch_bluetooth_time(monotonic_now), patch_all_discovered_devices([]):
|
||||||
|
async_fire_time_changed(
|
||||||
|
hass,
|
||||||
|
dt_util.utcnow()
|
||||||
|
+ timedelta(seconds=FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 15),
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
button = hass.states.get("button.tp358_4221_set_date_time")
|
||||||
|
|
||||||
|
assert button.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("setup_thermopro")
|
||||||
|
async def test_buttons_tp358_reavailable(hass: HomeAssistant) -> None:
|
||||||
|
"""Test TP358/TP393 set date&time button goes to unavailablity and recovers."""
|
||||||
|
start_monotonic = time.monotonic()
|
||||||
|
assert not hass.states.async_all()
|
||||||
|
assert not hass.states.get("button.tp358_4221_set_date_time")
|
||||||
|
inject_bluetooth_service_info(hass, TP358_SERVICE_INFO)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
button = hass.states.get("button.tp358_4221_set_date_time")
|
||||||
|
assert button is not None
|
||||||
|
assert button.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
# Fast-forward time without BLE advertisements
|
||||||
|
monotonic_now = start_monotonic + FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 15
|
||||||
|
|
||||||
|
with patch_bluetooth_time(monotonic_now), patch_all_discovered_devices([]):
|
||||||
|
async_fire_time_changed(
|
||||||
|
hass,
|
||||||
|
dt_util.utcnow()
|
||||||
|
+ timedelta(seconds=FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS + 15),
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
button = hass.states.get("button.tp358_4221_set_date_time")
|
||||||
|
|
||||||
|
assert button.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
inject_bluetooth_service_info(hass, TP358_SERVICE_INFO)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
button = hass.states.get("button.tp358_4221_set_date_time")
|
||||||
|
|
||||||
|
assert button.state == STATE_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("setup_thermopro")
|
||||||
|
async def test_buttons_tp358_press(
|
||||||
|
hass: HomeAssistant, mock_now: datetime, mock_thermoprodevice: ThermoProDevice
|
||||||
|
) -> None:
|
||||||
|
"""Test TP358/TP393 set date&time button press."""
|
||||||
|
assert not hass.states.async_all()
|
||||||
|
assert not hass.states.get("button.tp358_4221_set_date_time")
|
||||||
|
inject_bluetooth_service_info(hass, TP358_SERVICE_INFO)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert hass.states.get("button.tp358_4221_set_date_time")
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"button",
|
||||||
|
"press",
|
||||||
|
{ATTR_ENTITY_ID: "button.tp358_4221_set_date_time"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_thermoprodevice.set_datetime.assert_awaited_once_with(mock_now, am_pm=False)
|
||||||
|
|
||||||
|
button_state = hass.states.get("button.tp358_4221_set_date_time")
|
||||||
|
assert button_state.state != STATE_UNKNOWN
|
Loading…
x
Reference in New Issue
Block a user