Add server side events to Smlight integration (#125553)

* Register SSE client

* Add switch events for settings changes

* Mock sse settings events

* Apply suggestions from code review

Co-authored-by: Paarth Shah <mail@shahpaarth.com>

* access callbacks from mock call_args

---------

Co-authored-by: Paarth Shah <mail@shahpaarth.com>
This commit is contained in:
TimL 2024-09-11 20:53:08 +10:00 committed by GitHub
parent 2f68bbd27a
commit b8ce687ec2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 55 additions and 26 deletions

View File

@ -3,6 +3,7 @@
from dataclasses import dataclass
from pysmlight import Api2, Info, Sensors
from pysmlight.const import Settings, SettingsProp
from pysmlight.exceptions import SmlightAuthError, SmlightConnectionError
from homeassistant.config_entries import ConfigEntry
@ -44,6 +45,10 @@ class SmDataUpdateCoordinator(DataUpdateCoordinator[SmData]):
self.client = Api2(host=host, session=async_get_clientsession(hass))
self.legacy_api: int = 0
self.config_entry.async_create_background_task(
hass, self.client.sse.client(), "smlight-sse-client"
)
async def _async_setup(self) -> None:
"""Authenticate if needed during initial setup."""
if await self.client.check_auth_needed():
@ -78,6 +83,13 @@ class SmDataUpdateCoordinator(DataUpdateCoordinator[SmData]):
translation_key="unsupported_firmware",
)
def update_setting(self, setting: Settings, value: bool | int) -> None:
"""Update the sensor value from event."""
prop = SettingsProp[setting.name].value
setattr(self.data.sensors, prop, value)
self.async_set_updated_data(self.data)
async def _async_update_data(self) -> SmData:
"""Fetch data from the SMLIGHT device."""
try:

View File

@ -7,7 +7,7 @@ from dataclasses import dataclass
import logging
from typing import Any
from pysmlight import Sensors
from pysmlight import Sensors, SettingsEvent
from pysmlight.const import Settings
from homeassistant.components.switch import (
@ -16,7 +16,7 @@ from homeassistant.components.switch import (
SwitchEntityDescription,
)
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import SmConfigEntry
@ -86,22 +86,33 @@ class SmSwitch(SmEntity, SwitchEntity):
self._page, self._toggle = description.setting.value
async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass."""
await super().async_added_to_hass()
self.async_on_remove(
self.coordinator.client.sse.register_settings_cb(
self.entity_description.setting, self.event_callback
)
)
async def set_smlight(self, state: bool) -> None:
"""Set the state on SLZB device."""
await self.coordinator.client.set_toggle(self._page, self._toggle, state)
@callback
def event_callback(self, event: SettingsEvent) -> None:
"""Handle switch events from the SLZB device."""
if event.setting is not None:
self.coordinator.update_setting(
self.entity_description.setting, event.setting[self._toggle]
)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
self._attr_is_on = True
self.async_write_ha_state()
await self.set_smlight(True)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
self._attr_is_on = False
self.async_write_ha_state()
await self.set_smlight(False)
@property

View File

@ -3,6 +3,7 @@
from collections.abc import AsyncGenerator, Generator
from unittest.mock import AsyncMock, MagicMock, patch
from pysmlight.sse import sseClient
from pysmlight.web import CmdWrapper, Info, Sensors
import pytest
@ -89,6 +90,7 @@ def mock_smlight_client(request: pytest.FixtureRequest) -> Generator[MagicMock]:
api.cmds = AsyncMock(spec_set=CmdWrapper)
api.set_toggle = AsyncMock()
api.sse = MagicMock(spec_set=sseClient)
yield api

View File

@ -1,14 +1,13 @@
"""Tests for the SMLIGHT switch platform."""
from collections.abc import Callable
from unittest.mock import MagicMock
from freezegun.api import FrozenDateTimeFactory
from pysmlight import Sensors
from pysmlight import SettingsEvent
from pysmlight.const import Settings
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.smlight.const import SCAN_INTERVAL
from homeassistant.components.switch import (
DOMAIN as SWITCH_DOMAIN,
SERVICE_TURN_OFF,
@ -20,7 +19,7 @@ from homeassistant.helpers import entity_registry as er
from .conftest import setup_integration
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
from tests.common import MockConfigEntry, snapshot_platform
pytestmark = [
pytest.mark.usefixtures(
@ -48,18 +47,16 @@ async def test_switch_setup(
@pytest.mark.parametrize(
("entity", "setting", "field"),
("entity", "setting"),
[
("disable_leds", Settings.DISABLE_LEDS, "disable_leds"),
("led_night_mode", Settings.NIGHT_MODE, "night_mode"),
("auto_zigbee_update", Settings.ZB_AUTOUPDATE, "auto_zigbee"),
("disable_leds", Settings.DISABLE_LEDS),
("led_night_mode", Settings.NIGHT_MODE),
("auto_zigbee_update", Settings.ZB_AUTOUPDATE),
],
)
async def test_switches(
hass: HomeAssistant,
entity: str,
field: str,
freezer: FrozenDateTimeFactory,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
setting: Settings,
@ -82,11 +79,21 @@ async def test_switches(
assert len(mock_smlight_client.set_toggle.mock_calls) == 1
mock_smlight_client.set_toggle.assert_called_once_with(_page, _toggle, True)
mock_smlight_client.get_sensors.return_value = Sensors(**{field: True})
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
event_function: Callable[[SettingsEvent], None] = next(
(
call_args[0][1]
for call_args in mock_smlight_client.sse.register_settings_cb.call_args_list
if setting == call_args[0][0]
),
None,
)
async def _call_event_function(state: bool = True):
event_function(SettingsEvent(page=_page, origin="ha", setting={_toggle: state}))
await hass.async_block_till_done()
await _call_event_function(state=True)
state = hass.states.get(entity_id)
assert state.state == STATE_ON
@ -100,11 +107,8 @@ async def test_switches(
assert len(mock_smlight_client.set_toggle.mock_calls) == 2
mock_smlight_client.set_toggle.assert_called_with(_page, _toggle, False)
mock_smlight_client.get_sensors.return_value = Sensors(**{field: False})
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
await _call_event_function(state=False)
state = hass.states.get(entity_id)
assert state.state == STATE_OFF