Store Switcher runtime data in config entry (#118054)

This commit is contained in:
Shay Levy 2024-05-25 13:17:33 +03:00 committed by GitHub
parent 3f76b865fa
commit e8226a8056
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 42 additions and 73 deletions

View File

@ -4,15 +4,14 @@ from __future__ import annotations
import logging
from aioswitcher.bridge import SwitcherBridge
from aioswitcher.device import SwitcherBase
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform
from homeassistant.core import Event, HomeAssistant, callback
from .const import DATA_DEVICE, DOMAIN
from .coordinator import SwitcherDataUpdateCoordinator
from .utils import async_start_bridge, async_stop_bridge
PLATFORMS = [
Platform.BUTTON,
@ -25,20 +24,20 @@ PLATFORMS = [
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
type SwitcherConfigEntry = ConfigEntry[dict[str, SwitcherDataUpdateCoordinator]]
async def async_setup_entry(hass: HomeAssistant, entry: SwitcherConfigEntry) -> bool:
"""Set up Switcher from a config entry."""
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][DATA_DEVICE] = {}
@callback
def on_device_data_callback(device: SwitcherBase) -> None:
"""Use as a callback for device data."""
coordinators = entry.runtime_data
# Existing device update device data
if device.device_id in hass.data[DOMAIN][DATA_DEVICE]:
coordinator: SwitcherDataUpdateCoordinator = hass.data[DOMAIN][DATA_DEVICE][
device.device_id
]
if coordinator := coordinators.get(device.device_id):
coordinator.async_set_updated_data(device)
return
@ -52,18 +51,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
device.device_type.hex_rep,
)
coordinator = hass.data[DOMAIN][DATA_DEVICE][device.device_id] = (
SwitcherDataUpdateCoordinator(hass, entry, device)
)
coordinator = SwitcherDataUpdateCoordinator(hass, entry, device)
coordinator.async_setup()
coordinators[device.device_id] = coordinator
# Must be ready before dispatcher is called
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
await async_start_bridge(hass, on_device_data_callback)
entry.runtime_data = {}
bridge = SwitcherBridge(on_device_data_callback)
await bridge.start()
async def stop_bridge(event: Event) -> None:
await async_stop_bridge(hass)
async def stop_bridge(event: Event | None = None) -> None:
await bridge.stop()
entry.async_on_unload(stop_bridge)
entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_bridge)
@ -72,12 +74,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: SwitcherConfigEntry) -> bool:
"""Unload a config entry."""
await async_stop_bridge(hass)
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(DATA_DEVICE)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@ -15,7 +15,6 @@ from aioswitcher.api.remotes import SwitcherBreezeRemote
from aioswitcher.device import DeviceCategory
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
@ -25,6 +24,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import SwitcherConfigEntry
from .const import SIGNAL_DEVICE_ADD
from .coordinator import SwitcherDataUpdateCoordinator
from .utils import get_breeze_remote_manager
@ -78,7 +78,7 @@ THERMOSTAT_BUTTONS = [
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: SwitcherConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Switcher button from config entry."""

View File

@ -25,7 +25,6 @@ from homeassistant.components.climate import (
ClimateEntityFeature,
HVACMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
@ -35,6 +34,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import SwitcherConfigEntry
from .const import SIGNAL_DEVICE_ADD
from .coordinator import SwitcherDataUpdateCoordinator
from .utils import get_breeze_remote_manager
@ -61,7 +61,7 @@ HA_TO_DEVICE_FAN = {value: key for key, value in DEVICE_FAN_TO_HA.items()}
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: SwitcherConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Switcher climate from config entry."""

View File

@ -2,9 +2,6 @@
DOMAIN = "switcher_kis"
DATA_BRIDGE = "bridge"
DATA_DEVICE = "device"
DISCOVERY_TIME_SEC = 12
SIGNAL_DEVICE_ADD = "switcher_device_add"

View File

@ -6,24 +6,23 @@ from dataclasses import asdict
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .const import DATA_DEVICE, DOMAIN
from . import SwitcherConfigEntry
TO_REDACT = {"device_id", "device_key", "ip_address", "mac_address"}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry
hass: HomeAssistant, entry: SwitcherConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
devices = hass.data[DOMAIN][DATA_DEVICE]
coordinators = entry.runtime_data
return async_redact_data(
{
"entry": entry.as_dict(),
"devices": [asdict(devices[d].data) for d in devices],
"devices": [asdict(coordinators[d].data) for d in coordinators],
},
TO_REDACT,
)

View File

@ -3,9 +3,7 @@
from __future__ import annotations
import asyncio
from collections.abc import Callable
import logging
from typing import Any
from aioswitcher.api.remotes import SwitcherBreezeRemoteManager
from aioswitcher.bridge import SwitcherBase, SwitcherBridge
@ -13,29 +11,11 @@ from aioswitcher.bridge import SwitcherBase, SwitcherBridge
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import singleton
from .const import DATA_BRIDGE, DISCOVERY_TIME_SEC, DOMAIN
from .const import DISCOVERY_TIME_SEC
_LOGGER = logging.getLogger(__name__)
async def async_start_bridge(
hass: HomeAssistant, on_device_callback: Callable[[SwitcherBase], Any]
) -> None:
"""Start switcher UDP bridge."""
bridge = hass.data[DOMAIN][DATA_BRIDGE] = SwitcherBridge(on_device_callback)
_LOGGER.debug("Starting Switcher bridge")
await bridge.start()
async def async_stop_bridge(hass: HomeAssistant) -> None:
"""Stop switcher UDP bridge."""
bridge: SwitcherBridge = hass.data[DOMAIN].get(DATA_BRIDGE)
if bridge is not None:
_LOGGER.debug("Stopping Switcher bridge")
await bridge.stop()
hass.data[DOMAIN].pop(DATA_BRIDGE)
async def async_has_devices(hass: HomeAssistant) -> bool:
"""Discover Switcher devices."""
_LOGGER.debug("Starting discovery")

View File

@ -18,9 +18,15 @@ def mock_setup_entry() -> Generator[AsyncMock, None, None]:
@pytest.fixture
def mock_bridge(request):
"""Return a mocked SwitcherBridge."""
with patch(
"homeassistant.components.switcher_kis.utils.SwitcherBridge", autospec=True
) as bridge_mock:
with (
patch(
"homeassistant.components.switcher_kis.SwitcherBridge", autospec=True
) as bridge_mock,
patch(
"homeassistant.components.switcher_kis.utils.SwitcherBridge",
new=bridge_mock,
),
):
bridge = bridge_mock.return_value
bridge.devices = []

View File

@ -4,11 +4,7 @@ from datetime import timedelta
import pytest
from homeassistant.components.switcher_kis.const import (
DATA_DEVICE,
DOMAIN,
MAX_UPDATE_INTERVAL_SEC,
)
from homeassistant.components.switcher_kis.const import MAX_UPDATE_INTERVAL_SEC
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
@ -24,15 +20,14 @@ async def test_update_fail(
hass: HomeAssistant, mock_bridge, caplog: pytest.LogCaptureFixture
) -> None:
"""Test entities state unavailable when updates fail.."""
await init_integration(hass)
entry = await init_integration(hass)
assert mock_bridge
mock_bridge.mock_callbacks(DUMMY_SWITCHER_DEVICES)
await hass.async_block_till_done()
assert mock_bridge.is_running is True
assert len(hass.data[DOMAIN]) == 2
assert len(hass.data[DOMAIN][DATA_DEVICE]) == 2
assert len(entry.runtime_data) == 2
async_fire_time_changed(
hass, dt_util.utcnow() + timedelta(seconds=MAX_UPDATE_INTERVAL_SEC + 1)
@ -77,11 +72,9 @@ async def test_entry_unload(hass: HomeAssistant, mock_bridge) -> None:
assert entry.state is ConfigEntryState.LOADED
assert mock_bridge.is_running is True
assert len(hass.data[DOMAIN]) == 2
await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
assert entry.state is ConfigEntryState.NOT_LOADED
assert mock_bridge.is_running is False
assert len(hass.data[DOMAIN]) == 0

View File

@ -2,7 +2,6 @@
import pytest
from homeassistant.components.switcher_kis.const import DATA_DEVICE, DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.util import slugify
@ -32,12 +31,11 @@ DEVICE_SENSORS_TUPLE = (
@pytest.mark.parametrize("mock_bridge", [DUMMY_SWITCHER_DEVICES], indirect=True)
async def test_sensor_platform(hass: HomeAssistant, mock_bridge) -> None:
"""Test sensor platform."""
await init_integration(hass)
entry = await init_integration(hass)
assert mock_bridge
assert mock_bridge.is_running is True
assert len(hass.data[DOMAIN]) == 2
assert len(hass.data[DOMAIN][DATA_DEVICE]) == 2
assert len(entry.runtime_data) == 2
for device, sensors in DEVICE_SENSORS_TUPLE:
for sensor, field in sensors: