mirror of
https://github.com/home-assistant/core.git
synced 2026-09-27 01:46:11 -04:00
Add Celsius temperature support to Hot Spring (#183068)
Co-authored-by: Moustachauve <2206577+Moustachauve@users.noreply.github.com>
This commit is contained in:
co-authored by
Moustachauve
parent
159f83af3d
commit
2f3fb26a07
@@ -2,6 +2,8 @@
|
||||
|
||||
from typing import override
|
||||
|
||||
from hotspring import TemperatureUnit
|
||||
|
||||
from homeassistant.components.number import (
|
||||
NumberDeviceClass,
|
||||
NumberEntity,
|
||||
@@ -17,14 +19,18 @@ from .helpers import hotspring_exception_handler
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
MIN_TEMP_FAHRENHEIT = 80.0
|
||||
MAX_TEMP_FAHRENHEIT = 104.0
|
||||
STEP_FAHRENHEIT = 1.0
|
||||
|
||||
MIN_TEMP_CELSIUS = 26.0
|
||||
MAX_TEMP_CELSIUS = 40.0
|
||||
STEP_CELSIUS = 0.5
|
||||
|
||||
TARGET_TEMPERATURE_DESCRIPTION = NumberEntityDescription(
|
||||
key="target_temperature",
|
||||
translation_key="target_temperature",
|
||||
device_class=NumberDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||
native_min_value=80.0,
|
||||
native_max_value=104.0,
|
||||
native_step=1.0,
|
||||
)
|
||||
|
||||
|
||||
@@ -53,6 +59,43 @@ class HotSpringNumberEntity(HotSpringEntity, NumberEntity):
|
||||
super().__init__(coordinator, description.key)
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
def _is_celsius(self) -> bool:
|
||||
"""Return True if the spa is configured in Celsius."""
|
||||
return self.coordinator.data.heater.temperature_unit is TemperatureUnit.CELSIUS
|
||||
|
||||
@property
|
||||
@override
|
||||
def native_unit_of_measurement(self) -> str:
|
||||
"""Return the unit of measurement."""
|
||||
if self._is_celsius:
|
||||
return UnitOfTemperature.CELSIUS
|
||||
return UnitOfTemperature.FAHRENHEIT
|
||||
|
||||
@property
|
||||
@override
|
||||
def native_min_value(self) -> float:
|
||||
"""Return the minimum value."""
|
||||
if self._is_celsius:
|
||||
return MIN_TEMP_CELSIUS
|
||||
return MIN_TEMP_FAHRENHEIT
|
||||
|
||||
@property
|
||||
@override
|
||||
def native_max_value(self) -> float:
|
||||
"""Return the maximum value."""
|
||||
if self._is_celsius:
|
||||
return MAX_TEMP_CELSIUS
|
||||
return MAX_TEMP_FAHRENHEIT
|
||||
|
||||
@property
|
||||
@override
|
||||
def native_step(self) -> float:
|
||||
"""Return the step value."""
|
||||
if self._is_celsius:
|
||||
return STEP_CELSIUS
|
||||
return STEP_FAHRENHEIT
|
||||
|
||||
@property
|
||||
@override
|
||||
def native_value(self) -> float | None:
|
||||
@@ -63,5 +106,6 @@ class HotSpringNumberEntity(HotSpringEntity, NumberEntity):
|
||||
@override
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Set the target temperature."""
|
||||
await self.coordinator.hotspring.set_temperature(round(value))
|
||||
target = round(value / self.native_step) * self.native_step
|
||||
await self.coordinator.hotspring.set_temperature(target)
|
||||
self.coordinator.async_set_updated_data(self.coordinator.data)
|
||||
|
||||
@@ -4,7 +4,7 @@ from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import override
|
||||
|
||||
from hotspring import Spa
|
||||
from hotspring import Spa, TemperatureUnit
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
@@ -29,6 +29,7 @@ class HotSpringSensorEntityDescription(SensorEntityDescription):
|
||||
|
||||
exists_fn: Callable[[Spa], bool] = lambda _: True
|
||||
value_fn: Callable[[Spa], StateType]
|
||||
unit_fn: Callable[[Spa], str | None] | None = None
|
||||
|
||||
|
||||
SENSORS: tuple[HotSpringSensorEntityDescription, ...] = (
|
||||
@@ -37,8 +38,12 @@ SENSORS: tuple[HotSpringSensorEntityDescription, ...] = (
|
||||
translation_key="current_temperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
|
||||
value_fn=lambda spa: spa.heater.current_temperature,
|
||||
unit_fn=lambda spa: (
|
||||
UnitOfTemperature.CELSIUS
|
||||
if spa.heater.temperature_unit is TemperatureUnit.CELSIUS
|
||||
else UnitOfTemperature.FAHRENHEIT
|
||||
),
|
||||
),
|
||||
HotSpringSensorEntityDescription(
|
||||
key="water_care_120_day_timer",
|
||||
@@ -118,6 +123,14 @@ class HotSpringSensorEntity(HotSpringEntity, SensorEntity):
|
||||
super().__init__(coordinator, description.key)
|
||||
self.entity_description = description
|
||||
|
||||
@property
|
||||
@override
|
||||
def native_unit_of_measurement(self) -> str | None:
|
||||
"""Return the unit of measurement."""
|
||||
if self.entity_description.unit_fn is not None:
|
||||
return self.entity_description.unit_fn(self.coordinator.data)
|
||||
return super().native_unit_of_measurement
|
||||
|
||||
@property
|
||||
@override
|
||||
def native_value(self) -> StateType:
|
||||
|
||||
@@ -60,3 +60,22 @@
|
||||
'state': '40.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_set_target_temperature_celsius
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'ConnectedSpa_DDEEFF Target temperature',
|
||||
<NumberEntityCapabilityAttribute.MAX: 'max'>: 40.0,
|
||||
<NumberEntityCapabilityAttribute.MIN: 'min'>: 26.0,
|
||||
<NumberEntityCapabilityAttribute.MODE: 'mode'>: <NumberMode.AUTO: 'auto'>,
|
||||
<NumberEntityCapabilityAttribute.STEP: 'step'>: 0.5,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.connectedspa_ddeeff_target_temperature',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '38.5',
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -370,3 +370,19 @@
|
||||
'state': '1.0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_temperature_sensor_celsius
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'temperature',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'ConnectedSpa_DDEEFF Current temperature',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.connectedspa_ddeeff_current_temperature',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '38.5',
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from hotspring import HotSpringConnectionError, HotSpringError, Spa
|
||||
from hotspring import HotSpringConnectionError, HotSpringError, Spa, TemperatureUnit
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
@@ -45,8 +45,8 @@ async def test_set_target_temperature(
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.state == "40.0"
|
||||
|
||||
def set_temp_mock(value: int) -> None:
|
||||
device_fixture.heater.set_temperature = float(value)
|
||||
def set_temp_mock(value: float) -> None:
|
||||
device_fixture.heater.set_temperature = value
|
||||
|
||||
mock_hotspring.set_temperature.side_effect = set_temp_mock
|
||||
|
||||
@@ -60,7 +60,7 @@ async def test_set_target_temperature(
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_hotspring.set_temperature.assert_called_once_with(100)
|
||||
mock_hotspring.set_temperature.assert_called_once_with(100.0)
|
||||
mock_hotspring.update.assert_called_once()
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.state == "37.8"
|
||||
@@ -96,3 +96,38 @@ async def test_set_target_temperature_error(
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_set_target_temperature_celsius(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_hotspring: MagicMock,
|
||||
device_fixture: Spa,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test setting target temperature when the spa is configured in Celsius."""
|
||||
device_fixture.heater.temperature_unit = TemperatureUnit.CELSIUS
|
||||
device_fixture.heater.set_temperature = 38.5
|
||||
|
||||
def set_temp_mock(value: float) -> None:
|
||||
device_fixture.heater.set_temperature = value
|
||||
|
||||
mock_hotspring.set_temperature.side_effect = set_temp_mock
|
||||
|
||||
await setup_with_selected_platforms(hass, mock_config_entry, [Platform.NUMBER])
|
||||
|
||||
assert hass.states.get(ENTITY_ID) == snapshot
|
||||
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{
|
||||
ATTR_ENTITY_ID: ENTITY_ID,
|
||||
ATTR_VALUE: 37.5,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_hotspring.set_temperature.assert_called_once_with(37.5)
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.state == "37.5"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Tests for the Hot Spring sensor platform."""
|
||||
|
||||
from hotspring import Spa, TemperatureUnit
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
@@ -22,3 +23,18 @@ async def test_sensors(
|
||||
"""Test the sensor platform state."""
|
||||
await setup_with_selected_platforms(hass, mock_config_entry, [Platform.SENSOR])
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "mock_hotspring")
|
||||
async def test_temperature_sensor_celsius(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
device_fixture: Spa,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test the temperature sensor when the spa is configured in Celsius."""
|
||||
device_fixture.heater.temperature_unit = TemperatureUnit.CELSIUS
|
||||
device_fixture.heater.current_temperature = 38.5
|
||||
await setup_with_selected_platforms(hass, mock_config_entry, [Platform.SENSOR])
|
||||
|
||||
assert hass.states.get("sensor.connectedspa_ddeeff_current_temperature") == snapshot
|
||||
|
||||
Reference in New Issue
Block a user