diff --git a/homeassistant/components/fritzbox/const.py b/homeassistant/components/fritzbox/const.py index af2ec30312f..6af75449a29 100644 --- a/homeassistant/components/fritzbox/const.py +++ b/homeassistant/components/fritzbox/const.py @@ -13,9 +13,6 @@ ATTR_STATE_WINDOW_OPEN: Final = "window_open" ATTR_TEMPERATURE_UNIT: Final = "temperature_unit" -ATTR_TOTAL_CONSUMPTION: Final = "total_consumption" -ATTR_TOTAL_CONSUMPTION_UNIT: Final = "total_consumption_unit" - CONF_CONNECTIONS: Final = "connections" CONF_COORDINATOR: Final = "coordinator" diff --git a/homeassistant/components/fritzbox/sensor.py b/homeassistant/components/fritzbox/sensor.py index 0a83e3ba60c..24b3d9cc5ff 100644 --- a/homeassistant/components/fritzbox/sensor.py +++ b/homeassistant/components/fritzbox/sensor.py @@ -1,6 +1,8 @@ """Support for AVM FRITZ!SmartHome temperature sensor only devices.""" from __future__ import annotations +from datetime import datetime + from homeassistant.components.sensor import ( ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT, @@ -13,12 +15,17 @@ from homeassistant.const import ( ATTR_NAME, ATTR_UNIT_OF_MEASUREMENT, DEVICE_CLASS_BATTERY, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, + ENERGY_KILO_WATT_HOUR, PERCENTAGE, + POWER_WATT, TEMP_CELSIUS, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.util.dt import utc_from_timestamp from . import FritzBoxEntity from .const import ( @@ -68,11 +75,39 @@ async def async_setup_entry( ) ) + if device.has_powermeter: + entities.append( + FritzBoxPowerSensor( + { + ATTR_NAME: f"{device.name} Power Consumption", + ATTR_ENTITY_ID: f"{device.ain}_power_consumption", + ATTR_UNIT_OF_MEASUREMENT: POWER_WATT, + ATTR_DEVICE_CLASS: DEVICE_CLASS_POWER, + ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT, + }, + coordinator, + ain, + ) + ) + entities.append( + FritzBoxEnergySensor( + { + ATTR_NAME: f"{device.name} Total Energy", + ATTR_ENTITY_ID: f"{device.ain}_total_energy", + ATTR_UNIT_OF_MEASUREMENT: ENERGY_KILO_WATT_HOUR, + ATTR_DEVICE_CLASS: DEVICE_CLASS_ENERGY, + ATTR_STATE_CLASS: None, + }, + coordinator, + ain, + ) + ) + async_add_entities(entities) class FritzBoxBatterySensor(FritzBoxEntity, SensorEntity): - """The entity class for FRITZ!SmartHome sensors.""" + """The entity class for FRITZ!SmartHome battery sensors.""" @property def state(self) -> int | None: @@ -80,6 +115,30 @@ class FritzBoxBatterySensor(FritzBoxEntity, SensorEntity): return self.device.battery_level # type: ignore [no-any-return] +class FritzBoxPowerSensor(FritzBoxEntity, SensorEntity): + """The entity class for FRITZ!SmartHome power consumption sensors.""" + + @property + def state(self) -> float | None: + """Return the state of the sensor.""" + return self.device.power / 1000 # type: ignore [no-any-return] + + +class FritzBoxEnergySensor(FritzBoxEntity, SensorEntity): + """The entity class for FRITZ!SmartHome total energy sensors.""" + + @property + def state(self) -> float | None: + """Return the state of the sensor.""" + return (self.device.energy or 0.0) / 1000 + + @property + def last_reset(self) -> datetime: + """Return the time when the sensor was last reset, if any.""" + # device does not provide timestamp of initialization + return utc_from_timestamp(0) + + class FritzBoxTempSensor(FritzBoxEntity, SensorEntity): """The entity class for FRITZ!SmartHome temperature sensors.""" diff --git a/homeassistant/components/fritzbox/switch.py b/homeassistant/components/fritzbox/switch.py index 22b2adf5800..62d1cb1ecf8 100644 --- a/homeassistant/components/fritzbox/switch.py +++ b/homeassistant/components/fritzbox/switch.py @@ -10,10 +10,7 @@ from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, ATTR_NAME, - ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT, - ENERGY_KILO_WATT_HOUR, - TEMP_CELSIUS, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -22,16 +19,11 @@ from . import FritzBoxEntity from .const import ( ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_LOCKED, - ATTR_TEMPERATURE_UNIT, - ATTR_TOTAL_CONSUMPTION, - ATTR_TOTAL_CONSUMPTION_UNIT, CONF_COORDINATOR, DOMAIN as FRITZBOX_DOMAIN, ) from .model import SwitchExtraAttributes -ATTR_TOTAL_CONSUMPTION_UNIT_VALUE = ENERGY_KILO_WATT_HOUR - async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback @@ -91,22 +83,4 @@ class FritzboxSwitch(FritzBoxEntity, SwitchEntity): ATTR_STATE_DEVICE_LOCKED: self.device.device_lock, ATTR_STATE_LOCKED: self.device.lock, } - - if self.device.has_powermeter: - attrs[ - ATTR_TOTAL_CONSUMPTION - ] = f"{((self.device.energy or 0.0) / 1000):.3f}" - attrs[ATTR_TOTAL_CONSUMPTION_UNIT] = ATTR_TOTAL_CONSUMPTION_UNIT_VALUE - if self.device.has_temperature_sensor: - attrs[ATTR_TEMPERATURE] = str( - self.hass.config.units.temperature( - self.device.temperature, TEMP_CELSIUS - ) - ) - attrs[ATTR_TEMPERATURE_UNIT] = self.hass.config.units.temperature_unit return attrs - - @property - def current_power_w(self) -> float: - """Return the current power usage in W.""" - return self.device.power / 1000 # type: ignore [no-any-return] diff --git a/tests/components/fritzbox/__init__.py b/tests/components/fritzbox/__init__.py index 3ff4b71364e..da6bd982d9d 100644 --- a/tests/components/fritzbox/__init__.py +++ b/tests/components/fritzbox/__init__.py @@ -55,6 +55,7 @@ class FritzDeviceBinarySensorMock(FritzDeviceBaseMock): battery_level = 23 fw_version = "1.2.3" has_alarm = True + has_powermeter = False has_switch = False has_temperature_sensor = False has_thermostat = False @@ -73,6 +74,7 @@ class FritzDeviceClimateMock(FritzDeviceBaseMock): eco_temperature = 16.0 fw_version = "1.2.3" has_alarm = False + has_powermeter = False has_switch = False has_temperature_sensor = False has_thermostat = True @@ -91,6 +93,7 @@ class FritzDeviceSensorMock(FritzDeviceBaseMock): device_lock = "fake_locked_device" fw_version = "1.2.3" has_alarm = False + has_powermeter = False has_switch = False has_temperature_sensor = True has_thermostat = False @@ -107,6 +110,7 @@ class FritzDeviceSwitchMock(FritzDeviceBaseMock): energy = 1234 fw_version = "1.2.3" has_alarm = False + has_powermeter = True has_switch = True has_temperature_sensor = True has_thermostat = False diff --git a/tests/components/fritzbox/test_switch.py b/tests/components/fritzbox/test_switch.py index 4bace3834fb..cb6ae85f889 100644 --- a/tests/components/fritzbox/test_switch.py +++ b/tests/components/fritzbox/test_switch.py @@ -7,24 +7,22 @@ from requests.exceptions import HTTPError from homeassistant.components.fritzbox.const import ( ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_LOCKED, - ATTR_TEMPERATURE_UNIT, - ATTR_TOTAL_CONSUMPTION, - ATTR_TOTAL_CONSUMPTION_UNIT, DOMAIN as FB_DOMAIN, ) from homeassistant.components.sensor import ( + ATTR_LAST_RESET, ATTR_STATE_CLASS, DOMAIN as SENSOR_DOMAIN, STATE_CLASS_MEASUREMENT, ) -from homeassistant.components.switch import ATTR_CURRENT_POWER_W, DOMAIN +from homeassistant.components.switch import DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, - ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT, CONF_DEVICES, ENERGY_KILO_WATT_HOUR, + POWER_WATT, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_ON, @@ -51,14 +49,9 @@ async def test_setup(hass: HomeAssistant, fritz: Mock): state = hass.states.get(ENTITY_ID) assert state assert state.state == STATE_ON - assert state.attributes[ATTR_CURRENT_POWER_W] == 5.678 assert state.attributes[ATTR_FRIENDLY_NAME] == CONF_FAKE_NAME assert state.attributes[ATTR_STATE_DEVICE_LOCKED] == "fake_locked_device" assert state.attributes[ATTR_STATE_LOCKED] == "fake_locked" - assert state.attributes[ATTR_TEMPERATURE] == "1.23" - assert state.attributes[ATTR_TEMPERATURE_UNIT] == TEMP_CELSIUS - assert state.attributes[ATTR_TOTAL_CONSUMPTION] == "1.234" - assert state.attributes[ATTR_TOTAL_CONSUMPTION_UNIT] == ENERGY_KILO_WATT_HOUR assert ATTR_STATE_CLASS not in state.attributes state = hass.states.get(f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_temperature") @@ -70,6 +63,21 @@ async def test_setup(hass: HomeAssistant, fritz: Mock): assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == TEMP_CELSIUS assert state.attributes[ATTR_STATE_CLASS] == STATE_CLASS_MEASUREMENT + state = hass.states.get(f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_power_consumption") + assert state + assert state.state == "5.678" + assert state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Power Consumption" + assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == POWER_WATT + assert state.attributes[ATTR_STATE_CLASS] == STATE_CLASS_MEASUREMENT + + state = hass.states.get(f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_total_energy") + assert state + assert state.state == "1.234" + assert state.attributes[ATTR_LAST_RESET] == "1970-01-01T00:00:00+00:00" + assert state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Total Energy" + assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == ENERGY_KILO_WATT_HOUR + assert ATTR_STATE_CLASS not in state.attributes + async def test_turn_on(hass: HomeAssistant, fritz: Mock): """Test turn device on."""