mirror of
https://github.com/home-assistant/core.git
synced 2025-12-25 05:26:47 +00:00
Pooldose: add number platform (#157787)
This commit is contained in:
@@ -17,7 +17,12 @@ from .coordinator import PooldoseConfigEntry, PooldoseCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH]
|
||||
PLATFORMS: list[Platform] = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.NUMBER,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
]
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, entry: PooldoseConfigEntry) -> bool:
|
||||
|
||||
@@ -68,6 +68,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"cl_target": {
|
||||
"default": "mdi:pool"
|
||||
},
|
||||
"ofa_cl_lower": {
|
||||
"default": "mdi:arrow-down-bold"
|
||||
},
|
||||
"ofa_cl_upper": {
|
||||
"default": "mdi:arrow-up-bold"
|
||||
},
|
||||
"ofa_orp_lower": {
|
||||
"default": "mdi:arrow-down-bold"
|
||||
},
|
||||
"ofa_orp_upper": {
|
||||
"default": "mdi:arrow-up-bold"
|
||||
},
|
||||
"ofa_ph_lower": {
|
||||
"default": "mdi:arrow-down-bold"
|
||||
},
|
||||
"ofa_ph_upper": {
|
||||
"default": "mdi:arrow-up-bold"
|
||||
},
|
||||
"orp_target": {
|
||||
"default": "mdi:water-check"
|
||||
},
|
||||
"ph_target": {
|
||||
"default": "mdi:ph"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"cl": {
|
||||
"default": "mdi:pool"
|
||||
|
||||
142
homeassistant/components/pooldose/number.py
Normal file
142
homeassistant/components/pooldose/number.py
Normal file
@@ -0,0 +1,142 @@
|
||||
"""Number entities for the Seko PoolDose integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
from homeassistant.components.number import (
|
||||
NumberDeviceClass,
|
||||
NumberEntity,
|
||||
NumberEntityDescription,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
EntityCategory,
|
||||
UnitOfElectricPotential,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import PooldoseConfigEntry
|
||||
from .entity import PooldoseEntity
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .coordinator import PooldoseCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
NUMBER_DESCRIPTIONS: tuple[NumberEntityDescription, ...] = (
|
||||
NumberEntityDescription(
|
||||
key="ph_target",
|
||||
translation_key="ph_target",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
device_class=NumberDeviceClass.PH,
|
||||
),
|
||||
NumberEntityDescription(
|
||||
key="orp_target",
|
||||
translation_key="orp_target",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
device_class=NumberDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT,
|
||||
),
|
||||
NumberEntityDescription(
|
||||
key="cl_target",
|
||||
translation_key="cl_target",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
),
|
||||
NumberEntityDescription(
|
||||
key="ofa_ph_lower",
|
||||
translation_key="ofa_ph_lower",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
device_class=NumberDeviceClass.PH,
|
||||
),
|
||||
NumberEntityDescription(
|
||||
key="ofa_ph_upper",
|
||||
translation_key="ofa_ph_upper",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
device_class=NumberDeviceClass.PH,
|
||||
),
|
||||
NumberEntityDescription(
|
||||
key="ofa_orp_lower",
|
||||
translation_key="ofa_orp_lower",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
device_class=NumberDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT,
|
||||
),
|
||||
NumberEntityDescription(
|
||||
key="ofa_orp_upper",
|
||||
translation_key="ofa_orp_upper",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
device_class=NumberDeviceClass.VOLTAGE,
|
||||
native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT,
|
||||
),
|
||||
NumberEntityDescription(
|
||||
key="ofa_cl_lower",
|
||||
translation_key="ofa_cl_lower",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
),
|
||||
NumberEntityDescription(
|
||||
key="ofa_cl_upper",
|
||||
translation_key="ofa_cl_upper",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: PooldoseConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up PoolDose number entities from a config entry."""
|
||||
if TYPE_CHECKING:
|
||||
assert config_entry.unique_id is not None
|
||||
|
||||
coordinator = config_entry.runtime_data
|
||||
number_data = coordinator.data.get("number", {})
|
||||
serial_number = config_entry.unique_id
|
||||
|
||||
async_add_entities(
|
||||
PooldoseNumber(coordinator, serial_number, coordinator.device_info, description)
|
||||
for description in NUMBER_DESCRIPTIONS
|
||||
if description.key in number_data
|
||||
)
|
||||
|
||||
|
||||
class PooldoseNumber(PooldoseEntity, NumberEntity):
|
||||
"""Number entity for the Seko PoolDose Python API."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: PooldoseCoordinator,
|
||||
serial_number: str,
|
||||
device_info: Any,
|
||||
description: NumberEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the number."""
|
||||
super().__init__(coordinator, serial_number, device_info, description, "number")
|
||||
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 number attributes."""
|
||||
data = cast(dict, self.get_data())
|
||||
self._attr_native_value = data["value"]
|
||||
self._attr_native_min_value = data["min"]
|
||||
self._attr_native_max_value = data["max"]
|
||||
self._attr_native_step = data["step"]
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Set new value."""
|
||||
await self.coordinator.client.set_number(self.entity_description.key, value)
|
||||
self._attr_native_value = value
|
||||
self.async_write_ha_state()
|
||||
@@ -68,6 +68,35 @@
|
||||
"name": "Auxiliary relay 3 status"
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"cl_target": {
|
||||
"name": "Chlorine target"
|
||||
},
|
||||
"ofa_cl_lower": {
|
||||
"name": "Chlorine overfeed alarm lower limit"
|
||||
},
|
||||
"ofa_cl_upper": {
|
||||
"name": "Chlorine overfeed alarm upper limit"
|
||||
},
|
||||
"ofa_orp_lower": {
|
||||
"name": "ORP overfeed alarm lower limit"
|
||||
},
|
||||
"ofa_orp_upper": {
|
||||
"name": "ORP overfeed alarm upper limit"
|
||||
},
|
||||
"ofa_ph_lower": {
|
||||
"name": "pH overfeed alarm lower limit"
|
||||
},
|
||||
"ofa_ph_upper": {
|
||||
"name": "pH overfeed alarm upper limit"
|
||||
},
|
||||
"orp_target": {
|
||||
"name": "ORP target"
|
||||
},
|
||||
"ph_target": {
|
||||
"name": "pH target"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"cl": {
|
||||
"name": "Chlorine"
|
||||
|
||||
@@ -141,6 +141,48 @@
|
||||
"min": 0,
|
||||
"max": 65535,
|
||||
"step": 0.01
|
||||
},
|
||||
"ofa_ph_lower": {
|
||||
"value": 6,
|
||||
"unit": null,
|
||||
"min": 0,
|
||||
"max": 14,
|
||||
"step": 0.1
|
||||
},
|
||||
"ofa_ph_upper": {
|
||||
"value": 8,
|
||||
"unit": null,
|
||||
"min": 0,
|
||||
"max": 14,
|
||||
"step": 0.1
|
||||
},
|
||||
"ofa_orp_lower": {
|
||||
"value": 600,
|
||||
"unit": "mV",
|
||||
"min": 0,
|
||||
"max": 1000,
|
||||
"step": 1
|
||||
},
|
||||
"ofa_orp_upper": {
|
||||
"value": 800,
|
||||
"unit": "mV",
|
||||
"min": 0,
|
||||
"max": 1000,
|
||||
"step": 1
|
||||
},
|
||||
"ofa_cl_lower": {
|
||||
"value": 0.2,
|
||||
"unit": "ppm",
|
||||
"min": 0,
|
||||
"max": 10,
|
||||
"step": 0.1
|
||||
},
|
||||
"ofa_cl_upper": {
|
||||
"value": 0.9,
|
||||
"unit": "ppm",
|
||||
"min": 0,
|
||||
"max": 10,
|
||||
"step": 0.1
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
|
||||
526
tests/components/pooldose/snapshots/test_number.ambr
Normal file
526
tests/components/pooldose/snapshots/test_number.ambr
Normal file
@@ -0,0 +1,526 @@
|
||||
# serializer version: 1
|
||||
# name: test_all_numbers[number.pool_device_chlorine_overfeed_alarm_lower_limit-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 10,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 0.1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.pool_device_chlorine_overfeed_alarm_lower_limit',
|
||||
'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 overfeed alarm lower limit',
|
||||
'platform': 'pooldose',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'ofa_cl_lower',
|
||||
'unique_id': 'TEST123456789_ofa_cl_lower',
|
||||
'unit_of_measurement': 'ppm',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_numbers[number.pool_device_chlorine_overfeed_alarm_lower_limit-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Pool Device Chlorine overfeed alarm lower limit',
|
||||
'max': 10,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 0.1,
|
||||
'unit_of_measurement': 'ppm',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.pool_device_chlorine_overfeed_alarm_lower_limit',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.2',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_numbers[number.pool_device_chlorine_overfeed_alarm_upper_limit-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 10,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 0.1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.pool_device_chlorine_overfeed_alarm_upper_limit',
|
||||
'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 overfeed alarm upper limit',
|
||||
'platform': 'pooldose',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'ofa_cl_upper',
|
||||
'unique_id': 'TEST123456789_ofa_cl_upper',
|
||||
'unit_of_measurement': 'ppm',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_numbers[number.pool_device_chlorine_overfeed_alarm_upper_limit-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Pool Device Chlorine overfeed alarm upper limit',
|
||||
'max': 10,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 0.1,
|
||||
'unit_of_measurement': 'ppm',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.pool_device_chlorine_overfeed_alarm_upper_limit',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.9',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_numbers[number.pool_device_chlorine_target-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 65535,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 0.01,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.pool_device_chlorine_target',
|
||||
'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 target',
|
||||
'platform': 'pooldose',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'cl_target',
|
||||
'unique_id': 'TEST123456789_cl_target',
|
||||
'unit_of_measurement': 'ppm',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_numbers[number.pool_device_chlorine_target-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Pool Device Chlorine target',
|
||||
'max': 65535,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 0.01,
|
||||
'unit_of_measurement': 'ppm',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.pool_device_chlorine_target',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '1',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_numbers[number.pool_device_orp_overfeed_alarm_lower_limit-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 1000,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.pool_device_orp_overfeed_alarm_lower_limit',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <NumberDeviceClass.VOLTAGE: 'voltage'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'ORP overfeed alarm lower limit',
|
||||
'platform': 'pooldose',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'ofa_orp_lower',
|
||||
'unique_id': 'TEST123456789_ofa_orp_lower',
|
||||
'unit_of_measurement': <UnitOfElectricPotential.MILLIVOLT: 'mV'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_numbers[number.pool_device_orp_overfeed_alarm_lower_limit-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'voltage',
|
||||
'friendly_name': 'Pool Device ORP overfeed alarm lower limit',
|
||||
'max': 1000,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
'unit_of_measurement': <UnitOfElectricPotential.MILLIVOLT: 'mV'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.pool_device_orp_overfeed_alarm_lower_limit',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '600',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_numbers[number.pool_device_orp_overfeed_alarm_upper_limit-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 1000,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.pool_device_orp_overfeed_alarm_upper_limit',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <NumberDeviceClass.VOLTAGE: 'voltage'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'ORP overfeed alarm upper limit',
|
||||
'platform': 'pooldose',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'ofa_orp_upper',
|
||||
'unique_id': 'TEST123456789_ofa_orp_upper',
|
||||
'unit_of_measurement': <UnitOfElectricPotential.MILLIVOLT: 'mV'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_numbers[number.pool_device_orp_overfeed_alarm_upper_limit-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'voltage',
|
||||
'friendly_name': 'Pool Device ORP overfeed alarm upper limit',
|
||||
'max': 1000,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
'unit_of_measurement': <UnitOfElectricPotential.MILLIVOLT: 'mV'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.pool_device_orp_overfeed_alarm_upper_limit',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '800',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_numbers[number.pool_device_orp_target-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 850,
|
||||
'min': 400,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.pool_device_orp_target',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <NumberDeviceClass.VOLTAGE: 'voltage'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'ORP target',
|
||||
'platform': 'pooldose',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'orp_target',
|
||||
'unique_id': 'TEST123456789_orp_target',
|
||||
'unit_of_measurement': <UnitOfElectricPotential.MILLIVOLT: 'mV'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_numbers[number.pool_device_orp_target-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'voltage',
|
||||
'friendly_name': 'Pool Device ORP target',
|
||||
'max': 850,
|
||||
'min': 400,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
'unit_of_measurement': <UnitOfElectricPotential.MILLIVOLT: 'mV'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.pool_device_orp_target',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '680',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_numbers[number.pool_device_ph_overfeed_alarm_lower_limit-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 14,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 0.1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.pool_device_ph_overfeed_alarm_lower_limit',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <NumberDeviceClass.PH: 'ph'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'pH overfeed alarm lower limit',
|
||||
'platform': 'pooldose',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'ofa_ph_lower',
|
||||
'unique_id': 'TEST123456789_ofa_ph_lower',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_numbers[number.pool_device_ph_overfeed_alarm_lower_limit-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'ph',
|
||||
'friendly_name': 'Pool Device pH overfeed alarm lower limit',
|
||||
'max': 14,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 0.1,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.pool_device_ph_overfeed_alarm_lower_limit',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '6',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_numbers[number.pool_device_ph_overfeed_alarm_upper_limit-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 14,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 0.1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.pool_device_ph_overfeed_alarm_upper_limit',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <NumberDeviceClass.PH: 'ph'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'pH overfeed alarm upper limit',
|
||||
'platform': 'pooldose',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'ofa_ph_upper',
|
||||
'unique_id': 'TEST123456789_ofa_ph_upper',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_numbers[number.pool_device_ph_overfeed_alarm_upper_limit-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'ph',
|
||||
'friendly_name': 'Pool Device pH overfeed alarm upper limit',
|
||||
'max': 14,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 0.1,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.pool_device_ph_overfeed_alarm_upper_limit',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '8',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_numbers[number.pool_device_ph_target-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 8,
|
||||
'min': 6,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 0.1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.pool_device_ph_target',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <NumberDeviceClass.PH: 'ph'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'pH target',
|
||||
'platform': 'pooldose',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'ph_target',
|
||||
'unique_id': 'TEST123456789_ph_target',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_numbers[number.pool_device_ph_target-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'ph',
|
||||
'friendly_name': 'Pool Device pH target',
|
||||
'max': 8,
|
||||
'min': 6,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 0.1,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.pool_device_ph_target',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '6.5',
|
||||
})
|
||||
# ---
|
||||
114
tests/components/pooldose/test_number.py
Normal file
114
tests/components/pooldose/test_number.py
Normal file
@@ -0,0 +1,114 @@
|
||||
"""Tests for the Seko PoolDose number platform."""
|
||||
|
||||
from copy import deepcopy
|
||||
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.number import DOMAIN as NUMBER_DOMAIN, SERVICE_SET_VALUE
|
||||
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
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.NUMBER]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
|
||||
async def test_all_numbers(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the Pooldose numbers."""
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_number_entity_unavailable_no_coordinator_data(
|
||||
hass: HomeAssistant,
|
||||
init_integration: MockConfigEntry,
|
||||
mock_pooldose_client: AsyncMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test number entity becomes unavailable when coordinator has no data."""
|
||||
# Verify entity has a state initially
|
||||
ph_target_state = hass.states.get("number.pool_device_ph_target")
|
||||
assert ph_target_state.state == "6.5"
|
||||
|
||||
# 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
|
||||
ph_target_state = hass.states.get("number.pool_device_ph_target")
|
||||
assert ph_target_state.state == "unavailable"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_number_state_changes(
|
||||
hass: HomeAssistant,
|
||||
init_integration: MockConfigEntry,
|
||||
mock_pooldose_client: AsyncMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test number state changes when coordinator updates."""
|
||||
# Initial state
|
||||
ph_target_state = hass.states.get("number.pool_device_ph_target")
|
||||
assert ph_target_state.state == "6.5"
|
||||
|
||||
# Update coordinator data with number value changed
|
||||
current_data = mock_pooldose_client.instant_values_structured.return_value[1]
|
||||
updated_data = deepcopy(current_data)
|
||||
updated_data["number"]["ph_target"]["value"] = 7.2
|
||||
|
||||
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_target_state = hass.states.get("number.pool_device_ph_target")
|
||||
assert ph_target_state.state == "7.2"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_set_number_value(
|
||||
hass: HomeAssistant,
|
||||
mock_pooldose_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test setting a number value."""
|
||||
# Verify initial state
|
||||
ph_target_state = hass.states.get("number.pool_device_ph_target")
|
||||
assert ph_target_state.state == "6.5"
|
||||
|
||||
# Set new value
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_ENTITY_ID: "number.pool_device_ph_target", "value": 7.0},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
# Verify API was called
|
||||
mock_pooldose_client.set_number.assert_called_once_with("ph_target", 7.0)
|
||||
|
||||
# Verify state updated immediately (optimistic update)
|
||||
ph_target_state = hass.states.get("number.pool_device_ph_target")
|
||||
assert ph_target_state.state == "7.0"
|
||||
Reference in New Issue
Block a user