Skip to content
Merged
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
3 changes: 2 additions & 1 deletion homeassistant/components/fritzbox/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ def _update_fritz_devices(self) -> dict[str, FritzhomeDevice]:
if (
device.has_powermeter
and device.present
and hasattr(device, "voltage")
and isinstance(device.voltage, int)
and device.voltage <= 0
and isinstance(device.power, int)
and device.power <= 0
and device.energy <= 0
):
Expand Down
80 changes: 56 additions & 24 deletions homeassistant/components/fritzbox/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,52 @@ class FritzSensorEntityDescription(
"""Description for Fritz!Smarthome sensor entities."""


def suitable_eco_temperature(device: FritzhomeDevice) -> bool:
"""Check suitablity for eco temperature sensor."""
return device.has_thermostat and device.eco_temperature is not None


def suitable_comfort_temperature(device: FritzhomeDevice) -> bool:
"""Check suitablity for comfort temperature sensor."""
return device.has_thermostat and device.comfort_temperature is not None


def suitable_nextchange_temperature(device: FritzhomeDevice) -> bool:
"""Check suitablity for next scheduled temperature sensor."""
return device.has_thermostat and device.nextchange_temperature is not None


def suitable_nextchange_time(device: FritzhomeDevice) -> bool:
"""Check suitablity for next scheduled changed time sensor."""
return device.has_thermostat and device.nextchange_endperiod is not None


def suitable_temperature(device: FritzhomeDevice) -> bool:
"""Check suitablity for temperature sensor."""
return device.has_temperature_sensor and not device.has_thermostat


def value_electric_current(device: FritzhomeDevice) -> float:
"""Return native value for electric current sensor."""
if isinstance(device.power, int) and isinstance(device.voltage, int):
return round(device.power / device.voltage, 3)
return 0.0


def value_nextchange_preset(device: FritzhomeDevice) -> str:
"""Return native value for next scheduled preset sensor."""
if device.nextchange_temperature == device.eco_temperature:
return PRESET_ECO
return PRESET_COMFORT


def value_scheduled_preset(device: FritzhomeDevice) -> str:
"""Return native value for current scheduled preset sensor."""
if device.nextchange_temperature == device.eco_temperature:
return PRESET_COMFORT
return PRESET_ECO


SENSOR_TYPES: Final[tuple[FritzSensorEntityDescription, ...]] = (
FritzSensorEntityDescription(
key="temperature",
Expand All @@ -57,9 +103,7 @@ class FritzSensorEntityDescription(
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
suitable=lambda device: (
device.has_temperature_sensor and not device.has_thermostat
),
suitable=suitable_temperature,
native_value=lambda device: device.temperature, # type: ignore[no-any-return]
),
FritzSensorEntityDescription(
Expand Down Expand Up @@ -105,9 +149,7 @@ class FritzSensorEntityDescription(
device_class=SensorDeviceClass.CURRENT,
state_class=SensorStateClass.MEASUREMENT,
suitable=lambda device: device.has_powermeter, # type: ignore[no-any-return]
native_value=lambda device: round(device.power / device.voltage, 3)
if device.power and getattr(device, "voltage", None)
else 0.0,
native_value=value_electric_current,
),
FritzSensorEntityDescription(
key="total_energy",
Expand All @@ -124,53 +166,43 @@ class FritzSensorEntityDescription(
name="Comfort Temperature",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
suitable=lambda device: device.has_thermostat
and device.comfort_temperature is not None,
suitable=suitable_comfort_temperature,
native_value=lambda device: device.comfort_temperature, # type: ignore[no-any-return]
),
FritzSensorEntityDescription(
key="eco_temperature",
name="Eco Temperature",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
suitable=lambda device: device.has_thermostat
and device.eco_temperature is not None,
suitable=suitable_eco_temperature,
native_value=lambda device: device.eco_temperature, # type: ignore[no-any-return]
),
FritzSensorEntityDescription(
key="nextchange_temperature",
name="Next Scheduled Temperature",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
suitable=lambda device: device.has_thermostat
and device.nextchange_temperature is not None,
suitable=suitable_nextchange_temperature,
native_value=lambda device: device.nextchange_temperature, # type: ignore[no-any-return]
),
FritzSensorEntityDescription(
key="nextchange_time",
name="Next Scheduled Change Time",
device_class=SensorDeviceClass.TIMESTAMP,
suitable=lambda device: device.has_thermostat
and device.nextchange_endperiod is not None,
suitable=suitable_nextchange_time,
native_value=lambda device: utc_from_timestamp(device.nextchange_endperiod),
),
FritzSensorEntityDescription(
key="nextchange_preset",
name="Next Scheduled Preset",
suitable=lambda device: device.has_thermostat
and device.nextchange_temperature is not None,
native_value=lambda device: PRESET_ECO
if device.nextchange_temperature == device.eco_temperature
else PRESET_COMFORT,
suitable=suitable_nextchange_temperature,
native_value=value_nextchange_preset,
),
FritzSensorEntityDescription(
key="scheduled_preset",
name="Current Scheduled Preset",
suitable=lambda device: device.has_thermostat
and device.nextchange_temperature is not None,
native_value=lambda device: PRESET_COMFORT
if device.nextchange_temperature == device.eco_temperature
else PRESET_ECO,
suitable=suitable_nextchange_temperature,
native_value=value_scheduled_preset,
),
)

Expand Down
18 changes: 17 additions & 1 deletion tests/components/fritzbox/test_climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from requests.exceptions import HTTPError

from homeassistant.components.climate.const import (
from homeassistant.components.climate import (
ATTR_CURRENT_TEMPERATURE,
ATTR_HVAC_MODE,
ATTR_HVAC_MODES,
Expand Down Expand Up @@ -140,6 +140,22 @@ async def test_setup(hass: HomeAssistant, fritz: Mock):
)
assert ATTR_STATE_CLASS not in state.attributes

device.nextchange_temperature = 16

next_update = dt_util.utcnow() + timedelta(seconds=200)
async_fire_time_changed(hass, next_update)
await hass.async_block_till_done()

state = hass.states.get(f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_next_scheduled_preset")
assert state
assert state.state == PRESET_ECO

state = hass.states.get(
f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_current_scheduled_preset"
)
assert state
assert state.state == PRESET_COMFORT


async def test_target_temperature_on(hass: HomeAssistant, fritz: Mock):
"""Test turn device on."""
Expand Down
18 changes: 18 additions & 0 deletions tests/components/fritzbox/test_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,21 @@ async def test_assume_device_unavailable(hass: HomeAssistant, fritz: Mock):
state = hass.states.get(ENTITY_ID)
assert state
assert state.state == STATE_UNAVAILABLE


async def test_device_current_unavailable(hass: HomeAssistant, fritz: Mock):
"""Test current in case voltage and power are not available."""
device = FritzDeviceSwitchMock()
device.voltage = None
device.power = None
assert await setup_config_entry(
hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz
)

state = hass.states.get(ENTITY_ID)
assert state
assert state.state == STATE_ON

state = hass.states.get(f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_electric_current")
assert state
assert state.state == "0.0"