Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .strict-typing
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ homeassistant.components.russound_rio.*
homeassistant.components.ruuvi_gateway.*
homeassistant.components.ruuvitag_ble.*
homeassistant.components.samsungtv.*
homeassistant.components.saunum.*
homeassistant.components.scene.*
homeassistant.components.schedule.*
homeassistant.components.schlage.*
Expand Down
20 changes: 17 additions & 3 deletions homeassistant/components/saunum/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
HVACAction,
HVACMode,
)
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
Expand Down Expand Up @@ -57,6 +57,8 @@ class LeilSaunaClimate(LeilSaunaEntity, ClimateEntity):
ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE
)
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_precision = PRECISION_WHOLE
_attr_target_temperature_step = 1.0
_attr_min_temp = MIN_TEMPERATURE
_attr_max_temp = MAX_TEMPERATURE
_attr_fan_modes = [FAN_OFF, FAN_LOW, FAN_MEDIUM, FAN_HIGH]
Expand Down Expand Up @@ -143,10 +145,22 @@ async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set new fan mode."""
if not self.coordinator.data.session_active:
raise ServiceValidationError(
"Cannot change fan mode when sauna session is not active",
translation_domain=DOMAIN,
translation_key="session_not_active",
)

await self.coordinator.client.async_set_fan_speed(FAN_MODE_TO_SPEED[fan_mode])
try:
await self.coordinator.client.async_set_fan_speed(
FAN_MODE_TO_SPEED[fan_mode]
)
except SaunumException as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_value_failed",
translation_placeholders={
"entity": "fan_mode",
"value": fan_mode,
},
) from err

await self.coordinator.async_request_refresh()
1 change: 1 addition & 0 deletions homeassistant/components/saunum/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
class LeilSaunaEntity(CoordinatorEntity[LeilSaunaCoordinator]):
"""Base entity for Saunum Leil Sauna."""

__slots__ = ("_attr_device_info", "_attr_unique_id")
_attr_has_entity_name = True

def __init__(self, coordinator: LeilSaunaCoordinator) -> None:
Expand Down
7 changes: 5 additions & 2 deletions homeassistant/components/saunum/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from typing import Any
from typing import TYPE_CHECKING, Any

from pysaunum import SaunumException

Expand All @@ -15,6 +15,9 @@
from .const import DOMAIN
from .entity import LeilSaunaEntity

if TYPE_CHECKING:
from .coordinator import LeilSaunaCoordinator

PARALLEL_UPDATES = 1


Expand All @@ -35,7 +38,7 @@ class LeilSaunaLight(LeilSaunaEntity, LightEntity):
_attr_color_mode = ColorMode.ONOFF
_attr_supported_color_modes = {ColorMode.ONOFF}

def __init__(self, coordinator) -> None:
def __init__(self, coordinator: LeilSaunaCoordinator) -> None:
"""Initialize the light entity."""
super().__init__(coordinator)
# Override unique_id to differentiate from climate entity
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/saunum/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["pysaunum"],
"quality_scale": "gold",
"quality_scale": "platinum",
"requirements": ["pysaunum==0.2.0"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/saunum/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,4 @@ rules:
inject-websession:
status: exempt
comment: Integration uses Modbus TCP protocol and does not make HTTP requests.
strict-typing: todo
strict-typing: done
3 changes: 3 additions & 0 deletions homeassistant/components/saunum/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@
}
},
"sensor": {
"current_temperature": {
"name": "Current temperature"
},
"heater_elements_active": {
"name": "Heater elements active",
"unit_of_measurement": "heater elements"
Expand Down
10 changes: 10 additions & 0 deletions mypy.ini

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion tests/components/saunum/snapshots/test_climate.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
]),
'max_temp': 100,
'min_temp': 40,
'target_temp_step': 1.0,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
Expand Down Expand Up @@ -50,7 +51,7 @@
# name: test_entities[climate.saunum_leil-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 75.0,
'current_temperature': 75,
'fan_mode': 'medium',
'fan_modes': list([
'off',
Expand All @@ -67,6 +68,7 @@
'max_temp': 100,
'min_temp': 40,
'supported_features': <ClimateEntityFeature: 9>,
'target_temp_step': 1.0,
'temperature': 80,
}),
'context': <ANY>,
Expand Down
30 changes: 30 additions & 0 deletions tests/components/saunum/test_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,3 +378,33 @@ async def test_fan_mode_session_not_active_error(
{ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_LOW},
blocking=True,
)


@pytest.mark.usefixtures("init_integration")
async def test_fan_mode_error_handling(
hass: HomeAssistant,
mock_saunum_client,
) -> None:
"""Test error handling when setting fan mode fails."""
entity_id = "climate.saunum_leil"

# Ensure session is active
mock_saunum_client.async_get_data.return_value.session_active = True

# Make the client method raise an exception
mock_saunum_client.async_set_fan_speed.side_effect = SaunumException(
"Communication error"
)

# Try to call the service and expect HomeAssistantError
with pytest.raises(HomeAssistantError) as exc_info:
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_FAN_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_LOW},
blocking=True,
)

# Verify the exception has the correct translation key
assert exc_info.value.translation_key == "set_value_failed"
assert exc_info.value.translation_domain == "saunum"
Loading