1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-17 15:44:52 +01:00

Minor Saunum integration improvements (#164705)

This commit is contained in:
mettolen
2026-03-09 20:22:27 +02:00
committed by GitHub
parent 7681caa936
commit c5e0c78cbc
5 changed files with 45 additions and 28 deletions

View File

@@ -2,7 +2,7 @@
from __future__ import annotations
from pysaunum import SaunumClient, SaunumConnectionError
from pysaunum import SaunumClient, SaunumConnectionError, SaunumTimeoutError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, Platform
@@ -40,7 +40,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: LeilSaunaConfigEntry) ->
try:
client = await SaunumClient.create(host)
except SaunumConnectionError as exc:
except (SaunumConnectionError, SaunumTimeoutError) as exc:
raise ConfigEntryNotReady(f"Error connecting to {host}: {exc}") from exc
entry.async_on_unload(client.async_close)

View File

@@ -6,7 +6,14 @@ import asyncio
from datetime import timedelta
from typing import Any
from pysaunum import MAX_TEMPERATURE, MIN_TEMPERATURE, SaunumException
from pysaunum import (
DEFAULT_DURATION,
DEFAULT_FAN_DURATION,
DEFAULT_TEMPERATURE,
MAX_TEMPERATURE,
MIN_TEMPERATURE,
SaunumException,
)
from homeassistant.components.climate import (
FAN_HIGH,
@@ -149,7 +156,7 @@ class LeilSaunaClimate(LeilSaunaEntity, ClimateEntity):
def preset_mode(self) -> str | None:
"""Return the current preset mode."""
sauna_type = self.coordinator.data.sauna_type
if sauna_type is not None and sauna_type in self._preset_name_map:
if sauna_type in self._preset_name_map:
return self._preset_name_map[sauna_type]
return self._preset_name_map[0]
@@ -242,9 +249,9 @@ class LeilSaunaClimate(LeilSaunaEntity, ClimateEntity):
async def async_start_session(
self,
duration: timedelta = timedelta(minutes=120),
target_temperature: int = 80,
fan_duration: timedelta = timedelta(minutes=10),
duration: timedelta = timedelta(minutes=DEFAULT_DURATION),
target_temperature: int = DEFAULT_TEMPERATURE,
fan_duration: timedelta = timedelta(minutes=DEFAULT_FAN_DURATION),
) -> None:
"""Start a sauna session with custom parameters."""
if self.coordinator.data.door_open:

View File

@@ -7,6 +7,8 @@ from dataclasses import dataclass
from typing import TYPE_CHECKING
from pysaunum import (
DEFAULT_DURATION,
DEFAULT_FAN_DURATION,
MAX_DURATION,
MAX_FAN_DURATION,
MIN_DURATION,
@@ -35,10 +37,6 @@ if TYPE_CHECKING:
PARALLEL_UPDATES = 0
# Default values when device returns None or invalid data
DEFAULT_DURATION_MIN = 120
DEFAULT_FAN_DURATION_MIN = 15
@dataclass(frozen=True, kw_only=True)
class LeilSaunaNumberEntityDescription(NumberEntityDescription):
@@ -59,8 +57,8 @@ NUMBERS: tuple[LeilSaunaNumberEntityDescription, ...] = (
native_step=1,
value_fn=lambda data: (
duration
if (duration := data.sauna_duration) is not None and duration > MIN_DURATION
else DEFAULT_DURATION_MIN
if (duration := data.sauna_duration) > MIN_DURATION
else DEFAULT_DURATION
),
set_value_fn=lambda client, value: client.async_set_sauna_duration(int(value)),
),
@@ -74,8 +72,8 @@ NUMBERS: tuple[LeilSaunaNumberEntityDescription, ...] = (
native_step=1,
value_fn=lambda data: (
fan_dur
if (fan_dur := data.fan_duration) is not None and fan_dur > MIN_FAN_DURATION
else DEFAULT_FAN_DURATION_MIN
if (fan_dur := data.fan_duration) > MIN_FAN_DURATION
else DEFAULT_FAN_DURATION
),
set_value_fn=lambda client, value: client.async_set_fan_duration(int(value)),
),

View File

@@ -4,7 +4,15 @@ from __future__ import annotations
from datetime import timedelta
from pysaunum import MAX_DURATION, MAX_FAN_DURATION, MAX_TEMPERATURE, MIN_TEMPERATURE
from pysaunum import (
DEFAULT_DURATION,
DEFAULT_FAN_DURATION,
DEFAULT_TEMPERATURE,
MAX_DURATION,
MAX_FAN_DURATION,
MAX_TEMPERATURE,
MIN_TEMPERATURE,
)
import voluptuous as vol
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
@@ -29,17 +37,21 @@ def async_setup_services(hass: HomeAssistant) -> None:
SERVICE_START_SESSION,
entity_domain=CLIMATE_DOMAIN,
schema={
vol.Optional(ATTR_DURATION, default=timedelta(minutes=120)): vol.All(
vol.Optional(
ATTR_DURATION, default=timedelta(minutes=DEFAULT_DURATION)
): vol.All(
cv.time_period,
vol.Range(
min=timedelta(minutes=1),
max=timedelta(minutes=MAX_DURATION),
),
),
vol.Optional(ATTR_TARGET_TEMPERATURE, default=80): vol.All(
vol.Optional(ATTR_TARGET_TEMPERATURE, default=DEFAULT_TEMPERATURE): vol.All(
cv.positive_int, vol.Range(min=MIN_TEMPERATURE, max=MAX_TEMPERATURE)
),
vol.Optional(ATTR_FAN_DURATION, default=timedelta(minutes=10)): vol.All(
vol.Optional(
ATTR_FAN_DURATION, default=timedelta(minutes=DEFAULT_FAN_DURATION)
): vol.All(
cv.time_period,
vol.Range(
min=timedelta(minutes=1),

View File

@@ -5,7 +5,7 @@ from __future__ import annotations
from dataclasses import replace
from unittest.mock import MagicMock
from pysaunum import SaunumException
from pysaunum import DEFAULT_DURATION, DEFAULT_FAN_DURATION, SaunumException
import pytest
from syrupy.assertion import SnapshotAssertion
@@ -50,7 +50,7 @@ async def test_set_sauna_duration(
# Verify initial state
state = hass.states.get(entity_id)
assert state is not None
assert state.state == "120"
assert state.state == str(DEFAULT_DURATION)
# Set new duration
await hass.services.async_call(
@@ -75,7 +75,7 @@ async def test_set_fan_duration(
# Verify initial state
state = hass.states.get(entity_id)
assert state is not None
assert state.state == "10"
assert state.state == str(DEFAULT_FAN_DURATION)
# Set new duration
await hass.services.async_call(
@@ -151,13 +151,13 @@ async def test_number_with_default_duration(
mock_config_entry: MockConfigEntry,
mock_saunum_client: MagicMock,
) -> None:
"""Test number entities use default when device returns None."""
# Set duration to None (device hasn't set it yet)
"""Test number entities use default when device returns 0."""
# Set duration to 0 (device hasn't set it yet / sauna type default)
base_data = mock_saunum_client.async_get_data.return_value
mock_saunum_client.async_get_data.return_value = replace(
base_data,
sauna_duration=None,
fan_duration=None,
sauna_duration=0,
fan_duration=0,
)
mock_config_entry.add_to_hass(hass)
@@ -167,11 +167,11 @@ async def test_number_with_default_duration(
# Should show default values
sauna_duration_state = hass.states.get("number.saunum_leil_sauna_duration")
assert sauna_duration_state is not None
assert sauna_duration_state.state == "120" # DEFAULT_DURATION_MIN
assert sauna_duration_state.state == str(DEFAULT_DURATION)
fan_duration_state = hass.states.get("number.saunum_leil_fan_duration")
assert fan_duration_state is not None
assert fan_duration_state.state == "15" # DEFAULT_FAN_DURATION_MIN
assert fan_duration_state.state == str(DEFAULT_FAN_DURATION)
async def test_number_with_valid_duration_from_device(