1
0
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:
Niracler
2025-11-19 16:47:45 +08:00
committed by GitHub
parent b45294ded3
commit 9311a87bf5
9 changed files with 75 additions and 46 deletions

View File

@@ -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:

View File

@@ -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()

View File

@@ -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"]
}

View File

@@ -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
View File

@@ -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

View File

@@ -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

View File

@@ -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}"
)

View File

@@ -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

View File

@@ -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"