1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-08 09:38:58 +01:00

Add scene platform for Sunricher DALI integration (#157808)

Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
Niracler
2025-12-19 00:42:30 +08:00
committed by GitHub
parent c2440c4ebd
commit 9c59d528af
13 changed files with 524 additions and 121 deletions
@@ -2,6 +2,7 @@
from __future__ import annotations
import asyncio
import logging
from PySrDaliGateway import DaliGateway
@@ -23,7 +24,7 @@ from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from .const import CONF_SERIAL_NUMBER, DOMAIN, MANUFACTURER
from .types import DaliCenterConfigEntry, DaliCenterData
_PLATFORMS: list[Platform] = [Platform.LIGHT]
_PLATFORMS: list[Platform] = [Platform.LIGHT, Platform.SCENE]
_LOGGER = logging.getLogger(__name__)
@@ -48,7 +49,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: DaliCenterConfigEntry) -
) from exc
try:
devices = await gateway.discover_devices()
devices, scenes = await asyncio.gather(
gateway.discover_devices(),
gateway.discover_scenes(),
)
except DaliGatewayError as exc:
raise ConfigEntryNotReady(
"Unable to discover devices from the gateway"
@@ -70,6 +74,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: DaliCenterConfigEntry) -
entry.runtime_data = DaliCenterData(
gateway=gateway,
devices=devices,
scenes=scenes,
)
await hass.config_entries.async_forward_entry_setups(entry, _PLATFORMS)
@@ -0,0 +1,57 @@
"""Base entity for Sunricher DALI integration."""
from __future__ import annotations
import logging
from PySrDaliGateway import CallbackEventType, DaliObjectBase, Device
from homeassistant.core import callback
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
class DaliCenterEntity(Entity):
"""Base entity for DALI Center objects (devices, scenes, etc.)."""
_attr_has_entity_name = True
_attr_should_poll = False
def __init__(self, dali_object: DaliObjectBase) -> None:
"""Initialize base entity."""
self._dali_object = dali_object
self._attr_unique_id = dali_object.unique_id
self._unavailable_logged = False
self._attr_available = True
async def async_added_to_hass(self) -> None:
"""Register availability listener."""
self.async_on_remove(
self._dali_object.register_listener(
CallbackEventType.ONLINE_STATUS,
self._handle_availability,
)
)
@callback
def _handle_availability(self, available: bool) -> None:
"""Handle availability changes."""
if not available and not self._unavailable_logged:
_LOGGER.info("Entity %s became unavailable", self.entity_id)
self._unavailable_logged = True
elif available and self._unavailable_logged:
_LOGGER.info("Entity %s is back online", self.entity_id)
self._unavailable_logged = False
self._attr_available = available
self.schedule_update_ha_state()
class DaliDeviceEntity(DaliCenterEntity):
"""Base entity for DALI Device objects."""
def __init__(self, device: Device) -> None:
"""Initialize device entity."""
super().__init__(device)
self._attr_available = device.status == "online"
@@ -22,6 +22,7 @@ from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN, MANUFACTURER
from .entity import DaliDeviceEntity
from .types import DaliCenterConfigEntry
_LOGGER = logging.getLogger(__name__)
@@ -45,10 +46,9 @@ async def async_setup_entry(
)
class DaliCenterLight(LightEntity):
class DaliCenterLight(DaliDeviceEntity, LightEntity):
"""Representation of a Sunricher DALI Light."""
_attr_has_entity_name = True
_attr_name = None
_attr_is_on: bool | None = None
_attr_brightness: int | None = None
@@ -60,11 +60,8 @@ class DaliCenterLight(LightEntity):
def __init__(self, light: Device) -> None:
"""Initialize the light entity."""
super().__init__(light)
self._light = light
self._unavailable_logged = False
self._attr_unique_id = light.unique_id
self._attr_available = light.status == "online"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, light.dev_id)},
name=light.name,
@@ -111,6 +108,7 @@ class DaliCenterLight(LightEntity):
async def async_added_to_hass(self) -> None:
"""Handle entity addition to Home Assistant."""
await super().async_added_to_hass()
self.async_on_remove(
self._light.register_listener(
@@ -118,27 +116,10 @@ class DaliCenterLight(LightEntity):
)
)
self.async_on_remove(
self._light.register_listener(
CallbackEventType.ONLINE_STATUS, self._handle_availability
)
)
# read_status() only queues a request on the gateway and relies on the
# current event loop via call_later, so it must run in the loop thread.
self._light.read_status()
@callback
def _handle_availability(self, available: bool) -> None:
self._attr_available = available
if not available and not self._unavailable_logged:
_LOGGER.info("Light %s became unavailable", self._attr_unique_id)
self._unavailable_logged = True
elif available and self._unavailable_logged:
_LOGGER.info("Light %s is back online", self._attr_unique_id)
self._unavailable_logged = False
self.schedule_update_ha_state()
@callback
def _handle_device_update(self, status: LightStatus) -> None:
if status.get("is_on") is not None:
@@ -11,5 +11,5 @@
"documentation": "https://www.home-assistant.io/integrations/sunricher_dali",
"iot_class": "local_push",
"quality_scale": "bronze",
"requirements": ["PySrDaliGateway==0.16.2"]
"requirements": ["PySrDaliGateway==0.18.0"]
}
@@ -0,0 +1,45 @@
"""Support for DALI Center Scene entities."""
import logging
from typing import Any
from PySrDaliGateway import Scene
from homeassistant.components.scene import Scene as SceneEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .entity import DaliCenterEntity
from .types import DaliCenterConfigEntry
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 1
async def async_setup_entry(
hass: HomeAssistant,
entry: DaliCenterConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up DALI Center scene entities from config entry."""
async_add_entities(DaliCenterScene(scene) for scene in entry.runtime_data.scenes)
class DaliCenterScene(DaliCenterEntity, SceneEntity):
"""Representation of a DALI Center Scene."""
def __init__(self, scene: Scene) -> None:
"""Initialize the DALI scene."""
super().__init__(scene)
self._scene = scene
self._attr_name = scene.name
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, scene.gw_sn)},
)
async def async_activate(self, **kwargs: Any) -> None:
"""Activate the DALI scene."""
await self.hass.async_add_executor_job(self._scene.activate)
@@ -2,7 +2,7 @@
from dataclasses import dataclass
from PySrDaliGateway import DaliGateway, Device
from PySrDaliGateway import DaliGateway, Device, Scene
from homeassistant.config_entries import ConfigEntry
@@ -13,6 +13,7 @@ class DaliCenterData:
gateway: DaliGateway
devices: list[Device]
scenes: list[Scene]
type DaliCenterConfigEntry = ConfigEntry[DaliCenterData]
+1 -1
View File
@@ -80,7 +80,7 @@ PyQRCode==1.2.1
PyRMVtransport==0.3.3
# homeassistant.components.sunricher_dali
PySrDaliGateway==0.16.2
PySrDaliGateway==0.18.0
# homeassistant.components.switchbot
PySwitchbot==0.74.0
+1 -1
View File
@@ -80,7 +80,7 @@ PyQRCode==1.2.1
PyRMVtransport==0.3.3
# homeassistant.components.sunricher_dali
PySrDaliGateway==0.16.2
PySrDaliGateway==0.18.0
# homeassistant.components.switchbot
PySwitchbot==0.74.0
@@ -16,3 +16,9 @@ def find_device_listener(
raise AssertionError(
f"Listener for event type {event_type} not found on device {device.dev_id}"
)
def trigger_availability_callback(device: MagicMock, available: bool) -> None:
"""Trigger availability callbacks registered on the device mock."""
callback = find_device_listener(device, CallbackEventType.ONLINE_STATUS)
callback(available)
+197 -61
View File
@@ -1,8 +1,10 @@
"""Common fixtures for the Sunricher DALI tests."""
from collections.abc import Generator
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
from PySrDaliGateway.helper import gen_device_unique_id, gen_group_unique_id
import pytest
from homeassistant.components.sunricher_dali.const import CONF_SERIAL_NUMBER, DOMAIN
@@ -12,10 +14,73 @@ from homeassistant.const import (
CONF_PASSWORD,
CONF_PORT,
CONF_USERNAME,
Platform,
)
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
GATEWAY_SERIAL = "6A242121110E"
GATEWAY_HOST = "192.168.1.100"
GATEWAY_PORT = 1883
DEVICE_DATA: list[dict[str, Any]] = [
{
"dev_id": "01010000026A242121110E",
"dev_type": "0101",
"name": "Dimmer 0000-02",
"model": "DALI DT6 Dimmable Driver",
"color_mode": "brightness",
"address": 2,
"channel": 0,
},
{
"dev_id": "01020000036A242121110E",
"dev_type": "0102",
"name": "CCT 0000-03",
"model": "DALI DT8 Tc Dimmable Driver",
"color_mode": "color_temp",
"address": 3,
"channel": 0,
},
{
"dev_id": "01030000046A242121110E",
"dev_type": "0103",
"name": "HS Color Light",
"model": "DALI HS Color Driver",
"color_mode": "hs",
"address": 4,
"channel": 0,
},
{
"dev_id": "01040000056A242121110E",
"dev_type": "0104",
"name": "RGBW Light",
"model": "DALI RGBW Driver",
"color_mode": "rgbw",
"address": 5,
"channel": 0,
},
]
@pytest.fixture
async def init_integration(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_gateway: MagicMock,
mock_devices: list[MagicMock],
platforms: list[Platform],
) -> MockConfigEntry:
"""Set up the integration for testing."""
mock_config_entry.add_to_hass(hass)
with patch("homeassistant.components.sunricher_dali._PLATFORMS", platforms):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
return mock_config_entry
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
@@ -23,36 +88,29 @@ def mock_config_entry() -> MockConfigEntry:
return MockConfigEntry(
domain=DOMAIN,
data={
CONF_SERIAL_NUMBER: "6A242121110E",
CONF_HOST: "192.168.1.100",
CONF_PORT: 1883,
CONF_SERIAL_NUMBER: GATEWAY_SERIAL,
CONF_HOST: GATEWAY_HOST,
CONF_PORT: GATEWAY_PORT,
CONF_NAME: "Test Gateway",
CONF_USERNAME: "gateway_user",
CONF_PASSWORD: "gateway_pass",
},
unique_id="6A242121110E",
unique_id=GATEWAY_SERIAL,
title="Test Gateway",
)
def _create_mock_device(
dev_id: str,
dev_type: str,
name: str,
model: str,
color_mode: str,
gw_sn: str = "6A242121110E",
) -> MagicMock:
"""Create a mock device with standard attributes."""
def _create_mock_device(device_data: dict[str, Any]) -> MagicMock:
"""Create a mock device from device data dict."""
device = MagicMock()
device.dev_id = dev_id
device.unique_id = dev_id
device.dev_id = device_data["dev_id"]
device.unique_id = device_data["dev_id"]
device.status = "online"
device.dev_type = dev_type
device.name = name
device.model = model
device.gw_sn = gw_sn
device.color_mode = color_mode
device.dev_type = device_data["dev_type"]
device.name = device_data["name"]
device.model = device_data["model"]
device.gw_sn = GATEWAY_SERIAL
device.color_mode = device_data["color_mode"]
device.turn_on = MagicMock()
device.turn_off = MagicMock()
device.read_status = MagicMock()
@@ -63,43 +121,23 @@ def _create_mock_device(
@pytest.fixture
def mock_devices() -> list[MagicMock]:
"""Return mocked Device objects."""
return [
_create_mock_device(
"01010000026A242121110E",
"0101",
"Dimmer 0000-02",
"DALI DT6 Dimmable Driver",
"brightness",
),
_create_mock_device(
"01020000036A242121110E",
"0102",
"CCT 0000-03",
"DALI DT8 Tc Dimmable Driver",
"color_temp",
),
_create_mock_device(
"01030000046A242121110E",
"0103",
"HS Color Light",
"DALI HS Color Driver",
"hs",
),
_create_mock_device(
"01040000056A242121110E",
"0104",
"RGBW Light",
"DALI RGBW Driver",
"rgbw",
),
_create_mock_device(
"01010000026A242121110E",
"0101",
"Duplicate Dimmer",
"DALI DT6 Dimmable Driver",
"brightness",
),
]
devices = [_create_mock_device(data) for data in DEVICE_DATA]
devices.append(_create_mock_device(DEVICE_DATA[0]))
return devices
def _create_scene_device_property(
dev_type: str, brightness: int = 128, **kwargs: Any
) -> dict[str, Any]:
"""Create scene device property dict with defaults."""
return {
"is_on": True,
"brightness": brightness,
"color_temp_kelvin": kwargs.get("color_temp_kelvin"),
"hs_color": kwargs.get("hs_color"),
"rgbw_color": kwargs.get("rgbw_color"),
"white_level": kwargs.get("white_level"),
}
@pytest.fixture
@@ -113,8 +151,105 @@ def mock_discovery(mock_gateway: MagicMock) -> Generator[MagicMock]:
yield mock_discovery
def _create_mock_scene(
scene_id: int,
name: str,
unique_id: str,
channel: int,
area_id: str,
devices: list[dict[str, Any]],
gw_sn: str = GATEWAY_SERIAL,
) -> MagicMock:
"""Create a mock scene with standard attributes."""
devices_with_ids: list[dict[str, Any]] = []
for device in devices:
device_with_id = dict(device)
device_with_id["unique_id"] = (
gen_group_unique_id(device["address"], device["channel"], gw_sn)
if device["dev_type"] == "0401"
else gen_device_unique_id(
device["dev_type"],
device["channel"],
device["address"],
gw_sn,
)
)
devices_with_ids.append(device_with_id)
scene = MagicMock()
scene.scene_id = scene_id
scene.name = name
scene.unique_id = unique_id
scene.gw_sn = gw_sn
scene.channel = channel
scene.activate = MagicMock()
scene.devices = devices_with_ids
scene_details: dict[str, Any] = {
"unique_id": unique_id,
"id": scene_id,
"name": name,
"channel": channel,
"area_id": area_id,
"devices": devices_with_ids,
}
scene.read_scene = AsyncMock(return_value=scene_details)
scene.register_listener = MagicMock(return_value=lambda: None)
return scene
@pytest.fixture
def mock_gateway(mock_devices: list[MagicMock]) -> Generator[MagicMock]:
def mock_scenes() -> list[MagicMock]:
"""Return mocked Scene objects."""
return [
_create_mock_scene(
scene_id=1,
name="Living Room Evening",
unique_id=f"scene_0001_0000_{GATEWAY_SERIAL}",
channel=0,
area_id="1",
devices=[
{
"dev_type": DEVICE_DATA[0]["dev_type"],
"channel": DEVICE_DATA[0]["channel"],
"address": DEVICE_DATA[0]["address"],
"gw_sn_obj": "",
"property": _create_scene_device_property("0101", brightness=128),
},
{
"dev_type": DEVICE_DATA[1]["dev_type"],
"channel": DEVICE_DATA[1]["channel"],
"address": DEVICE_DATA[1]["address"],
"gw_sn_obj": "",
"property": _create_scene_device_property(
"0102", brightness=200, color_temp_kelvin=3000
),
},
],
),
_create_mock_scene(
scene_id=2,
name="Kitchen Bright",
unique_id=f"scene_0002_0000_{GATEWAY_SERIAL}",
channel=0,
area_id="2",
devices=[
{
"dev_type": "0401",
"channel": 0,
"address": 1,
"gw_sn_obj": "",
"property": _create_scene_device_property("0401", brightness=255),
},
],
),
]
@pytest.fixture
def mock_gateway(
mock_devices: list[MagicMock], mock_scenes: list[MagicMock]
) -> Generator[MagicMock]:
"""Return a mocked DaliGateway."""
with (
patch(
@@ -126,15 +261,16 @@ def mock_gateway(mock_devices: list[MagicMock]) -> Generator[MagicMock]:
),
):
mock_gateway = mock_gateway_class.return_value
mock_gateway.gw_sn = "6A242121110E"
mock_gateway.gw_ip = "192.168.1.100"
mock_gateway.port = 1883
mock_gateway.gw_sn = GATEWAY_SERIAL
mock_gateway.gw_ip = GATEWAY_HOST
mock_gateway.port = GATEWAY_PORT
mock_gateway.name = "Test Gateway"
mock_gateway.username = "gateway_user"
mock_gateway.passwd = "gateway_pass"
mock_gateway.connect = AsyncMock()
mock_gateway.disconnect = AsyncMock()
mock_gateway.discover_devices = AsyncMock(return_value=mock_devices)
mock_gateway.discover_scenes = AsyncMock(return_value=mock_scenes)
yield mock_gateway
@@ -0,0 +1,97 @@
# serializer version: 1
# name: test_entities[scene.test_gateway_kitchen_bright-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'scene',
'entity_category': None,
'entity_id': 'scene.test_gateway_kitchen_bright',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Kitchen Bright',
'platform': 'sunricher_dali',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'scene_0002_0000_6A242121110E',
'unit_of_measurement': None,
})
# ---
# name: test_entities[scene.test_gateway_kitchen_bright-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Gateway Kitchen Bright',
}),
'context': <ANY>,
'entity_id': 'scene.test_gateway_kitchen_bright',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entities[scene.test_gateway_living_room_evening-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'scene',
'entity_category': None,
'entity_id': 'scene.test_gateway_living_room_evening',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Living Room Evening',
'platform': 'sunricher_dali',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'scene_0001_0000_6A242121110E',
'unit_of_measurement': None,
})
# ---
# name: test_entities[scene.test_gateway_living_room_evening-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Gateway Living Room Evening',
}),
'context': <ANY>,
'entity_id': 'scene.test_gateway_living_room_evening',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
+5 -31
View File
@@ -1,7 +1,7 @@
"""Test the Sunricher DALI light platform."""
from typing import Any
from unittest.mock import MagicMock, patch
from unittest.mock import MagicMock
from PySrDaliGateway import CallbackEventType
import pytest
@@ -16,7 +16,7 @@ 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 . import find_device_listener, trigger_availability_callback
from tests.common import MockConfigEntry, SnapshotAssertion, snapshot_platform
@@ -35,38 +35,12 @@ def _trigger_light_status_callback(
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
def platforms() -> list[Platform]:
"""Fixture to specify which platforms to test."""
return [Platform.LIGHT]
@pytest.fixture
async def init_integration(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_gateway: MagicMock,
mock_devices: list[MagicMock],
platforms: list[Platform],
) -> MockConfigEntry:
"""Set up the integration for testing."""
mock_config_entry.add_to_hass(hass)
with patch("homeassistant.components.sunricher_dali._PLATFORMS", platforms):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
return mock_config_entry
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
async def test_entities(
hass: HomeAssistant,
@@ -192,13 +166,13 @@ async def test_device_availability(
init_integration: MockConfigEntry,
mock_devices: list[MagicMock],
) -> None:
"""Test device availability changes."""
_trigger_availability_callback(mock_devices[0], TEST_DIMMER_DEVICE_ID, False)
"""Test availability changes are reflected in entity state."""
trigger_availability_callback(mock_devices[0], 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)
trigger_availability_callback(mock_devices[0], True)
await hass.async_block_till_done()
assert (state := hass.states.get(TEST_DIMMER_ENTITY_ID))
assert state.state != "unavailable"
@@ -0,0 +1,101 @@
"""Test the Sunricher DALI scene platform."""
from unittest.mock import MagicMock
import pytest
from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN, SERVICE_TURN_ON
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 trigger_availability_callback
from tests.common import MockConfigEntry, SnapshotAssertion, snapshot_platform
TEST_SCENE_1_ENTITY_ID = "scene.test_gateway_living_room_evening"
TEST_SCENE_2_ENTITY_ID = "scene.test_gateway_kitchen_bright"
TEST_DIMMER_ENTITY_ID = "light.dimmer_0000_02"
TEST_CCT_ENTITY_ID = "light.cct_0000_03"
@pytest.fixture
def platforms() -> list[Platform]:
"""Fixture to specify which platforms to test."""
return [Platform.SCENE]
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
async def test_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
device_registry: dr.DeviceRegistry,
mock_config_entry: MockConfigEntry,
mock_scenes: list[MagicMock],
) -> None:
"""Test the scene entities and their attributes."""
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
device_entry = device_registry.async_get_device(
identifiers={("sunricher_dali", "6A242121110E")}
)
assert device_entry
entity_entries = er.async_entries_for_config_entry(
entity_registry, mock_config_entry.entry_id
)
for entity_entry in entity_entries:
assert entity_entry.device_id == device_entry.id
state = hass.states.get(TEST_SCENE_1_ENTITY_ID)
assert state is not None
async def test_activate_scenes(
hass: HomeAssistant,
init_integration: MockConfigEntry,
mock_scenes: list[MagicMock],
) -> None:
"""Test activating single and multiple scenes."""
await hass.services.async_call(
SCENE_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: TEST_SCENE_1_ENTITY_ID},
blocking=True,
)
mock_scenes[0].activate.assert_called_once()
await hass.services.async_call(
SCENE_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: [TEST_SCENE_1_ENTITY_ID, TEST_SCENE_2_ENTITY_ID]},
blocking=True,
)
assert mock_scenes[0].activate.call_count == 2
mock_scenes[1].activate.assert_called_once()
async def test_scene_availability(
hass: HomeAssistant,
init_integration: MockConfigEntry,
mock_scenes: list[MagicMock],
) -> None:
"""Test scene availability changes when gateway goes offline."""
state = hass.states.get(TEST_SCENE_1_ENTITY_ID)
assert state is not None
assert state.state != "unavailable"
# Simulate gateway going offline
trigger_availability_callback(mock_scenes[0], False)
await hass.async_block_till_done()
state = hass.states.get(TEST_SCENE_1_ENTITY_ID)
assert state.state == "unavailable"
# Simulate gateway coming back online
trigger_availability_callback(mock_scenes[0], True)
await hass.async_block_till_done()
state = hass.states.get(TEST_SCENE_1_ENTITY_ID)
assert state.state != "unavailable"