diff --git a/.strict-typing b/.strict-typing index 5b4721d7e6e..625f80e38c0 100644 --- a/.strict-typing +++ b/.strict-typing @@ -455,6 +455,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.* diff --git a/homeassistant/components/saunum/climate.py b/homeassistant/components/saunum/climate.py index f90703edea2..85fa57e369d 100644 --- a/homeassistant/components/saunum/climate.py +++ b/homeassistant/components/saunum/climate.py @@ -17,7 +17,7 @@ from homeassistant.components.climate import ( 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 @@ -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] @@ -143,10 +145,18 @@ class LeilSaunaClimate(LeilSaunaEntity, ClimateEntity): """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_fan_mode_failed", + ) from err + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/saunum/light.py b/homeassistant/components/saunum/light.py index 179672b4737..30be9924f08 100644 --- a/homeassistant/components/saunum/light.py +++ b/homeassistant/components/saunum/light.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING, Any from pysaunum import SaunumException @@ -15,6 +15,9 @@ from . import LeilSaunaConfigEntry from .const import DOMAIN from .entity import LeilSaunaEntity +if TYPE_CHECKING: + from .coordinator import LeilSaunaCoordinator + PARALLEL_UPDATES = 1 @@ -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 diff --git a/homeassistant/components/saunum/manifest.json b/homeassistant/components/saunum/manifest.json index cc5f943004e..8b86d55323c 100644 --- a/homeassistant/components/saunum/manifest.json +++ b/homeassistant/components/saunum/manifest.json @@ -7,6 +7,6 @@ "integration_type": "device", "iot_class": "local_polling", "loggers": ["pysaunum"], - "quality_scale": "gold", + "quality_scale": "platinum", "requirements": ["pysaunum==0.2.0"] } diff --git a/homeassistant/components/saunum/number.py b/homeassistant/components/saunum/number.py index cd12df201cc..0a59127ffd6 100644 --- a/homeassistant/components/saunum/number.py +++ b/homeassistant/components/saunum/number.py @@ -133,11 +133,7 @@ class LeilSaunaNumber(LeilSaunaEntity, NumberEntity): except SaunumException as err: raise HomeAssistantError( translation_domain=DOMAIN, - translation_key="set_value_failed", - translation_placeholders={ - "entity": self.entity_description.key, - "value": str(value), - }, + translation_key=f"set_{self.entity_description.key}_failed", ) from err await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/saunum/quality_scale.yaml b/homeassistant/components/saunum/quality_scale.yaml index 546bb34ee51..4a7d29777b4 100644 --- a/homeassistant/components/saunum/quality_scale.yaml +++ b/homeassistant/components/saunum/quality_scale.yaml @@ -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 diff --git a/homeassistant/components/saunum/strings.json b/homeassistant/components/saunum/strings.json index e72fad37fa6..a2c4d8e51db 100644 --- a/homeassistant/components/saunum/strings.json +++ b/homeassistant/components/saunum/strings.json @@ -89,6 +89,12 @@ "session_not_active": { "message": "Cannot change fan mode when sauna session is not active" }, + "set_fan_duration_failed": { + "message": "Failed to set fan duration" + }, + "set_fan_mode_failed": { + "message": "Failed to set fan mode" + }, "set_hvac_mode_failed": { "message": "Failed to set HVAC mode to {hvac_mode}" }, @@ -98,11 +104,11 @@ "set_light_on_failed": { "message": "Failed to turn on light" }, + "set_sauna_duration_failed": { + "message": "Failed to set sauna duration" + }, "set_temperature_failed": { "message": "Failed to set temperature to {temperature}" - }, - "set_value_failed": { - "message": "Failed to set {entity} to {value}" } } } diff --git a/mypy.ini b/mypy.ini index 35f9cbd7a05..da80d719894 100644 --- a/mypy.ini +++ b/mypy.ini @@ -4306,6 +4306,16 @@ disallow_untyped_defs = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.saunum.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.scene.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/tests/components/saunum/snapshots/test_climate.ambr b/tests/components/saunum/snapshots/test_climate.ambr index a52f7d690a2..47a9b17cc88 100644 --- a/tests/components/saunum/snapshots/test_climate.ambr +++ b/tests/components/saunum/snapshots/test_climate.ambr @@ -17,6 +17,7 @@ ]), 'max_temp': 100, 'min_temp': 40, + 'target_temp_step': 1.0, }), 'config_entry_id': , 'config_subentry_id': , @@ -51,7 +52,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', @@ -68,6 +69,7 @@ 'max_temp': 100, 'min_temp': 40, 'supported_features': , + 'target_temp_step': 1.0, 'temperature': 80, }), 'context': , diff --git a/tests/components/saunum/test_climate.py b/tests/components/saunum/test_climate.py index 73b72b7923a..269dba0907b 100644 --- a/tests/components/saunum/test_climate.py +++ b/tests/components/saunum/test_climate.py @@ -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_fan_mode_failed" + assert exc_info.value.translation_domain == "saunum" diff --git a/tests/components/saunum/test_sensor.py b/tests/components/saunum/test_sensor.py index 9fa223ecaa0..dc28812e87c 100644 --- a/tests/components/saunum/test_sensor.py +++ b/tests/components/saunum/test_sensor.py @@ -52,7 +52,7 @@ async def test_sensor_not_created_when_value_is_none( assert await hass.config_entries.async_setup(mock_config_entry.entry_id) await hass.async_block_till_done() - assert hass.states.get("sensor.saunum_leil_temperature") is None + assert hass.states.get("sensor.saunum_leil_current_temperature") is None assert hass.states.get("sensor.saunum_leil_heater_elements_active") is None assert hass.states.get("sensor.saunum_leil_on_time") is None