Add Button platform for Smlight integration (#124970)

* Add button platform for smlight integration

* Add strings required for button platform

* Add commands api to smlight mock client

* Add tests for smlight button platform

* Move entity category to class

* Disable by default Zigbee flash mode
This commit is contained in:
TimL 2024-09-05 03:10:59 +10:00 committed by GitHub
parent 416a2de179
commit 7266a16295
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 179 additions and 1 deletions

View File

@ -9,6 +9,7 @@ from homeassistant.core import HomeAssistant
from .coordinator import SmDataUpdateCoordinator from .coordinator import SmDataUpdateCoordinator
PLATFORMS: list[Platform] = [ PLATFORMS: list[Platform] = [
Platform.BUTTON,
Platform.SENSOR, Platform.SENSOR,
] ]
type SmConfigEntry = ConfigEntry[SmDataUpdateCoordinator] type SmConfigEntry = ConfigEntry[SmDataUpdateCoordinator]

View File

@ -0,0 +1,87 @@
"""Support for SLZB-06 buttons."""
from __future__ import annotations
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
import logging
from typing import Final
from pysmlight.web import CmdWrapper
from homeassistant.components.button import (
ButtonDeviceClass,
ButtonEntity,
ButtonEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .coordinator import SmDataUpdateCoordinator
from .entity import SmEntity
_LOGGER = logging.getLogger(__name__)
@dataclass(frozen=True, kw_only=True)
class SmButtonDescription(ButtonEntityDescription):
"""Class to describe a Button entity."""
press_fn: Callable[[CmdWrapper], Awaitable[None]]
BUTTONS: Final = [
SmButtonDescription(
key="core_restart",
translation_key="core_restart",
device_class=ButtonDeviceClass.RESTART,
press_fn=lambda cmd: cmd.reboot(),
),
SmButtonDescription(
key="zigbee_restart",
translation_key="zigbee_restart",
device_class=ButtonDeviceClass.RESTART,
press_fn=lambda cmd: cmd.zb_restart(),
),
SmButtonDescription(
key="zigbee_flash_mode",
translation_key="zigbee_flash_mode",
entity_registry_enabled_default=False,
press_fn=lambda cmd: cmd.zb_bootloader(),
),
]
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up SMLIGHT buttons based on a config entry."""
coordinator = entry.runtime_data
async_add_entities(SmButton(coordinator, button) for button in BUTTONS)
class SmButton(SmEntity, ButtonEntity):
"""Defines a SLZB-06 button."""
entity_description: SmButtonDescription
_attr_entity_category = EntityCategory.CONFIG
def __init__(
self,
coordinator: SmDataUpdateCoordinator,
description: SmButtonDescription,
) -> None:
"""Initialize SLZB-06 button entity."""
super().__init__(coordinator)
self.entity_description = description
self._attr_unique_id = f"{coordinator.unique_id}-{description.key}"
async def async_press(self) -> None:
"""Trigger button press."""
await self.entity_description.press_fn(self.coordinator.client.cmds)

View File

@ -44,6 +44,17 @@
"ram_usage": { "ram_usage": {
"name": "RAM usage" "name": "RAM usage"
} }
},
"button": {
"core_restart": {
"name": "Core restart"
},
"zigbee_restart": {
"name": "Zigbee restart"
},
"zigbee_flash_mode": {
"name": "Zigbee flash mode"
}
} }
} }
} }

View File

@ -3,7 +3,7 @@
from collections.abc import AsyncGenerator, Generator from collections.abc import AsyncGenerator, Generator
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import AsyncMock, MagicMock, patch
from pysmlight.web import Info, Sensors from pysmlight.web import CmdWrapper, Info, Sensors
import pytest import pytest
from homeassistant.components.smlight import PLATFORMS from homeassistant.components.smlight import PLATFORMS
@ -75,6 +75,8 @@ def mock_smlight_client(request: pytest.FixtureRequest) -> Generator[MagicMock]:
api.check_auth_needed.return_value = False api.check_auth_needed.return_value = False
api.authenticate.return_value = True api.authenticate.return_value = True
api.cmds = AsyncMock(spec_set=CmdWrapper)
yield api yield api

View File

@ -0,0 +1,77 @@
"""Tests for SMLIGHT SLZB-06 button entities."""
from unittest.mock import MagicMock
import pytest
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .conftest import setup_integration
from tests.common import MockConfigEntry
@pytest.fixture
def platforms() -> Platform | list[Platform]:
"""Platforms, which should be loaded during the test."""
return [Platform.BUTTON]
@pytest.mark.parametrize(
("entity_id", "method"),
[
("core_restart", "reboot"),
("zigbee_flash_mode", "zb_bootloader"),
("zigbee_restart", "zb_restart"),
],
)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_buttons(
hass: HomeAssistant,
entity_id: str,
entity_registry: er.EntityRegistry,
method: str,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
) -> None:
"""Test creation of button entities."""
await setup_integration(hass, mock_config_entry)
state = hass.states.get(f"button.mock_title_{entity_id}")
assert state is not None
assert state.state == STATE_UNKNOWN
entry = entity_registry.async_get(f"button.mock_title_{entity_id}")
assert entry is not None
assert entry.unique_id == f"aa:bb:cc:dd:ee:ff-{entity_id}"
mock_method = getattr(mock_smlight_client.cmds, method)
await hass.services.async_call(
BUTTON_DOMAIN,
SERVICE_PRESS,
{ATTR_ENTITY_ID: f"button.mock_title_{entity_id}"},
blocking=True,
)
assert len(mock_method.mock_calls) == 1
mock_method.assert_called_with()
@pytest.mark.usefixtures("mock_smlight_client")
async def test_disabled_by_default_button(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the disabled by default flash mode button."""
await setup_integration(hass, mock_config_entry)
assert not hass.states.get("button.mock_title_zigbee_flash_mode")
assert (entry := entity_registry.async_get("button.mock_title_zigbee_flash_mode"))
assert entry.disabled
assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION