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:
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)),
|
||||
),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user