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:
@@ -19,6 +19,7 @@ PLATFORMS: list[Platform] = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
Platform.LIGHT,
|
||||
Platform.REMOTE,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.UPDATE,
|
||||
|
||||
70
homeassistant/components/smlight/remote.py
Normal file
70
homeassistant/components/smlight/remote.py
Normal 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)
|
||||
@@ -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": {
|
||||
|
||||
157
tests/components/smlight/test_remote.py
Normal file
157
tests/components/smlight/test_remote.py
Normal 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)
|
||||
Reference in New Issue
Block a user