mirror of
https://github.com/home-assistant/core.git
synced 2025-12-20 02:48:57 +00:00
Pooldose: Add select platform (#159240)
This commit is contained in:
@@ -20,6 +20,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
PLATFORMS: list[Platform] = [
|
PLATFORMS: list[Platform] = [
|
||||||
Platform.BINARY_SENSOR,
|
Platform.BINARY_SENSOR,
|
||||||
Platform.NUMBER,
|
Platform.NUMBER,
|
||||||
|
Platform.SELECT,
|
||||||
Platform.SENSOR,
|
Platform.SENSOR,
|
||||||
Platform.SWITCH,
|
Platform.SWITCH,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,15 +7,16 @@ from homeassistant.const import UnitOfTemperature, UnitOfVolume, UnitOfVolumeFlo
|
|||||||
DOMAIN = "pooldose"
|
DOMAIN = "pooldose"
|
||||||
MANUFACTURER = "SEKO"
|
MANUFACTURER = "SEKO"
|
||||||
|
|
||||||
# Mapping of device units (upper case) to Home Assistant units
|
# Unit mappings for select entities (water meter and flow rate)
|
||||||
|
# Keys match API values exactly: lowercase for m3/m3/h, uppercase L for L/L/s
|
||||||
UNIT_MAPPING: dict[str, str] = {
|
UNIT_MAPPING: dict[str, str] = {
|
||||||
# Temperature units
|
# Temperature units
|
||||||
"°C": UnitOfTemperature.CELSIUS,
|
"°C": UnitOfTemperature.CELSIUS,
|
||||||
"°F": UnitOfTemperature.FAHRENHEIT,
|
"°F": UnitOfTemperature.FAHRENHEIT,
|
||||||
# Volume flow rate units
|
# Volume flow rate units
|
||||||
"M3/H": UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
|
"m3/h": UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
|
||||||
"L/S": UnitOfVolumeFlowRate.LITERS_PER_SECOND,
|
"L/s": UnitOfVolumeFlowRate.LITERS_PER_SECOND,
|
||||||
# Volume units
|
# Volume units
|
||||||
"L": UnitOfVolume.LITERS,
|
"L": UnitOfVolume.LITERS,
|
||||||
"M3": UnitOfVolume.CUBIC_METERS,
|
"m3": UnitOfVolume.CUBIC_METERS,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,32 @@
|
|||||||
"default": "mdi:ph"
|
"default": "mdi:ph"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"select": {
|
||||||
|
"cl_type_dosing_method": {
|
||||||
|
"default": "mdi:beaker"
|
||||||
|
},
|
||||||
|
"cl_type_dosing_set": {
|
||||||
|
"default": "mdi:pool"
|
||||||
|
},
|
||||||
|
"flow_rate_unit": {
|
||||||
|
"default": "mdi:pipe-valve"
|
||||||
|
},
|
||||||
|
"orp_type_dosing_method": {
|
||||||
|
"default": "mdi:beaker"
|
||||||
|
},
|
||||||
|
"orp_type_dosing_set": {
|
||||||
|
"default": "mdi:water-check"
|
||||||
|
},
|
||||||
|
"ph_type_dosing_method": {
|
||||||
|
"default": "mdi:beaker"
|
||||||
|
},
|
||||||
|
"ph_type_dosing_set": {
|
||||||
|
"default": "mdi:ph"
|
||||||
|
},
|
||||||
|
"water_meter_unit": {
|
||||||
|
"default": "mdi:water"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"cl": {
|
"cl": {
|
||||||
"default": "mdi:pool"
|
"default": "mdi:pool"
|
||||||
|
|||||||
160
homeassistant/components/pooldose/select.py
Normal file
160
homeassistant/components/pooldose/select.py
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
"""Select entities for the Seko PoolDose integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import logging
|
||||||
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
|
|
||||||
|
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||||
|
from homeassistant.const import EntityCategory, UnitOfVolume, UnitOfVolumeFlowRate
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
|
from . import PooldoseConfigEntry
|
||||||
|
from .const import UNIT_MAPPING
|
||||||
|
from .entity import PooldoseEntity
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .coordinator import PooldoseCoordinator
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, kw_only=True)
|
||||||
|
class PooldoseSelectEntityDescription(SelectEntityDescription):
|
||||||
|
"""Describes PoolDose select entity."""
|
||||||
|
|
||||||
|
use_unit_conversion: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
SELECT_DESCRIPTIONS: tuple[PooldoseSelectEntityDescription, ...] = (
|
||||||
|
PooldoseSelectEntityDescription(
|
||||||
|
key="water_meter_unit",
|
||||||
|
translation_key="water_meter_unit",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
options=[UnitOfVolume.LITERS, UnitOfVolume.CUBIC_METERS],
|
||||||
|
use_unit_conversion=True,
|
||||||
|
),
|
||||||
|
PooldoseSelectEntityDescription(
|
||||||
|
key="flow_rate_unit",
|
||||||
|
translation_key="flow_rate_unit",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
options=[
|
||||||
|
UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
|
||||||
|
UnitOfVolumeFlowRate.LITERS_PER_SECOND,
|
||||||
|
],
|
||||||
|
use_unit_conversion=True,
|
||||||
|
),
|
||||||
|
PooldoseSelectEntityDescription(
|
||||||
|
key="ph_type_dosing_set",
|
||||||
|
translation_key="ph_type_dosing_set",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
options=["alcalyne", "acid"],
|
||||||
|
),
|
||||||
|
PooldoseSelectEntityDescription(
|
||||||
|
key="ph_type_dosing_method",
|
||||||
|
translation_key="ph_type_dosing_method",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
options=["off", "proportional", "on_off", "timed"],
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
PooldoseSelectEntityDescription(
|
||||||
|
key="orp_type_dosing_set",
|
||||||
|
translation_key="orp_type_dosing_set",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
options=["low", "high"],
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
PooldoseSelectEntityDescription(
|
||||||
|
key="orp_type_dosing_method",
|
||||||
|
translation_key="orp_type_dosing_method",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
options=["off", "proportional", "on_off", "timed"],
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
PooldoseSelectEntityDescription(
|
||||||
|
key="cl_type_dosing_set",
|
||||||
|
translation_key="cl_type_dosing_set",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
options=["low", "high"],
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
PooldoseSelectEntityDescription(
|
||||||
|
key="cl_type_dosing_method",
|
||||||
|
translation_key="cl_type_dosing_method",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
options=["off", "proportional", "on_off", "timed"],
|
||||||
|
entity_registry_enabled_default=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: PooldoseConfigEntry,
|
||||||
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up PoolDose select entities from a config entry."""
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
assert config_entry.unique_id is not None
|
||||||
|
|
||||||
|
coordinator = config_entry.runtime_data
|
||||||
|
select_data = coordinator.data["select"]
|
||||||
|
serial_number = config_entry.unique_id
|
||||||
|
|
||||||
|
async_add_entities(
|
||||||
|
PooldoseSelect(coordinator, serial_number, coordinator.device_info, description)
|
||||||
|
for description in SELECT_DESCRIPTIONS
|
||||||
|
if description.key in select_data
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PooldoseSelect(PooldoseEntity, SelectEntity):
|
||||||
|
"""Select entity for the Seko PoolDose Python API."""
|
||||||
|
|
||||||
|
entity_description: PooldoseSelectEntityDescription
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: PooldoseCoordinator,
|
||||||
|
serial_number: str,
|
||||||
|
device_info: Any,
|
||||||
|
description: PooldoseSelectEntityDescription,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the select."""
|
||||||
|
super().__init__(coordinator, serial_number, device_info, description, "select")
|
||||||
|
self._async_update_attrs()
|
||||||
|
|
||||||
|
def _handle_coordinator_update(self) -> None:
|
||||||
|
"""Handle updated data from the coordinator."""
|
||||||
|
self._async_update_attrs()
|
||||||
|
super()._handle_coordinator_update()
|
||||||
|
|
||||||
|
def _async_update_attrs(self) -> None:
|
||||||
|
"""Update select attributes."""
|
||||||
|
data = cast(dict, self.get_data())
|
||||||
|
api_value = cast(str, data["value"])
|
||||||
|
|
||||||
|
# Convert API value to Home Assistant unit if unit conversion is enabled
|
||||||
|
if self.entity_description.use_unit_conversion:
|
||||||
|
# Map API value (e.g., "m3") to HA unit (e.g., "m³")
|
||||||
|
self._attr_current_option = UNIT_MAPPING.get(api_value, api_value)
|
||||||
|
else:
|
||||||
|
self._attr_current_option = api_value
|
||||||
|
|
||||||
|
async def async_select_option(self, option: str) -> None:
|
||||||
|
"""Change the selected option."""
|
||||||
|
# Convert Home Assistant unit to API value if unit conversion is enabled
|
||||||
|
if self.entity_description.use_unit_conversion:
|
||||||
|
# Invert UNIT_MAPPING to get API value from HA unit
|
||||||
|
reverse_map = {v: k for k, v in UNIT_MAPPING.items()}
|
||||||
|
api_value = reverse_map.get(option, option)
|
||||||
|
else:
|
||||||
|
api_value = option
|
||||||
|
|
||||||
|
await self.coordinator.client.set_select(self.entity_description.key, api_value)
|
||||||
|
self._attr_current_option = option
|
||||||
|
self.async_write_ha_state()
|
||||||
@@ -32,14 +32,14 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
class PooldoseSensorEntityDescription(SensorEntityDescription):
|
class PooldoseSensorEntityDescription(SensorEntityDescription):
|
||||||
"""Describes PoolDose sensor entity."""
|
"""Describes PoolDose sensor entity."""
|
||||||
|
|
||||||
use_dynamic_unit: bool = False
|
use_unit_conversion: bool = False
|
||||||
|
|
||||||
|
|
||||||
SENSOR_DESCRIPTIONS: tuple[PooldoseSensorEntityDescription, ...] = (
|
SENSOR_DESCRIPTIONS: tuple[PooldoseSensorEntityDescription, ...] = (
|
||||||
PooldoseSensorEntityDescription(
|
PooldoseSensorEntityDescription(
|
||||||
key="temperature",
|
key="temperature",
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
use_dynamic_unit=True,
|
use_unit_conversion=True,
|
||||||
),
|
),
|
||||||
PooldoseSensorEntityDescription(key="ph", device_class=SensorDeviceClass.PH),
|
PooldoseSensorEntityDescription(key="ph", device_class=SensorDeviceClass.PH),
|
||||||
PooldoseSensorEntityDescription(
|
PooldoseSensorEntityDescription(
|
||||||
@@ -57,14 +57,14 @@ SENSOR_DESCRIPTIONS: tuple[PooldoseSensorEntityDescription, ...] = (
|
|||||||
key="flow_rate",
|
key="flow_rate",
|
||||||
translation_key="flow_rate",
|
translation_key="flow_rate",
|
||||||
device_class=SensorDeviceClass.VOLUME_FLOW_RATE,
|
device_class=SensorDeviceClass.VOLUME_FLOW_RATE,
|
||||||
use_dynamic_unit=True,
|
use_unit_conversion=True,
|
||||||
),
|
),
|
||||||
PooldoseSensorEntityDescription(
|
PooldoseSensorEntityDescription(
|
||||||
key="water_meter_total_permanent",
|
key="water_meter_total_permanent",
|
||||||
translation_key="water_meter_total_permanent",
|
translation_key="water_meter_total_permanent",
|
||||||
device_class=SensorDeviceClass.VOLUME,
|
device_class=SensorDeviceClass.VOLUME,
|
||||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
use_dynamic_unit=True,
|
use_unit_conversion=True,
|
||||||
),
|
),
|
||||||
PooldoseSensorEntityDescription(
|
PooldoseSensorEntityDescription(
|
||||||
key="ph_type_dosing",
|
key="ph_type_dosing",
|
||||||
@@ -227,12 +227,12 @@ class PooldoseSensor(PooldoseEntity, SensorEntity):
|
|||||||
def native_unit_of_measurement(self) -> str | None:
|
def native_unit_of_measurement(self) -> str | None:
|
||||||
"""Return the unit of measurement."""
|
"""Return the unit of measurement."""
|
||||||
if (
|
if (
|
||||||
self.entity_description.use_dynamic_unit
|
self.entity_description.use_unit_conversion
|
||||||
and (data := self.get_data()) is not None
|
and (data := self.get_data()) is not None
|
||||||
and (device_unit := data.get("unit"))
|
and (device_unit := data.get("unit"))
|
||||||
):
|
):
|
||||||
# Map device unit (upper case) to Home Assistant unit, return None if unknown
|
# Map device unit to Home Assistant unit, return None if unknown
|
||||||
return UNIT_MAPPING.get(device_unit.upper())
|
return UNIT_MAPPING.get(device_unit)
|
||||||
|
|
||||||
# Fall back to static unit from entity description
|
# Fall back to static unit from entity description
|
||||||
return super().native_unit_of_measurement
|
return super().native_unit_of_measurement
|
||||||
|
|||||||
@@ -97,6 +97,62 @@
|
|||||||
"name": "pH target"
|
"name": "pH target"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"select": {
|
||||||
|
"cl_type_dosing_method": {
|
||||||
|
"name": "Chlorine dosing method",
|
||||||
|
"state": {
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"on_off": "[%key:component::pooldose::entity::sensor::peristaltic_ph_dosing::state::on_off%]",
|
||||||
|
"proportional": "[%key:component::pooldose::entity::sensor::peristaltic_ph_dosing::state::proportional%]",
|
||||||
|
"timed": "[%key:component::pooldose::entity::sensor::peristaltic_ph_dosing::state::timed%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cl_type_dosing_set": {
|
||||||
|
"name": "Chlorine dosing set",
|
||||||
|
"state": {
|
||||||
|
"high": "[%key:common::state::high%]",
|
||||||
|
"low": "[%key:common::state::low%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flow_rate_unit": {
|
||||||
|
"name": "Flow rate unit"
|
||||||
|
},
|
||||||
|
"orp_type_dosing_method": {
|
||||||
|
"name": "ORP dosing method",
|
||||||
|
"state": {
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"on_off": "[%key:component::pooldose::entity::sensor::peristaltic_ph_dosing::state::on_off%]",
|
||||||
|
"proportional": "[%key:component::pooldose::entity::sensor::peristaltic_ph_dosing::state::proportional%]",
|
||||||
|
"timed": "[%key:component::pooldose::entity::sensor::peristaltic_ph_dosing::state::timed%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"orp_type_dosing_set": {
|
||||||
|
"name": "ORP dosing set",
|
||||||
|
"state": {
|
||||||
|
"high": "[%key:common::state::high%]",
|
||||||
|
"low": "[%key:common::state::low%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ph_type_dosing_method": {
|
||||||
|
"name": "pH dosing method",
|
||||||
|
"state": {
|
||||||
|
"off": "[%key:common::state::off%]",
|
||||||
|
"on_off": "[%key:component::pooldose::entity::sensor::peristaltic_ph_dosing::state::on_off%]",
|
||||||
|
"proportional": "[%key:component::pooldose::entity::sensor::peristaltic_ph_dosing::state::proportional%]",
|
||||||
|
"timed": "[%key:component::pooldose::entity::sensor::peristaltic_ph_dosing::state::timed%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ph_type_dosing_set": {
|
||||||
|
"name": "pH dosing set",
|
||||||
|
"state": {
|
||||||
|
"acid": "Acid (pH-)",
|
||||||
|
"alcalyne": "Alkaline (pH+)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"water_meter_unit": {
|
||||||
|
"name": "Water meter unit"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"cl": {
|
"cl": {
|
||||||
"name": "Chlorine"
|
"name": "Chlorine"
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ def mock_pooldose_client(device_info: dict[str, Any]) -> Generator[MagicMock]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
client.set_switch = AsyncMock(return_value=RequestStatus.SUCCESS)
|
client.set_switch = AsyncMock(return_value=RequestStatus.SUCCESS)
|
||||||
|
client.set_select = AsyncMock(return_value=RequestStatus.SUCCESS)
|
||||||
client.is_connected = True
|
client.is_connected = True
|
||||||
yield client
|
yield client
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
},
|
},
|
||||||
"flow_rate": {
|
"flow_rate": {
|
||||||
"value": 150,
|
"value": 150,
|
||||||
"unit": "l/s"
|
"unit": "L/s"
|
||||||
},
|
},
|
||||||
"ph_type_dosing": {
|
"ph_type_dosing": {
|
||||||
"value": "alcalyne",
|
"value": "alcalyne",
|
||||||
@@ -198,7 +198,28 @@
|
|||||||
},
|
},
|
||||||
"select": {
|
"select": {
|
||||||
"water_meter_unit": {
|
"water_meter_unit": {
|
||||||
"value": "m³"
|
"value": "m3"
|
||||||
|
},
|
||||||
|
"flow_rate_unit": {
|
||||||
|
"value": "L/s"
|
||||||
|
},
|
||||||
|
"ph_type_dosing_set": {
|
||||||
|
"value": "acid"
|
||||||
|
},
|
||||||
|
"ph_type_dosing_method": {
|
||||||
|
"value": "proportional"
|
||||||
|
},
|
||||||
|
"orp_type_dosing_set": {
|
||||||
|
"value": "low"
|
||||||
|
},
|
||||||
|
"orp_type_dosing_method": {
|
||||||
|
"value": "on_off"
|
||||||
|
},
|
||||||
|
"cl_type_dosing_set": {
|
||||||
|
"value": "high"
|
||||||
|
},
|
||||||
|
"cl_type_dosing_method": {
|
||||||
|
"value": "timed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
469
tests/components/pooldose/snapshots/test_select.ambr
Normal file
469
tests/components/pooldose/snapshots/test_select.ambr
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
# serializer version: 1
|
||||||
|
# name: test_all_selects[select.pool_device_chlorine_dosing_method-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'options': list([
|
||||||
|
'off',
|
||||||
|
'proportional',
|
||||||
|
'on_off',
|
||||||
|
'timed',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'select',
|
||||||
|
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||||
|
'entity_id': 'select.pool_device_chlorine_dosing_method',
|
||||||
|
'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': 'Chlorine dosing method',
|
||||||
|
'platform': 'pooldose',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'cl_type_dosing_method',
|
||||||
|
'unique_id': 'TEST123456789_cl_type_dosing_method',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_selects[select.pool_device_chlorine_dosing_method-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Pool Device Chlorine dosing method',
|
||||||
|
'options': list([
|
||||||
|
'off',
|
||||||
|
'proportional',
|
||||||
|
'on_off',
|
||||||
|
'timed',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'select.pool_device_chlorine_dosing_method',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'timed',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_selects[select.pool_device_chlorine_dosing_set-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'options': list([
|
||||||
|
'low',
|
||||||
|
'high',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'select',
|
||||||
|
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||||
|
'entity_id': 'select.pool_device_chlorine_dosing_set',
|
||||||
|
'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': 'Chlorine dosing set',
|
||||||
|
'platform': 'pooldose',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'cl_type_dosing_set',
|
||||||
|
'unique_id': 'TEST123456789_cl_type_dosing_set',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_selects[select.pool_device_chlorine_dosing_set-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Pool Device Chlorine dosing set',
|
||||||
|
'options': list([
|
||||||
|
'low',
|
||||||
|
'high',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'select.pool_device_chlorine_dosing_set',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'high',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_selects[select.pool_device_flow_rate_unit-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'options': list([
|
||||||
|
<UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR: 'm³/h'>,
|
||||||
|
<UnitOfVolumeFlowRate.LITERS_PER_SECOND: 'L/s'>,
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'select',
|
||||||
|
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||||
|
'entity_id': 'select.pool_device_flow_rate_unit',
|
||||||
|
'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': 'Flow rate unit',
|
||||||
|
'platform': 'pooldose',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'flow_rate_unit',
|
||||||
|
'unique_id': 'TEST123456789_flow_rate_unit',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_selects[select.pool_device_flow_rate_unit-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Pool Device Flow rate unit',
|
||||||
|
'options': list([
|
||||||
|
<UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR: 'm³/h'>,
|
||||||
|
<UnitOfVolumeFlowRate.LITERS_PER_SECOND: 'L/s'>,
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'select.pool_device_flow_rate_unit',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'L/s',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_selects[select.pool_device_orp_dosing_method-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'options': list([
|
||||||
|
'off',
|
||||||
|
'proportional',
|
||||||
|
'on_off',
|
||||||
|
'timed',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'select',
|
||||||
|
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||||
|
'entity_id': 'select.pool_device_orp_dosing_method',
|
||||||
|
'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': 'ORP dosing method',
|
||||||
|
'platform': 'pooldose',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'orp_type_dosing_method',
|
||||||
|
'unique_id': 'TEST123456789_orp_type_dosing_method',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_selects[select.pool_device_orp_dosing_method-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Pool Device ORP dosing method',
|
||||||
|
'options': list([
|
||||||
|
'off',
|
||||||
|
'proportional',
|
||||||
|
'on_off',
|
||||||
|
'timed',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'select.pool_device_orp_dosing_method',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'on_off',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_selects[select.pool_device_orp_dosing_set-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'options': list([
|
||||||
|
'low',
|
||||||
|
'high',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'select',
|
||||||
|
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||||
|
'entity_id': 'select.pool_device_orp_dosing_set',
|
||||||
|
'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': 'ORP dosing set',
|
||||||
|
'platform': 'pooldose',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'orp_type_dosing_set',
|
||||||
|
'unique_id': 'TEST123456789_orp_type_dosing_set',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_selects[select.pool_device_orp_dosing_set-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Pool Device ORP dosing set',
|
||||||
|
'options': list([
|
||||||
|
'low',
|
||||||
|
'high',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'select.pool_device_orp_dosing_set',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'low',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_selects[select.pool_device_ph_dosing_method-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'options': list([
|
||||||
|
'off',
|
||||||
|
'proportional',
|
||||||
|
'on_off',
|
||||||
|
'timed',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'select',
|
||||||
|
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||||
|
'entity_id': 'select.pool_device_ph_dosing_method',
|
||||||
|
'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': 'pH dosing method',
|
||||||
|
'platform': 'pooldose',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'ph_type_dosing_method',
|
||||||
|
'unique_id': 'TEST123456789_ph_type_dosing_method',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_selects[select.pool_device_ph_dosing_method-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Pool Device pH dosing method',
|
||||||
|
'options': list([
|
||||||
|
'off',
|
||||||
|
'proportional',
|
||||||
|
'on_off',
|
||||||
|
'timed',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'select.pool_device_ph_dosing_method',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'proportional',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_selects[select.pool_device_ph_dosing_set-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'options': list([
|
||||||
|
'alcalyne',
|
||||||
|
'acid',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'select',
|
||||||
|
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||||
|
'entity_id': 'select.pool_device_ph_dosing_set',
|
||||||
|
'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': 'pH dosing set',
|
||||||
|
'platform': 'pooldose',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'ph_type_dosing_set',
|
||||||
|
'unique_id': 'TEST123456789_ph_type_dosing_set',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_selects[select.pool_device_ph_dosing_set-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Pool Device pH dosing set',
|
||||||
|
'options': list([
|
||||||
|
'alcalyne',
|
||||||
|
'acid',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'select.pool_device_ph_dosing_set',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'acid',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_selects[select.pool_device_water_meter_unit-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'options': list([
|
||||||
|
<UnitOfVolume.LITERS: 'L'>,
|
||||||
|
<UnitOfVolume.CUBIC_METERS: 'm³'>,
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'select',
|
||||||
|
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||||
|
'entity_id': 'select.pool_device_water_meter_unit',
|
||||||
|
'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': 'Water meter unit',
|
||||||
|
'platform': 'pooldose',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'water_meter_unit',
|
||||||
|
'unique_id': 'TEST123456789_water_meter_unit',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_selects[select.pool_device_water_meter_unit-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Pool Device Water meter unit',
|
||||||
|
'options': list([
|
||||||
|
<UnitOfVolume.LITERS: 'L'>,
|
||||||
|
<UnitOfVolume.CUBIC_METERS: 'm³'>,
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'select.pool_device_water_meter_unit',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'm³',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
242
tests/components/pooldose/test_select.py
Normal file
242
tests/components/pooldose/test_select.py
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
"""Tests for the Seko PoolDose select platform."""
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
from pooldose.request_status import RequestStatus
|
||||||
|
import pytest
|
||||||
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
|
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
|
||||||
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID,
|
||||||
|
ATTR_OPTION,
|
||||||
|
Platform,
|
||||||
|
UnitOfVolume,
|
||||||
|
UnitOfVolumeFlowRate,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def platforms() -> list[Platform]:
|
||||||
|
"""Fixture to specify platforms to test."""
|
||||||
|
return [Platform.SELECT]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
|
||||||
|
async def test_all_selects(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test the Pooldose select entities."""
|
||||||
|
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
|
||||||
|
async def test_select_entity_unavailable_no_coordinator_data(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
init_integration: MockConfigEntry,
|
||||||
|
mock_pooldose_client: AsyncMock,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test select entity becomes unavailable when coordinator has no data."""
|
||||||
|
# Verify entity has a state initially
|
||||||
|
water_meter_state = hass.states.get("select.pool_device_water_meter_unit")
|
||||||
|
assert water_meter_state.state == UnitOfVolume.CUBIC_METERS
|
||||||
|
|
||||||
|
# Update coordinator data to None
|
||||||
|
mock_pooldose_client.instant_values_structured.return_value = (None, None)
|
||||||
|
freezer.tick(timedelta(minutes=5))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Check entity becomes unavailable
|
||||||
|
water_meter_state = hass.states.get("select.pool_device_water_meter_unit")
|
||||||
|
assert water_meter_state.state == "unavailable"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
|
||||||
|
async def test_select_state_changes(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
init_integration: MockConfigEntry,
|
||||||
|
mock_pooldose_client: AsyncMock,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test select state changes when coordinator updates."""
|
||||||
|
# Initial state
|
||||||
|
ph_method_state = hass.states.get("select.pool_device_ph_dosing_method")
|
||||||
|
assert ph_method_state.state == "proportional"
|
||||||
|
|
||||||
|
# Update coordinator data with select value changed
|
||||||
|
current_data = mock_pooldose_client.instant_values_structured.return_value[1]
|
||||||
|
updated_data = current_data.copy()
|
||||||
|
updated_data["select"]["ph_type_dosing_method"]["value"] = "timed"
|
||||||
|
|
||||||
|
mock_pooldose_client.instant_values_structured.return_value = (
|
||||||
|
RequestStatus.SUCCESS,
|
||||||
|
updated_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
freezer.tick(timedelta(minutes=5))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Check state changed
|
||||||
|
ph_method_state = hass.states.get("select.pool_device_ph_dosing_method")
|
||||||
|
assert ph_method_state.state == "timed"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
|
||||||
|
async def test_select_option_unit_conversion(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_pooldose_client: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test selecting an option with unit conversion (HA unit -> API value)."""
|
||||||
|
# Verify initial state is m³ (displayed as Unicode)
|
||||||
|
water_meter_state = hass.states.get("select.pool_device_water_meter_unit")
|
||||||
|
assert water_meter_state.state == UnitOfVolume.CUBIC_METERS
|
||||||
|
|
||||||
|
# Select Liters option
|
||||||
|
await hass.services.async_call(
|
||||||
|
SELECT_DOMAIN,
|
||||||
|
"select_option",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: "select.pool_device_water_meter_unit",
|
||||||
|
ATTR_OPTION: UnitOfVolume.LITERS,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify API was called with "L" (not Unicode)
|
||||||
|
mock_pooldose_client.set_select.assert_called_once_with("water_meter_unit", "L")
|
||||||
|
|
||||||
|
# Verify state updated to L (Unicode)
|
||||||
|
water_meter_state = hass.states.get("select.pool_device_water_meter_unit")
|
||||||
|
assert water_meter_state.state == UnitOfVolume.LITERS
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
|
||||||
|
async def test_select_option_flow_rate_unit_conversion(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_pooldose_client: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test selecting flow rate unit with conversion."""
|
||||||
|
# Verify initial state
|
||||||
|
flow_rate_state = hass.states.get("select.pool_device_flow_rate_unit")
|
||||||
|
assert flow_rate_state.state == UnitOfVolumeFlowRate.LITERS_PER_SECOND
|
||||||
|
|
||||||
|
# Select cubic meters per hour
|
||||||
|
await hass.services.async_call(
|
||||||
|
SELECT_DOMAIN,
|
||||||
|
"select_option",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: "select.pool_device_flow_rate_unit",
|
||||||
|
ATTR_OPTION: UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify API was called with "m3/h" (not Unicode m³/h)
|
||||||
|
mock_pooldose_client.set_select.assert_called_once_with("flow_rate_unit", "m3/h")
|
||||||
|
|
||||||
|
# Verify state updated to m³/h (with Unicode)
|
||||||
|
flow_rate_state = hass.states.get("select.pool_device_flow_rate_unit")
|
||||||
|
assert flow_rate_state.state == UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("init_integration")
|
||||||
|
async def test_select_option_no_conversion(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_pooldose_client: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test selecting an option without unit conversion."""
|
||||||
|
# Verify initial state
|
||||||
|
ph_set_state = hass.states.get("select.pool_device_ph_dosing_set")
|
||||||
|
assert ph_set_state.state == "acid"
|
||||||
|
|
||||||
|
# Select alkaline option
|
||||||
|
await hass.services.async_call(
|
||||||
|
SELECT_DOMAIN,
|
||||||
|
"select_option",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: "select.pool_device_ph_dosing_set",
|
||||||
|
ATTR_OPTION: "alcalyne",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify API was called with exact value
|
||||||
|
mock_pooldose_client.set_select.assert_called_once_with(
|
||||||
|
"ph_type_dosing_set", "alcalyne"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify state updated
|
||||||
|
ph_set_state = hass.states.get("select.pool_device_ph_dosing_set")
|
||||||
|
assert ph_set_state.state == "alcalyne"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
|
||||||
|
async def test_select_dosing_method_options(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_pooldose_client: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test selecting different dosing method options."""
|
||||||
|
# Test ORP dosing method
|
||||||
|
orp_method_state = hass.states.get("select.pool_device_orp_dosing_method")
|
||||||
|
assert orp_method_state.state == "on_off"
|
||||||
|
|
||||||
|
# Change to proportional
|
||||||
|
await hass.services.async_call(
|
||||||
|
SELECT_DOMAIN,
|
||||||
|
"select_option",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: "select.pool_device_orp_dosing_method",
|
||||||
|
ATTR_OPTION: "proportional",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify API call
|
||||||
|
mock_pooldose_client.set_select.assert_called_once_with(
|
||||||
|
"orp_type_dosing_method", "proportional"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify state
|
||||||
|
orp_method_state = hass.states.get("select.pool_device_orp_dosing_method")
|
||||||
|
assert orp_method_state.state == "proportional"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
|
||||||
|
async def test_select_dosing_set_high_low(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_pooldose_client: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test selecting high/low dosing intensity."""
|
||||||
|
# Chlorine dosing set starts as high in fixture
|
||||||
|
cl_set_state = hass.states.get("select.pool_device_chlorine_dosing_set")
|
||||||
|
assert cl_set_state.state == "high"
|
||||||
|
|
||||||
|
# Change to low
|
||||||
|
await hass.services.async_call(
|
||||||
|
SELECT_DOMAIN,
|
||||||
|
"select_option",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: "select.pool_device_chlorine_dosing_set",
|
||||||
|
ATTR_OPTION: "low",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify API call
|
||||||
|
mock_pooldose_client.set_select.assert_called_once_with("cl_type_dosing_set", "low")
|
||||||
|
|
||||||
|
# Verify state
|
||||||
|
cl_set_state = hass.states.get("select.pool_device_chlorine_dosing_set")
|
||||||
|
assert cl_set_state.state == "low"
|
||||||
Reference in New Issue
Block a user