Fritzbox enable temp sensor (#52558)
This commit is contained in:
parent
258162d933
commit
24e07bc154
14 changed files with 205 additions and 63 deletions
|
@ -6,6 +6,7 @@ from datetime import timedelta
|
||||||
from pyfritzhome import Fritzhome, FritzhomeDevice, LoginError
|
from pyfritzhome import Fritzhome, FritzhomeDevice, LoginError
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import ATTR_STATE_CLASS
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_DEVICE_CLASS,
|
ATTR_DEVICE_CLASS,
|
||||||
|
@ -16,10 +17,12 @@ from homeassistant.const import (
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_STOP,
|
||||||
|
TEMP_CELSIUS,
|
||||||
)
|
)
|
||||||
from homeassistant.core import Event, HomeAssistant
|
from homeassistant.core import Event, HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
|
from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries
|
||||||
from homeassistant.helpers.update_coordinator import (
|
from homeassistant.helpers.update_coordinator import (
|
||||||
CoordinatorEntity,
|
CoordinatorEntity,
|
||||||
DataUpdateCoordinator,
|
DataUpdateCoordinator,
|
||||||
|
@ -81,6 +84,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
|
def _update_unique_id(entry: RegistryEntry) -> dict[str, str] | None:
|
||||||
|
"""Update unique ID of entity entry."""
|
||||||
|
if (
|
||||||
|
entry.unit_of_measurement == TEMP_CELSIUS
|
||||||
|
and "_temperature" not in entry.unique_id
|
||||||
|
):
|
||||||
|
new_unique_id = f"{entry.unique_id}_temperature"
|
||||||
|
LOGGER.info(
|
||||||
|
"Migrating unique_id [%s] to [%s]", entry.unique_id, new_unique_id
|
||||||
|
)
|
||||||
|
return {"new_unique_id": new_unique_id}
|
||||||
|
return None
|
||||||
|
|
||||||
|
await async_migrate_entries(hass, entry.entry_id, _update_unique_id)
|
||||||
|
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||||
|
|
||||||
def logout_fritzbox(event: Event) -> None:
|
def logout_fritzbox(event: Event) -> None:
|
||||||
|
@ -123,6 +141,7 @@ class FritzBoxEntity(CoordinatorEntity):
|
||||||
self._unique_id = entity_info[ATTR_ENTITY_ID]
|
self._unique_id = entity_info[ATTR_ENTITY_ID]
|
||||||
self._unit_of_measurement = entity_info[ATTR_UNIT_OF_MEASUREMENT]
|
self._unit_of_measurement = entity_info[ATTR_UNIT_OF_MEASUREMENT]
|
||||||
self._device_class = entity_info[ATTR_DEVICE_CLASS]
|
self._device_class = entity_info[ATTR_DEVICE_CLASS]
|
||||||
|
self._attr_state_class = entity_info[ATTR_STATE_CLASS]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device(self) -> FritzhomeDevice:
|
def device(self) -> FritzhomeDevice:
|
||||||
|
|
|
@ -5,6 +5,7 @@ from homeassistant.components.binary_sensor import (
|
||||||
DEVICE_CLASS_WINDOW,
|
DEVICE_CLASS_WINDOW,
|
||||||
BinarySensorEntity,
|
BinarySensorEntity,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.sensor import ATTR_STATE_CLASS
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_DEVICE_CLASS,
|
ATTR_DEVICE_CLASS,
|
||||||
|
@ -37,6 +38,7 @@ async def async_setup_entry(
|
||||||
ATTR_ENTITY_ID: f"{device.ain}",
|
ATTR_ENTITY_ID: f"{device.ain}",
|
||||||
ATTR_UNIT_OF_MEASUREMENT: None,
|
ATTR_UNIT_OF_MEASUREMENT: None,
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_WINDOW,
|
ATTR_DEVICE_CLASS: DEVICE_CLASS_WINDOW,
|
||||||
|
ATTR_STATE_CLASS: None,
|
||||||
},
|
},
|
||||||
coordinator,
|
coordinator,
|
||||||
ain,
|
ain,
|
||||||
|
|
|
@ -13,6 +13,7 @@ from homeassistant.components.climate.const import (
|
||||||
SUPPORT_PRESET_MODE,
|
SUPPORT_PRESET_MODE,
|
||||||
SUPPORT_TARGET_TEMPERATURE,
|
SUPPORT_TARGET_TEMPERATURE,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.sensor import ATTR_STATE_CLASS
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_BATTERY_LEVEL,
|
ATTR_BATTERY_LEVEL,
|
||||||
|
@ -74,6 +75,7 @@ async def async_setup_entry(
|
||||||
ATTR_ENTITY_ID: f"{device.ain}",
|
ATTR_ENTITY_ID: f"{device.ain}",
|
||||||
ATTR_UNIT_OF_MEASUREMENT: None,
|
ATTR_UNIT_OF_MEASUREMENT: None,
|
||||||
ATTR_DEVICE_CLASS: None,
|
ATTR_DEVICE_CLASS: None,
|
||||||
|
ATTR_STATE_CLASS: None,
|
||||||
},
|
},
|
||||||
coordinator,
|
coordinator,
|
||||||
ain,
|
ain,
|
||||||
|
|
|
@ -11,6 +11,7 @@ class EntityInfo(TypedDict):
|
||||||
entity_id: str
|
entity_id: str
|
||||||
unit_of_measurement: str | None
|
unit_of_measurement: str | None
|
||||||
device_class: str | None
|
device_class: str | None
|
||||||
|
state_class: str | None
|
||||||
|
|
||||||
|
|
||||||
class ClimateExtraAttributes(TypedDict, total=False):
|
class ClimateExtraAttributes(TypedDict, total=False):
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
"""Support for AVM FRITZ!SmartHome temperature sensor only devices."""
|
"""Support for AVM FRITZ!SmartHome temperature sensor only devices."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from homeassistant.components.sensor import SensorEntity
|
from homeassistant.components.sensor import (
|
||||||
|
ATTR_STATE_CLASS,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
SensorEntity,
|
||||||
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_DEVICE_CLASS,
|
ATTR_DEVICE_CLASS,
|
||||||
|
@ -34,18 +38,15 @@ async def async_setup_entry(
|
||||||
coordinator = hass.data[FRITZBOX_DOMAIN][entry.entry_id][CONF_COORDINATOR]
|
coordinator = hass.data[FRITZBOX_DOMAIN][entry.entry_id][CONF_COORDINATOR]
|
||||||
|
|
||||||
for ain, device in coordinator.data.items():
|
for ain, device in coordinator.data.items():
|
||||||
if (
|
if device.has_temperature_sensor and not device.has_thermostat:
|
||||||
device.has_temperature_sensor
|
|
||||||
and not device.has_switch
|
|
||||||
and not device.has_thermostat
|
|
||||||
):
|
|
||||||
entities.append(
|
entities.append(
|
||||||
FritzBoxTempSensor(
|
FritzBoxTempSensor(
|
||||||
{
|
{
|
||||||
ATTR_NAME: f"{device.name}",
|
ATTR_NAME: f"{device.name} Temperature",
|
||||||
ATTR_ENTITY_ID: f"{device.ain}",
|
ATTR_ENTITY_ID: f"{device.ain}_temperature",
|
||||||
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS,
|
ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS,
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
},
|
},
|
||||||
coordinator,
|
coordinator,
|
||||||
ain,
|
ain,
|
||||||
|
@ -60,6 +61,7 @@ async def async_setup_entry(
|
||||||
ATTR_ENTITY_ID: f"{device.ain}_battery",
|
ATTR_ENTITY_ID: f"{device.ain}_battery",
|
||||||
ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE,
|
ATTR_UNIT_OF_MEASUREMENT: PERCENTAGE,
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY,
|
ATTR_DEVICE_CLASS: DEVICE_CLASS_BATTERY,
|
||||||
|
ATTR_STATE_CLASS: None,
|
||||||
},
|
},
|
||||||
coordinator,
|
coordinator,
|
||||||
ain,
|
ain,
|
||||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import ATTR_STATE_CLASS
|
||||||
from homeassistant.components.switch import SwitchEntity
|
from homeassistant.components.switch import SwitchEntity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
@ -50,6 +51,7 @@ async def async_setup_entry(
|
||||||
ATTR_ENTITY_ID: f"{device.ain}",
|
ATTR_ENTITY_ID: f"{device.ain}",
|
||||||
ATTR_UNIT_OF_MEASUREMENT: None,
|
ATTR_UNIT_OF_MEASUREMENT: None,
|
||||||
ATTR_DEVICE_CLASS: None,
|
ATTR_DEVICE_CLASS: None,
|
||||||
|
ATTR_STATE_CLASS: None,
|
||||||
},
|
},
|
||||||
coordinator,
|
coordinator,
|
||||||
ain,
|
ain,
|
||||||
|
|
|
@ -5,22 +5,16 @@ from typing import Any
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
from homeassistant.components.fritzbox.const import DOMAIN
|
from homeassistant.components.fritzbox.const import DOMAIN
|
||||||
from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from .const import (
|
||||||
|
CONF_FAKE_AIN,
|
||||||
|
CONF_FAKE_MANUFACTURER,
|
||||||
|
CONF_FAKE_NAME,
|
||||||
|
CONF_FAKE_PRODUCTNAME,
|
||||||
|
)
|
||||||
|
|
||||||
MOCK_CONFIG = {
|
from tests.common import MockConfigEntry
|
||||||
DOMAIN: {
|
|
||||||
CONF_DEVICES: [
|
|
||||||
{
|
|
||||||
CONF_HOST: "fake_host",
|
|
||||||
CONF_PASSWORD: "fake_pass",
|
|
||||||
CONF_USERNAME: "fake_user",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def setup_config_entry(
|
async def setup_config_entry(
|
||||||
|
@ -45,27 +39,32 @@ async def setup_config_entry(
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class FritzDeviceBinarySensorMock(Mock):
|
class FritzDeviceBaseMock(Mock):
|
||||||
|
"""base mock of a AVM Fritz!Box binary sensor device."""
|
||||||
|
|
||||||
|
ain = CONF_FAKE_AIN
|
||||||
|
manufacturer = CONF_FAKE_MANUFACTURER
|
||||||
|
name = CONF_FAKE_NAME
|
||||||
|
productname = CONF_FAKE_PRODUCTNAME
|
||||||
|
|
||||||
|
|
||||||
|
class FritzDeviceBinarySensorMock(FritzDeviceBaseMock):
|
||||||
"""Mock of a AVM Fritz!Box binary sensor device."""
|
"""Mock of a AVM Fritz!Box binary sensor device."""
|
||||||
|
|
||||||
ain = "fake_ain"
|
|
||||||
alert_state = "fake_state"
|
alert_state = "fake_state"
|
||||||
|
battery_level = 23
|
||||||
fw_version = "1.2.3"
|
fw_version = "1.2.3"
|
||||||
has_alarm = True
|
has_alarm = True
|
||||||
has_switch = False
|
has_switch = False
|
||||||
has_temperature_sensor = False
|
has_temperature_sensor = False
|
||||||
has_thermostat = False
|
has_thermostat = False
|
||||||
manufacturer = "fake_manufacturer"
|
|
||||||
name = "fake_name"
|
|
||||||
present = True
|
present = True
|
||||||
productname = "fake_productname"
|
|
||||||
|
|
||||||
|
|
||||||
class FritzDeviceClimateMock(Mock):
|
class FritzDeviceClimateMock(FritzDeviceBaseMock):
|
||||||
"""Mock of a AVM Fritz!Box climate device."""
|
"""Mock of a AVM Fritz!Box climate device."""
|
||||||
|
|
||||||
actual_temperature = 18.0
|
actual_temperature = 18.0
|
||||||
ain = "fake_ain"
|
|
||||||
alert_state = "fake_state"
|
alert_state = "fake_state"
|
||||||
battery_level = 23
|
battery_level = 23
|
||||||
battery_low = True
|
battery_low = True
|
||||||
|
@ -79,19 +78,15 @@ class FritzDeviceClimateMock(Mock):
|
||||||
has_thermostat = True
|
has_thermostat = True
|
||||||
holiday_active = "fake_holiday"
|
holiday_active = "fake_holiday"
|
||||||
lock = "fake_locked"
|
lock = "fake_locked"
|
||||||
manufacturer = "fake_manufacturer"
|
|
||||||
name = "fake_name"
|
|
||||||
present = True
|
present = True
|
||||||
productname = "fake_productname"
|
|
||||||
summer_active = "fake_summer"
|
summer_active = "fake_summer"
|
||||||
target_temperature = 19.5
|
target_temperature = 19.5
|
||||||
window_open = "fake_window"
|
window_open = "fake_window"
|
||||||
|
|
||||||
|
|
||||||
class FritzDeviceSensorMock(Mock):
|
class FritzDeviceSensorMock(FritzDeviceBaseMock):
|
||||||
"""Mock of a AVM Fritz!Box sensor device."""
|
"""Mock of a AVM Fritz!Box sensor device."""
|
||||||
|
|
||||||
ain = "fake_ain"
|
|
||||||
battery_level = 23
|
battery_level = 23
|
||||||
device_lock = "fake_locked_device"
|
device_lock = "fake_locked_device"
|
||||||
fw_version = "1.2.3"
|
fw_version = "1.2.3"
|
||||||
|
@ -100,17 +95,14 @@ class FritzDeviceSensorMock(Mock):
|
||||||
has_temperature_sensor = True
|
has_temperature_sensor = True
|
||||||
has_thermostat = False
|
has_thermostat = False
|
||||||
lock = "fake_locked"
|
lock = "fake_locked"
|
||||||
manufacturer = "fake_manufacturer"
|
|
||||||
name = "fake_name"
|
|
||||||
present = True
|
present = True
|
||||||
productname = "fake_productname"
|
|
||||||
temperature = 1.23
|
temperature = 1.23
|
||||||
|
|
||||||
|
|
||||||
class FritzDeviceSwitchMock(Mock):
|
class FritzDeviceSwitchMock(FritzDeviceBaseMock):
|
||||||
"""Mock of a AVM Fritz!Box switch device."""
|
"""Mock of a AVM Fritz!Box switch device."""
|
||||||
|
|
||||||
ain = "fake_ain"
|
battery_level = None
|
||||||
device_lock = "fake_locked_device"
|
device_lock = "fake_locked_device"
|
||||||
energy = 1234
|
energy = 1234
|
||||||
fw_version = "1.2.3"
|
fw_version = "1.2.3"
|
||||||
|
@ -120,9 +112,6 @@ class FritzDeviceSwitchMock(Mock):
|
||||||
has_thermostat = False
|
has_thermostat = False
|
||||||
switch_state = "fake_state"
|
switch_state = "fake_state"
|
||||||
lock = "fake_locked"
|
lock = "fake_locked"
|
||||||
manufacturer = "fake_manufacturer"
|
|
||||||
name = "fake_name"
|
|
||||||
power = 5678
|
power = 5678
|
||||||
present = True
|
present = True
|
||||||
productname = "fake_productname"
|
temperature = 1.23
|
||||||
temperature = 135
|
|
||||||
|
|
20
tests/components/fritzbox/const.py
Normal file
20
tests/components/fritzbox/const.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
"""Constants for fritzbox tests."""
|
||||||
|
from homeassistant.components.fritzbox.const import DOMAIN
|
||||||
|
from homeassistant.const import CONF_DEVICES, CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||||
|
|
||||||
|
MOCK_CONFIG = {
|
||||||
|
DOMAIN: {
|
||||||
|
CONF_DEVICES: [
|
||||||
|
{
|
||||||
|
CONF_HOST: "fake_host",
|
||||||
|
CONF_PASSWORD: "fake_pass",
|
||||||
|
CONF_USERNAME: "fake_user",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CONF_FAKE_NAME = "fake_name"
|
||||||
|
CONF_FAKE_AIN = "fake_ain"
|
||||||
|
CONF_FAKE_MANUFACTURER = "fake_manufacturer"
|
||||||
|
CONF_FAKE_PRODUCTNAME = "fake_productname"
|
|
@ -7,21 +7,25 @@ from requests.exceptions import HTTPError
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import DOMAIN
|
from homeassistant.components.binary_sensor import DOMAIN
|
||||||
from homeassistant.components.fritzbox.const import DOMAIN as FB_DOMAIN
|
from homeassistant.components.fritzbox.const import DOMAIN as FB_DOMAIN
|
||||||
|
from homeassistant.components.sensor import ATTR_STATE_CLASS, DOMAIN as SENSOR_DOMAIN
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_DEVICE_CLASS,
|
ATTR_DEVICE_CLASS,
|
||||||
ATTR_FRIENDLY_NAME,
|
ATTR_FRIENDLY_NAME,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
CONF_DEVICES,
|
CONF_DEVICES,
|
||||||
|
PERCENTAGE,
|
||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from . import MOCK_CONFIG, FritzDeviceBinarySensorMock, setup_config_entry
|
from . import FritzDeviceBinarySensorMock, setup_config_entry
|
||||||
|
from .const import CONF_FAKE_NAME, MOCK_CONFIG
|
||||||
|
|
||||||
from tests.common import async_fire_time_changed
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
ENTITY_ID = f"{DOMAIN}.fake_name"
|
ENTITY_ID = f"{DOMAIN}.{CONF_FAKE_NAME}"
|
||||||
|
|
||||||
|
|
||||||
async def test_setup(hass: HomeAssistant, fritz: Mock):
|
async def test_setup(hass: HomeAssistant, fritz: Mock):
|
||||||
|
@ -34,8 +38,16 @@ async def test_setup(hass: HomeAssistant, fritz: Mock):
|
||||||
state = hass.states.get(ENTITY_ID)
|
state = hass.states.get(ENTITY_ID)
|
||||||
assert state
|
assert state
|
||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
assert state.attributes[ATTR_FRIENDLY_NAME] == "fake_name"
|
assert state.attributes[ATTR_FRIENDLY_NAME] == CONF_FAKE_NAME
|
||||||
assert state.attributes[ATTR_DEVICE_CLASS] == "window"
|
assert state.attributes[ATTR_DEVICE_CLASS] == "window"
|
||||||
|
assert ATTR_STATE_CLASS not in state.attributes
|
||||||
|
|
||||||
|
state = hass.states.get(f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_battery")
|
||||||
|
assert state
|
||||||
|
assert state.state == "23"
|
||||||
|
assert state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Battery"
|
||||||
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == PERCENTAGE
|
||||||
|
assert ATTR_STATE_CLASS not in state.attributes
|
||||||
|
|
||||||
|
|
||||||
async def test_is_off(hass: HomeAssistant, fritz: Mock):
|
async def test_is_off(hass: HomeAssistant, fritz: Mock):
|
||||||
|
|
|
@ -30,21 +30,25 @@ from homeassistant.components.fritzbox.const import (
|
||||||
ATTR_STATE_WINDOW_OPEN,
|
ATTR_STATE_WINDOW_OPEN,
|
||||||
DOMAIN as FB_DOMAIN,
|
DOMAIN as FB_DOMAIN,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.sensor import ATTR_STATE_CLASS, DOMAIN as SENSOR_DOMAIN
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_BATTERY_LEVEL,
|
ATTR_BATTERY_LEVEL,
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
ATTR_FRIENDLY_NAME,
|
ATTR_FRIENDLY_NAME,
|
||||||
ATTR_TEMPERATURE,
|
ATTR_TEMPERATURE,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
CONF_DEVICES,
|
CONF_DEVICES,
|
||||||
|
PERCENTAGE,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from . import MOCK_CONFIG, FritzDeviceClimateMock, setup_config_entry
|
from . import FritzDeviceClimateMock, setup_config_entry
|
||||||
|
from .const import CONF_FAKE_NAME, MOCK_CONFIG
|
||||||
|
|
||||||
from tests.common import async_fire_time_changed
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
ENTITY_ID = f"{DOMAIN}.fake_name"
|
ENTITY_ID = f"{DOMAIN}.{CONF_FAKE_NAME}"
|
||||||
|
|
||||||
|
|
||||||
async def test_setup(hass: HomeAssistant, fritz: Mock):
|
async def test_setup(hass: HomeAssistant, fritz: Mock):
|
||||||
|
@ -58,7 +62,7 @@ async def test_setup(hass: HomeAssistant, fritz: Mock):
|
||||||
assert state
|
assert state
|
||||||
assert state.attributes[ATTR_BATTERY_LEVEL] == 23
|
assert state.attributes[ATTR_BATTERY_LEVEL] == 23
|
||||||
assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 18
|
assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 18
|
||||||
assert state.attributes[ATTR_FRIENDLY_NAME] == "fake_name"
|
assert state.attributes[ATTR_FRIENDLY_NAME] == CONF_FAKE_NAME
|
||||||
assert state.attributes[ATTR_HVAC_MODES] == [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
assert state.attributes[ATTR_HVAC_MODES] == [HVAC_MODE_HEAT, HVAC_MODE_OFF]
|
||||||
assert state.attributes[ATTR_MAX_TEMP] == 28
|
assert state.attributes[ATTR_MAX_TEMP] == 28
|
||||||
assert state.attributes[ATTR_MIN_TEMP] == 8
|
assert state.attributes[ATTR_MIN_TEMP] == 8
|
||||||
|
@ -71,8 +75,16 @@ async def test_setup(hass: HomeAssistant, fritz: Mock):
|
||||||
assert state.attributes[ATTR_STATE_SUMMER_MODE] == "fake_summer"
|
assert state.attributes[ATTR_STATE_SUMMER_MODE] == "fake_summer"
|
||||||
assert state.attributes[ATTR_STATE_WINDOW_OPEN] == "fake_window"
|
assert state.attributes[ATTR_STATE_WINDOW_OPEN] == "fake_window"
|
||||||
assert state.attributes[ATTR_TEMPERATURE] == 19.5
|
assert state.attributes[ATTR_TEMPERATURE] == 19.5
|
||||||
|
assert ATTR_STATE_CLASS not in state.attributes
|
||||||
assert state.state == HVAC_MODE_HEAT
|
assert state.state == HVAC_MODE_HEAT
|
||||||
|
|
||||||
|
state = hass.states.get(f"{SENSOR_DOMAIN}.{CONF_FAKE_NAME}_battery")
|
||||||
|
assert state
|
||||||
|
assert state.state == "23"
|
||||||
|
assert state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Battery"
|
||||||
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == PERCENTAGE
|
||||||
|
assert ATTR_STATE_CLASS not in state.attributes
|
||||||
|
|
||||||
|
|
||||||
async def test_target_temperature_on(hass: HomeAssistant, fritz: Mock):
|
async def test_target_temperature_on(hass: HomeAssistant, fritz: Mock):
|
||||||
"""Test turn device on."""
|
"""Test turn device on."""
|
||||||
|
|
|
@ -21,14 +21,14 @@ from homeassistant.data_entry_flow import (
|
||||||
RESULT_TYPE_FORM,
|
RESULT_TYPE_FORM,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import MOCK_CONFIG
|
from .const import CONF_FAKE_NAME, MOCK_CONFIG
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
MOCK_USER_DATA = MOCK_CONFIG[DOMAIN][CONF_DEVICES][0]
|
MOCK_USER_DATA = MOCK_CONFIG[DOMAIN][CONF_DEVICES][0]
|
||||||
MOCK_SSDP_DATA = {
|
MOCK_SSDP_DATA = {
|
||||||
ATTR_SSDP_LOCATION: "https://fake_host:12345/test",
|
ATTR_SSDP_LOCATION: "https://fake_host:12345/test",
|
||||||
ATTR_UPNP_FRIENDLY_NAME: "fake_name",
|
ATTR_UPNP_FRIENDLY_NAME: CONF_FAKE_NAME,
|
||||||
ATTR_UPNP_UDN: "uuid:only-a-test",
|
ATTR_UPNP_UDN: "uuid:only-a-test",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ async def test_ssdp(hass: HomeAssistant, fritz: Mock):
|
||||||
user_input={CONF_PASSWORD: "fake_pass", CONF_USERNAME: "fake_user"},
|
user_input={CONF_PASSWORD: "fake_pass", CONF_USERNAME: "fake_user"},
|
||||||
)
|
)
|
||||||
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||||
assert result["title"] == "fake_name"
|
assert result["title"] == CONF_FAKE_NAME
|
||||||
assert result["data"][CONF_HOST] == "fake_host"
|
assert result["data"][CONF_HOST] == "fake_host"
|
||||||
assert result["data"][CONF_PASSWORD] == "fake_pass"
|
assert result["data"][CONF_PASSWORD] == "fake_pass"
|
||||||
assert result["data"][CONF_USERNAME] == "fake_user"
|
assert result["data"][CONF_USERNAME] == "fake_user"
|
||||||
|
|
|
@ -7,6 +7,7 @@ from pyfritzhome import LoginError
|
||||||
from requests.exceptions import HTTPError
|
from requests.exceptions import HTTPError
|
||||||
|
|
||||||
from homeassistant.components.fritzbox.const import DOMAIN as FB_DOMAIN
|
from homeassistant.components.fritzbox.const import DOMAIN as FB_DOMAIN
|
||||||
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
@ -15,10 +16,13 @@ from homeassistant.const import (
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
|
TEMP_CELSIUS,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
from . import MOCK_CONFIG, FritzDeviceSwitchMock, setup_config_entry
|
from . import FritzDeviceSwitchMock, setup_config_entry
|
||||||
|
from .const import CONF_FAKE_AIN, CONF_FAKE_NAME, MOCK_CONFIG
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
@ -38,6 +42,58 @@ async def test_setup(hass: HomeAssistant, fritz: Mock):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update_unique_id(hass: HomeAssistant, fritz: Mock):
|
||||||
|
"""Test unique_id update of integration."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=FB_DOMAIN,
|
||||||
|
data=MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0],
|
||||||
|
unique_id="any",
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
entity = entity_registry.async_get_or_create(
|
||||||
|
SENSOR_DOMAIN,
|
||||||
|
FB_DOMAIN,
|
||||||
|
CONF_FAKE_AIN,
|
||||||
|
unit_of_measurement=TEMP_CELSIUS,
|
||||||
|
config_entry=entry,
|
||||||
|
)
|
||||||
|
assert entity.unique_id == CONF_FAKE_AIN
|
||||||
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entity_migrated = entity_registry.async_get(entity.entity_id)
|
||||||
|
assert entity_migrated
|
||||||
|
assert entity_migrated.unique_id == f"{CONF_FAKE_AIN}_temperature"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update_unique_id_no_change(hass: HomeAssistant, fritz: Mock):
|
||||||
|
"""Test unique_id is not updated of integration."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=FB_DOMAIN,
|
||||||
|
data=MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0],
|
||||||
|
unique_id="any",
|
||||||
|
)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
entity = entity_registry.async_get_or_create(
|
||||||
|
SENSOR_DOMAIN,
|
||||||
|
FB_DOMAIN,
|
||||||
|
f"{CONF_FAKE_AIN}_temperature",
|
||||||
|
unit_of_measurement=TEMP_CELSIUS,
|
||||||
|
config_entry=entry,
|
||||||
|
)
|
||||||
|
assert entity.unique_id == f"{CONF_FAKE_AIN}_temperature"
|
||||||
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entity_migrated = entity_registry.async_get(entity.entity_id)
|
||||||
|
assert entity_migrated
|
||||||
|
assert entity_migrated.unique_id == f"{CONF_FAKE_AIN}_temperature"
|
||||||
|
|
||||||
|
|
||||||
async def test_coordinator_update_after_reboot(hass: HomeAssistant, fritz: Mock):
|
async def test_coordinator_update_after_reboot(hass: HomeAssistant, fritz: Mock):
|
||||||
"""Test coordinator after reboot."""
|
"""Test coordinator after reboot."""
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
|
@ -74,7 +130,7 @@ async def test_coordinator_update_after_password_change(
|
||||||
async def test_unload_remove(hass: HomeAssistant, fritz: Mock):
|
async def test_unload_remove(hass: HomeAssistant, fritz: Mock):
|
||||||
"""Test unload and remove of integration."""
|
"""Test unload and remove of integration."""
|
||||||
fritz().get_devices.return_value = [FritzDeviceSwitchMock()]
|
fritz().get_devices.return_value = [FritzDeviceSwitchMock()]
|
||||||
entity_id = f"{SWITCH_DOMAIN}.fake_name"
|
entity_id = f"{SWITCH_DOMAIN}.{CONF_FAKE_NAME}"
|
||||||
|
|
||||||
entry = MockConfigEntry(
|
entry = MockConfigEntry(
|
||||||
domain=FB_DOMAIN,
|
domain=FB_DOMAIN,
|
||||||
|
|
|
@ -9,7 +9,11 @@ from homeassistant.components.fritzbox.const import (
|
||||||
ATTR_STATE_LOCKED,
|
ATTR_STATE_LOCKED,
|
||||||
DOMAIN as FB_DOMAIN,
|
DOMAIN as FB_DOMAIN,
|
||||||
)
|
)
|
||||||
from homeassistant.components.sensor import DOMAIN
|
from homeassistant.components.sensor import (
|
||||||
|
ATTR_STATE_CLASS,
|
||||||
|
DOMAIN,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_FRIENDLY_NAME,
|
ATTR_FRIENDLY_NAME,
|
||||||
ATTR_UNIT_OF_MEASUREMENT,
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
|
@ -20,11 +24,12 @@ from homeassistant.const import (
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from . import MOCK_CONFIG, FritzDeviceSensorMock, setup_config_entry
|
from . import FritzDeviceSensorMock, setup_config_entry
|
||||||
|
from .const import CONF_FAKE_NAME, MOCK_CONFIG
|
||||||
|
|
||||||
from tests.common import async_fire_time_changed
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
ENTITY_ID = f"{DOMAIN}.fake_name"
|
ENTITY_ID = f"{DOMAIN}.{CONF_FAKE_NAME}"
|
||||||
|
|
||||||
|
|
||||||
async def test_setup(hass: HomeAssistant, fritz: Mock):
|
async def test_setup(hass: HomeAssistant, fritz: Mock):
|
||||||
|
@ -33,20 +38,23 @@ async def test_setup(hass: HomeAssistant, fritz: Mock):
|
||||||
assert await setup_config_entry(
|
assert await setup_config_entry(
|
||||||
hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz
|
hass, MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0], ENTITY_ID, device, fritz
|
||||||
)
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get(ENTITY_ID)
|
state = hass.states.get(f"{ENTITY_ID}_temperature")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == "1.23"
|
assert state.state == "1.23"
|
||||||
assert state.attributes[ATTR_FRIENDLY_NAME] == "fake_name"
|
assert state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Temperature"
|
||||||
assert state.attributes[ATTR_STATE_DEVICE_LOCKED] == "fake_locked_device"
|
assert state.attributes[ATTR_STATE_DEVICE_LOCKED] == "fake_locked_device"
|
||||||
assert state.attributes[ATTR_STATE_LOCKED] == "fake_locked"
|
assert state.attributes[ATTR_STATE_LOCKED] == "fake_locked"
|
||||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == TEMP_CELSIUS
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == TEMP_CELSIUS
|
||||||
|
assert state.attributes[ATTR_STATE_CLASS] == STATE_CLASS_MEASUREMENT
|
||||||
|
|
||||||
state = hass.states.get(f"{ENTITY_ID}_battery")
|
state = hass.states.get(f"{ENTITY_ID}_battery")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == "23"
|
assert state.state == "23"
|
||||||
assert state.attributes[ATTR_FRIENDLY_NAME] == "fake_name Battery"
|
assert state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Battery"
|
||||||
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == PERCENTAGE
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == PERCENTAGE
|
||||||
|
assert ATTR_STATE_CLASS not in state.attributes
|
||||||
|
|
||||||
|
|
||||||
async def test_update(hass: HomeAssistant, fritz: Mock):
|
async def test_update(hass: HomeAssistant, fritz: Mock):
|
||||||
|
|
|
@ -12,11 +12,17 @@ from homeassistant.components.fritzbox.const import (
|
||||||
ATTR_TOTAL_CONSUMPTION_UNIT,
|
ATTR_TOTAL_CONSUMPTION_UNIT,
|
||||||
DOMAIN as FB_DOMAIN,
|
DOMAIN as FB_DOMAIN,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.sensor import (
|
||||||
|
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 ATTR_CURRENT_POWER_W, DOMAIN
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
ATTR_FRIENDLY_NAME,
|
ATTR_FRIENDLY_NAME,
|
||||||
ATTR_TEMPERATURE,
|
ATTR_TEMPERATURE,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
CONF_DEVICES,
|
CONF_DEVICES,
|
||||||
ENERGY_KILO_WATT_HOUR,
|
ENERGY_KILO_WATT_HOUR,
|
||||||
SERVICE_TURN_OFF,
|
SERVICE_TURN_OFF,
|
||||||
|
@ -27,11 +33,12 @@ from homeassistant.const import (
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from . import MOCK_CONFIG, FritzDeviceSwitchMock, setup_config_entry
|
from . import FritzDeviceSwitchMock, setup_config_entry
|
||||||
|
from .const import CONF_FAKE_NAME, MOCK_CONFIG
|
||||||
|
|
||||||
from tests.common import async_fire_time_changed
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
ENTITY_ID = f"{DOMAIN}.fake_name"
|
ENTITY_ID = f"{DOMAIN}.{CONF_FAKE_NAME}"
|
||||||
|
|
||||||
|
|
||||||
async def test_setup(hass: HomeAssistant, fritz: Mock):
|
async def test_setup(hass: HomeAssistant, fritz: Mock):
|
||||||
|
@ -45,13 +52,23 @@ async def test_setup(hass: HomeAssistant, fritz: Mock):
|
||||||
assert state
|
assert state
|
||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
assert state.attributes[ATTR_CURRENT_POWER_W] == 5.678
|
assert state.attributes[ATTR_CURRENT_POWER_W] == 5.678
|
||||||
assert state.attributes[ATTR_FRIENDLY_NAME] == "fake_name"
|
assert state.attributes[ATTR_FRIENDLY_NAME] == CONF_FAKE_NAME
|
||||||
assert state.attributes[ATTR_STATE_DEVICE_LOCKED] == "fake_locked_device"
|
assert state.attributes[ATTR_STATE_DEVICE_LOCKED] == "fake_locked_device"
|
||||||
assert state.attributes[ATTR_STATE_LOCKED] == "fake_locked"
|
assert state.attributes[ATTR_STATE_LOCKED] == "fake_locked"
|
||||||
assert state.attributes[ATTR_TEMPERATURE] == "135"
|
assert state.attributes[ATTR_TEMPERATURE] == "1.23"
|
||||||
assert state.attributes[ATTR_TEMPERATURE_UNIT] == TEMP_CELSIUS
|
assert state.attributes[ATTR_TEMPERATURE_UNIT] == TEMP_CELSIUS
|
||||||
assert state.attributes[ATTR_TOTAL_CONSUMPTION] == "1.234"
|
assert state.attributes[ATTR_TOTAL_CONSUMPTION] == "1.234"
|
||||||
assert state.attributes[ATTR_TOTAL_CONSUMPTION_UNIT] == ENERGY_KILO_WATT_HOUR
|
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")
|
||||||
|
assert state
|
||||||
|
assert state.state == "1.23"
|
||||||
|
assert state.attributes[ATTR_FRIENDLY_NAME] == f"{CONF_FAKE_NAME} Temperature"
|
||||||
|
assert state.attributes[ATTR_STATE_DEVICE_LOCKED] == "fake_locked_device"
|
||||||
|
assert state.attributes[ATTR_STATE_LOCKED] == "fake_locked"
|
||||||
|
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == TEMP_CELSIUS
|
||||||
|
assert state.attributes[ATTR_STATE_CLASS] == STATE_CLASS_MEASUREMENT
|
||||||
|
|
||||||
|
|
||||||
async def test_turn_on(hass: HomeAssistant, fritz: Mock):
|
async def test_turn_on(hass: HomeAssistant, fritz: Mock):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue