Add HDFury integration (#159996)

This commit is contained in:
Glenn de Haan
2026-01-08 12:21:04 +01:00
committed by GitHub
parent a1edf0a77c
commit 341c441e61
20 changed files with 903 additions and 0 deletions

2
CODEOWNERS generated
View File

@@ -661,6 +661,8 @@ build.json @home-assistant/supervisor
/tests/components/harmony/ @ehendrix23 @bdraco @mkeesey @Aohzan
/homeassistant/components/hassio/ @home-assistant/supervisor
/tests/components/hassio/ @home-assistant/supervisor
/homeassistant/components/hdfury/ @glenndehaan
/tests/components/hdfury/ @glenndehaan
/homeassistant/components/hdmi_cec/ @inytar
/tests/components/hdmi_cec/ @inytar
/homeassistant/components/heatmiser/ @andylockran

View File

@@ -0,0 +1,29 @@
"""The HDFury Integration."""
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .coordinator import HDFuryConfigEntry, HDFuryCoordinator
PLATFORMS = [
Platform.SELECT,
]
async def async_setup_entry(hass: HomeAssistant, entry: HDFuryConfigEntry) -> bool:
"""Set up HDFury as config entry."""
coordinator = HDFuryCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: HDFuryConfigEntry) -> bool:
"""Unload a HDFury config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -0,0 +1,54 @@
"""Config flow for HDFury Integration."""
from typing import Any
from hdfury import HDFuryAPI, HDFuryError
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
class HDFuryConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle Config Flow for HDFury."""
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle Initial Setup."""
errors: dict[str, str] = {}
if user_input is not None:
host = user_input[CONF_HOST]
serial = await self._validate_connection(host)
if serial is not None:
await self.async_set_unique_id(serial)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=f"HDFury ({host})", data=user_input
)
errors["base"] = "cannot_connect"
return self.async_show_form(
step_id="user",
data_schema=vol.Schema({vol.Required(CONF_HOST): str}),
errors=errors,
)
async def _validate_connection(self, host: str) -> str | None:
"""Try to fetch serial number to confirm it's a valid HDFury device."""
client = HDFuryAPI(host, async_get_clientsession(self.hass))
try:
data = await client.get_board()
except HDFuryError:
return None
return data["serial"]

View File

@@ -0,0 +1,3 @@
"""Constants for HDFury Integration."""
DOMAIN = "hdfury"

View File

@@ -0,0 +1,67 @@
"""DataUpdateCoordinator for HDFury Integration."""
from dataclasses import dataclass
from datetime import timedelta
import logging
from typing import Final
from hdfury import HDFuryAPI, HDFuryError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL: Final = timedelta(seconds=60)
type HDFuryConfigEntry = ConfigEntry[HDFuryCoordinator]
@dataclass(kw_only=True, frozen=True)
class HDFuryData:
"""HDFury Data Class."""
board: dict[str, str]
info: dict[str, str]
config: dict[str, str]
class HDFuryCoordinator(DataUpdateCoordinator[HDFuryData]):
"""HDFury Device Coordinator Class."""
def __init__(self, hass: HomeAssistant, entry: HDFuryConfigEntry) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,
_LOGGER,
config_entry=entry,
name="HDFury",
update_interval=SCAN_INTERVAL,
)
self.host: str = entry.data[CONF_HOST]
self.client = HDFuryAPI(self.host, async_get_clientsession(hass))
async def _async_update_data(self) -> HDFuryData:
"""Fetch the latest device data."""
try:
board = await self.client.get_board()
info = await self.client.get_info()
config = await self.client.get_config()
except HDFuryError as error:
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="communication_error",
) from error
return HDFuryData(
board=board,
info=info,
config=config,
)

View File

@@ -0,0 +1,39 @@
"""Base class for HDFury entities."""
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .coordinator import HDFuryCoordinator
class HDFuryEntity(CoordinatorEntity[HDFuryCoordinator]):
"""Common elements for all entities."""
_attr_has_entity_name = True
def __init__(
self, coordinator: HDFuryCoordinator, entity_description: EntityDescription
) -> None:
"""Initialize the entity."""
super().__init__(coordinator)
self.entity_description = entity_description
self._attr_unique_id = (
f"{coordinator.data.board['serial']}_{entity_description.key}"
)
self._attr_device_info = DeviceInfo(
name=f"HDFury {coordinator.data.board['hostname']}",
manufacturer="HDFury",
model=coordinator.data.board["hostname"].split("-")[0],
serial_number=coordinator.data.board["serial"],
sw_version=coordinator.data.board["version"].removeprefix("FW: "),
hw_version=coordinator.data.board.get("pcbv"),
configuration_url=f"http://{coordinator.host}",
connections={
(dr.CONNECTION_NETWORK_MAC, coordinator.data.config["macaddr"])
},
)

View File

@@ -0,0 +1,15 @@
{
"entity": {
"select": {
"opmode": {
"default": "mdi:cogs"
},
"portseltx0": {
"default": "mdi:hdmi-port"
},
"portseltx1": {
"default": "mdi:hdmi-port"
}
}
}
}

View File

@@ -0,0 +1,11 @@
{
"domain": "hdfury",
"name": "HDFury",
"codeowners": ["@glenndehaan"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/hdfury",
"integration_type": "device",
"iot_class": "local_polling",
"quality_scale": "bronze",
"requirements": ["hdfury==1.3.1"]
}

View File

@@ -0,0 +1,74 @@
rules:
# Bronze
action-setup:
status: exempt
comment: Integration does not register custom actions.
appropriate-polling: done
brands: done
common-modules: done
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
docs-actions:
status: exempt
comment: Integration does not register custom actions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
entity-event-setup:
status: exempt
comment: Entities do not explicitly subscribe to events.
entity-unique-id: done
has-entity-name: done
runtime-data: done
test-before-configure: done
test-before-setup: done
unique-config-entry: done
# Silver
action-exceptions: done
config-entry-unloading: done
docs-configuration-parameters:
status: exempt
comment: Integration has no options flow.
docs-installation-parameters: done
entity-unavailable: done
integration-owner: done
log-when-unavailable: done
parallel-updates: todo
reauthentication-flow:
status: exempt
comment: Integration has no authentication flow.
test-coverage: todo
# Gold
devices: done
diagnostics: todo
discovery-update-info: todo
discovery: todo
docs-data-update: todo
docs-examples: todo
docs-known-limitations: todo
docs-supported-devices: done
docs-supported-functions: done
docs-troubleshooting: todo
docs-use-cases: done
dynamic-devices:
status: exempt
comment: Device type integration.
entity-category: done
entity-device-class: done
entity-disabled-by-default: todo
entity-translations: done
exception-translations: done
icon-translations: done
reconfiguration-flow: todo
repair-issues: todo
stale-devices:
status: exempt
comment: Device type integration.
# Platinum
async-dependency: done
inject-websession: done
strict-typing: todo

View File

@@ -0,0 +1,122 @@
"""Select platform for HDFury Integration."""
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from hdfury import (
OPERATION_MODES,
TX0_INPUT_PORTS,
TX1_INPUT_PORTS,
HDFuryAPI,
HDFuryError,
)
from homeassistant.components.select import SelectEntity, SelectEntityDescription
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, HDFuryCoordinator
from .entity import HDFuryEntity
@dataclass(kw_only=True, frozen=True)
class HDFurySelectEntityDescription(SelectEntityDescription):
"""Description for HDFury select entities."""
set_value_fn: Callable[[HDFuryAPI, str], Awaitable[None]]
SELECT_PORTS: tuple[HDFurySelectEntityDescription, ...] = (
HDFurySelectEntityDescription(
key="portseltx0",
translation_key="portseltx0",
options=list(TX0_INPUT_PORTS.keys()),
set_value_fn=lambda coordinator, value: _set_ports(coordinator),
),
HDFurySelectEntityDescription(
key="portseltx1",
translation_key="portseltx1",
options=list(TX1_INPUT_PORTS.keys()),
set_value_fn=lambda coordinator, value: _set_ports(coordinator),
),
)
SELECT_OPERATION_MODE: HDFurySelectEntityDescription = HDFurySelectEntityDescription(
key="opmode",
translation_key="opmode",
options=list(OPERATION_MODES.keys()),
set_value_fn=lambda coordinator, value: coordinator.client.set_operation_mode(
value
),
)
async def _set_ports(coordinator: HDFuryCoordinator) -> None:
tx0 = coordinator.data.info.get("portseltx0")
tx1 = coordinator.data.info.get("portseltx1")
if tx0 is None or tx1 is None:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="tx_state_error",
translation_placeholders={"details": f"tx0={tx0}, tx1={tx1}"},
)
await coordinator.client.set_port_selection(tx0, tx1)
async def async_setup_entry(
hass: HomeAssistant,
entry: HDFuryConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up selects using the platform schema."""
coordinator = entry.runtime_data
entities: list[HDFuryEntity] = []
for description in SELECT_PORTS:
if description.key not in coordinator.data.info:
continue
entities.append(HDFurySelect(coordinator, description))
# Add OPMODE select if present
if "opmode" in coordinator.data.info:
entities.append(HDFurySelect(coordinator, SELECT_OPERATION_MODE))
async_add_entities(entities)
class HDFurySelect(HDFuryEntity, SelectEntity):
"""HDFury Select Class."""
entity_description: HDFurySelectEntityDescription
@property
def current_option(self) -> str:
"""Return the current option."""
return self.coordinator.data.info[self.entity_description.key]
async def async_select_option(self, option: str) -> None:
"""Update the current option."""
# Update local data first
self.coordinator.data.info[self.entity_description.key] = option
# Send command to device
try:
await self.entity_description.set_value_fn(self.coordinator, option)
except HDFuryError as error:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="communication_error",
) from error
# Trigger HA coordinator refresh
await self.coordinator.async_request_refresh()

View File

@@ -0,0 +1,64 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"step": {
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]"
},
"data_description": {
"host": "Hostname or IP address of your HDFury device."
},
"description": "Set up your HDFury to integrate with Home Assistant."
}
}
},
"entity": {
"select": {
"opmode": {
"name": "Operation mode",
"state": {
"0": "Mode 0 - Splitter TX0/TX1 FRL5 VRR",
"1": "Mode 1 - Splitter TX0/TX1 UPSCALE FRL5",
"2": "Mode 2 - Matrix TMDS",
"3": "Mode 3 - Matrix FRL->TMDS",
"4": "Mode 4 - Matrix DOWNSCALE",
"5": "Mode 5 - Matrix RX0:FRL5 + RX1-3:TMDS"
}
},
"portseltx0": {
"name": "Port select TX0",
"state": {
"0": "Input 0",
"1": "Input 1",
"2": "Input 2",
"3": "Input 3",
"4": "Copy TX1"
}
},
"portseltx1": {
"name": "Port select TX1",
"state": {
"0": "Input 0",
"1": "Input 1",
"2": "Input 2",
"3": "Input 3",
"4": "Copy TX0"
}
}
}
},
"exceptions": {
"communication_error": {
"message": "An error occurred while communicating with HDFury device"
},
"tx_state_error": {
"message": "An error occurred while validating TX states: {details}"
}
}
}

View File

@@ -278,6 +278,7 @@ FLOWS = {
"habitica",
"hanna",
"harmony",
"hdfury",
"heos",
"here_travel_time",
"hikvision",

View File

@@ -2678,6 +2678,12 @@
"config_flow": false,
"iot_class": "local_polling"
},
"hdfury": {
"name": "HDFury",
"integration_type": "device",
"config_flow": true,
"iot_class": "local_polling"
},
"hdmi_cec": {
"name": "HDMI-CEC",
"integration_type": "hub",

3
requirements_all.txt generated
View File

@@ -1184,6 +1184,9 @@ hassil==3.5.0
# homeassistant.components.jewish_calendar
hdate[astral]==1.1.2
# homeassistant.components.hdfury
hdfury==1.3.1
# homeassistant.components.heatmiser
heatmiserV3==2.0.4

View File

@@ -1051,6 +1051,9 @@ hassil==3.5.0
# homeassistant.components.jewish_calendar
hdate[astral]==1.1.2
# homeassistant.components.hdfury
hdfury==1.3.1
# homeassistant.components.here_travel_time
here-routing==1.2.0

View File

@@ -0,0 +1,13 @@
"""Tests for the HDFury integration."""
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
"""Fixture for setting up the integration."""
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()

View File

@@ -0,0 +1,79 @@
"""Common fixtures for the HDFury tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
from homeassistant.components.hdfury.const import DOMAIN
from homeassistant.const import CONF_HOST
from tests.common import MockConfigEntry
TEST_HOST = "192.168.1.123"
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.hdfury.async_setup_entry",
return_value=True,
) as mock_setup_entry:
yield mock_setup_entry
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Return the default mocked config entry."""
return MockConfigEntry(
domain=DOMAIN,
unique_id="000123456789",
data={
CONF_HOST: TEST_HOST,
},
)
@pytest.fixture(autouse=True)
def mock_hdfury_client() -> Generator[AsyncMock]:
"""Mock a HDFury client."""
with (
patch(
"homeassistant.components.hdfury.config_flow.HDFuryAPI",
autospec=True,
) as mock_cf_client,
patch(
"homeassistant.components.hdfury.coordinator.HDFuryAPI",
autospec=True,
) as mock_coord_client,
):
# Config flow client
cf_client = mock_cf_client.return_value
cf_client.get_board = AsyncMock(
return_value={
"hostname": "VRROOM-02",
"ipaddress": "192.168.1.123",
"serial": "000123456789",
"pcbv": "3",
"version": "FW: 0.61",
}
)
# Coordinator client
coord_client = mock_coord_client.return_value
coord_client.get_board = cf_client.get_board
coord_client.get_info = AsyncMock(
return_value={
"portseltx0": "0",
"portseltx1": "4",
"opmode": "0",
}
)
coord_client.get_config = AsyncMock(
return_value={
"macaddr": "c7:1c:df:9d:f6:40",
}
)
yield coord_client

View File

@@ -0,0 +1,192 @@
# serializer version: 1
# name: test_select_entities[select.hdfury_vrroom_02_operation_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'0',
'1',
'2',
'3',
'4',
'5',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': None,
'entity_id': 'select.hdfury_vrroom_02_operation_mode',
'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': 'Operation mode',
'platform': 'hdfury',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'opmode',
'unique_id': '000123456789_opmode',
'unit_of_measurement': None,
})
# ---
# name: test_select_entities[select.hdfury_vrroom_02_operation_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'HDFury VRROOM-02 Operation mode',
'options': list([
'0',
'1',
'2',
'3',
'4',
'5',
]),
}),
'context': <ANY>,
'entity_id': 'select.hdfury_vrroom_02_operation_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0',
})
# ---
# name: test_select_entities[select.hdfury_vrroom_02_port_select_tx0-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'0',
'1',
'2',
'3',
'4',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': None,
'entity_id': 'select.hdfury_vrroom_02_port_select_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': 'Port select TX0',
'platform': 'hdfury',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'portseltx0',
'unique_id': '000123456789_portseltx0',
'unit_of_measurement': None,
})
# ---
# name: test_select_entities[select.hdfury_vrroom_02_port_select_tx0-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'HDFury VRROOM-02 Port select TX0',
'options': list([
'0',
'1',
'2',
'3',
'4',
]),
}),
'context': <ANY>,
'entity_id': 'select.hdfury_vrroom_02_port_select_tx0',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '0',
})
# ---
# name: test_select_entities[select.hdfury_vrroom_02_port_select_tx1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'0',
'1',
'2',
'3',
'4',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'select',
'entity_category': None,
'entity_id': 'select.hdfury_vrroom_02_port_select_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': 'Port select TX1',
'platform': 'hdfury',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'portseltx1',
'unique_id': '000123456789_portseltx1',
'unit_of_measurement': None,
})
# ---
# name: test_select_entities[select.hdfury_vrroom_02_port_select_tx1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'HDFury VRROOM-02 Port select TX1',
'options': list([
'0',
'1',
'2',
'3',
'4',
]),
}),
'context': <ANY>,
'entity_id': 'select.hdfury_vrroom_02_port_select_tx1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '4',
})
# ---

View File

@@ -0,0 +1,96 @@
"""Test the HDFury config flow."""
from unittest.mock import AsyncMock
from hdfury import HDFuryError
from homeassistant.components.hdfury.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry
async def test_async_step_user_gets_form_and_creates_entry(
hass: HomeAssistant,
mock_hdfury_client: AsyncMock,
mock_setup_entry: AsyncMock,
) -> None:
"""Test that the we can view the form and that the config flow creates an entry."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_HOST: "192.168.1.123"},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"] == {
CONF_HOST: "192.168.1.123",
}
assert result["result"].unique_id == "000123456789"
async def test_abort_if_already_configured(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_setup_entry: AsyncMock,
) -> None:
"""Test that we abort if we attempt to submit the same entry twice."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_HOST: "192.168.1.123"},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
async def test_successful_recovery_after_connection_error(
hass: HomeAssistant,
mock_hdfury_client: AsyncMock,
mock_setup_entry: AsyncMock,
) -> None:
"""Test error shown when connection fails."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
# Simulate a connection error by raising a HDFuryError
mock_hdfury_client.get_board.side_effect = HDFuryError()
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_HOST: "192.168.1.123"},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": "cannot_connect"}
# Simulate successful connection on retry
mock_hdfury_client.get_board.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_HOST: "192.168.1.123"},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["data"] == {
CONF_HOST: "192.168.1.123",
}
assert result["result"].unique_id == "000123456789"

View File

@@ -0,0 +1,30 @@
"""Tests for the HDFury select platform."""
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
import homeassistant.helpers.entity_registry as er
from . import setup_integration
from tests.common import MockConfigEntry, snapshot_platform
@pytest.fixture
def platforms() -> list[Platform]:
"""Fixture to specify platforms to test."""
return [Platform.SELECT]
async def test_select_entities(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test HDFury select entities."""
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)