1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-08 17:49:37 +01:00

Add HDFury switch platform (#160620)

This commit is contained in:
Glenn de Haan
2026-01-09 18:08:37 +01:00
committed by GitHub
parent a26c910db7
commit 694d67d2d5
7 changed files with 797 additions and 0 deletions
@@ -8,6 +8,7 @@ from .coordinator import HDFuryConfigEntry, HDFuryCoordinator
PLATFORMS = [
Platform.BUTTON,
Platform.SELECT,
Platform.SWITCH,
]
@@ -15,6 +15,38 @@
"portseltx1": {
"default": "mdi:hdmi-port"
}
},
"switch": {
"autosw": {
"default": "mdi:import"
},
"htpcmode0": {
"default": "mdi:desktop-classic"
},
"htpcmode1": {
"default": "mdi:desktop-classic"
},
"htpcmode2": {
"default": "mdi:desktop-classic"
},
"htpcmode3": {
"default": "mdi:desktop-classic"
},
"iractive": {
"default": "mdi:remote"
},
"mutetx0": {
"default": "mdi:volume-mute"
},
"mutetx1": {
"default": "mdi:volume-mute"
},
"oled": {
"default": "mdi:cellphone-information"
},
"relay": {
"default": "mdi:electric-switch"
}
}
}
}
@@ -56,6 +56,38 @@
"4": "Copy TX0"
}
}
},
"switch": {
"autosw": {
"name": "Auto switch inputs"
},
"htpcmode0": {
"name": "HTPC mode RX0"
},
"htpcmode1": {
"name": "HTPC mode RX1"
},
"htpcmode2": {
"name": "HTPC mode RX2"
},
"htpcmode3": {
"name": "HTPC mode RX3"
},
"iractive": {
"name": "Infrared"
},
"mutetx0": {
"name": "Mute audio TX0"
},
"mutetx1": {
"name": "Mute audio TX1"
},
"oled": {
"name": "OLED display"
},
"relay": {
"name": "Relay"
}
}
},
"exceptions": {
+142
View File
@@ -0,0 +1,142 @@
"""Switch platform for HDFury Integration."""
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from typing import Any
from hdfury import HDFuryAPI, HDFuryError
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .coordinator import HDFuryConfigEntry
from .entity import HDFuryEntity
@dataclass(kw_only=True, frozen=True)
class HDFurySwitchEntityDescription(SwitchEntityDescription):
"""Description for HDFury switch entities."""
set_value_fn: Callable[[HDFuryAPI, str], Awaitable[None]]
SWITCHES: tuple[HDFurySwitchEntityDescription, ...] = (
HDFurySwitchEntityDescription(
key="autosw",
translation_key="autosw",
entity_category=EntityCategory.CONFIG,
set_value_fn=lambda client, value: client.set_auto_switch_inputs(value),
),
HDFurySwitchEntityDescription(
key="htpcmode0",
translation_key="htpcmode0",
entity_category=EntityCategory.CONFIG,
set_value_fn=lambda client, value: client.set_htpc_mode_rx0(value),
),
HDFurySwitchEntityDescription(
key="htpcmode1",
translation_key="htpcmode1",
entity_category=EntityCategory.CONFIG,
set_value_fn=lambda client, value: client.set_htpc_mode_rx1(value),
),
HDFurySwitchEntityDescription(
key="htpcmode2",
translation_key="htpcmode2",
entity_category=EntityCategory.CONFIG,
set_value_fn=lambda client, value: client.set_htpc_mode_rx2(value),
),
HDFurySwitchEntityDescription(
key="htpcmode3",
translation_key="htpcmode3",
entity_category=EntityCategory.CONFIG,
set_value_fn=lambda client, value: client.set_htpc_mode_rx3(value),
),
HDFurySwitchEntityDescription(
key="mutetx0",
translation_key="mutetx0",
entity_category=EntityCategory.CONFIG,
set_value_fn=lambda client, value: client.set_mute_tx0_audio(value),
),
HDFurySwitchEntityDescription(
key="mutetx1",
translation_key="mutetx1",
entity_category=EntityCategory.CONFIG,
set_value_fn=lambda client, value: client.set_mute_tx1_audio(value),
),
HDFurySwitchEntityDescription(
key="oled",
translation_key="oled",
entity_category=EntityCategory.CONFIG,
set_value_fn=lambda client, value: client.set_oled(value),
),
HDFurySwitchEntityDescription(
key="iractive",
translation_key="iractive",
entity_category=EntityCategory.CONFIG,
set_value_fn=lambda client, value: client.set_ir_active(value),
),
HDFurySwitchEntityDescription(
key="relay",
translation_key="relay",
entity_category=EntityCategory.CONFIG,
set_value_fn=lambda client, value: client.set_relay(value),
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: HDFuryConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up switches using the platform schema."""
coordinator = entry.runtime_data
async_add_entities(
HDFurySwitch(coordinator, description)
for description in SWITCHES
if description.key in coordinator.data.config
)
class HDFurySwitch(HDFuryEntity, SwitchEntity):
"""Base HDFury Switch Class."""
entity_description: HDFurySwitchEntityDescription
@property
def is_on(self) -> bool:
"""Set Switch State."""
return self.coordinator.data.config.get(self.entity_description.key) == "1"
async def async_turn_on(self, **kwargs: Any) -> None:
"""Handle Switch On Event."""
try:
await self.entity_description.set_value_fn(self.coordinator.client, "on")
except HDFuryError as error:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="communication_error",
) from error
await self.coordinator.async_request_refresh()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Handle Switch Off Event."""
try:
await self.entity_description.set_value_fn(self.coordinator.client, "off")
except HDFuryError as error:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="communication_error",
) from error
await self.coordinator.async_request_refresh()
+10
View File
@@ -72,7 +72,17 @@ def mock_hdfury_client() -> Generator[AsyncMock]:
)
coord_client.get_config = AsyncMock(
return_value={
"autosw": "1",
"iractive": "1",
"htpcmode0": "0",
"htpcmode1": "0",
"htpcmode2": "0",
"htpcmode3": "0",
"mutetx0": "1",
"mutetx1": "1",
"relay": "0",
"macaddr": "c7:1c:df:9d:f6:40",
"oled": "1",
}
)
@@ -0,0 +1,481 @@
# serializer version: 1
# name: test_switch_entities[switch.hdfury_vrroom_02_auto_switch_inputs-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.hdfury_vrroom_02_auto_switch_inputs',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Auto switch inputs',
'platform': 'hdfury',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'autosw',
'unique_id': '000123456789_autosw',
'unit_of_measurement': None,
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_auto_switch_inputs-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'HDFury VRROOM-02 Auto switch inputs',
}),
'context': <ANY>,
'entity_id': 'switch.hdfury_vrroom_02_auto_switch_inputs',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_htpc_mode_rx0-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.hdfury_vrroom_02_htpc_mode_rx0',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'HTPC mode RX0',
'platform': 'hdfury',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'htpcmode0',
'unique_id': '000123456789_htpcmode0',
'unit_of_measurement': None,
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_htpc_mode_rx0-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'HDFury VRROOM-02 HTPC mode RX0',
}),
'context': <ANY>,
'entity_id': 'switch.hdfury_vrroom_02_htpc_mode_rx0',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_htpc_mode_rx1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.hdfury_vrroom_02_htpc_mode_rx1',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'HTPC mode RX1',
'platform': 'hdfury',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'htpcmode1',
'unique_id': '000123456789_htpcmode1',
'unit_of_measurement': None,
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_htpc_mode_rx1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'HDFury VRROOM-02 HTPC mode RX1',
}),
'context': <ANY>,
'entity_id': 'switch.hdfury_vrroom_02_htpc_mode_rx1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_htpc_mode_rx2-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.hdfury_vrroom_02_htpc_mode_rx2',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'HTPC mode RX2',
'platform': 'hdfury',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'htpcmode2',
'unique_id': '000123456789_htpcmode2',
'unit_of_measurement': None,
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_htpc_mode_rx2-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'HDFury VRROOM-02 HTPC mode RX2',
}),
'context': <ANY>,
'entity_id': 'switch.hdfury_vrroom_02_htpc_mode_rx2',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_htpc_mode_rx3-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.hdfury_vrroom_02_htpc_mode_rx3',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'HTPC mode RX3',
'platform': 'hdfury',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'htpcmode3',
'unique_id': '000123456789_htpcmode3',
'unit_of_measurement': None,
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_htpc_mode_rx3-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'HDFury VRROOM-02 HTPC mode RX3',
}),
'context': <ANY>,
'entity_id': 'switch.hdfury_vrroom_02_htpc_mode_rx3',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_infrared-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.hdfury_vrroom_02_infrared',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Infrared',
'platform': 'hdfury',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'iractive',
'unique_id': '000123456789_iractive',
'unit_of_measurement': None,
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_infrared-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'HDFury VRROOM-02 Infrared',
}),
'context': <ANY>,
'entity_id': 'switch.hdfury_vrroom_02_infrared',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_mute_audio_tx0-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.hdfury_vrroom_02_mute_audio_tx0',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Mute audio TX0',
'platform': 'hdfury',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'mutetx0',
'unique_id': '000123456789_mutetx0',
'unit_of_measurement': None,
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_mute_audio_tx0-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'HDFury VRROOM-02 Mute audio TX0',
}),
'context': <ANY>,
'entity_id': 'switch.hdfury_vrroom_02_mute_audio_tx0',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_mute_audio_tx1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.hdfury_vrroom_02_mute_audio_tx1',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Mute audio TX1',
'platform': 'hdfury',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'mutetx1',
'unique_id': '000123456789_mutetx1',
'unit_of_measurement': None,
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_mute_audio_tx1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'HDFury VRROOM-02 Mute audio TX1',
}),
'context': <ANY>,
'entity_id': 'switch.hdfury_vrroom_02_mute_audio_tx1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_oled_display-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.hdfury_vrroom_02_oled_display',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'OLED display',
'platform': 'hdfury',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'oled',
'unique_id': '000123456789_oled',
'unit_of_measurement': None,
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_oled_display-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'HDFury VRROOM-02 OLED display',
}),
'context': <ANY>,
'entity_id': 'switch.hdfury_vrroom_02_oled_display',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_relay-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'switch',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'switch.hdfury_vrroom_02_relay',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Relay',
'platform': 'hdfury',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'relay',
'unique_id': '000123456789_relay',
'unit_of_measurement': None,
})
# ---
# name: test_switch_entities[switch.hdfury_vrroom_02_relay-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'HDFury VRROOM-02 Relay',
}),
'context': <ANY>,
'entity_id': 'switch.hdfury_vrroom_02_relay',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
+99
View File
@@ -0,0 +1,99 @@
"""Tests for the HDFury switch platform."""
from unittest.mock import AsyncMock
from hdfury import HDFuryError
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry, snapshot_platform
async def test_switch_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test HDFury switch entities."""
await setup_integration(hass, mock_config_entry, [Platform.SWITCH])
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
("entity_id", "method", "service"),
[
(
"switch.hdfury_vrroom_02_auto_switch_inputs",
"set_auto_switch_inputs",
"turn_on",
),
(
"switch.hdfury_vrroom_02_auto_switch_inputs",
"set_auto_switch_inputs",
"turn_off",
),
("switch.hdfury_vrroom_02_oled_display", "set_oled", "turn_on"),
("switch.hdfury_vrroom_02_oled_display", "set_oled", "turn_off"),
],
)
async def test_switch_turn_on_off(
hass: HomeAssistant,
mock_hdfury_client: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_id: str,
method: str,
service: str,
) -> None:
"""Test turning device switches on and off."""
await setup_integration(hass, mock_config_entry, [Platform.SWITCH])
await hass.services.async_call(
"switch",
service,
{"entity_id": entity_id},
blocking=True,
)
getattr(mock_hdfury_client, method).assert_awaited_once()
@pytest.mark.parametrize(
("service", "method"),
[
("turn_on", "set_auto_switch_inputs"),
("turn_off", "set_auto_switch_inputs"),
],
)
async def test_switch_turn_error(
hass: HomeAssistant,
mock_hdfury_client: AsyncMock,
mock_config_entry: MockConfigEntry,
service: str,
method: str,
) -> None:
"""Test switch turn on/off raises HomeAssistantError on API failure."""
getattr(mock_hdfury_client, method).side_effect = HDFuryError()
await setup_integration(hass, mock_config_entry, [Platform.SWITCH])
with pytest.raises(
HomeAssistantError,
match="An error occurred while communicating with HDFury device",
):
await hass.services.async_call(
"switch",
service,
{"entity_id": "switch.hdfury_vrroom_02_auto_switch_inputs"},
blocking=True,
)