mirror of
https://github.com/home-assistant/core.git
synced 2026-03-25 05:28:12 +00:00
Compare commits
6 Commits
dev
...
lg_infrare
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8560b4f020 | ||
|
|
1b68ba1d1d | ||
|
|
8834f8f38f | ||
|
|
0a929b4fe9 | ||
|
|
7d43fba039 | ||
|
|
f08444b271 |
@@ -6,7 +6,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
PLATFORMS = [Platform.MEDIA_PLAYER]
|
||||
PLATFORMS = [Platform.BUTTON, Platform.MEDIA_PLAYER]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
147
homeassistant/components/lg_infrared/button.py
Normal file
147
homeassistant/components/lg_infrared/button.py
Normal file
@@ -0,0 +1,147 @@
|
||||
"""Button platform for LG IR integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from infrared_protocols.codes.lg.tv import LGTVCode
|
||||
|
||||
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import CONF_DEVICE_TYPE, CONF_INFRARED_ENTITY_ID, LGDeviceType
|
||||
from .entity import LgIrEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class LgIrButtonEntityDescription(ButtonEntityDescription):
|
||||
"""Describes LG IR button entity."""
|
||||
|
||||
command_code: LGTVCode
|
||||
|
||||
|
||||
TV_BUTTON_DESCRIPTIONS: tuple[LgIrButtonEntityDescription, ...] = (
|
||||
LgIrButtonEntityDescription(
|
||||
key="power_on", translation_key="power_on", command_code=LGTVCode.POWER_ON
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="power_off", translation_key="power_off", command_code=LGTVCode.POWER_OFF
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="hdmi_1", translation_key="hdmi_1", command_code=LGTVCode.HDMI_1
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="hdmi_2", translation_key="hdmi_2", command_code=LGTVCode.HDMI_2
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="hdmi_3", translation_key="hdmi_3", command_code=LGTVCode.HDMI_3
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="hdmi_4", translation_key="hdmi_4", command_code=LGTVCode.HDMI_4
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="exit", translation_key="exit", command_code=LGTVCode.EXIT
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="info", translation_key="info", command_code=LGTVCode.INFO
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="guide", translation_key="guide", command_code=LGTVCode.GUIDE
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="up", translation_key="up", command_code=LGTVCode.NAV_UP
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="down", translation_key="down", command_code=LGTVCode.NAV_DOWN
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="left", translation_key="left", command_code=LGTVCode.NAV_LEFT
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="right", translation_key="right", command_code=LGTVCode.NAV_RIGHT
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="ok", translation_key="ok", command_code=LGTVCode.OK
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="back", translation_key="back", command_code=LGTVCode.BACK
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="home", translation_key="home", command_code=LGTVCode.HOME
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="menu", translation_key="menu", command_code=LGTVCode.MENU
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="input", translation_key="input", command_code=LGTVCode.INPUT
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="num_0", translation_key="num_0", command_code=LGTVCode.NUM_0
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="num_1", translation_key="num_1", command_code=LGTVCode.NUM_1
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="num_2", translation_key="num_2", command_code=LGTVCode.NUM_2
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="num_3", translation_key="num_3", command_code=LGTVCode.NUM_3
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="num_4", translation_key="num_4", command_code=LGTVCode.NUM_4
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="num_5", translation_key="num_5", command_code=LGTVCode.NUM_5
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="num_6", translation_key="num_6", command_code=LGTVCode.NUM_6
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="num_7", translation_key="num_7", command_code=LGTVCode.NUM_7
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="num_8", translation_key="num_8", command_code=LGTVCode.NUM_8
|
||||
),
|
||||
LgIrButtonEntityDescription(
|
||||
key="num_9", translation_key="num_9", command_code=LGTVCode.NUM_9
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up LG IR buttons from config entry."""
|
||||
infrared_entity_id = entry.data[CONF_INFRARED_ENTITY_ID]
|
||||
device_type = entry.data[CONF_DEVICE_TYPE]
|
||||
if device_type == LGDeviceType.TV:
|
||||
async_add_entities(
|
||||
LgIrButton(entry, infrared_entity_id, description)
|
||||
for description in TV_BUTTON_DESCRIPTIONS
|
||||
)
|
||||
|
||||
|
||||
class LgIrButton(LgIrEntity, ButtonEntity):
|
||||
"""LG IR button entity."""
|
||||
|
||||
entity_description: LgIrButtonEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
entry: ConfigEntry,
|
||||
infrared_entity_id: str,
|
||||
description: LgIrButtonEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize LG IR button."""
|
||||
super().__init__(entry, infrared_entity_id, unique_id_suffix=description.key)
|
||||
self.entity_description = description
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Press the button."""
|
||||
await self._send_command(self.entity_description.command_code)
|
||||
@@ -19,6 +19,94 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"button": {
|
||||
"back": {
|
||||
"name": "Back"
|
||||
},
|
||||
"down": {
|
||||
"name": "Down"
|
||||
},
|
||||
"exit": {
|
||||
"name": "Exit"
|
||||
},
|
||||
"guide": {
|
||||
"name": "Guide"
|
||||
},
|
||||
"hdmi_1": {
|
||||
"name": "HDMI 1"
|
||||
},
|
||||
"hdmi_2": {
|
||||
"name": "HDMI 2"
|
||||
},
|
||||
"hdmi_3": {
|
||||
"name": "HDMI 3"
|
||||
},
|
||||
"hdmi_4": {
|
||||
"name": "HDMI 4"
|
||||
},
|
||||
"home": {
|
||||
"name": "Home"
|
||||
},
|
||||
"info": {
|
||||
"name": "Info"
|
||||
},
|
||||
"input": {
|
||||
"name": "Input"
|
||||
},
|
||||
"left": {
|
||||
"name": "Left"
|
||||
},
|
||||
"menu": {
|
||||
"name": "Menu"
|
||||
},
|
||||
"num_0": {
|
||||
"name": "Number 0"
|
||||
},
|
||||
"num_1": {
|
||||
"name": "Number 1"
|
||||
},
|
||||
"num_2": {
|
||||
"name": "Number 2"
|
||||
},
|
||||
"num_3": {
|
||||
"name": "Number 3"
|
||||
},
|
||||
"num_4": {
|
||||
"name": "Number 4"
|
||||
},
|
||||
"num_5": {
|
||||
"name": "Number 5"
|
||||
},
|
||||
"num_6": {
|
||||
"name": "Number 6"
|
||||
},
|
||||
"num_7": {
|
||||
"name": "Number 7"
|
||||
},
|
||||
"num_8": {
|
||||
"name": "Number 8"
|
||||
},
|
||||
"num_9": {
|
||||
"name": "Number 9"
|
||||
},
|
||||
"ok": {
|
||||
"name": "OK"
|
||||
},
|
||||
"power_off": {
|
||||
"name": "Power off"
|
||||
},
|
||||
"power_on": {
|
||||
"name": "Power on"
|
||||
},
|
||||
"right": {
|
||||
"name": "Right"
|
||||
},
|
||||
"up": {
|
||||
"name": "Up"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
"device_type": {
|
||||
"options": {
|
||||
|
||||
@@ -13,6 +13,7 @@ from homeassistant.components.infrared import (
|
||||
DOMAIN as INFRARED_DOMAIN,
|
||||
InfraredEntity,
|
||||
)
|
||||
from homeassistant.components.lg_infrared import PLATFORMS
|
||||
from homeassistant.components.lg_infrared.const import (
|
||||
CONF_DEVICE_TYPE,
|
||||
CONF_INFRARED_ENTITY_ID,
|
||||
@@ -68,7 +69,7 @@ def mock_infrared_entity() -> MockInfraredEntity:
|
||||
@pytest.fixture
|
||||
def platforms() -> list[Platform]:
|
||||
"""Return platforms to set up."""
|
||||
return [Platform.MEDIA_PLAYER]
|
||||
return PLATFORMS
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
1401
tests/components/lg_infrared/snapshots/test_button.ambr
Normal file
1401
tests/components/lg_infrared/snapshots/test_button.ambr
Normal file
File diff suppressed because it is too large
Load Diff
107
tests/components/lg_infrared/test_button.py
Normal file
107
tests/components/lg_infrared/test_button.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""Tests for the LG Infrared button platform."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from infrared_protocols.codes.lg.tv import LGTVCode
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
|
||||
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 .conftest import MockInfraredEntity
|
||||
from .utils import check_availability_follows_ir_entity
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> list[Platform]:
|
||||
"""Return platforms to set up."""
|
||||
return [Platform.BUTTON]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_entities(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_registry: er.EntityRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test all button entities are created with correct attributes."""
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
# Verify all entities belong to the same device
|
||||
device_entry = device_registry.async_get_device(
|
||||
identifiers={("lg_infrared", mock_config_entry.entry_id)}
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "expected_code"),
|
||||
[
|
||||
("button.lg_tv_power_on", LGTVCode.POWER_ON),
|
||||
("button.lg_tv_power_off", LGTVCode.POWER_OFF),
|
||||
("button.lg_tv_hdmi_1", LGTVCode.HDMI_1),
|
||||
("button.lg_tv_hdmi_2", LGTVCode.HDMI_2),
|
||||
("button.lg_tv_hdmi_3", LGTVCode.HDMI_3),
|
||||
("button.lg_tv_hdmi_4", LGTVCode.HDMI_4),
|
||||
("button.lg_tv_exit", LGTVCode.EXIT),
|
||||
("button.lg_tv_info", LGTVCode.INFO),
|
||||
("button.lg_tv_guide", LGTVCode.GUIDE),
|
||||
("button.lg_tv_up", LGTVCode.NAV_UP),
|
||||
("button.lg_tv_down", LGTVCode.NAV_DOWN),
|
||||
("button.lg_tv_left", LGTVCode.NAV_LEFT),
|
||||
("button.lg_tv_right", LGTVCode.NAV_RIGHT),
|
||||
("button.lg_tv_ok", LGTVCode.OK),
|
||||
("button.lg_tv_back", LGTVCode.BACK),
|
||||
("button.lg_tv_home", LGTVCode.HOME),
|
||||
("button.lg_tv_menu", LGTVCode.MENU),
|
||||
("button.lg_tv_input", LGTVCode.INPUT),
|
||||
("button.lg_tv_number_0", LGTVCode.NUM_0),
|
||||
("button.lg_tv_number_1", LGTVCode.NUM_1),
|
||||
("button.lg_tv_number_2", LGTVCode.NUM_2),
|
||||
("button.lg_tv_number_3", LGTVCode.NUM_3),
|
||||
("button.lg_tv_number_4", LGTVCode.NUM_4),
|
||||
("button.lg_tv_number_5", LGTVCode.NUM_5),
|
||||
("button.lg_tv_number_6", LGTVCode.NUM_6),
|
||||
("button.lg_tv_number_7", LGTVCode.NUM_7),
|
||||
("button.lg_tv_number_8", LGTVCode.NUM_8),
|
||||
("button.lg_tv_number_9", LGTVCode.NUM_9),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_button_press_sends_correct_code(
|
||||
hass: HomeAssistant,
|
||||
mock_infrared_entity: MockInfraredEntity,
|
||||
entity_id: str,
|
||||
expected_code: LGTVCode,
|
||||
) -> None:
|
||||
"""Test pressing a button sends the correct IR code."""
|
||||
await hass.services.async_call(
|
||||
BUTTON_DOMAIN,
|
||||
SERVICE_PRESS,
|
||||
{ATTR_ENTITY_ID: entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert len(mock_infrared_entity.send_command_calls) == 1
|
||||
assert mock_infrared_entity.send_command_calls[0] == expected_code
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_button_availability_follows_ir_entity(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test button becomes unavailable when IR entity is unavailable."""
|
||||
entity_id = "button.lg_tv_power_on"
|
||||
await check_availability_follows_ir_entity(hass, entity_id)
|
||||
@@ -19,11 +19,12 @@ from homeassistant.components.media_player import (
|
||||
SERVICE_VOLUME_MUTE,
|
||||
SERVICE_VOLUME_UP,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform
|
||||
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 .conftest import MOCK_INFRARED_ENTITY_ID, MockInfraredEntity
|
||||
from .conftest import MockInfraredEntity
|
||||
from .utils import check_availability_follows_ir_entity
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
@@ -99,23 +100,4 @@ async def test_media_player_availability_follows_ir_entity(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test media player becomes unavailable when IR entity is unavailable."""
|
||||
# Initially available
|
||||
state = hass.states.get(MEDIA_PLAYER_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
# Make IR entity unavailable
|
||||
hass.states.async_set(MOCK_INFRARED_ENTITY_ID, STATE_UNAVAILABLE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(MEDIA_PLAYER_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
# Restore IR entity
|
||||
hass.states.async_set(MOCK_INFRARED_ENTITY_ID, "2026-01-01T00:00:00.000")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(MEDIA_PLAYER_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
await check_availability_follows_ir_entity(hass, MEDIA_PLAYER_ENTITY_ID)
|
||||
|
||||
33
tests/components/lg_infrared/utils.py
Normal file
33
tests/components/lg_infrared/utils.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""Tests for the LG Infrared integration."""
|
||||
|
||||
from homeassistant.const import STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .conftest import MOCK_INFRARED_ENTITY_ID
|
||||
|
||||
|
||||
async def check_availability_follows_ir_entity(
|
||||
hass: HomeAssistant,
|
||||
entity_id: str,
|
||||
) -> None:
|
||||
"""Check that entity becomes unavailable when IR entity is unavailable."""
|
||||
# Initially available
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
# Make IR entity unavailable
|
||||
hass.states.async_set(MOCK_INFRARED_ENTITY_ID, STATE_UNAVAILABLE)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
# Restore IR entity
|
||||
hass.states.async_set(MOCK_INFRARED_ENTITY_ID, "2026-01-01T00:00:00.000")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
Reference in New Issue
Block a user