Add strict type annotations to fitbit (#50740)

* add strict type annotations

* cast json_load()

* apply suggestions

* move SCAN_INTERVAL back to platform file

* apply suggestion

* apply suggestion

* apply suggestions

* rename to PARENT_PLATFORM_SCHEMA
This commit is contained in:
Michael 2021-05-21 14:33:54 +02:00 committed by GitHub
parent b4bb7c38ce
commit 8c5c8ed153
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 264 additions and 169 deletions

View File

@ -309,7 +309,7 @@ omit =
homeassistant/components/firmata/pin.py homeassistant/components/firmata/pin.py
homeassistant/components/firmata/sensor.py homeassistant/components/firmata/sensor.py
homeassistant/components/firmata/switch.py homeassistant/components/firmata/switch.py
homeassistant/components/fitbit/sensor.py homeassistant/components/fitbit/*
homeassistant/components/fixer/sensor.py homeassistant/components/fixer/sensor.py
homeassistant/components/fleetgo/device_tracker.py homeassistant/components/fleetgo/device_tracker.py
homeassistant/components/flexit/climate.py homeassistant/components/flexit/climate.py

View File

@ -21,6 +21,7 @@ homeassistant.components.camera.*
homeassistant.components.cover.* homeassistant.components.cover.*
homeassistant.components.device_automation.* homeassistant.components.device_automation.*
homeassistant.components.elgato.* homeassistant.components.elgato.*
homeassistant.components.fitbit.*
homeassistant.components.fritzbox.* homeassistant.components.fritzbox.*
homeassistant.components.frontend.* homeassistant.components.frontend.*
homeassistant.components.geo_location.* homeassistant.components.geo_location.*

View File

@ -0,0 +1,148 @@
"""Constants for the Fitbit platform."""
from __future__ import annotations
from typing import Final
from homeassistant.const import (
CONF_CLIENT_ID,
CONF_CLIENT_SECRET,
LENGTH_FEET,
MASS_KILOGRAMS,
MASS_MILLIGRAMS,
PERCENTAGE,
TIME_MILLISECONDS,
TIME_MINUTES,
)
ATTR_ACCESS_TOKEN: Final = "access_token"
ATTR_REFRESH_TOKEN: Final = "refresh_token"
ATTR_LAST_SAVED_AT: Final = "last_saved_at"
ATTR_DURATION: Final = "duration"
ATTR_DISTANCE: Final = "distance"
ATTR_ELEVATION: Final = "elevation"
ATTR_HEIGHT: Final = "height"
ATTR_WEIGHT: Final = "weight"
ATTR_BODY: Final = "body"
ATTR_LIQUIDS: Final = "liquids"
ATTR_BLOOD_GLUCOSE: Final = "blood glucose"
ATTR_BATTERY: Final = "battery"
CONF_MONITORED_RESOURCES: Final = "monitored_resources"
CONF_CLOCK_FORMAT: Final = "clock_format"
ATTRIBUTION: Final = "Data provided by Fitbit.com"
FITBIT_AUTH_CALLBACK_PATH: Final = "/api/fitbit/callback"
FITBIT_AUTH_START: Final = "/api/fitbit"
FITBIT_CONFIG_FILE: Final = "fitbit.conf"
FITBIT_DEFAULT_RESOURCES: Final[list[str]] = ["activities/steps"]
DEFAULT_CONFIG: Final[dict[str, str]] = {
CONF_CLIENT_ID: "CLIENT_ID_HERE",
CONF_CLIENT_SECRET: "CLIENT_SECRET_HERE",
}
DEFAULT_CLOCK_FORMAT: Final = "24H"
FITBIT_RESOURCES_LIST: Final[dict[str, tuple[str, str | None, str]]] = {
"activities/activityCalories": ("Activity Calories", "cal", "fire"),
"activities/calories": ("Calories", "cal", "fire"),
"activities/caloriesBMR": ("Calories BMR", "cal", "fire"),
"activities/distance": ("Distance", "", "map-marker"),
"activities/elevation": ("Elevation", "", "walk"),
"activities/floors": ("Floors", "floors", "walk"),
"activities/heart": ("Resting Heart Rate", "bpm", "heart-pulse"),
"activities/minutesFairlyActive": ("Minutes Fairly Active", TIME_MINUTES, "walk"),
"activities/minutesLightlyActive": ("Minutes Lightly Active", TIME_MINUTES, "walk"),
"activities/minutesSedentary": (
"Minutes Sedentary",
TIME_MINUTES,
"seat-recline-normal",
),
"activities/minutesVeryActive": ("Minutes Very Active", TIME_MINUTES, "run"),
"activities/steps": ("Steps", "steps", "walk"),
"activities/tracker/activityCalories": ("Tracker Activity Calories", "cal", "fire"),
"activities/tracker/calories": ("Tracker Calories", "cal", "fire"),
"activities/tracker/distance": ("Tracker Distance", "", "map-marker"),
"activities/tracker/elevation": ("Tracker Elevation", "", "walk"),
"activities/tracker/floors": ("Tracker Floors", "floors", "walk"),
"activities/tracker/minutesFairlyActive": (
"Tracker Minutes Fairly Active",
TIME_MINUTES,
"walk",
),
"activities/tracker/minutesLightlyActive": (
"Tracker Minutes Lightly Active",
TIME_MINUTES,
"walk",
),
"activities/tracker/minutesSedentary": (
"Tracker Minutes Sedentary",
TIME_MINUTES,
"seat-recline-normal",
),
"activities/tracker/minutesVeryActive": (
"Tracker Minutes Very Active",
TIME_MINUTES,
"run",
),
"activities/tracker/steps": ("Tracker Steps", "steps", "walk"),
"body/bmi": ("BMI", "BMI", "human"),
"body/fat": ("Body Fat", PERCENTAGE, "human"),
"body/weight": ("Weight", "", "human"),
"devices/battery": ("Battery", None, "battery"),
"sleep/awakeningsCount": ("Awakenings Count", "times awaken", "sleep"),
"sleep/efficiency": ("Sleep Efficiency", PERCENTAGE, "sleep"),
"sleep/minutesAfterWakeup": ("Minutes After Wakeup", TIME_MINUTES, "sleep"),
"sleep/minutesAsleep": ("Sleep Minutes Asleep", TIME_MINUTES, "sleep"),
"sleep/minutesAwake": ("Sleep Minutes Awake", TIME_MINUTES, "sleep"),
"sleep/minutesToFallAsleep": (
"Sleep Minutes to Fall Asleep",
TIME_MINUTES,
"sleep",
),
"sleep/startTime": ("Sleep Start Time", None, "clock"),
"sleep/timeInBed": ("Sleep Time in Bed", TIME_MINUTES, "hotel"),
}
FITBIT_MEASUREMENTS: Final[dict[str, dict[str, str]]] = {
"en_US": {
ATTR_DURATION: TIME_MILLISECONDS,
ATTR_DISTANCE: "mi",
ATTR_ELEVATION: LENGTH_FEET,
ATTR_HEIGHT: "in",
ATTR_WEIGHT: "lbs",
ATTR_BODY: "in",
ATTR_LIQUIDS: "fl. oz.",
ATTR_BLOOD_GLUCOSE: f"{MASS_MILLIGRAMS}/dL",
ATTR_BATTERY: "",
},
"en_GB": {
ATTR_DURATION: TIME_MILLISECONDS,
ATTR_DISTANCE: "kilometers",
ATTR_ELEVATION: "meters",
ATTR_HEIGHT: "centimeters",
ATTR_WEIGHT: "stone",
ATTR_BODY: "centimeters",
ATTR_LIQUIDS: "milliliters",
ATTR_BLOOD_GLUCOSE: "mmol/L",
ATTR_BATTERY: "",
},
"metric": {
ATTR_DURATION: TIME_MILLISECONDS,
ATTR_DISTANCE: "kilometers",
ATTR_ELEVATION: "meters",
ATTR_HEIGHT: "centimeters",
ATTR_WEIGHT: MASS_KILOGRAMS,
ATTR_BODY: "centimeters",
ATTR_LIQUIDS: "milliliters",
ATTR_BLOOD_GLUCOSE: "mmol/L",
ATTR_BATTERY: "",
},
}
BATTERY_LEVELS: Final[dict[str, int]] = {
"High": 100,
"Medium": 50,
"Low": 20,
"Empty": 0,
}

View File

@ -1,162 +1,70 @@
"""Support for the Fitbit API.""" """Support for the Fitbit API."""
from __future__ import annotations
import datetime import datetime
import logging import logging
import os import os
import time import time
from typing import Any, Final, cast
from aiohttp.web import Request
from fitbit import Fitbit from fitbit import Fitbit
from fitbit.api import FitbitOauth2Client from fitbit.api import FitbitOauth2Client
from oauthlib.oauth2.rfc6749.errors import MismatchingStateError, MissingTokenError from oauthlib.oauth2.rfc6749.errors import MismatchingStateError, MissingTokenError
import voluptuous as vol import voluptuous as vol
from homeassistant.components.http import HomeAssistantView from homeassistant.components.http import HomeAssistantView
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.components.sensor import (
PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA,
SensorEntity,
)
from homeassistant.const import ( from homeassistant.const import (
ATTR_ATTRIBUTION, ATTR_ATTRIBUTION,
CONF_CLIENT_ID, CONF_CLIENT_ID,
CONF_CLIENT_SECRET, CONF_CLIENT_SECRET,
CONF_UNIT_SYSTEM, CONF_UNIT_SYSTEM,
LENGTH_FEET,
MASS_KILOGRAMS,
MASS_MILLIGRAMS,
PERCENTAGE,
TIME_MILLISECONDS,
TIME_MINUTES,
) )
from homeassistant.core import callback from homeassistant.core import HomeAssistant, callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.helpers.icon import icon_for_battery_level
from homeassistant.helpers.network import get_url from homeassistant.helpers.network import get_url
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util.json import load_json, save_json from homeassistant.util.json import load_json, save_json
_CONFIGURING = {} from .const import (
_LOGGER = logging.getLogger(__name__) ATTR_ACCESS_TOKEN,
ATTR_LAST_SAVED_AT,
ATTR_REFRESH_TOKEN,
ATTRIBUTION,
BATTERY_LEVELS,
CONF_CLOCK_FORMAT,
CONF_MONITORED_RESOURCES,
DEFAULT_CLOCK_FORMAT,
DEFAULT_CONFIG,
FITBIT_AUTH_CALLBACK_PATH,
FITBIT_AUTH_START,
FITBIT_CONFIG_FILE,
FITBIT_DEFAULT_RESOURCES,
FITBIT_MEASUREMENTS,
FITBIT_RESOURCES_LIST,
)
ATTR_ACCESS_TOKEN = "access_token" _LOGGER: Final = logging.getLogger(__name__)
ATTR_REFRESH_TOKEN = "refresh_token"
ATTR_LAST_SAVED_AT = "last_saved_at"
CONF_MONITORED_RESOURCES = "monitored_resources" _CONFIGURING: dict[str, str] = {}
CONF_CLOCK_FORMAT = "clock_format"
ATTRIBUTION = "Data provided by Fitbit.com"
FITBIT_AUTH_CALLBACK_PATH = "/api/fitbit/callback" SCAN_INTERVAL: Final = datetime.timedelta(minutes=30)
FITBIT_AUTH_START = "/api/fitbit"
FITBIT_CONFIG_FILE = "fitbit.conf"
FITBIT_DEFAULT_RESOURCES = ["activities/steps"]
SCAN_INTERVAL = datetime.timedelta(minutes=30) PLATFORM_SCHEMA: Final = PARENT_PLATFORM_SCHEMA.extend(
DEFAULT_CONFIG = {
CONF_CLIENT_ID: "CLIENT_ID_HERE",
CONF_CLIENT_SECRET: "CLIENT_SECRET_HERE",
}
FITBIT_RESOURCES_LIST = {
"activities/activityCalories": ["Activity Calories", "cal", "fire"],
"activities/calories": ["Calories", "cal", "fire"],
"activities/caloriesBMR": ["Calories BMR", "cal", "fire"],
"activities/distance": ["Distance", "", "map-marker"],
"activities/elevation": ["Elevation", "", "walk"],
"activities/floors": ["Floors", "floors", "walk"],
"activities/heart": ["Resting Heart Rate", "bpm", "heart-pulse"],
"activities/minutesFairlyActive": ["Minutes Fairly Active", TIME_MINUTES, "walk"],
"activities/minutesLightlyActive": ["Minutes Lightly Active", TIME_MINUTES, "walk"],
"activities/minutesSedentary": [
"Minutes Sedentary",
TIME_MINUTES,
"seat-recline-normal",
],
"activities/minutesVeryActive": ["Minutes Very Active", TIME_MINUTES, "run"],
"activities/steps": ["Steps", "steps", "walk"],
"activities/tracker/activityCalories": ["Tracker Activity Calories", "cal", "fire"],
"activities/tracker/calories": ["Tracker Calories", "cal", "fire"],
"activities/tracker/distance": ["Tracker Distance", "", "map-marker"],
"activities/tracker/elevation": ["Tracker Elevation", "", "walk"],
"activities/tracker/floors": ["Tracker Floors", "floors", "walk"],
"activities/tracker/minutesFairlyActive": [
"Tracker Minutes Fairly Active",
TIME_MINUTES,
"walk",
],
"activities/tracker/minutesLightlyActive": [
"Tracker Minutes Lightly Active",
TIME_MINUTES,
"walk",
],
"activities/tracker/minutesSedentary": [
"Tracker Minutes Sedentary",
TIME_MINUTES,
"seat-recline-normal",
],
"activities/tracker/minutesVeryActive": [
"Tracker Minutes Very Active",
TIME_MINUTES,
"run",
],
"activities/tracker/steps": ["Tracker Steps", "steps", "walk"],
"body/bmi": ["BMI", "BMI", "human"],
"body/fat": ["Body Fat", PERCENTAGE, "human"],
"body/weight": ["Weight", "", "human"],
"devices/battery": ["Battery", None, None],
"sleep/awakeningsCount": ["Awakenings Count", "times awaken", "sleep"],
"sleep/efficiency": ["Sleep Efficiency", PERCENTAGE, "sleep"],
"sleep/minutesAfterWakeup": ["Minutes After Wakeup", TIME_MINUTES, "sleep"],
"sleep/minutesAsleep": ["Sleep Minutes Asleep", TIME_MINUTES, "sleep"],
"sleep/minutesAwake": ["Sleep Minutes Awake", TIME_MINUTES, "sleep"],
"sleep/minutesToFallAsleep": [
"Sleep Minutes to Fall Asleep",
TIME_MINUTES,
"sleep",
],
"sleep/startTime": ["Sleep Start Time", None, "clock"],
"sleep/timeInBed": ["Sleep Time in Bed", TIME_MINUTES, "hotel"],
}
FITBIT_MEASUREMENTS = {
"en_US": {
"duration": TIME_MILLISECONDS,
"distance": "mi",
"elevation": LENGTH_FEET,
"height": "in",
"weight": "lbs",
"body": "in",
"liquids": "fl. oz.",
"blood glucose": f"{MASS_MILLIGRAMS}/dL",
"battery": "",
},
"en_GB": {
"duration": TIME_MILLISECONDS,
"distance": "kilometers",
"elevation": "meters",
"height": "centimeters",
"weight": "stone",
"body": "centimeters",
"liquids": "milliliters",
"blood glucose": "mmol/L",
"battery": "",
},
"metric": {
"duration": TIME_MILLISECONDS,
"distance": "kilometers",
"elevation": "meters",
"height": "centimeters",
"weight": MASS_KILOGRAMS,
"body": "centimeters",
"liquids": "milliliters",
"blood glucose": "mmol/L",
"battery": "",
},
}
BATTERY_LEVELS = {"High": 100, "Medium": 50, "Low": 20, "Empty": 0}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{ {
vol.Optional( vol.Optional(
CONF_MONITORED_RESOURCES, default=FITBIT_DEFAULT_RESOURCES CONF_MONITORED_RESOURCES, default=FITBIT_DEFAULT_RESOURCES
): vol.All(cv.ensure_list, [vol.In(FITBIT_RESOURCES_LIST)]), ): vol.All(cv.ensure_list, [vol.In(FITBIT_RESOURCES_LIST)]),
vol.Optional(CONF_CLOCK_FORMAT, default="24H"): vol.In(["12H", "24H"]), vol.Optional(CONF_CLOCK_FORMAT, default=DEFAULT_CLOCK_FORMAT): vol.In(
["12H", "24H"]
),
vol.Optional(CONF_UNIT_SYSTEM, default="default"): vol.In( vol.Optional(CONF_UNIT_SYSTEM, default="default"): vol.In(
["en_GB", "en_US", "metric", "default"] ["en_GB", "en_US", "metric", "default"]
), ),
@ -164,11 +72,17 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
) )
def request_app_setup(hass, config, add_entities, config_path, discovery_info=None): def request_app_setup(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
config_path: str,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Assist user with configuring the Fitbit dev application.""" """Assist user with configuring the Fitbit dev application."""
configurator = hass.components.configurator configurator = hass.components.configurator
def fitbit_configuration_callback(callback_data): def fitbit_configuration_callback(fields: list[dict[str, str]]) -> None:
"""Handle configuration updates.""" """Handle configuration updates."""
config_path = hass.config.path(FITBIT_CONFIG_FILE) config_path = hass.config.path(FITBIT_CONFIG_FILE)
if os.path.isfile(config_path): if os.path.isfile(config_path):
@ -206,7 +120,7 @@ def request_app_setup(hass, config, add_entities, config_path, discovery_info=No
) )
def request_oauth_completion(hass): def request_oauth_completion(hass: HomeAssistant) -> None:
"""Request user complete Fitbit OAuth2 flow.""" """Request user complete Fitbit OAuth2 flow."""
configurator = hass.components.configurator configurator = hass.components.configurator
if "fitbit" in _CONFIGURING: if "fitbit" in _CONFIGURING:
@ -216,7 +130,7 @@ def request_oauth_completion(hass):
return return
def fitbit_configuration_callback(callback_data): def fitbit_configuration_callback(fields: list[dict[str, str]]) -> None:
"""Handle configuration updates.""" """Handle configuration updates."""
start_url = f"{get_url(hass)}{FITBIT_AUTH_START}" start_url = f"{get_url(hass)}{FITBIT_AUTH_START}"
@ -231,28 +145,37 @@ def request_oauth_completion(hass):
) )
def setup_platform(hass, config, add_entities, discovery_info=None): def setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Fitbit sensor.""" """Set up the Fitbit sensor."""
config_path = hass.config.path(FITBIT_CONFIG_FILE) config_path = hass.config.path(FITBIT_CONFIG_FILE)
if os.path.isfile(config_path): if os.path.isfile(config_path):
config_file = load_json(config_path) config_file: ConfigType = cast(ConfigType, load_json(config_path))
if config_file == DEFAULT_CONFIG: if config_file == DEFAULT_CONFIG:
request_app_setup( request_app_setup(
hass, config, add_entities, config_path, discovery_info=None hass, config, add_entities, config_path, discovery_info=None
) )
return False return
else: else:
save_json(config_path, DEFAULT_CONFIG) save_json(config_path, DEFAULT_CONFIG)
request_app_setup(hass, config, add_entities, config_path, discovery_info=None) request_app_setup(hass, config, add_entities, config_path, discovery_info=None)
return False return
if "fitbit" in _CONFIGURING: if "fitbit" in _CONFIGURING:
hass.components.configurator.request_done(_CONFIGURING.pop("fitbit")) hass.components.configurator.request_done(_CONFIGURING.pop("fitbit"))
access_token = config_file.get(ATTR_ACCESS_TOKEN) access_token: str | None = config_file.get(ATTR_ACCESS_TOKEN)
refresh_token = config_file.get(ATTR_REFRESH_TOKEN) refresh_token: str | None = config_file.get(ATTR_REFRESH_TOKEN)
expires_at = config_file.get(ATTR_LAST_SAVED_AT) expires_at: int | None = config_file.get(ATTR_LAST_SAVED_AT)
if None not in (access_token, refresh_token): if (
access_token is not None
and refresh_token is not None
and expires_at is not None
):
authd_client = Fitbit( authd_client = Fitbit(
config_file.get(CONF_CLIENT_ID), config_file.get(CONF_CLIENT_ID),
config_file.get(CONF_CLIENT_SECRET), config_file.get(CONF_CLIENT_SECRET),
@ -278,8 +201,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
dev = [] dev = []
registered_devs = authd_client.get_devices() registered_devs = authd_client.get_devices()
clock_format = config.get(CONF_CLOCK_FORMAT) clock_format = config.get(CONF_CLOCK_FORMAT, DEFAULT_CLOCK_FORMAT)
for resource in config.get(CONF_MONITORED_RESOURCES): for resource in config.get(CONF_MONITORED_RESOURCES, FITBIT_DEFAULT_RESOURCES):
# monitor battery for all linked FitBit devices # monitor battery for all linked FitBit devices
if resource == "devices/battery": if resource == "devices/battery":
@ -339,16 +262,21 @@ class FitbitAuthCallbackView(HomeAssistantView):
url = FITBIT_AUTH_CALLBACK_PATH url = FITBIT_AUTH_CALLBACK_PATH
name = "api:fitbit:callback" name = "api:fitbit:callback"
def __init__(self, config, add_entities, oauth): def __init__(
self,
config: ConfigType,
add_entities: AddEntitiesCallback,
oauth: FitbitOauth2Client,
) -> None:
"""Initialize the OAuth callback view.""" """Initialize the OAuth callback view."""
self.config = config self.config = config
self.add_entities = add_entities self.add_entities = add_entities
self.oauth = oauth self.oauth = oauth
@callback @callback
async def get(self, request): async def get(self, request: Request) -> str:
"""Finish OAuth callback request.""" """Finish OAuth callback request."""
hass = request.app["hass"] hass: HomeAssistant = request.app["hass"]
data = request.query data = request.query
response_message = """Fitbit has been successfully authorized! response_message = """Fitbit has been successfully authorized!
@ -408,8 +336,14 @@ class FitbitSensor(SensorEntity):
"""Implementation of a Fitbit sensor.""" """Implementation of a Fitbit sensor."""
def __init__( def __init__(
self, client, config_path, resource_type, is_metric, clock_format, extra=None self,
): client: Fitbit,
config_path: str,
resource_type: str,
is_metric: bool,
clock_format: str,
extra: dict[str, str] | None = None,
) -> None:
"""Initialize the Fitbit sensor.""" """Initialize the Fitbit sensor."""
self.client = client self.client = client
self.config_path = config_path self.config_path = config_path
@ -418,7 +352,7 @@ class FitbitSensor(SensorEntity):
self.clock_format = clock_format self.clock_format = clock_format
self.extra = extra self.extra = extra
self._name = FITBIT_RESOURCES_LIST[self.resource_type][0] self._name = FITBIT_RESOURCES_LIST[self.resource_type][0]
if self.extra: if self.extra is not None:
self._name = f"{self.extra.get('deviceVersion')} Battery" self._name = f"{self.extra.get('deviceVersion')} Battery"
unit_type = FITBIT_RESOURCES_LIST[self.resource_type][1] unit_type = FITBIT_RESOURCES_LIST[self.resource_type][1]
if unit_type == "": if unit_type == "":
@ -432,48 +366,53 @@ class FitbitSensor(SensorEntity):
measurement_system = FITBIT_MEASUREMENTS["en_US"] measurement_system = FITBIT_MEASUREMENTS["en_US"]
unit_type = measurement_system[split_resource[-1]] unit_type = measurement_system[split_resource[-1]]
self._unit_of_measurement = unit_type self._unit_of_measurement = unit_type
self._state = 0 self._state: str | None = None
@property @property
def name(self): def name(self) -> str:
"""Return the name of the sensor.""" """Return the name of the sensor."""
return self._name return self._name
@property @property
def state(self): def state(self) -> str | None:
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self._state return self._state
@property @property
def unit_of_measurement(self): def unit_of_measurement(self) -> str | None:
"""Return the unit of measurement of this entity, if any.""" """Return the unit of measurement of this entity, if any."""
return self._unit_of_measurement return self._unit_of_measurement
@property @property
def icon(self): def icon(self) -> str:
"""Icon to use in the frontend, if any.""" """Icon to use in the frontend, if any."""
if self.resource_type == "devices/battery" and self.extra: if self.resource_type == "devices/battery" and self.extra is not None:
battery_level = BATTERY_LEVELS[self.extra.get("battery")] extra_battery = self.extra.get("battery")
return icon_for_battery_level(battery_level=battery_level, charging=None) if extra_battery is not None:
return f"mdi:{FITBIT_RESOURCES_LIST[self.resource_type][2]}" battery_level = BATTERY_LEVELS.get(extra_battery)
if battery_level is not None:
return icon_for_battery_level(battery_level=battery_level)
fitbit_ressource = FITBIT_RESOURCES_LIST[self.resource_type]
return f"mdi:{fitbit_ressource[2]}"
@property @property
def extra_state_attributes(self): def extra_state_attributes(self) -> dict[str, str | None]:
"""Return the state attributes.""" """Return the state attributes."""
attrs = {} attrs: dict[str, str | None] = {}
attrs[ATTR_ATTRIBUTION] = ATTRIBUTION attrs[ATTR_ATTRIBUTION] = ATTRIBUTION
if self.extra: if self.extra is not None:
attrs["model"] = self.extra.get("deviceVersion") attrs["model"] = self.extra.get("deviceVersion")
attrs["type"] = self.extra.get("type").lower() extra_type = self.extra.get("type")
attrs["type"] = extra_type.lower() if extra_type is not None else None
return attrs return attrs
def update(self): def update(self) -> None:
"""Get the latest data from the Fitbit API and update the states.""" """Get the latest data from the Fitbit API and update the states."""
if self.resource_type == "devices/battery" and self.extra: if self.resource_type == "devices/battery" and self.extra is not None:
registered_devs = self.client.get_devices() registered_devs: list[dict[str, Any]] = self.client.get_devices()
device_id = self.extra.get("id") device_id = self.extra.get("id")
self.extra = list( self.extra = list(
filter(lambda device: device.get("id") == device_id, registered_devs) filter(lambda device: device.get("id") == device_id, registered_devs)

View File

@ -242,6 +242,17 @@ no_implicit_optional = true
warn_return_any = true warn_return_any = true
warn_unreachable = true warn_unreachable = true
[mypy-homeassistant.components.fitbit.*]
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.fritzbox.*] [mypy-homeassistant.components.fritzbox.*]
check_untyped_defs = true check_untyped_defs = true
disallow_incomplete_defs = true disallow_incomplete_defs = true
@ -884,9 +895,6 @@ ignore_errors = true
[mypy-homeassistant.components.firmata.*] [mypy-homeassistant.components.firmata.*]
ignore_errors = true ignore_errors = true
[mypy-homeassistant.components.fitbit.*]
ignore_errors = true
[mypy-homeassistant.components.flo.*] [mypy-homeassistant.components.flo.*]
ignore_errors = true ignore_errors = true

View File

@ -63,7 +63,6 @@ IGNORED_MODULES: Final[list[str]] = [
"homeassistant.components.fints.*", "homeassistant.components.fints.*",
"homeassistant.components.fireservicerota.*", "homeassistant.components.fireservicerota.*",
"homeassistant.components.firmata.*", "homeassistant.components.firmata.*",
"homeassistant.components.fitbit.*",
"homeassistant.components.flo.*", "homeassistant.components.flo.*",
"homeassistant.components.fortios.*", "homeassistant.components.fortios.*",
"homeassistant.components.foscam.*", "homeassistant.components.foscam.*",