mirror of
https://github.com/home-assistant/core.git
synced 2025-12-24 12:59:34 +00:00
Refactor Sunricher DALI integration to use direct device callbacks (#155315)
This commit is contained in:
@@ -18,7 +18,6 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
|
||||
from .const import CONF_SERIAL_NUMBER, DOMAIN, MANUFACTURER
|
||||
from .types import DaliCenterConfigEntry, DaliCenterData
|
||||
@@ -47,12 +46,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: DaliCenterConfigEntry) -
|
||||
"You can try to delete the gateway and add it again"
|
||||
) from exc
|
||||
|
||||
def on_online_status(dev_id: str, available: bool) -> None:
|
||||
signal = f"{DOMAIN}_update_available_{dev_id}"
|
||||
hass.add_job(async_dispatcher_send, hass, signal, available)
|
||||
|
||||
gateway.on_online_status = on_online_status
|
||||
|
||||
try:
|
||||
devices = await gateway.discover_devices()
|
||||
except DaliGatewayError as exc:
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from PySrDaliGateway import Device
|
||||
from PySrDaliGateway import CallbackEventType, Device
|
||||
from PySrDaliGateway.helper import is_light_device
|
||||
from PySrDaliGateway.types import LightStatus
|
||||
|
||||
@@ -19,10 +19,6 @@ from homeassistant.components.light import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect,
|
||||
async_dispatcher_send,
|
||||
)
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER
|
||||
@@ -40,15 +36,8 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up Sunricher DALI light entities from config entry."""
|
||||
runtime_data = entry.runtime_data
|
||||
gateway = runtime_data.gateway
|
||||
devices = runtime_data.devices
|
||||
|
||||
def _on_light_status(dev_id: str, status: LightStatus) -> None:
|
||||
signal = f"{DOMAIN}_update_{dev_id}"
|
||||
hass.add_job(async_dispatcher_send, hass, signal, status)
|
||||
|
||||
gateway.on_light_status = _on_light_status
|
||||
|
||||
async_add_entities(
|
||||
DaliCenterLight(device)
|
||||
for device in devices
|
||||
@@ -123,14 +112,16 @@ class DaliCenterLight(LightEntity):
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Handle entity addition to Home Assistant."""
|
||||
|
||||
signal = f"{DOMAIN}_update_{self._attr_unique_id}"
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(self.hass, signal, self._handle_device_update)
|
||||
self._light.register_listener(
|
||||
CallbackEventType.LIGHT_STATUS, self._handle_device_update
|
||||
)
|
||||
)
|
||||
|
||||
signal = f"{DOMAIN}_update_available_{self._attr_unique_id}"
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(self.hass, signal, self._handle_availability)
|
||||
self._light.register_listener(
|
||||
CallbackEventType.ONLINE_STATUS, self._handle_availability
|
||||
)
|
||||
)
|
||||
|
||||
# read_status() only queues a request on the gateway and relies on the
|
||||
@@ -187,4 +178,4 @@ class DaliCenterLight(LightEntity):
|
||||
):
|
||||
self._attr_rgbw_color = status["rgbw_color"]
|
||||
|
||||
self.async_write_ha_state()
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/sunricher_dali",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["PySrDaliGateway==0.13.1"]
|
||||
"requirements": ["PySrDaliGateway==0.16.2"]
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ rules:
|
||||
log-when-unavailable: done
|
||||
parallel-updates: done
|
||||
reauthentication-flow: todo
|
||||
test-coverage: todo
|
||||
test-coverage: done
|
||||
|
||||
# Gold
|
||||
devices: done
|
||||
|
||||
2
requirements_all.txt
generated
2
requirements_all.txt
generated
@@ -80,7 +80,7 @@ PyQRCode==1.2.1
|
||||
PyRMVtransport==0.3.3
|
||||
|
||||
# homeassistant.components.sunricher_dali
|
||||
PySrDaliGateway==0.13.1
|
||||
PySrDaliGateway==0.16.2
|
||||
|
||||
# homeassistant.components.switchbot
|
||||
PySwitchbot==0.73.0
|
||||
|
||||
2
requirements_test_all.txt
generated
2
requirements_test_all.txt
generated
@@ -77,7 +77,7 @@ PyQRCode==1.2.1
|
||||
PyRMVtransport==0.3.3
|
||||
|
||||
# homeassistant.components.sunricher_dali
|
||||
PySrDaliGateway==0.13.1
|
||||
PySrDaliGateway==0.16.2
|
||||
|
||||
# homeassistant.components.switchbot
|
||||
PySwitchbot==0.73.0
|
||||
|
||||
@@ -1 +1,18 @@
|
||||
"""Tests for the Sunricher Sunricher DALI integration."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from PySrDaliGateway import CallbackEventType
|
||||
|
||||
|
||||
def find_device_listener(
|
||||
device: MagicMock, event_type: CallbackEventType
|
||||
) -> Callable[..., None]:
|
||||
"""Find the registered listener callback for a specific device and event type."""
|
||||
for call in device.register_listener.call_args_list:
|
||||
if call[0][0] == event_type:
|
||||
return call[0][1]
|
||||
raise AssertionError(
|
||||
f"Listener for event type {event_type} not found on device {device.dev_id}"
|
||||
)
|
||||
|
||||
@@ -56,6 +56,7 @@ def _create_mock_device(
|
||||
device.turn_on = MagicMock()
|
||||
device.turn_off = MagicMock()
|
||||
device.read_status = MagicMock()
|
||||
device.register_listener = MagicMock(return_value=lambda: None)
|
||||
return device
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from PySrDaliGateway import CallbackEventType
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.light import (
|
||||
@@ -15,6 +16,8 @@ from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from . import find_device_listener
|
||||
|
||||
from tests.common import MockConfigEntry, SnapshotAssertion, snapshot_platform
|
||||
|
||||
TEST_DIMMER_ENTITY_ID = "light.dimmer_0000_02"
|
||||
@@ -24,13 +27,20 @@ TEST_HS_DEVICE_ID = "01030000046A242121110E"
|
||||
TEST_RGBW_DEVICE_ID = "01040000056A242121110E"
|
||||
|
||||
|
||||
def _dispatch_status(
|
||||
gateway: MagicMock, device_id: str, status: dict[str, Any]
|
||||
def _trigger_light_status_callback(
|
||||
device: MagicMock, device_id: str, status: dict[str, Any]
|
||||
) -> None:
|
||||
"""Invoke the status callback registered on the gateway mock."""
|
||||
callback = gateway.on_light_status
|
||||
assert callable(callback)
|
||||
callback(device_id, status)
|
||||
"""Trigger the light status callbacks registered on the device mock."""
|
||||
callback = find_device_listener(device, CallbackEventType.LIGHT_STATUS)
|
||||
callback(status)
|
||||
|
||||
|
||||
def _trigger_availability_callback(
|
||||
device: MagicMock, device_id: str, available: bool
|
||||
) -> None:
|
||||
"""Trigger the availability callbacks registered on the device mock."""
|
||||
callback = find_device_listener(device, CallbackEventType.ONLINE_STATUS)
|
||||
callback(available)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -133,26 +143,25 @@ async def test_turn_on_with_brightness(
|
||||
)
|
||||
|
||||
|
||||
async def test_dispatcher_connection(
|
||||
async def test_callback_registration(
|
||||
hass: HomeAssistant,
|
||||
init_integration: MockConfigEntry,
|
||||
mock_devices: list[MagicMock],
|
||||
mock_gateway: MagicMock,
|
||||
) -> None:
|
||||
"""Test that dispatcher signals are properly connected."""
|
||||
entity_entry = er.async_get(hass).async_get(TEST_DIMMER_ENTITY_ID)
|
||||
assert entity_entry is not None
|
||||
|
||||
state = hass.states.get(TEST_DIMMER_ENTITY_ID)
|
||||
assert state is not None
|
||||
"""Test that callbacks are properly registered and triggered."""
|
||||
state_before = hass.states.get(TEST_DIMMER_ENTITY_ID)
|
||||
assert state_before is not None
|
||||
|
||||
status_update: dict[str, Any] = {"is_on": True, "brightness": 128}
|
||||
|
||||
_dispatch_status(mock_gateway, TEST_DIMMER_DEVICE_ID, status_update)
|
||||
_trigger_light_status_callback(
|
||||
mock_devices[0], TEST_DIMMER_DEVICE_ID, status_update
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state_after = hass.states.get(TEST_DIMMER_ENTITY_ID)
|
||||
assert state_after is not None
|
||||
assert state_after.state == "on"
|
||||
assert state_after.attributes.get("brightness") == 128
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -168,10 +177,28 @@ async def test_dispatcher_connection(
|
||||
async def test_status_updates(
|
||||
hass: HomeAssistant,
|
||||
init_integration: MockConfigEntry,
|
||||
mock_gateway: MagicMock,
|
||||
mock_devices: list[MagicMock],
|
||||
device_id: str,
|
||||
status_update: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test various status updates for different device types."""
|
||||
_dispatch_status(mock_gateway, device_id, status_update)
|
||||
device = next(d for d in mock_devices if d.dev_id == device_id)
|
||||
_trigger_light_status_callback(device, device_id, status_update)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def test_device_availability(
|
||||
hass: HomeAssistant,
|
||||
init_integration: MockConfigEntry,
|
||||
mock_devices: list[MagicMock],
|
||||
) -> None:
|
||||
"""Test device availability changes."""
|
||||
_trigger_availability_callback(mock_devices[0], TEST_DIMMER_DEVICE_ID, False)
|
||||
await hass.async_block_till_done()
|
||||
assert (state := hass.states.get(TEST_DIMMER_ENTITY_ID))
|
||||
assert state.state == "unavailable"
|
||||
|
||||
_trigger_availability_callback(mock_devices[0], TEST_DIMMER_DEVICE_ID, True)
|
||||
await hass.async_block_till_done()
|
||||
assert (state := hass.states.get(TEST_DIMMER_ENTITY_ID))
|
||||
assert state.state != "unavailable"
|
||||
|
||||
Reference in New Issue
Block a user