diff --git a/homeassistant/components/fritzbox/sensor.py b/homeassistant/components/fritzbox/sensor.py index d28727c01f5..dbfdc2f9c95 100644 --- a/homeassistant/components/fritzbox/sensor.py +++ b/homeassistant/components/fritzbox/sensor.py @@ -83,20 +83,38 @@ def entity_category_temperature(device: FritzhomeDevice) -> EntityCategory | Non return None -def value_nextchange_preset(device: FritzhomeDevice) -> str: +def value_nextchange_preset(device: FritzhomeDevice) -> str | None: """Return native value for next scheduled preset sensor.""" + if not device.nextchange_endperiod: + return None if device.nextchange_temperature == device.eco_temperature: return PRESET_ECO return PRESET_COMFORT -def value_scheduled_preset(device: FritzhomeDevice) -> str: +def value_scheduled_preset(device: FritzhomeDevice) -> str | None: """Return native value for current scheduled preset sensor.""" + if not device.nextchange_endperiod: + return None if device.nextchange_temperature == device.eco_temperature: return PRESET_COMFORT return PRESET_ECO +def value_nextchange_temperature(device: FritzhomeDevice) -> float | None: + """Return native value for next scheduled temperature time sensor.""" + if device.nextchange_endperiod and isinstance(device.nextchange_temperature, float): + return device.nextchange_temperature + return None + + +def value_nextchange_time(device: FritzhomeDevice) -> datetime | None: + """Return native value for next scheduled changed time sensor.""" + if device.nextchange_endperiod: + return utc_from_timestamp(device.nextchange_endperiod) + return None + + SENSOR_TYPES: Final[tuple[FritzSensorEntityDescription, ...]] = ( FritzSensorEntityDescription( key="temperature", @@ -181,7 +199,7 @@ SENSOR_TYPES: Final[tuple[FritzSensorEntityDescription, ...]] = ( device_class=SensorDeviceClass.TEMPERATURE, entity_category=EntityCategory.DIAGNOSTIC, suitable=suitable_nextchange_temperature, - native_value=lambda device: device.nextchange_temperature, + native_value=value_nextchange_temperature, ), FritzSensorEntityDescription( key="nextchange_time", @@ -189,7 +207,7 @@ SENSOR_TYPES: Final[tuple[FritzSensorEntityDescription, ...]] = ( device_class=SensorDeviceClass.TIMESTAMP, entity_category=EntityCategory.DIAGNOSTIC, suitable=suitable_nextchange_time, - native_value=lambda device: utc_from_timestamp(device.nextchange_endperiod), + native_value=value_nextchange_time, ), FritzSensorEntityDescription( key="nextchange_preset", diff --git a/tests/components/fritzbox/__init__.py b/tests/components/fritzbox/__init__.py index bd68615212d..034b86497db 100644 --- a/tests/components/fritzbox/__init__.py +++ b/tests/components/fritzbox/__init__.py @@ -5,7 +5,6 @@ from __future__ import annotations from typing import Any from unittest.mock import Mock -from homeassistant.components.climate import PRESET_COMFORT, PRESET_ECO from homeassistant.components.fritzbox.const import DOMAIN from homeassistant.core import HomeAssistant @@ -110,9 +109,7 @@ class FritzDeviceClimateMock(FritzEntityBaseMock): target_temperature = 19.5 window_open = "fake_window" nextchange_temperature = 22.0 - nextchange_endperiod = 0 - nextchange_preset = PRESET_COMFORT - scheduled_preset = PRESET_ECO + nextchange_endperiod = 1726855200 class FritzDeviceClimateWithoutTempSensorMock(FritzDeviceClimateMock): diff --git a/tests/components/fritzbox/test_climate.py b/tests/components/fritzbox/test_climate.py index 358eeaa714e..2f1e0d37001 100644 --- a/tests/components/fritzbox/test_climate.py +++ b/tests/components/fritzbox/test_climate.py @@ -123,7 +123,7 @@ async def test_setup(hass: HomeAssistant, fritz: Mock) -> None: f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_next_scheduled_change_time" ) assert state - assert state.state == "1970-01-01T00:00:00+00:00" + assert state.state == "2024-09-20T18:00:00+00:00" assert ( state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Next scheduled change time" diff --git a/tests/components/fritzbox/test_sensor.py b/tests/components/fritzbox/test_sensor.py index 63d0b67d7f4..753e4d6d3a3 100644 --- a/tests/components/fritzbox/test_sensor.py +++ b/tests/components/fritzbox/test_sensor.py @@ -3,8 +3,10 @@ from datetime import timedelta from unittest.mock import Mock +import pytest from requests.exceptions import HTTPError +from homeassistant.components.climate import PRESET_COMFORT, PRESET_ECO from homeassistant.components.fritzbox.const import DOMAIN as FB_DOMAIN from homeassistant.components.sensor import ATTR_STATE_CLASS, DOMAIN, SensorStateClass from homeassistant.const import ( @@ -12,6 +14,7 @@ from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, CONF_DEVICES, PERCENTAGE, + STATE_UNKNOWN, EntityCategory, UnitOfTemperature, ) @@ -19,7 +22,12 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er import homeassistant.util.dt as dt_util -from . import FritzDeviceSensorMock, set_devices, setup_config_entry +from . import ( + FritzDeviceClimateMock, + FritzDeviceSensorMock, + set_devices, + setup_config_entry, +) from .const import CONF_FAKE_NAME, MOCK_CONFIG from tests.common import async_fire_time_changed @@ -132,3 +140,55 @@ async def test_discover_new_device(hass: HomeAssistant, fritz: Mock) -> None: state = hass.states.get(f"{DOMAIN}.new_device_temperature") assert state + + +@pytest.mark.parametrize( + ("next_changes", "expected_states"), + [ + ( + [0, 16], + [STATE_UNKNOWN, STATE_UNKNOWN, STATE_UNKNOWN, STATE_UNKNOWN], + ), + ( + [0, 22], + [STATE_UNKNOWN, STATE_UNKNOWN, STATE_UNKNOWN, STATE_UNKNOWN], + ), + ( + [1726855200, 16.0], + ["2024-09-20T18:00:00+00:00", "16.0", PRESET_ECO, PRESET_COMFORT], + ), + ( + [1726855200, 22.0], + ["2024-09-20T18:00:00+00:00", "22.0", PRESET_COMFORT, PRESET_ECO], + ), + ], +) +async def test_next_change_sensors( + hass: HomeAssistant, fritz: Mock, next_changes: list, expected_states: list +) -> None: + """Test next change sensors.""" + device = FritzDeviceClimateMock() + device.nextchange_endperiod = next_changes[0] + device.nextchange_temperature = next_changes[1] + + assert await setup_config_entry( + hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz + ) + + base_name = f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}" + + state = hass.states.get(f"{base_name}_next_scheduled_change_time") + assert state + assert state.state == expected_states[0] + + state = hass.states.get(f"{base_name}_next_scheduled_temperature") + assert state + assert state.state == expected_states[1] + + state = hass.states.get(f"{base_name}_next_scheduled_preset") + assert state + assert state.state == expected_states[2] + + state = hass.states.get(f"{base_name}_current_scheduled_preset") + assert state + assert state.state == expected_states[3]