Add button platform to Persang Infrared (#181161)

This commit is contained in:
Dr.Blank
2026-09-03 13:21:40 +02:00
committed by GitHub
parent 0b41a9e967
commit 038ca082c3
8 changed files with 993 additions and 7 deletions
@@ -4,7 +4,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:
@@ -0,0 +1,93 @@
"""Button platform for the Persang Infrared integration.
Only commands the media player has no slot for live here: the mode and EQ
cycles, scan and repeat, and the numeric keypad.
"""
from dataclasses import dataclass
from typing import override
from infrared_protocols.codes.persang.speaker import PersangSpeakerCode
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_INFRARED_EMITTER_ENTITY_ID
from .entity import PersangIrEntity
PARALLEL_UPDATES = 1
@dataclass(frozen=True, kw_only=True)
class PersangIrButtonEntityDescription(ButtonEntityDescription):
"""Describes a Persang IR button entity."""
command_code: PersangSpeakerCode
BUTTON_DESCRIPTIONS: tuple[PersangIrButtonEntityDescription, ...] = (
PersangIrButtonEntityDescription(
key="mode",
translation_key="mode",
command_code=PersangSpeakerCode.MODE,
),
PersangIrButtonEntityDescription(
key="eq",
translation_key="eq",
command_code=PersangSpeakerCode.EQ,
),
PersangIrButtonEntityDescription(
key="scan",
translation_key="scan",
command_code=PersangSpeakerCode.SCAN,
),
PersangIrButtonEntityDescription(
key="repeat",
translation_key="repeat",
command_code=PersangSpeakerCode.REPEAT,
),
*(
PersangIrButtonEntityDescription(
key=f"num_{digit}",
translation_key=f"num_{digit}",
command_code=PersangSpeakerCode[f"NUM_{digit}"],
)
for digit in range(10)
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Persang IR buttons from a config entry."""
infrared_entity_id = entry.data[CONF_INFRARED_EMITTER_ENTITY_ID]
async_add_entities(
PersangIrButton(entry, infrared_entity_id, description)
for description in BUTTON_DESCRIPTIONS
)
class PersangIrButton(PersangIrEntity, ButtonEntity):
"""Persang IR button entity."""
entity_description: PersangIrButtonEntityDescription
def __init__(
self,
entry: ConfigEntry,
infrared_entity_id: str,
description: PersangIrButtonEntityDescription,
) -> None:
"""Initialize Persang IR button."""
super().__init__(entry, infrared_entity_id, unique_id_suffix=description.key)
self.entity_description = description
@override
async def async_press(self) -> None:
"""Press the button."""
await self._send_command(self.entity_description.command_code.to_command())
@@ -12,10 +12,19 @@ class PersangIrEntity(InfraredEmitterConsumerEntity):
_attr_has_entity_name = True
def __init__(self, entry: ConfigEntry, infrared_entity_id: str) -> None:
def __init__(
self,
entry: ConfigEntry,
infrared_entity_id: str,
unique_id_suffix: str | None = None,
) -> None:
"""Initialize Persang IR entity."""
self._infrared_emitter_entity_id = infrared_entity_id
self._attr_unique_id = entry.entry_id
self._attr_unique_id = (
entry.entry_id
if unique_id_suffix is None
else f"{entry.entry_id}_{unique_id_suffix}"
)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, entry.entry_id)},
name="Persang speaker",
@@ -0,0 +1,48 @@
{
"entity": {
"button": {
"eq": {
"default": "mdi:equalizer"
},
"mode": {
"default": "mdi:playlist-music"
},
"num_0": {
"default": "mdi:numeric-0"
},
"num_1": {
"default": "mdi:numeric-1"
},
"num_2": {
"default": "mdi:numeric-2"
},
"num_3": {
"default": "mdi:numeric-3"
},
"num_4": {
"default": "mdi:numeric-4"
},
"num_5": {
"default": "mdi:numeric-5"
},
"num_6": {
"default": "mdi:numeric-6"
},
"num_7": {
"default": "mdi:numeric-7"
},
"num_8": {
"default": "mdi:numeric-8"
},
"num_9": {
"default": "mdi:numeric-9"
},
"repeat": {
"default": "mdi:repeat"
},
"scan": {
"default": "mdi:motion-play"
}
}
}
}
@@ -100,10 +100,7 @@ rules:
status: exempt
comment: |
This integration does not raise exceptions.
icon-translations:
status: exempt
comment: |
This integration does not use custom icons.
icon-translations: done
reconfiguration-flow: todo
repair-issues:
status: exempt
@@ -16,5 +16,51 @@
"title": "Set up Persang IR speaker"
}
}
},
"entity": {
"button": {
"eq": {
"name": "Equalizer"
},
"mode": {
"name": "Mode"
},
"num_0": {
"name": "[%key:common::entity::button::num_0::name%]"
},
"num_1": {
"name": "[%key:common::entity::button::num_1::name%]"
},
"num_2": {
"name": "[%key:common::entity::button::num_2::name%]"
},
"num_3": {
"name": "[%key:common::entity::button::num_3::name%]"
},
"num_4": {
"name": "[%key:common::entity::button::num_4::name%]"
},
"num_5": {
"name": "[%key:common::entity::button::num_5::name%]"
},
"num_6": {
"name": "[%key:common::entity::button::num_6::name%]"
},
"num_7": {
"name": "[%key:common::entity::button::num_7::name%]"
},
"num_8": {
"name": "[%key:common::entity::button::num_8::name%]"
},
"num_9": {
"name": "[%key:common::entity::button::num_9::name%]"
},
"repeat": {
"name": "Repeat"
},
"scan": {
"name": "Scan"
}
}
}
}
@@ -0,0 +1,701 @@
# serializer version: 1
# name: test_entities[button.persang_speaker_equalizer-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.persang_speaker_equalizer',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Equalizer',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Equalizer',
'platform': 'persang_infrared',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'eq',
'unique_id': '01JTEST0000000000000000000_eq',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.persang_speaker_equalizer-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Persang speaker Equalizer',
}),
'context': <ANY>,
'entity_id': 'button.persang_speaker_equalizer',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entities[button.persang_speaker_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.persang_speaker_mode',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Mode',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Mode',
'platform': 'persang_infrared',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'mode',
'unique_id': '01JTEST0000000000000000000_mode',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.persang_speaker_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Persang speaker Mode',
}),
'context': <ANY>,
'entity_id': 'button.persang_speaker_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entities[button.persang_speaker_number_0-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.persang_speaker_number_0',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Number 0',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Number 0',
'platform': 'persang_infrared',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'num_0',
'unique_id': '01JTEST0000000000000000000_num_0',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.persang_speaker_number_0-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Persang speaker Number 0',
}),
'context': <ANY>,
'entity_id': 'button.persang_speaker_number_0',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entities[button.persang_speaker_number_1-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.persang_speaker_number_1',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Number 1',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Number 1',
'platform': 'persang_infrared',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'num_1',
'unique_id': '01JTEST0000000000000000000_num_1',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.persang_speaker_number_1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Persang speaker Number 1',
}),
'context': <ANY>,
'entity_id': 'button.persang_speaker_number_1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entities[button.persang_speaker_number_2-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.persang_speaker_number_2',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Number 2',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Number 2',
'platform': 'persang_infrared',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'num_2',
'unique_id': '01JTEST0000000000000000000_num_2',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.persang_speaker_number_2-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Persang speaker Number 2',
}),
'context': <ANY>,
'entity_id': 'button.persang_speaker_number_2',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entities[button.persang_speaker_number_3-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.persang_speaker_number_3',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Number 3',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Number 3',
'platform': 'persang_infrared',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'num_3',
'unique_id': '01JTEST0000000000000000000_num_3',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.persang_speaker_number_3-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Persang speaker Number 3',
}),
'context': <ANY>,
'entity_id': 'button.persang_speaker_number_3',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entities[button.persang_speaker_number_4-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.persang_speaker_number_4',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Number 4',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Number 4',
'platform': 'persang_infrared',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'num_4',
'unique_id': '01JTEST0000000000000000000_num_4',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.persang_speaker_number_4-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Persang speaker Number 4',
}),
'context': <ANY>,
'entity_id': 'button.persang_speaker_number_4',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entities[button.persang_speaker_number_5-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.persang_speaker_number_5',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Number 5',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Number 5',
'platform': 'persang_infrared',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'num_5',
'unique_id': '01JTEST0000000000000000000_num_5',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.persang_speaker_number_5-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Persang speaker Number 5',
}),
'context': <ANY>,
'entity_id': 'button.persang_speaker_number_5',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entities[button.persang_speaker_number_6-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.persang_speaker_number_6',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Number 6',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Number 6',
'platform': 'persang_infrared',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'num_6',
'unique_id': '01JTEST0000000000000000000_num_6',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.persang_speaker_number_6-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Persang speaker Number 6',
}),
'context': <ANY>,
'entity_id': 'button.persang_speaker_number_6',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entities[button.persang_speaker_number_7-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.persang_speaker_number_7',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Number 7',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Number 7',
'platform': 'persang_infrared',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'num_7',
'unique_id': '01JTEST0000000000000000000_num_7',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.persang_speaker_number_7-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Persang speaker Number 7',
}),
'context': <ANY>,
'entity_id': 'button.persang_speaker_number_7',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entities[button.persang_speaker_number_8-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.persang_speaker_number_8',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Number 8',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Number 8',
'platform': 'persang_infrared',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'num_8',
'unique_id': '01JTEST0000000000000000000_num_8',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.persang_speaker_number_8-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Persang speaker Number 8',
}),
'context': <ANY>,
'entity_id': 'button.persang_speaker_number_8',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entities[button.persang_speaker_number_9-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.persang_speaker_number_9',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Number 9',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Number 9',
'platform': 'persang_infrared',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'num_9',
'unique_id': '01JTEST0000000000000000000_num_9',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.persang_speaker_number_9-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Persang speaker Number 9',
}),
'context': <ANY>,
'entity_id': 'button.persang_speaker_number_9',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entities[button.persang_speaker_repeat-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.persang_speaker_repeat',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Repeat',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Repeat',
'platform': 'persang_infrared',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'repeat',
'unique_id': '01JTEST0000000000000000000_repeat',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.persang_speaker_repeat-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Persang speaker Repeat',
}),
'context': <ANY>,
'entity_id': 'button.persang_speaker_repeat',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_entities[button.persang_speaker_scan-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.persang_speaker_scan',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Scan',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Scan',
'platform': 'persang_infrared',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'scan',
'unique_id': '01JTEST0000000000000000000_scan',
'unit_of_measurement': None,
})
# ---
# name: test_entities[button.persang_speaker_scan-state]
StateSnapshot({
'attributes': ReadOnlyDict({
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Persang speaker Scan',
}),
'context': <ANY>,
'entity_id': 'button.persang_speaker_scan',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
@@ -0,0 +1,92 @@
"""Tests for the Persang Infrared button platform."""
from infrared_protocols.codes.persang.speaker import PersangSpeakerCode
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 MOCK_INFRARED_EMITTER_ENTITY_ID
from tests.common import MockConfigEntry, snapshot_platform
from tests.components.common import assert_availability_follows_source_entity
from tests.components.infrared.common import MockInfraredEmitterEntity
BUTTON_ENTITY_ID_MODE = "button.persang_speaker_mode"
@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 the correct attributes."""
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
device_entry = device_registry.async_get_device_by_identifier(
("persang_infrared", mock_config_entry.entry_id), 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.persang_speaker_mode", PersangSpeakerCode.MODE),
("button.persang_speaker_equalizer", PersangSpeakerCode.EQ),
("button.persang_speaker_scan", PersangSpeakerCode.SCAN),
("button.persang_speaker_repeat", PersangSpeakerCode.REPEAT),
("button.persang_speaker_number_0", PersangSpeakerCode.NUM_0),
("button.persang_speaker_number_1", PersangSpeakerCode.NUM_1),
("button.persang_speaker_number_2", PersangSpeakerCode.NUM_2),
("button.persang_speaker_number_3", PersangSpeakerCode.NUM_3),
("button.persang_speaker_number_4", PersangSpeakerCode.NUM_4),
("button.persang_speaker_number_5", PersangSpeakerCode.NUM_5),
("button.persang_speaker_number_6", PersangSpeakerCode.NUM_6),
("button.persang_speaker_number_7", PersangSpeakerCode.NUM_7),
("button.persang_speaker_number_8", PersangSpeakerCode.NUM_8),
("button.persang_speaker_number_9", PersangSpeakerCode.NUM_9),
],
)
@pytest.mark.usefixtures("init_integration")
async def test_button_press_sends_correct_code(
hass: HomeAssistant,
mock_infrared_emitter_entity: MockInfraredEmitterEntity,
entity_id: str,
expected_code: PersangSpeakerCode,
) -> 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_emitter_entity.send_command_calls) == 1
assert mock_infrared_emitter_entity.send_command_calls[0] == expected_code
@pytest.mark.usefixtures("init_integration")
async def test_button_availability_follows_ir_entity(hass: HomeAssistant) -> None:
"""Test a button becomes unavailable when the IR entity is unavailable."""
await assert_availability_follows_source_entity(
hass, BUTTON_ENTITY_ID_MODE, MOCK_INFRARED_EMITTER_ENTITY_ID
)