1
0
mirror of https://github.com/home-assistant/core.git synced 2026-02-15 07:36:16 +00:00

Add select for compit integration (#152778)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Przemko92
2026-01-28 18:35:15 +01:00
committed by GitHub
parent 8a00aa8550
commit 66af6565bf
9 changed files with 1037 additions and 5 deletions

View File

@@ -11,6 +11,7 @@ from .coordinator import CompitConfigEntry, CompitDataUpdateCoordinator
PLATFORMS = [
Platform.CLIMATE,
Platform.SELECT,
]

View File

@@ -0,0 +1,105 @@
{
"entity": {
"select": {
"aero_by_pass": {
"default": "mdi:valve",
"state": {
"off": "mdi:valve-closed",
"on": "mdi:valve-open"
}
},
"buffer_mode": {
"default": "mdi:database",
"state": {
"disabled": "mdi:water-boiler-off",
"schedule": "mdi:calendar-clock"
}
},
"dhw_circulation": {
"default": "mdi:pump",
"state": {
"disabled": "mdi:pump-off",
"schedule": "mdi:calendar-clock"
}
},
"heating_source_of_correction": {
"default": "mdi:tune-variant",
"state": {
"disabled": "mdi:cancel",
"nano_nr_1": "mdi:thermostat-box",
"nano_nr_2": "mdi:thermostat-box",
"nano_nr_3": "mdi:thermostat-box",
"nano_nr_4": "mdi:thermostat-box",
"nano_nr_5": "mdi:thermostat-box",
"no_corrections": "mdi:cancel",
"schedule": "mdi:calendar-clock",
"thermostat": "mdi:thermostat"
}
},
"language": {
"default": "mdi:translate"
},
"mixer_mode": {
"default": "mdi:valve",
"state": {
"disabled": "mdi:cancel",
"nano_nr_1": "mdi:thermostat-box",
"nano_nr_2": "mdi:thermostat-box",
"nano_nr_3": "mdi:thermostat-box",
"nano_nr_4": "mdi:thermostat-box",
"nano_nr_5": "mdi:thermostat-box",
"schedule": "mdi:calendar-clock",
"thermostat": "mdi:thermostat"
}
},
"mixer_mode_zone": {
"default": "mdi:valve",
"state": {
"disabled": "mdi:cancel",
"nano_nr_1": "mdi:thermostat-box",
"nano_nr_2": "mdi:thermostat-box",
"nano_nr_3": "mdi:thermostat-box",
"nano_nr_4": "mdi:thermostat-box",
"nano_nr_5": "mdi:thermostat-box",
"schedule": "mdi:calendar-clock",
"thermostat": "mdi:thermostat"
}
},
"nano_work_mode": {
"default": "mdi:cog-outline",
"state": {
"christmas": "mdi:pine-tree",
"manual_0": "mdi:home-floor-0",
"manual_1": "mdi:home-floor-1",
"manual_2": "mdi:home-floor-2",
"manual_3": "mdi:home-floor-3",
"out_of_home": "mdi:home-export-outline",
"schedule": "mdi:calendar-clock"
}
},
"operating_mode": {
"default": "mdi:cog",
"state": {
"disabled": "mdi:cog-off",
"eco": "mdi:leaf"
}
},
"solarcomp_operating_mode": {
"default": "mdi:heating-coil",
"state": {
"de_icing": "mdi:snowflake-melt",
"disabled": "mdi:cancel",
"holiday": "mdi:beach"
}
},
"work_mode": {
"default": "mdi:cog-outline",
"state": {
"cooling": "mdi:snowflake-thermometer",
"summer": "mdi:weather-sunny",
"winter": "mdi:snowflake"
}
}
}
}
}

View File

@@ -73,10 +73,7 @@ rules:
This integration does not have any entities that should disabled by default.
entity-translations: done
exception-translations: todo
icon-translations:
status: exempt
comment: |
There is no need for icon translations.
icon-translations: done
reconfiguration-flow: todo
repair-issues: todo
stale-devices: todo

View File

@@ -0,0 +1,432 @@
"""Select platform for Compit integration."""
from dataclasses import dataclass
from compit_inext_api.consts import CompitParameter
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, MANUFACTURER_NAME
from .coordinator import CompitConfigEntry, CompitDataUpdateCoordinator
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)
class CompitDeviceDescription:
"""Class to describe a Compit device."""
name: str
"""Name of the device."""
parameters: dict[CompitParameter, SelectEntityDescription]
"""Parameters of the device."""
DESCRIPTIONS: dict[CompitParameter, SelectEntityDescription] = {
CompitParameter.LANGUAGE: SelectEntityDescription(
key=CompitParameter.LANGUAGE.value,
translation_key="language",
options=[
"polish",
"english",
],
),
CompitParameter.AEROKONFBYPASS: SelectEntityDescription(
key=CompitParameter.AEROKONFBYPASS.value,
translation_key="aero_by_pass",
options=[
"off",
"auto",
"on",
],
),
CompitParameter.NANO_MODE: SelectEntityDescription(
key=CompitParameter.NANO_MODE.value,
translation_key="nano_work_mode",
options=[
"manual_3",
"manual_2",
"manual_1",
"manual_0",
"schedule",
"christmas",
"out_of_home",
],
),
CompitParameter.R900_OPERATING_MODE: SelectEntityDescription(
key=CompitParameter.R900_OPERATING_MODE.value,
translation_key="operating_mode",
options=[
"disabled",
"eco",
"hybrid",
],
),
CompitParameter.SOLAR_COMP_OPERATING_MODE: SelectEntityDescription(
key=CompitParameter.SOLAR_COMP_OPERATING_MODE.value,
translation_key="solarcomp_operating_mode",
options=[
"auto",
"de_icing",
"holiday",
"disabled",
],
),
CompitParameter.R490_OPERATING_MODE: SelectEntityDescription(
key=CompitParameter.R490_OPERATING_MODE.value,
translation_key="operating_mode",
options=[
"disabled",
"eco",
"hybrid",
],
),
CompitParameter.WORK_MODE: SelectEntityDescription(
key=CompitParameter.WORK_MODE.value,
translation_key="work_mode",
options=[
"winter",
"summer",
"cooling",
],
),
CompitParameter.R470_OPERATING_MODE: SelectEntityDescription(
key=CompitParameter.R470_OPERATING_MODE.value,
translation_key="operating_mode",
options=[
"disabled",
"auto",
"eco",
],
),
CompitParameter.HEATING_SOURCE_OF_CORRECTION: SelectEntityDescription(
key=CompitParameter.HEATING_SOURCE_OF_CORRECTION.value,
translation_key="heating_source_of_correction",
options=[
"no_corrections",
"schedule",
"thermostat",
"nano_nr_1",
"nano_nr_2",
"nano_nr_3",
"nano_nr_4",
"nano_nr_5",
],
),
CompitParameter.BIOMAX_MIXER_MODE_ZONE_1: SelectEntityDescription(
key=CompitParameter.BIOMAX_MIXER_MODE_ZONE_1.value,
translation_key="mixer_mode_zone",
options=[
"disabled",
"without_thermostat",
"schedule",
"thermostat",
"nano_nr_1",
"nano_nr_2",
"nano_nr_3",
"nano_nr_4",
"nano_nr_5",
],
translation_placeholders={"zone": "1"},
),
CompitParameter.BIOMAX_MIXER_MODE_ZONE_2: SelectEntityDescription(
key=CompitParameter.BIOMAX_MIXER_MODE_ZONE_2.value,
translation_key="mixer_mode_zone",
options=[
"disabled",
"without_thermostat",
"schedule",
"thermostat",
"nano_nr_1",
"nano_nr_2",
"nano_nr_3",
"nano_nr_4",
"nano_nr_5",
],
translation_placeholders={"zone": "2"},
),
CompitParameter.DHW_CIRCULATION_MODE: SelectEntityDescription(
key=CompitParameter.DHW_CIRCULATION_MODE.value,
translation_key="dhw_circulation",
options=[
"disabled",
"constant",
"schedule",
],
),
CompitParameter.BIOMAX_HEATING_SOURCE_OF_CORRECTION: SelectEntityDescription(
key=CompitParameter.BIOMAX_HEATING_SOURCE_OF_CORRECTION.value,
translation_key="heating_source_of_correction",
options=[
"disabled",
"no_corrections",
"schedule",
"thermostat",
"nano_nr_1",
"nano_nr_2",
"nano_nr_3",
"nano_nr_4",
"nano_nr_5",
],
),
CompitParameter.MIXER_MODE: SelectEntityDescription(
key=CompitParameter.MIXER_MODE.value,
translation_key="mixer_mode",
options=[
"no_corrections",
"schedule",
"thermostat",
"nano_nr_1",
"nano_nr_2",
"nano_nr_3",
"nano_nr_4",
"nano_nr_5",
],
),
CompitParameter.R480_OPERATING_MODE: SelectEntityDescription(
key=CompitParameter.R480_OPERATING_MODE.value,
translation_key="operating_mode",
options=[
"disabled",
"eco",
"hybrid",
],
),
CompitParameter.BUFFER_MODE: SelectEntityDescription(
key=CompitParameter.BUFFER_MODE.value,
translation_key="buffer_mode",
options=[
"schedule",
"manual",
"disabled",
],
),
}
DEVICE_DEFINITIONS: dict[int, CompitDeviceDescription] = {
223: CompitDeviceDescription(
name="Nano Color 2",
parameters={
CompitParameter.LANGUAGE: DESCRIPTIONS[CompitParameter.LANGUAGE],
CompitParameter.AEROKONFBYPASS: DESCRIPTIONS[
CompitParameter.AEROKONFBYPASS
],
},
),
12: CompitDeviceDescription(
name="Nano Color",
parameters={
CompitParameter.LANGUAGE: DESCRIPTIONS[CompitParameter.LANGUAGE],
CompitParameter.AEROKONFBYPASS: DESCRIPTIONS[
CompitParameter.AEROKONFBYPASS
],
},
),
7: CompitDeviceDescription(
name="Nano One",
parameters={
CompitParameter.LANGUAGE: DESCRIPTIONS[CompitParameter.LANGUAGE],
CompitParameter.NANO_MODE: DESCRIPTIONS[CompitParameter.NANO_MODE],
},
),
224: CompitDeviceDescription(
name="R 900",
parameters={
CompitParameter.R900_OPERATING_MODE: DESCRIPTIONS[
CompitParameter.R900_OPERATING_MODE
],
},
),
45: CompitDeviceDescription(
name="SolarComp971",
parameters={
CompitParameter.SOLAR_COMP_OPERATING_MODE: DESCRIPTIONS[
CompitParameter.SOLAR_COMP_OPERATING_MODE
],
},
),
99: CompitDeviceDescription(
name="SolarComp971C",
parameters={
CompitParameter.SOLAR_COMP_OPERATING_MODE: DESCRIPTIONS[
CompitParameter.SOLAR_COMP_OPERATING_MODE
],
},
),
44: CompitDeviceDescription(
name="SolarComp 951",
parameters={
CompitParameter.SOLAR_COMP_OPERATING_MODE: DESCRIPTIONS[
CompitParameter.SOLAR_COMP_OPERATING_MODE
],
},
),
92: CompitDeviceDescription(
name="r490",
parameters={
CompitParameter.R490_OPERATING_MODE: DESCRIPTIONS[
CompitParameter.R490_OPERATING_MODE
],
CompitParameter.WORK_MODE: DESCRIPTIONS[CompitParameter.WORK_MODE],
},
),
34: CompitDeviceDescription(
name="r470",
parameters={
CompitParameter.R470_OPERATING_MODE: DESCRIPTIONS[
CompitParameter.R470_OPERATING_MODE
],
CompitParameter.HEATING_SOURCE_OF_CORRECTION: DESCRIPTIONS[
CompitParameter.HEATING_SOURCE_OF_CORRECTION
],
},
),
201: CompitDeviceDescription(
name="BioMax775",
parameters={
CompitParameter.BIOMAX_MIXER_MODE_ZONE_1: DESCRIPTIONS[
CompitParameter.BIOMAX_MIXER_MODE_ZONE_1
],
CompitParameter.BIOMAX_MIXER_MODE_ZONE_2: DESCRIPTIONS[
CompitParameter.BIOMAX_MIXER_MODE_ZONE_2
],
CompitParameter.DHW_CIRCULATION_MODE: DESCRIPTIONS[
CompitParameter.DHW_CIRCULATION_MODE
],
},
),
36: CompitDeviceDescription(
name="BioMax742",
parameters={
CompitParameter.BIOMAX_HEATING_SOURCE_OF_CORRECTION: DESCRIPTIONS[
CompitParameter.BIOMAX_HEATING_SOURCE_OF_CORRECTION
],
CompitParameter.BIOMAX_MIXER_MODE_ZONE_1: DESCRIPTIONS[
CompitParameter.BIOMAX_MIXER_MODE_ZONE_1
],
CompitParameter.DHW_CIRCULATION_MODE: DESCRIPTIONS[
CompitParameter.DHW_CIRCULATION_MODE
],
},
),
75: CompitDeviceDescription(
name="BioMax772",
parameters={
CompitParameter.BIOMAX_MIXER_MODE_ZONE_1: DESCRIPTIONS[
CompitParameter.BIOMAX_MIXER_MODE_ZONE_1
],
CompitParameter.BIOMAX_MIXER_MODE_ZONE_2: DESCRIPTIONS[
CompitParameter.BIOMAX_MIXER_MODE_ZONE_2
],
CompitParameter.DHW_CIRCULATION_MODE: DESCRIPTIONS[
CompitParameter.DHW_CIRCULATION_MODE
],
},
),
5: CompitDeviceDescription(
name="R350 T3",
parameters={
CompitParameter.MIXER_MODE: DESCRIPTIONS[CompitParameter.MIXER_MODE],
},
),
215: CompitDeviceDescription(
name="R480",
parameters={
CompitParameter.R480_OPERATING_MODE: DESCRIPTIONS[
CompitParameter.R480_OPERATING_MODE
],
CompitParameter.BUFFER_MODE: DESCRIPTIONS[CompitParameter.BUFFER_MODE],
},
),
}
async def async_setup_entry(
hass: HomeAssistant,
entry: CompitConfigEntry,
async_add_devices: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Compit select entities from a config entry."""
coordinator = entry.runtime_data
select_entities = []
for device_id, device in coordinator.connector.all_devices.items():
device_definition = DEVICE_DEFINITIONS.get(device.definition.code)
if not device_definition:
continue
for code, entity_description in device_definition.parameters.items():
param = next(
(p for p in device.state.params if p.code == entity_description.key),
None,
)
if param is None:
continue
select_entities.append(
CompitSelect(
coordinator,
device_id,
device_definition.name,
code,
entity_description,
)
)
async_add_devices(select_entities)
class CompitSelect(CoordinatorEntity[CompitDataUpdateCoordinator], SelectEntity):
"""Representation of a Compit select entity."""
def __init__(
self,
coordinator: CompitDataUpdateCoordinator,
device_id: int,
device_name: str,
parameter_code: CompitParameter,
entity_description: SelectEntityDescription,
) -> None:
"""Initialize the select entity."""
super().__init__(coordinator)
self.device_id = device_id
self.entity_description = entity_description
self._attr_has_entity_name = True
self._attr_unique_id = f"{device_id}_{entity_description.key}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, str(device_id))},
name=device_name,
manufacturer=MANUFACTURER_NAME,
model=device_name,
)
self.parameter_code = parameter_code
@property
def available(self) -> bool:
"""Return if entity is available."""
return (
super().available
and self.coordinator.connector.get_device(self.device_id) is not None
)
@property
def current_option(self) -> str | None:
"""Return the current option."""
return self.coordinator.connector.get_current_option(
self.device_id, self.parameter_code
)
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
await self.coordinator.connector.select_device_option(
self.device_id, self.parameter_code, option
)
self.async_write_ha_state()

View File

@@ -31,5 +31,120 @@
"title": "Connect to Compit iNext"
}
}
},
"entity": {
"select": {
"aero_by_pass": {
"name": "Bypass",
"state": {
"auto": "[%key:common::state::auto%]",
"off": "[%key:common::state::off%]",
"on": "[%key:common::state::on%]"
}
},
"buffer_mode": {
"name": "Buffer mode",
"state": {
"disabled": "[%key:common::state::disabled%]",
"manual": "[%key:common::state::manual%]",
"schedule": "Schedule"
}
},
"dhw_circulation": {
"name": "Domestic hot water circulation",
"state": {
"constant": "Constant",
"disabled": "[%key:common::state::disabled%]",
"schedule": "Schedule"
}
},
"heating_source_of_correction": {
"name": "Heating source of correction",
"state": {
"disabled": "[%key:common::state::disabled%]",
"nano_nr_1": "Nano 1",
"nano_nr_2": "Nano 2",
"nano_nr_3": "Nano 3",
"nano_nr_4": "Nano 4",
"nano_nr_5": "Nano 5",
"no_corrections": "No corrections",
"schedule": "Schedule",
"thermostat": "Thermostat"
}
},
"language": {
"name": "Language",
"state": {
"english": "English",
"polish": "Polish"
}
},
"mixer_mode": {
"name": "Mixer mode",
"state": {
"nano_nr_1": "Nano 1",
"nano_nr_2": "Nano 2",
"nano_nr_3": "Nano 3",
"nano_nr_4": "Nano 4",
"nano_nr_5": "Nano 5",
"no_corrections": "No corrections",
"schedule": "Schedule",
"thermostat": "Thermostat"
}
},
"mixer_mode_zone": {
"name": "Zone {zone} mixer mode",
"state": {
"disabled": "[%key:common::state::disabled%]",
"nano_nr_1": "Nano 1",
"nano_nr_2": "Nano 2",
"nano_nr_3": "Nano 3",
"nano_nr_4": "Nano 4",
"nano_nr_5": "Nano 5",
"no_corrections": "No corrections",
"schedule": "Schedule",
"thermostat": "Thermostat",
"without_thermostat": "Without thermostat"
}
},
"nano_work_mode": {
"name": "Nano work mode",
"state": {
"christmas": "Christmas",
"manual_0": "Manual 0",
"manual_1": "Manual 1",
"manual_2": "Manual 2",
"manual_3": "Manual 3",
"out_of_home": "Out of home",
"schedule": "Schedule"
}
},
"operating_mode": {
"name": "Operating mode",
"state": {
"auto": "[%key:common::state::auto%]",
"disabled": "[%key:common::state::disabled%]",
"eco": "Eco",
"hybrid": "Hybrid"
}
},
"solarcomp_operating_mode": {
"name": "Operating mode",
"state": {
"auto": "[%key:common::state::auto%]",
"de_icing": "De-icing",
"disabled": "[%key:common::state::disabled%]",
"holiday": "Holiday"
}
},
"work_mode": {
"name": "Current season",
"state": {
"cooling": "Cooling",
"summer": "Summer",
"winter": "Winter"
}
}
}
}
}

View File

@@ -1 +1,38 @@
"""Tests for the compit component."""
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_registry import EntityRegistry
from tests.common import MockConfigEntry
async def setup_integration(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Set up the Compit integration for testing."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
def snapshot_compit_entities(
hass: HomeAssistant,
entity_registry: EntityRegistry,
snapshot: SnapshotAssertion,
platform: Platform,
) -> None:
"""Snapshot Compit entities."""
entities = sorted(
hass.states.async_all(platform),
key=lambda state: state.entity_id,
)
for entity_state in entities:
entity_entry = entity_registry.async_get(entity_state.entity_id)
assert entity_entry and entity_entry == snapshot(
name=f"{entity_entry.entity_id}-entry"
)
assert entity_state == snapshot(name=f"{entity_entry.entity_id}-state")

View File

@@ -1,8 +1,9 @@
"""Common fixtures for the Compit tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
from unittest.mock import AsyncMock, MagicMock, patch
from compit_inext_api import CompitParameter
import pytest
from homeassistant.components.compit.const import DOMAIN
@@ -39,3 +40,74 @@ def mock_compit_api() -> Generator[AsyncMock]:
"homeassistant.components.compit.config_flow.CompitApiConnector.init",
) as mock_api:
yield mock_api
@pytest.fixture
def mock_connector():
"""Mock CompitApiConnector devices."""
mock_device_1 = MagicMock()
mock_device_1.definition.name = "Test Device 1"
mock_device_1.state.params = [
MagicMock(code="__tr_pracy_pc", value="eco"),
MagicMock(
code="__trybpracy", value="de_icing"
), # parameter not relevant for this device, should be ignored
]
mock_device_1.definition.code = 224 # R 900
mock_device_2 = MagicMock()
mock_device_2.state.params = [
MagicMock(code="_jezyk", value="english"),
MagicMock(code="__aerokonfbypass", value="off"),
]
mock_device_2.definition.code = 223 # Nano Color 2
mock_device_3 = MagicMock()
mock_device_3.definition.code = 999 # Unknown Device
mock_device_3.state = None
all_devices = {1: mock_device_1, 2: mock_device_2, 3: mock_device_3}
def mock_get_device(device_id: int):
return all_devices.get(device_id)
def get_current_option(device_id: int, parameter_code: CompitParameter):
return next(
(
p
for p in all_devices[device_id].state.params
if p.code == parameter_code.value
),
None,
).value
def select_device_option(
device_id: int, parameter_code: CompitParameter, value: str
):
next(
p
for p in all_devices[device_id].state.params
if p.code == parameter_code.value
).value = value
return True
mock_instance = MagicMock()
mock_instance.init = AsyncMock(return_value=True)
mock_instance.all_devices = all_devices
mock_instance.get_current_option = MagicMock(side_effect=get_current_option)
mock_instance.select_device_option = AsyncMock(side_effect=select_device_option)
mock_instance.update_state = AsyncMock()
mock_instance.get_device = MagicMock(side_effect=mock_get_device)
with (
patch(
"homeassistant.components.compit.CompitApiConnector",
return_value=mock_instance,
),
patch(
"homeassistant.components.compit.coordinator.CompitApiConnector",
return_value=mock_instance,
),
):
yield mock_instance

View File

@@ -0,0 +1,179 @@
# serializer version: 1
# name: test_select_entities_snapshot[select.nano_color_2_bypass-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'off',
'auto',
'on',
]),
}),
'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.nano_color_2_bypass',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Bypass',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Bypass',
'platform': 'compit',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'aero_by_pass',
'unique_id': '2___aerokonfbypass',
'unit_of_measurement': None,
})
# ---
# name: test_select_entities_snapshot[select.nano_color_2_bypass-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Nano Color 2 Bypass',
'options': list([
'off',
'auto',
'on',
]),
}),
'context': <ANY>,
'entity_id': 'select.nano_color_2_bypass',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_select_entities_snapshot[select.nano_color_2_language-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'polish',
'english',
]),
}),
'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.nano_color_2_language',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Language',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Language',
'platform': 'compit',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'language',
'unique_id': '2__jezyk',
'unit_of_measurement': None,
})
# ---
# name: test_select_entities_snapshot[select.nano_color_2_language-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Nano Color 2 Language',
'options': list([
'polish',
'english',
]),
}),
'context': <ANY>,
'entity_id': 'select.nano_color_2_language',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'english',
})
# ---
# name: test_select_entities_snapshot[select.r_900_operating_mode-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'disabled',
'eco',
'hybrid',
]),
}),
'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.r_900_operating_mode',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': 'Operating mode',
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Operating mode',
'platform': 'compit',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'operating_mode',
'unique_id': '1___tr_pracy_pc',
'unit_of_measurement': None,
})
# ---
# name: test_select_entities_snapshot[select.r_900_operating_mode-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'R 900 Operating mode',
'options': list([
'disabled',
'eco',
'hybrid',
]),
}),
'context': <ANY>,
'entity_id': 'select.r_900_operating_mode',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'eco',
})
# ---

View File

@@ -0,0 +1,94 @@
"""Tests for the Compit select platform."""
from typing import Any
from unittest.mock import MagicMock
from compit_inext_api.consts import CompitParameter
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er
from . import setup_integration, snapshot_compit_entities
from tests.common import MockConfigEntry
async def test_select_entities_snapshot(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
mock_config_entry: MockConfigEntry,
mock_connector: MagicMock,
snapshot: SnapshotAssertion,
) -> None:
"""Snapshot test for select entities creation, unique IDs, and device info."""
await setup_integration(hass, mock_config_entry)
snapshot_compit_entities(hass, entity_registry, snapshot, Platform.SELECT)
@pytest.mark.parametrize(
"mock_return_value",
[
None,
1,
"invalid",
],
)
async def test_select_unknown_device_parameters(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_connector: MagicMock,
mock_return_value: Any,
) -> None:
"""Test that select entity shows unknown when get_current_option returns various invalid values."""
mock_connector.get_current_option.side_effect = (
lambda device_id, parameter_code: mock_return_value
)
await setup_integration(hass, mock_config_entry)
state = hass.states.get("select.nano_color_2_language")
assert state is not None
assert state.state == "unknown"
async def test_select_option(
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_connector: MagicMock
) -> None:
"""Test selecting an option."""
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
"select",
"select_option",
{"entity_id": "select.nano_color_2_language", "option": "polish"},
blocking=True,
)
mock_connector.select_device_option.assert_called_once()
assert mock_connector.get_current_option(2, CompitParameter.LANGUAGE) == "polish"
async def test_select_invalid_option(
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_connector: MagicMock
) -> None:
"""Test selecting an invalid option."""
await setup_integration(hass, mock_config_entry)
with pytest.raises(
ServiceValidationError,
match=r"Option invalid is not valid for entity select\.nano_color_2_language",
):
await hass.services.async_call(
"select",
"select_option",
{"entity_id": "select.nano_color_2_language", "option": "invalid"},
blocking=True,
)
mock_connector.select_device_option.assert_not_called()