Add strict typing to fritzbox (#50486)
* enable strict typing * apply suggestions * set defaults for FritzboxConfigFlow * improvements and suggestions * another suggestion * tweaks * tweaks
This commit is contained in:
parent
d37a3cded0
commit
25b2fd0cee
12 changed files with 195 additions and 107 deletions
|
@ -13,6 +13,7 @@ homeassistant.components.camera.*
|
|||
homeassistant.components.cover.*
|
||||
homeassistant.components.device_automation.*
|
||||
homeassistant.components.elgato.*
|
||||
homeassistant.components.fritzbox.*
|
||||
homeassistant.components.frontend.*
|
||||
homeassistant.components.geo_location.*
|
||||
homeassistant.components.gios.*
|
||||
|
|
|
@ -17,14 +17,16 @@ from homeassistant.const import (
|
|||
CONF_USERNAME,
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
)
|
||||
|
||||
from .const import CONF_CONNECTIONS, CONF_COORDINATOR, DOMAIN, LOGGER, PLATFORMS
|
||||
from .model import EntityInfo
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
@ -63,7 +65,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
data[device.ain] = device
|
||||
return data
|
||||
|
||||
async def async_update_coordinator():
|
||||
async def async_update_coordinator() -> dict[str, FritzhomeDevice]:
|
||||
"""Fetch all device data."""
|
||||
return await hass.async_add_executor_job(_update_fritz_devices)
|
||||
|
||||
|
@ -81,7 +83,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
|
||||
def logout_fritzbox(event):
|
||||
def logout_fritzbox(event: Event) -> None:
|
||||
"""Close connections to this fritzbox."""
|
||||
fritz.logout()
|
||||
|
||||
|
@ -109,10 +111,10 @@ class FritzBoxEntity(CoordinatorEntity):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
entity_info: dict[str, str],
|
||||
coordinator: DataUpdateCoordinator,
|
||||
entity_info: EntityInfo,
|
||||
coordinator: DataUpdateCoordinator[dict[str, FritzhomeDevice]],
|
||||
ain: str,
|
||||
):
|
||||
) -> None:
|
||||
"""Initialize the FritzBox entity."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
|
@ -128,7 +130,7 @@ class FritzBoxEntity(CoordinatorEntity):
|
|||
return self.coordinator.data[self.ain]
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return device specific attributes."""
|
||||
return {
|
||||
"name": self.device.name,
|
||||
|
@ -139,21 +141,21 @@ class FritzBoxEntity(CoordinatorEntity):
|
|||
}
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
def unique_id(self) -> str:
|
||||
"""Return the unique ID of the device."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def name(self) -> str:
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
def unit_of_measurement(self) -> str | None:
|
||||
"""Return the unit of measurement."""
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
def device_class(self) -> str | None:
|
||||
"""Return the device class."""
|
||||
return self._device_class
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"""Support for Fritzbox binary sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
DEVICE_CLASS_WINDOW,
|
||||
BinarySensorEntity,
|
||||
|
@ -21,7 +23,7 @@ async def async_setup_entry(
|
|||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up the FRITZ!SmartHome binary sensor from ConfigEntry."""
|
||||
entities = []
|
||||
entities: list[FritzboxBinarySensor] = []
|
||||
coordinator = hass.data[FRITZBOX_DOMAIN][entry.entry_id][CONF_COORDINATOR]
|
||||
|
||||
for ain, device in coordinator.data.items():
|
||||
|
@ -48,8 +50,8 @@ class FritzboxBinarySensor(FritzBoxEntity, BinarySensorEntity):
|
|||
"""Representation of a binary FRITZ!SmartHome device."""
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if sensor is on."""
|
||||
if not self.device.present:
|
||||
return False
|
||||
return self.device.alert_state
|
||||
return self.device.alert_state # type: ignore [no-any-return]
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
"""Support for AVM FRITZ!SmartHome thermostate devices."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.climate import ClimateEntity
|
||||
from homeassistant.components.climate.const import (
|
||||
ATTR_HVAC_MODE,
|
||||
|
@ -34,6 +38,7 @@ from .const import (
|
|||
CONF_COORDINATOR,
|
||||
DOMAIN as FRITZBOX_DOMAIN,
|
||||
)
|
||||
from .model import ClimateExtraAttributes
|
||||
|
||||
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE
|
||||
|
||||
|
@ -55,7 +60,7 @@ async def async_setup_entry(
|
|||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up the FRITZ!SmartHome thermostat from ConfigEntry."""
|
||||
entities = []
|
||||
entities: list[FritzboxThermostat] = []
|
||||
coordinator = hass.data[FRITZBOX_DOMAIN][entry.entry_id][CONF_COORDINATOR]
|
||||
|
||||
for ain, device in coordinator.data.items():
|
||||
|
@ -82,53 +87,53 @@ class FritzboxThermostat(FritzBoxEntity, ClimateEntity):
|
|||
"""The thermostat class for FRITZ!SmartHome thermostates."""
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
def supported_features(self) -> int:
|
||||
"""Return the list of supported features."""
|
||||
return SUPPORT_FLAGS
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
def available(self) -> bool:
|
||||
"""Return if thermostat is available."""
|
||||
return self.device.present
|
||||
return self.device.present # type: ignore [no-any-return]
|
||||
|
||||
@property
|
||||
def temperature_unit(self):
|
||||
def temperature_unit(self) -> str:
|
||||
"""Return the unit of measurement that is used."""
|
||||
return TEMP_CELSIUS
|
||||
|
||||
@property
|
||||
def precision(self):
|
||||
def precision(self) -> float:
|
||||
"""Return precision 0.5."""
|
||||
return PRECISION_HALVES
|
||||
|
||||
@property
|
||||
def current_temperature(self):
|
||||
def current_temperature(self) -> float:
|
||||
"""Return the current temperature."""
|
||||
return self.device.actual_temperature
|
||||
return self.device.actual_temperature # type: ignore [no-any-return]
|
||||
|
||||
@property
|
||||
def target_temperature(self):
|
||||
def target_temperature(self) -> float:
|
||||
"""Return the temperature we try to reach."""
|
||||
if self.device.target_temperature == ON_API_TEMPERATURE:
|
||||
return ON_REPORT_SET_TEMPERATURE
|
||||
if self.device.target_temperature == OFF_API_TEMPERATURE:
|
||||
return OFF_REPORT_SET_TEMPERATURE
|
||||
return self.device.target_temperature
|
||||
return self.device.target_temperature # type: ignore [no-any-return]
|
||||
|
||||
async def async_set_temperature(self, **kwargs):
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature."""
|
||||
if ATTR_HVAC_MODE in kwargs:
|
||||
hvac_mode = kwargs.get(ATTR_HVAC_MODE)
|
||||
if kwargs.get(ATTR_HVAC_MODE) is not None:
|
||||
hvac_mode = kwargs[ATTR_HVAC_MODE]
|
||||
await self.async_set_hvac_mode(hvac_mode)
|
||||
elif ATTR_TEMPERATURE in kwargs:
|
||||
temperature = kwargs.get(ATTR_TEMPERATURE)
|
||||
elif kwargs.get(ATTR_TEMPERATURE) is not None:
|
||||
temperature = kwargs[ATTR_TEMPERATURE]
|
||||
await self.hass.async_add_executor_job(
|
||||
self.device.set_target_temperature, temperature
|
||||
)
|
||||
await self.coordinator.async_refresh()
|
||||
|
||||
@property
|
||||
def hvac_mode(self):
|
||||
def hvac_mode(self) -> str:
|
||||
"""Return the current operation mode."""
|
||||
if (
|
||||
self.device.target_temperature == OFF_REPORT_SET_TEMPERATURE
|
||||
|
@ -139,11 +144,11 @@ class FritzboxThermostat(FritzBoxEntity, ClimateEntity):
|
|||
return HVAC_MODE_HEAT
|
||||
|
||||
@property
|
||||
def hvac_modes(self):
|
||||
def hvac_modes(self) -> list[str]:
|
||||
"""Return the list of available operation modes."""
|
||||
return OPERATION_LIST
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode):
|
||||
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
|
||||
"""Set new operation mode."""
|
||||
if hvac_mode == HVAC_MODE_OFF:
|
||||
await self.async_set_temperature(temperature=OFF_REPORT_SET_TEMPERATURE)
|
||||
|
@ -153,7 +158,7 @@ class FritzboxThermostat(FritzBoxEntity, ClimateEntity):
|
|||
)
|
||||
|
||||
@property
|
||||
def preset_mode(self):
|
||||
def preset_mode(self) -> str | None:
|
||||
"""Return current preset mode."""
|
||||
if self.device.target_temperature == self.device.comfort_temperature:
|
||||
return PRESET_COMFORT
|
||||
|
@ -162,11 +167,11 @@ class FritzboxThermostat(FritzBoxEntity, ClimateEntity):
|
|||
return None
|
||||
|
||||
@property
|
||||
def preset_modes(self):
|
||||
def preset_modes(self) -> list[str]:
|
||||
"""Return supported preset modes."""
|
||||
return [PRESET_ECO, PRESET_COMFORT]
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode):
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set preset mode."""
|
||||
if preset_mode == PRESET_COMFORT:
|
||||
await self.async_set_temperature(
|
||||
|
@ -176,19 +181,19 @@ class FritzboxThermostat(FritzBoxEntity, ClimateEntity):
|
|||
await self.async_set_temperature(temperature=self.device.eco_temperature)
|
||||
|
||||
@property
|
||||
def min_temp(self):
|
||||
def min_temp(self) -> int:
|
||||
"""Return the minimum temperature."""
|
||||
return MIN_TEMPERATURE
|
||||
|
||||
@property
|
||||
def max_temp(self):
|
||||
def max_temp(self) -> int:
|
||||
"""Return the maximum temperature."""
|
||||
return MAX_TEMPERATURE
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
def extra_state_attributes(self) -> ClimateExtraAttributes:
|
||||
"""Return the device specific state attributes."""
|
||||
attrs = {
|
||||
attrs: ClimateExtraAttributes = {
|
||||
ATTR_STATE_BATTERY_LOW: self.device.battery_low,
|
||||
ATTR_STATE_DEVICE_LOCKED: self.device.device_lock,
|
||||
ATTR_STATE_LOCKED: self.device.lock,
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
"""Config flow for AVM FRITZ!SmartHome."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from pyfritzhome import Fritzhome, LoginError
|
||||
from requests.exceptions import HTTPError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.ssdp import (
|
||||
ATTR_SSDP_LOCATION,
|
||||
ATTR_UPNP_FRIENDLY_NAME,
|
||||
ATTR_UPNP_UDN,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlow
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers.typing import DiscoveryInfoType
|
||||
|
||||
from .const import DEFAULT_HOST, DEFAULT_USERNAME, DOMAIN
|
||||
|
||||
|
@ -36,22 +41,22 @@ RESULT_NOT_SUPPORTED = "not_supported"
|
|||
RESULT_SUCCESS = "success"
|
||||
|
||||
|
||||
class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a AVM FRITZ!SmartHome config flow."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
"""Initialize flow."""
|
||||
self._entry = None
|
||||
self._host = None
|
||||
self._name = None
|
||||
self._password = None
|
||||
self._username = None
|
||||
self._entry: ConfigEntry | None = None
|
||||
self._host: str | None = None
|
||||
self._name: str | None = None
|
||||
self._password: str | None = None
|
||||
self._username: str | None = None
|
||||
|
||||
def _get_entry(self):
|
||||
def _get_entry(self, name: str) -> FlowResult:
|
||||
return self.async_create_entry(
|
||||
title=self._name,
|
||||
title=name,
|
||||
data={
|
||||
CONF_HOST: self._host,
|
||||
CONF_PASSWORD: self._password,
|
||||
|
@ -59,7 +64,8 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
},
|
||||
)
|
||||
|
||||
async def _update_entry(self):
|
||||
async def _update_entry(self) -> None:
|
||||
assert self._entry is not None
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self._entry,
|
||||
data={
|
||||
|
@ -70,7 +76,7 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
)
|
||||
await self.hass.config_entries.async_reload(self._entry.entry_id)
|
||||
|
||||
def _try_connect(self):
|
||||
def _try_connect(self) -> str:
|
||||
"""Try to connect and check auth."""
|
||||
fritzbox = Fritzhome(
|
||||
host=self._host, user=self._username, password=self._password
|
||||
|
@ -87,7 +93,9 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
except OSError:
|
||||
return RESULT_NO_DEVICES_FOUND
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle a flow initialized by the user."""
|
||||
errors = {}
|
||||
|
||||
|
@ -95,14 +103,14 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
|
||||
|
||||
self._host = user_input[CONF_HOST]
|
||||
self._name = user_input[CONF_HOST]
|
||||
self._name = str(user_input[CONF_HOST])
|
||||
self._password = user_input[CONF_PASSWORD]
|
||||
self._username = user_input[CONF_USERNAME]
|
||||
|
||||
result = await self.hass.async_add_executor_job(self._try_connect)
|
||||
|
||||
if result == RESULT_SUCCESS:
|
||||
return self._get_entry()
|
||||
return self._get_entry(self._name)
|
||||
if result != RESULT_INVALID_AUTH:
|
||||
return self.async_abort(reason=result)
|
||||
errors["base"] = result
|
||||
|
@ -111,9 +119,10 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
step_id="user", data_schema=DATA_SCHEMA_USER, errors=errors
|
||||
)
|
||||
|
||||
async def async_step_ssdp(self, discovery_info):
|
||||
async def async_step_ssdp(self, discovery_info: DiscoveryInfoType) -> FlowResult:
|
||||
"""Handle a flow initialized by discovery."""
|
||||
host = urlparse(discovery_info[ATTR_SSDP_LOCATION]).hostname
|
||||
assert isinstance(host, str)
|
||||
self.context[CONF_HOST] = host
|
||||
|
||||
uuid = discovery_info.get(ATTR_UPNP_UDN)
|
||||
|
@ -135,12 +144,14 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
return self.async_abort(reason="already_configured")
|
||||
|
||||
self._host = host
|
||||
self._name = discovery_info.get(ATTR_UPNP_FRIENDLY_NAME) or host
|
||||
self._name = str(discovery_info.get(ATTR_UPNP_FRIENDLY_NAME) or host)
|
||||
|
||||
self.context["title_placeholders"] = {"name": self._name}
|
||||
return await self.async_step_confirm()
|
||||
|
||||
async def async_step_confirm(self, user_input=None):
|
||||
async def async_step_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle user-confirmation of discovered node."""
|
||||
errors = {}
|
||||
|
||||
|
@ -150,7 +161,8 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
result = await self.hass.async_add_executor_job(self._try_connect)
|
||||
|
||||
if result == RESULT_SUCCESS:
|
||||
return self._get_entry()
|
||||
assert self._name is not None
|
||||
return self._get_entry(self._name)
|
||||
if result != RESULT_INVALID_AUTH:
|
||||
return self.async_abort(reason=result)
|
||||
errors["base"] = result
|
||||
|
@ -162,16 +174,20 @@ class FritzboxConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_reauth(self, data):
|
||||
async def async_step_reauth(self, data: dict[str, str]) -> FlowResult:
|
||||
"""Trigger a reauthentication flow."""
|
||||
self._entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
|
||||
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
|
||||
assert entry is not None
|
||||
self._entry = entry
|
||||
self._host = data[CONF_HOST]
|
||||
self._name = data[CONF_HOST]
|
||||
self._name = str(data[CONF_HOST])
|
||||
self._username = data[CONF_USERNAME]
|
||||
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(self, user_input=None):
|
||||
async def async_step_reauth_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle reauthorization flow."""
|
||||
errors = {}
|
||||
|
||||
|
|
|
@ -1,26 +1,29 @@
|
|||
"""Constants for the AVM FRITZ!SmartHome integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Final
|
||||
|
||||
ATTR_STATE_BATTERY_LOW = "battery_low"
|
||||
ATTR_STATE_DEVICE_LOCKED = "device_locked"
|
||||
ATTR_STATE_HOLIDAY_MODE = "holiday_mode"
|
||||
ATTR_STATE_LOCKED = "locked"
|
||||
ATTR_STATE_SUMMER_MODE = "summer_mode"
|
||||
ATTR_STATE_WINDOW_OPEN = "window_open"
|
||||
ATTR_STATE_BATTERY_LOW: Final = "battery_low"
|
||||
ATTR_STATE_DEVICE_LOCKED: Final = "device_locked"
|
||||
ATTR_STATE_HOLIDAY_MODE: Final = "holiday_mode"
|
||||
ATTR_STATE_LOCKED: Final = "locked"
|
||||
ATTR_STATE_SUMMER_MODE: Final = "summer_mode"
|
||||
ATTR_STATE_WINDOW_OPEN: Final = "window_open"
|
||||
|
||||
ATTR_TEMPERATURE_UNIT = "temperature_unit"
|
||||
ATTR_TEMPERATURE_UNIT: Final = "temperature_unit"
|
||||
|
||||
ATTR_TOTAL_CONSUMPTION = "total_consumption"
|
||||
ATTR_TOTAL_CONSUMPTION_UNIT = "total_consumption_unit"
|
||||
ATTR_TOTAL_CONSUMPTION: Final = "total_consumption"
|
||||
ATTR_TOTAL_CONSUMPTION_UNIT: Final = "total_consumption_unit"
|
||||
|
||||
CONF_CONNECTIONS = "connections"
|
||||
CONF_COORDINATOR = "coordinator"
|
||||
CONF_CONNECTIONS: Final = "connections"
|
||||
CONF_COORDINATOR: Final = "coordinator"
|
||||
|
||||
DEFAULT_HOST = "fritz.box"
|
||||
DEFAULT_USERNAME = "admin"
|
||||
DEFAULT_HOST: Final = "fritz.box"
|
||||
DEFAULT_USERNAME: Final = "admin"
|
||||
|
||||
DOMAIN = "fritzbox"
|
||||
DOMAIN: Final = "fritzbox"
|
||||
|
||||
LOGGER: logging.Logger = logging.getLogger(__package__)
|
||||
LOGGER: Final[logging.Logger] = logging.getLogger(__package__)
|
||||
|
||||
PLATFORMS = ["binary_sensor", "climate", "switch", "sensor"]
|
||||
PLATFORMS: Final[list[str]] = ["binary_sensor", "climate", "switch", "sensor"]
|
||||
|
|
43
homeassistant/components/fritzbox/model.py
Normal file
43
homeassistant/components/fritzbox/model.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
"""Models for the AVM FRITZ!SmartHome integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class EntityInfo(TypedDict):
|
||||
"""TypedDict for EntityInfo."""
|
||||
|
||||
name: str
|
||||
entity_id: str
|
||||
unit_of_measurement: str | None
|
||||
device_class: str | None
|
||||
|
||||
|
||||
class ClimateExtraAttributes(TypedDict, total=False):
|
||||
"""TypedDict for climates extra attributes."""
|
||||
|
||||
battery_low: bool
|
||||
device_locked: bool
|
||||
locked: bool
|
||||
battery_level: int
|
||||
holiday_mode: bool
|
||||
summer_mode: bool
|
||||
window_open: bool
|
||||
|
||||
|
||||
class SensorExtraAttributes(TypedDict):
|
||||
"""TypedDict for sensors extra attributes."""
|
||||
|
||||
device_locked: bool
|
||||
locked: bool
|
||||
|
||||
|
||||
class SwitchExtraAttributes(TypedDict, total=False):
|
||||
"""TypedDict for sensors extra attributes."""
|
||||
|
||||
device_locked: bool
|
||||
locked: bool
|
||||
total_consumption: str
|
||||
total_consumption_unit: str
|
||||
temperature: str
|
||||
temperature_unit: str
|
|
@ -1,4 +1,6 @@
|
|||
"""Support for AVM FRITZ!SmartHome temperature sensor only devices."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
|
@ -20,13 +22,14 @@ from .const import (
|
|||
CONF_COORDINATOR,
|
||||
DOMAIN as FRITZBOX_DOMAIN,
|
||||
)
|
||||
from .model import SensorExtraAttributes
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up the FRITZ!SmartHome sensor from ConfigEntry."""
|
||||
entities = []
|
||||
entities: list[FritzBoxEntity] = []
|
||||
coordinator = hass.data[FRITZBOX_DOMAIN][entry.entry_id][CONF_COORDINATOR]
|
||||
|
||||
for ain, device in coordinator.data.items():
|
||||
|
@ -69,23 +72,23 @@ class FritzBoxBatterySensor(FritzBoxEntity, SensorEntity):
|
|||
"""The entity class for FRITZ!SmartHome sensors."""
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
def state(self) -> int | None:
|
||||
"""Return the state of the sensor."""
|
||||
return self.device.battery_level
|
||||
return self.device.battery_level # type: ignore [no-any-return]
|
||||
|
||||
|
||||
class FritzBoxTempSensor(FritzBoxEntity, SensorEntity):
|
||||
"""The entity class for FRITZ!SmartHome temperature sensors."""
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
def state(self) -> float | None:
|
||||
"""Return the state of the sensor."""
|
||||
return self.device.temperature
|
||||
return self.device.temperature # type: ignore [no-any-return]
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
def extra_state_attributes(self) -> SensorExtraAttributes:
|
||||
"""Return the state attributes of the device."""
|
||||
attrs = {
|
||||
attrs: SensorExtraAttributes = {
|
||||
ATTR_STATE_DEVICE_LOCKED: self.device.device_lock,
|
||||
ATTR_STATE_LOCKED: self.device.lock,
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
"""Support for AVM FRITZ!SmartHome switch devices."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
|
@ -23,6 +27,7 @@ from .const import (
|
|||
CONF_COORDINATOR,
|
||||
DOMAIN as FRITZBOX_DOMAIN,
|
||||
)
|
||||
from .model import SwitchExtraAttributes
|
||||
|
||||
ATTR_TOTAL_CONSUMPTION_UNIT_VALUE = ENERGY_KILO_WATT_HOUR
|
||||
|
||||
|
@ -31,7 +36,7 @@ async def async_setup_entry(
|
|||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up the FRITZ!SmartHome switch from ConfigEntry."""
|
||||
entities = []
|
||||
entities: list[FritzboxSwitch] = []
|
||||
coordinator = hass.data[FRITZBOX_DOMAIN][entry.entry_id][CONF_COORDINATOR]
|
||||
|
||||
for ain, device in coordinator.data.items():
|
||||
|
@ -58,31 +63,32 @@ class FritzboxSwitch(FritzBoxEntity, SwitchEntity):
|
|||
"""The switch class for FRITZ!SmartHome switches."""
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
def available(self) -> bool:
|
||||
"""Return if switch is available."""
|
||||
return self.device.present
|
||||
return self.device.present # type: ignore [no-any-return]
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if the switch is on."""
|
||||
return self.device.switch_state
|
||||
return self.device.switch_state # type: ignore [no-any-return]
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch on."""
|
||||
await self.hass.async_add_executor_job(self.device.set_switch_state_on)
|
||||
await self.coordinator.async_refresh()
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the switch off."""
|
||||
await self.hass.async_add_executor_job(self.device.set_switch_state_off)
|
||||
await self.coordinator.async_refresh()
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
def extra_state_attributes(self) -> SwitchExtraAttributes:
|
||||
"""Return the state attributes of the device."""
|
||||
attrs = {}
|
||||
attrs[ATTR_STATE_DEVICE_LOCKED] = self.device.device_lock
|
||||
attrs[ATTR_STATE_LOCKED] = self.device.lock
|
||||
attrs: SwitchExtraAttributes = {
|
||||
ATTR_STATE_DEVICE_LOCKED: self.device.device_lock,
|
||||
ATTR_STATE_LOCKED: self.device.lock,
|
||||
}
|
||||
|
||||
if self.device.has_powermeter:
|
||||
attrs[
|
||||
|
@ -99,6 +105,6 @@ class FritzboxSwitch(FritzBoxEntity, SwitchEntity):
|
|||
return attrs
|
||||
|
||||
@property
|
||||
def current_power_w(self):
|
||||
def current_power_w(self) -> float:
|
||||
"""Return the current power usage in W."""
|
||||
return self.device.power / 1000
|
||||
return self.device.power / 1000 # type: ignore [no-any-return]
|
||||
|
|
|
@ -294,7 +294,7 @@ ATTR_ID = "id"
|
|||
ATTR_NAME: Final = "name"
|
||||
|
||||
# Contains one string or a list of strings, each being an entity id
|
||||
ATTR_ENTITY_ID = "entity_id"
|
||||
ATTR_ENTITY_ID: Final = "entity_id"
|
||||
|
||||
# Contains one string or a list of strings, each being an area id
|
||||
ATTR_AREA_ID = "area_id"
|
||||
|
@ -314,7 +314,7 @@ ATTR_IDENTIFIERS: Final = "identifiers"
|
|||
ATTR_ICON = "icon"
|
||||
|
||||
# The unit of measurement if applicable
|
||||
ATTR_UNIT_OF_MEASUREMENT = "unit_of_measurement"
|
||||
ATTR_UNIT_OF_MEASUREMENT: Final = "unit_of_measurement"
|
||||
|
||||
CONF_UNIT_SYSTEM_METRIC: str = "metric"
|
||||
CONF_UNIT_SYSTEM_IMPERIAL: str = "imperial"
|
||||
|
@ -332,7 +332,7 @@ ATTR_MODEL: Final = "model"
|
|||
ATTR_SW_VERSION: Final = "sw_version"
|
||||
|
||||
ATTR_BATTERY_CHARGING = "battery_charging"
|
||||
ATTR_BATTERY_LEVEL = "battery_level"
|
||||
ATTR_BATTERY_LEVEL: Final = "battery_level"
|
||||
ATTR_WAKEUP = "wake_up_interval"
|
||||
|
||||
# For devices which support a code attribute
|
||||
|
@ -379,10 +379,10 @@ ATTR_RESTORED = "restored"
|
|||
ATTR_SUPPORTED_FEATURES = "supported_features"
|
||||
|
||||
# Class of device within its domain
|
||||
ATTR_DEVICE_CLASS = "device_class"
|
||||
ATTR_DEVICE_CLASS: Final = "device_class"
|
||||
|
||||
# Temperature attribute
|
||||
ATTR_TEMPERATURE = "temperature"
|
||||
ATTR_TEMPERATURE: Final = "temperature"
|
||||
|
||||
# #### UNITS OF MEASUREMENT ####
|
||||
# Power units
|
||||
|
|
14
mypy.ini
14
mypy.ini
|
@ -154,6 +154,17 @@ no_implicit_optional = true
|
|||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.fritzbox.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
disallow_subclassing_any = true
|
||||
disallow_untyped_calls = true
|
||||
disallow_untyped_decorators = true
|
||||
disallow_untyped_defs = true
|
||||
no_implicit_optional = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
|
||||
[mypy-homeassistant.components.frontend.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
@ -781,9 +792,6 @@ ignore_errors = true
|
|||
[mypy-homeassistant.components.freebox.*]
|
||||
ignore_errors = true
|
||||
|
||||
[mypy-homeassistant.components.fritzbox.*]
|
||||
ignore_errors = true
|
||||
|
||||
[mypy-homeassistant.components.garmin_connect.*]
|
||||
ignore_errors = true
|
||||
|
||||
|
|
|
@ -69,7 +69,6 @@ IGNORED_MODULES: Final[list[str]] = [
|
|||
"homeassistant.components.fortios.*",
|
||||
"homeassistant.components.foscam.*",
|
||||
"homeassistant.components.freebox.*",
|
||||
"homeassistant.components.fritzbox.*",
|
||||
"homeassistant.components.garmin_connect.*",
|
||||
"homeassistant.components.geniushub.*",
|
||||
"homeassistant.components.glances.*",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue