1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-02 08:26:41 +01:00

Add Remote platform to SMLIGHT Integration (#166728)

This commit is contained in:
TimL
2026-03-28 17:50:36 +11:00
committed by GitHub
parent 45def46a45
commit 12b485b17e
4 changed files with 236 additions and 0 deletions

View File

@@ -19,6 +19,7 @@ PLATFORMS: list[Platform] = [
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.LIGHT,
Platform.REMOTE,
Platform.SENSOR,
Platform.SWITCH,
Platform.UPDATE,

View File

@@ -0,0 +1,70 @@
"""Remote platform for SLZB-Ultima."""
import asyncio
from collections.abc import Iterable
from typing import Any
from pysmlight.exceptions import SmlightError
from pysmlight.models import IRPayload
from homeassistant.components.remote import (
ATTR_DELAY_SECS,
ATTR_NUM_REPEATS,
DEFAULT_DELAY_SECS,
DEFAULT_NUM_REPEATS,
RemoteEntity,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import SmConfigEntry, SmDataUpdateCoordinator
from .entity import SmEntity
PARALLEL_UPDATES = 1
async def async_setup_entry(
hass: HomeAssistant,
entry: SmConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Initialize remote for SLZB-Ultima device."""
coordinator = entry.runtime_data.data
if coordinator.data.info.has_peripherals:
async_add_entities([SmRemoteEntity(coordinator)])
class SmRemoteEntity(SmEntity, RemoteEntity):
"""Representation of a SLZB-Ultima remote."""
_attr_translation_key = "remote"
_attr_is_on = True
def __init__(self, coordinator: SmDataUpdateCoordinator) -> None:
"""Initialize the SLZB-Ultima remote."""
super().__init__(coordinator)
self._attr_unique_id = f"{coordinator.unique_id}-remote"
async def async_send_command(self, command: Iterable[str], **kwargs: Any) -> None:
"""Send a sequence of commands to a device."""
num_repeats = kwargs.get(ATTR_NUM_REPEATS, DEFAULT_NUM_REPEATS)
delay_secs = kwargs.get(ATTR_DELAY_SECS, DEFAULT_DELAY_SECS)
for _ in range(num_repeats):
for cmd in command:
try:
await self.coordinator.async_execute_command(
self.coordinator.client.actions.send_ir_code,
IRPayload(code=cmd),
)
except SmlightError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="send_ir_code_failed",
translation_placeholders={"error": str(err)},
) from err
await asyncio.sleep(delay_secs)

View File

@@ -84,6 +84,11 @@
"name": "Ambilight"
}
},
"remote": {
"remote": {
"name": "IR Remote"
}
},
"sensor": {
"core_temperature": {
"name": "Core chip temp"
@@ -159,6 +164,9 @@
},
"firmware_update_failed": {
"message": "Firmware update failed for {device_name}."
},
"send_ir_code_failed": {
"message": "Failed to send IR code: {error}."
}
},
"issues": {

View File

@@ -0,0 +1,157 @@
"""Tests for SLZB-Ultima remote entity."""
from unittest.mock import MagicMock, patch
from pysmlight import Info
from pysmlight.exceptions import SmlightError
from pysmlight.models import IRPayload
import pytest
from homeassistant.components.remote import (
ATTR_COMMAND,
ATTR_DELAY_SECS,
ATTR_NUM_REPEATS,
DOMAIN as REMOTE_DOMAIN,
SERVICE_SEND_COMMAND,
)
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from .conftest import setup_integration
from tests.common import MockConfigEntry
@pytest.fixture
def platforms() -> list[Platform]:
"""Platforms, which should be loaded during the test."""
return [Platform.REMOTE]
MOCK_ULTIMA = Info(
MAC="AA:BB:CC:DD:EE:FF",
model="SLZB-Ultima3",
)
async def test_remote_setup_ultima(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
) -> None:
"""Test remote entity is created for Ultima devices."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
await setup_integration(hass, mock_config_entry)
state = hass.states.get("remote.mock_title_ir_remote")
assert state is not None
async def test_remote_not_created_non_ultima(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
) -> None:
"""Test remote entity is not created for non-Ultima devices."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = Info(
MAC="AA:BB:CC:DD:EE:FF",
model="SLZB-MR1",
)
await setup_integration(hass, mock_config_entry)
state = hass.states.get("remote.mock_title_ir_remote")
assert state is None
async def test_remote_send_command(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
) -> None:
"""Test sending IR command."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
await setup_integration(hass, mock_config_entry)
entity_id = "remote.mock_title_ir_remote"
state = hass.states.get(entity_id)
assert state is not None
await hass.services.async_call(
REMOTE_DOMAIN,
SERVICE_SEND_COMMAND,
{
ATTR_ENTITY_ID: entity_id,
ATTR_COMMAND: ["my_code", "another_code"],
ATTR_DELAY_SECS: 0,
},
blocking=True,
)
assert mock_smlight_client.actions.send_ir_code.call_count == 2
mock_smlight_client.actions.send_ir_code.assert_any_call(IRPayload(code="my_code"))
mock_smlight_client.actions.send_ir_code.assert_any_call(
IRPayload(code="another_code")
)
async def test_remote_send_command_error(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
) -> None:
"""Test connection error handling."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
await setup_integration(hass, mock_config_entry)
entity_id = "remote.mock_title_ir_remote"
state = hass.states.get(entity_id)
assert state is not None
mock_smlight_client.actions.send_ir_code.side_effect = SmlightError("Failed")
with pytest.raises(HomeAssistantError) as exc_info:
await hass.services.async_call(
REMOTE_DOMAIN,
SERVICE_SEND_COMMAND,
{ATTR_ENTITY_ID: entity_id, ATTR_COMMAND: ["my_code"]},
blocking=True,
)
assert exc_info.value.translation_key == "send_ir_code_failed"
@patch("homeassistant.components.smlight.remote.asyncio.sleep")
async def test_remote_send_command_repeats(
mock_sleep: MagicMock,
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_smlight_client: MagicMock,
) -> None:
"""Test sending IR command with repeats and delay."""
mock_smlight_client.get_info.side_effect = None
mock_smlight_client.get_info.return_value = MOCK_ULTIMA
await setup_integration(hass, mock_config_entry)
entity_id = "remote.mock_title_ir_remote"
state = hass.states.get(entity_id)
assert state is not None
await hass.services.async_call(
REMOTE_DOMAIN,
SERVICE_SEND_COMMAND,
{
ATTR_ENTITY_ID: entity_id,
ATTR_COMMAND: ["my_code", "another_code"],
ATTR_NUM_REPEATS: 2,
ATTR_DELAY_SECS: 0.5,
},
blocking=True,
)
assert mock_smlight_client.actions.send_ir_code.call_count == 4
assert mock_sleep.call_count == 5
mock_sleep.assert_called_with(0.5)