diff --git a/.strict-typing b/.strict-typing index 915ac50d6a1..e8c4f83fa80 100644 --- a/.strict-typing +++ b/.strict-typing @@ -63,6 +63,7 @@ homeassistant.components.mailbox.* homeassistant.components.media_player.* homeassistant.components.mysensors.* homeassistant.components.nam.* +homeassistant.components.neato.* homeassistant.components.nest.* homeassistant.components.netatmo.* homeassistant.components.network.* diff --git a/homeassistant/components/neato/api.py b/homeassistant/components/neato/api.py index a22b1b48e74..cd26b009040 100644 --- a/homeassistant/components/neato/api.py +++ b/homeassistant/components/neato/api.py @@ -1,5 +1,8 @@ """API for Neato Botvac bound to Home Assistant OAuth.""" +from __future__ import annotations + from asyncio import run_coroutine_threadsafe +from typing import Any import pybotvac @@ -7,7 +10,7 @@ from homeassistant import config_entries, core from homeassistant.helpers import config_entry_oauth2_flow -class ConfigEntryAuth(pybotvac.OAuthSession): +class ConfigEntryAuth(pybotvac.OAuthSession): # type: ignore[misc] """Provide Neato Botvac authentication tied to an OAuth2 based config entry.""" def __init__( @@ -29,7 +32,7 @@ class ConfigEntryAuth(pybotvac.OAuthSession): self.session.async_ensure_token_valid(), self.hass.loop ).result() - return self.session.token["access_token"] + return self.session.token["access_token"] # type: ignore[no-any-return] class NeatoImplementation(config_entry_oauth2_flow.LocalOAuth2Implementation): @@ -39,7 +42,7 @@ class NeatoImplementation(config_entry_oauth2_flow.LocalOAuth2Implementation): """ @property - def extra_authorize_data(self) -> dict: + def extra_authorize_data(self) -> dict[str, Any]: """Extra data that needs to be appended to the authorize url.""" return {"client_secret": self.client_secret} diff --git a/homeassistant/components/neato/camera.py b/homeassistant/components/neato/camera.py index 9a2f47bcfa3..b6def2cfe38 100644 --- a/homeassistant/components/neato/camera.py +++ b/homeassistant/components/neato/camera.py @@ -1,10 +1,20 @@ """Support for loading picture from Neato.""" +from __future__ import annotations + from datetime import timedelta import logging +from typing import Any from pybotvac.exceptions import NeatoRobotException +from pybotvac.robot import Robot +from urllib3.response import HTTPResponse from homeassistant.components.camera import Camera +from homeassistant.components.neato import NeatoHub +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import ( NEATO_DOMAIN, @@ -20,11 +30,13 @@ SCAN_INTERVAL = timedelta(minutes=SCAN_INTERVAL_MINUTES) ATTR_GENERATED_AT = "generated_at" -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: """Set up Neato camera with config entry.""" dev = [] - neato = hass.data.get(NEATO_LOGIN) - mapdata = hass.data.get(NEATO_MAP_DATA) + neato: NeatoHub = hass.data[NEATO_LOGIN] + mapdata: dict[str, Any] | None = hass.data.get(NEATO_MAP_DATA) for robot in hass.data[NEATO_ROBOTS]: if "maps" in robot.traits: dev.append(NeatoCleaningMap(neato, robot, mapdata)) @@ -39,7 +51,9 @@ async def async_setup_entry(hass, entry, async_add_entities): class NeatoCleaningMap(Camera): """Neato cleaning map for last clean.""" - def __init__(self, neato, robot, mapdata): + def __init__( + self, neato: NeatoHub, robot: Robot, mapdata: dict[str, Any] | None + ) -> None: """Initialize Neato cleaning map.""" super().__init__() self.robot = robot @@ -47,24 +61,18 @@ class NeatoCleaningMap(Camera): self._mapdata = mapdata self._available = neato is not None self._robot_name = f"{self.robot.name} Cleaning Map" - self._robot_serial = self.robot.serial - self._generated_at = None - self._image_url = None - self._image = None + self._robot_serial: str = self.robot.serial + self._generated_at: str | None = None + self._image_url: str | None = None + self._image: bytes | None = None - def camera_image(self): + def camera_image(self) -> bytes | None: """Return image response.""" self.update() return self._image - def update(self): + def update(self) -> None: """Check the contents of the map list.""" - if self.neato is None: - _LOGGER.error("Error while updating '%s'", self.entity_id) - self._image = None - self._image_url = None - self._available = False - return _LOGGER.debug("Running camera update for '%s'", self.entity_id) try: @@ -80,7 +88,8 @@ class NeatoCleaningMap(Camera): return image_url = None - map_data = self._mapdata[self._robot_serial]["maps"][0] + if self._mapdata: + map_data: dict[str, Any] = self._mapdata[self._robot_serial]["maps"][0] image_url = map_data["url"] if image_url == self._image_url: _LOGGER.debug( @@ -89,7 +98,7 @@ class NeatoCleaningMap(Camera): return try: - image = self.neato.download_map(image_url) + image: HTTPResponse = self.neato.download_map(image_url) except NeatoRobotException as ex: if self._available: # Print only once when available _LOGGER.error( @@ -102,33 +111,33 @@ class NeatoCleaningMap(Camera): self._image = image.read() self._image_url = image_url - self._generated_at = map_data["generated_at"] + self._generated_at = map_data.get("generated_at") self._available = True @property - def name(self): + def name(self) -> str: """Return the name of this camera.""" return self._robot_name @property - def unique_id(self): + def unique_id(self) -> str: """Return unique ID.""" return self._robot_serial @property - def available(self): + def available(self) -> bool: """Return if the robot is available.""" return self._available @property - def device_info(self): + def device_info(self) -> DeviceInfo: """Device info for neato robot.""" return {"identifiers": {(NEATO_DOMAIN, self._robot_serial)}} @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the vacuum cleaner.""" - data = {} + data: dict[str, Any] = {} if self._generated_at is not None: data[ATTR_GENERATED_AT] = self._generated_at diff --git a/homeassistant/components/neato/config_flow.py b/homeassistant/components/neato/config_flow.py index c4ca9e45a89..07aea0a7e9c 100644 --- a/homeassistant/components/neato/config_flow.py +++ b/homeassistant/components/neato/config_flow.py @@ -2,10 +2,13 @@ from __future__ import annotations import logging +from types import MappingProxyType +from typing import Any import voluptuous as vol from homeassistant.config_entries import SOURCE_REAUTH +from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import config_entry_oauth2_flow from .const import NEATO_DOMAIN @@ -23,7 +26,9 @@ class OAuth2FlowHandler( """Return logger.""" return logging.getLogger(__name__) - async def async_step_user(self, user_input: dict | None = None) -> dict: + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Create an entry for the flow.""" current_entries = self._async_current_entries() if self.source != SOURCE_REAUTH and current_entries: @@ -32,11 +37,13 @@ class OAuth2FlowHandler( return await super().async_step_user(user_input=user_input) - async def async_step_reauth(self, data) -> dict: + async def async_step_reauth(self, data: MappingProxyType[str, Any]) -> FlowResult: """Perform reauth upon migration of old entries.""" return await self.async_step_reauth_confirm() - async def async_step_reauth_confirm(self, user_input: dict | None = None) -> dict: + async def async_step_reauth_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Confirm reauth upon migration of old entries.""" if user_input is None: return self.async_show_form( @@ -44,7 +51,7 @@ class OAuth2FlowHandler( ) return await self.async_step_user() - async def async_oauth_create_entry(self, data: dict) -> dict: + async def async_oauth_create_entry(self, data: dict[str, Any]) -> FlowResult: """Create an entry for the flow. Update an entry if one already exist.""" current_entries = self._async_current_entries() if self.source == SOURCE_REAUTH and current_entries: diff --git a/homeassistant/components/neato/hub.py b/homeassistant/components/neato/hub.py index b394507f408..cb639de4acb 100644 --- a/homeassistant/components/neato/hub.py +++ b/homeassistant/components/neato/hub.py @@ -3,7 +3,9 @@ from datetime import timedelta import logging from pybotvac import Account +from urllib3.response import HTTPResponse +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.util import Throttle @@ -21,23 +23,23 @@ class NeatoHub: self.my_neato: Account = neato @Throttle(timedelta(minutes=1)) - def update_robots(self): + def update_robots(self) -> None: """Update the robot states.""" _LOGGER.debug("Running HUB.update_robots %s", self._hass.data.get(NEATO_ROBOTS)) self._hass.data[NEATO_ROBOTS] = self.my_neato.robots self._hass.data[NEATO_PERSISTENT_MAPS] = self.my_neato.persistent_maps self._hass.data[NEATO_MAP_DATA] = self.my_neato.maps - def download_map(self, url): + def download_map(self, url: str) -> HTTPResponse: """Download a new map image.""" map_image_data = self.my_neato.get_map_image(url) return map_image_data - async def async_update_entry_unique_id(self, entry) -> str: + async def async_update_entry_unique_id(self, entry: ConfigEntry) -> str: """Update entry for unique_id.""" await self._hass.async_add_executor_job(self.my_neato.refresh_userdata) - unique_id = self.my_neato.unique_id + unique_id: str = self.my_neato.unique_id if entry.unique_id == unique_id: return unique_id diff --git a/homeassistant/components/neato/sensor.py b/homeassistant/components/neato/sensor.py index 98208698037..1cf10112b92 100644 --- a/homeassistant/components/neato/sensor.py +++ b/homeassistant/components/neato/sensor.py @@ -1,11 +1,20 @@ """Support for Neato sensors.""" +from __future__ import annotations + from datetime import timedelta import logging +from typing import Any from pybotvac.exceptions import NeatoRobotException +from pybotvac.robot import Robot +from homeassistant.components.neato import NeatoHub from homeassistant.components.sensor import DEVICE_CLASS_BATTERY, SensorEntity +from homeassistant.config_entries import ConfigEntry from homeassistant.const import PERCENTAGE +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import NEATO_DOMAIN, NEATO_LOGIN, NEATO_ROBOTS, SCAN_INTERVAL_MINUTES @@ -16,10 +25,12 @@ SCAN_INTERVAL = timedelta(minutes=SCAN_INTERVAL_MINUTES) BATTERY = "Battery" -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: """Set up the Neato sensor using config entry.""" dev = [] - neato = hass.data.get(NEATO_LOGIN) + neato: NeatoHub = hass.data[NEATO_LOGIN] for robot in hass.data[NEATO_ROBOTS]: dev.append(NeatoSensor(neato, robot)) @@ -33,15 +44,15 @@ async def async_setup_entry(hass, entry, async_add_entities): class NeatoSensor(SensorEntity): """Neato sensor.""" - def __init__(self, neato, robot): + def __init__(self, neato: NeatoHub, robot: Robot) -> None: """Initialize Neato sensor.""" self.robot = robot - self._available = False - self._robot_name = f"{self.robot.name} {BATTERY}" - self._robot_serial = self.robot.serial - self._state = None + self._available: bool = False + self._robot_name: str = f"{self.robot.name} {BATTERY}" + self._robot_serial: str = self.robot.serial + self._state: dict[str, Any] | None = None - def update(self): + def update(self) -> None: """Update Neato Sensor.""" try: self._state = self.robot.state @@ -58,36 +69,38 @@ class NeatoSensor(SensorEntity): _LOGGER.debug("self._state=%s", self._state) @property - def name(self): + def name(self) -> str: """Return the name of this sensor.""" return self._robot_name @property - def unique_id(self): + def unique_id(self) -> str: """Return unique ID.""" return self._robot_serial @property - def device_class(self): + def device_class(self) -> str: """Return the device class.""" return DEVICE_CLASS_BATTERY @property - def available(self): + def available(self) -> bool: """Return availability.""" return self._available @property - def state(self): + def state(self) -> str | None: """Return the state.""" - return self._state["details"]["charge"] if self._state else None + if self._state is not None: + return str(self._state["details"]["charge"]) + return None @property - def unit_of_measurement(self): + def unit_of_measurement(self) -> str: """Return unit of measurement.""" return PERCENTAGE @property - def device_info(self): + def device_info(self) -> DeviceInfo: """Device info for neato robot.""" return {"identifiers": {(NEATO_DOMAIN, self._robot_serial)}} diff --git a/homeassistant/components/neato/switch.py b/homeassistant/components/neato/switch.py index a3cc51b82c6..0e0d49f2b28 100644 --- a/homeassistant/components/neato/switch.py +++ b/homeassistant/components/neato/switch.py @@ -1,11 +1,19 @@ """Support for Neato Connected Vacuums switches.""" +from __future__ import annotations + from datetime import timedelta import logging +from typing import Any from pybotvac.exceptions import NeatoRobotException +from pybotvac.robot import Robot +from homeassistant.components.neato import NeatoHub +from homeassistant.config_entries import ConfigEntry from homeassistant.const import STATE_OFF, STATE_ON -from homeassistant.helpers.entity import ToggleEntity +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import DeviceInfo, ToggleEntity +from homeassistant.helpers.entity_platform import AddEntitiesCallback from .const import NEATO_DOMAIN, NEATO_LOGIN, NEATO_ROBOTS, SCAN_INTERVAL_MINUTES @@ -18,10 +26,13 @@ SWITCH_TYPE_SCHEDULE = "schedule" SWITCH_TYPES = {SWITCH_TYPE_SCHEDULE: ["Schedule"]} -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: """Set up Neato switch with config entry.""" dev = [] - neato = hass.data.get(NEATO_LOGIN) + neato: NeatoHub = hass.data[NEATO_LOGIN] + for robot in hass.data[NEATO_ROBOTS]: for type_name in SWITCH_TYPES: dev.append(NeatoConnectedSwitch(neato, robot, type_name)) @@ -36,18 +47,18 @@ async def async_setup_entry(hass, entry, async_add_entities): class NeatoConnectedSwitch(ToggleEntity): """Neato Connected Switches.""" - def __init__(self, neato, robot, switch_type): + def __init__(self, neato: NeatoHub, robot: Robot, switch_type: str) -> None: """Initialize the Neato Connected switches.""" self.type = switch_type self.robot = robot self._available = False self._robot_name = f"{self.robot.name} {SWITCH_TYPES[self.type][0]}" - self._state = None - self._schedule_state = None + self._state: dict[str, Any] | None = None + self._schedule_state: str | None = None self._clean_state = None - self._robot_serial = self.robot.serial + self._robot_serial: str = self.robot.serial - def update(self): + def update(self) -> None: """Update the states of Neato switches.""" _LOGGER.debug("Running Neato switch update for '%s'", self.entity_id) try: @@ -65,7 +76,7 @@ class NeatoConnectedSwitch(ToggleEntity): _LOGGER.debug("self._state=%s", self._state) if self.type == SWITCH_TYPE_SCHEDULE: _LOGGER.debug("State: %s", self._state) - if self._state["details"]["isScheduleEnabled"]: + if self._state is not None and self._state["details"]["isScheduleEnabled"]: self._schedule_state = STATE_ON else: self._schedule_state = STATE_OFF @@ -74,34 +85,33 @@ class NeatoConnectedSwitch(ToggleEntity): ) @property - def name(self): + def name(self) -> str: """Return the name of the switch.""" return self._robot_name @property - def available(self): + def available(self) -> bool: """Return True if entity is available.""" return self._available @property - def unique_id(self): + def unique_id(self) -> str: """Return a unique ID.""" return self._robot_serial @property - def is_on(self): + def is_on(self) -> bool: """Return true if switch is on.""" - if self.type == SWITCH_TYPE_SCHEDULE: - if self._schedule_state == STATE_ON: - return True - return False + return bool( + self.type == SWITCH_TYPE_SCHEDULE and self._schedule_state == STATE_ON + ) @property - def device_info(self): + def device_info(self) -> DeviceInfo: """Device info for neato robot.""" return {"identifiers": {(NEATO_DOMAIN, self._robot_serial)}} - def turn_on(self, **kwargs): + def turn_on(self, **kwargs: Any) -> None: """Turn the switch on.""" if self.type == SWITCH_TYPE_SCHEDULE: try: @@ -111,7 +121,7 @@ class NeatoConnectedSwitch(ToggleEntity): "Neato switch connection error '%s': %s", self.entity_id, ex ) - def turn_off(self, **kwargs): + def turn_off(self, **kwargs: Any) -> None: """Turn the switch off.""" if self.type == SWITCH_TYPE_SCHEDULE: try: diff --git a/homeassistant/components/neato/vacuum.py b/homeassistant/components/neato/vacuum.py index b6cf43a6a3e..527cd4dce23 100644 --- a/homeassistant/components/neato/vacuum.py +++ b/homeassistant/components/neato/vacuum.py @@ -1,7 +1,11 @@ """Support for Neato Connected Vacuums.""" +from __future__ import annotations + from datetime import timedelta import logging +from typing import Any +from pybotvac import Robot from pybotvac.exceptions import NeatoRobotException import voluptuous as vol @@ -24,9 +28,14 @@ from homeassistant.components.vacuum import ( SUPPORT_STOP, StateVacuumEntity, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_MODE +from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv, entity_platform +from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from . import NeatoHub from .const import ( ACTION, ALERTS, @@ -72,12 +81,14 @@ ATTR_CATEGORY = "category" ATTR_ZONE = "zone" -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: """Set up Neato vacuum with config entry.""" dev = [] - neato = hass.data.get(NEATO_LOGIN) - mapdata = hass.data.get(NEATO_MAP_DATA) - persistent_maps = hass.data.get(NEATO_PERSISTENT_MAPS) + neato: NeatoHub = hass.data[NEATO_LOGIN] + mapdata: dict[str, Any] | None = hass.data.get(NEATO_MAP_DATA) + persistent_maps: dict[str, Any] | None = hass.data.get(NEATO_PERSISTENT_MAPS) for robot in hass.data[NEATO_ROBOTS]: dev.append(NeatoConnectedVacuum(neato, robot, mapdata, persistent_maps)) @@ -105,33 +116,39 @@ async def async_setup_entry(hass, entry, async_add_entities): class NeatoConnectedVacuum(StateVacuumEntity): """Representation of a Neato Connected Vacuum.""" - def __init__(self, neato, robot, mapdata, persistent_maps): + def __init__( + self, + neato: NeatoHub, + robot: Robot, + mapdata: dict[str, Any] | None, + persistent_maps: dict[str, Any] | None, + ) -> None: """Initialize the Neato Connected Vacuum.""" self.robot = robot - self._available = neato is not None + self._available: bool = neato is not None self._mapdata = mapdata - self._name = f"{self.robot.name}" - self._robot_has_map = self.robot.has_persistent_maps + self._name: str = f"{self.robot.name}" + self._robot_has_map: bool = self.robot.has_persistent_maps self._robot_maps = persistent_maps - self._robot_serial = self.robot.serial - self._status_state = None - self._clean_state = None - self._state = None - self._clean_time_start = None - self._clean_time_stop = None - self._clean_area = None - self._clean_battery_start = None - self._clean_battery_end = None - self._clean_susp_charge_count = None - self._clean_susp_time = None - self._clean_pause_time = None - self._clean_error_time = None - self._launched_from = None - self._battery_level = None - self._robot_boundaries = [] - self._robot_stats = None + self._robot_serial: str = self.robot.serial + self._status_state: str | None = None + self._clean_state: str | None = None + self._state: dict[str, Any] | None = None + self._clean_time_start: str | None = None + self._clean_time_stop: str | None = None + self._clean_area: float | None = None + self._clean_battery_start: int | None = None + self._clean_battery_end: int | None = None + self._clean_susp_charge_count: int | None = None + self._clean_susp_time: int | None = None + self._clean_pause_time: int | None = None + self._clean_error_time: int | None = None + self._launched_from: str | None = None + self._battery_level: int | None = None + self._robot_boundaries: list = [] + self._robot_stats: dict[str, Any] | None = None - def update(self): + def update(self) -> None: """Update the states of Neato Vacuums.""" _LOGGER.debug("Running Neato Vacuums update for '%s'", self.entity_id) try: @@ -151,6 +168,8 @@ class NeatoConnectedVacuum(StateVacuumEntity): self._available = False return + if self._state is None: + return self._available = True _LOGGER.debug("self._state=%s", self._state) if "alert" in self._state: @@ -198,10 +217,12 @@ class NeatoConnectedVacuum(StateVacuumEntity): self._battery_level = self._state["details"]["charge"] - if not self._mapdata.get(self._robot_serial, {}).get("maps", []): + if self._mapdata is None or not self._mapdata.get(self._robot_serial, {}).get( + "maps", [] + ): return - mapdata = self._mapdata[self._robot_serial]["maps"][0] + mapdata: dict[str, Any] = self._mapdata[self._robot_serial]["maps"][0] self._clean_time_start = mapdata["start_at"] self._clean_time_stop = mapdata["end_at"] self._clean_area = mapdata["cleaned_area"] @@ -215,10 +236,11 @@ class NeatoConnectedVacuum(StateVacuumEntity): if ( self._robot_has_map + and self._state and self._state["availableServices"]["maps"] != "basic-1" - and self._robot_maps[self._robot_serial] + and self._robot_maps ): - allmaps = self._robot_maps[self._robot_serial] + allmaps: dict = self._robot_maps[self._robot_serial] _LOGGER.debug( "Found the following maps for '%s': %s", self.entity_id, allmaps ) @@ -249,44 +271,44 @@ class NeatoConnectedVacuum(StateVacuumEntity): ) @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return self._name @property - def supported_features(self): + def supported_features(self) -> int: """Flag vacuum cleaner robot features that are supported.""" return SUPPORT_NEATO @property - def battery_level(self): + def battery_level(self) -> int | None: """Return the battery level of the vacuum cleaner.""" return self._battery_level @property - def available(self): + def available(self) -> bool: """Return if the robot is available.""" return self._available @property - def icon(self): + def icon(self) -> str: """Return neato specific icon.""" return "mdi:robot-vacuum-variant" @property - def state(self): + def state(self) -> str | None: """Return the status of the vacuum cleaner.""" return self._clean_state @property - def unique_id(self): + def unique_id(self) -> str: """Return a unique ID.""" return self._robot_serial @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict[str, Any]: """Return the state attributes of the vacuum cleaner.""" - data = {} + data: dict[str, Any] = {} if self._status_state is not None: data[ATTR_STATUS] = self._status_state @@ -314,28 +336,32 @@ class NeatoConnectedVacuum(StateVacuumEntity): return data @property - def device_info(self): + def device_info(self) -> DeviceInfo: """Device info for neato robot.""" - info = {"identifiers": {(NEATO_DOMAIN, self._robot_serial)}, "name": self._name} + info: DeviceInfo = { + "identifiers": {(NEATO_DOMAIN, self._robot_serial)}, + "name": self._name, + } if self._robot_stats: info["manufacturer"] = self._robot_stats["battery"]["vendor"] info["model"] = self._robot_stats["model"] info["sw_version"] = self._robot_stats["firmware"] return info - def start(self): + def start(self) -> None: """Start cleaning or resume cleaning.""" - try: - if self._state["state"] == 1: - self.robot.start_cleaning() - elif self._state["state"] == 3: - self.robot.resume_cleaning() - except NeatoRobotException as ex: - _LOGGER.error( - "Neato vacuum connection error for '%s': %s", self.entity_id, ex - ) + if self._state: + try: + if self._state["state"] == 1: + self.robot.start_cleaning() + elif self._state["state"] == 3: + self.robot.resume_cleaning() + except NeatoRobotException as ex: + _LOGGER.error( + "Neato vacuum connection error for '%s': %s", self.entity_id, ex + ) - def pause(self): + def pause(self) -> None: """Pause the vacuum.""" try: self.robot.pause_cleaning() @@ -344,7 +370,7 @@ class NeatoConnectedVacuum(StateVacuumEntity): "Neato vacuum connection error for '%s': %s", self.entity_id, ex ) - def return_to_base(self, **kwargs): + def return_to_base(self, **kwargs: Any) -> None: """Set the vacuum cleaner to return to the dock.""" try: if self._clean_state == STATE_CLEANING: @@ -356,7 +382,7 @@ class NeatoConnectedVacuum(StateVacuumEntity): "Neato vacuum connection error for '%s': %s", self.entity_id, ex ) - def stop(self, **kwargs): + def stop(self, **kwargs: Any) -> None: """Stop the vacuum cleaner.""" try: self.robot.stop_cleaning() @@ -365,7 +391,7 @@ class NeatoConnectedVacuum(StateVacuumEntity): "Neato vacuum connection error for '%s': %s", self.entity_id, ex ) - def locate(self, **kwargs): + def locate(self, **kwargs: Any) -> None: """Locate the robot by making it emit a sound.""" try: self.robot.locate() @@ -374,7 +400,7 @@ class NeatoConnectedVacuum(StateVacuumEntity): "Neato vacuum connection error for '%s': %s", self.entity_id, ex ) - def clean_spot(self, **kwargs): + def clean_spot(self, **kwargs: Any) -> None: """Run a spot cleaning starting from the base.""" try: self.robot.start_spot_cleaning() @@ -383,7 +409,9 @@ class NeatoConnectedVacuum(StateVacuumEntity): "Neato vacuum connection error for '%s': %s", self.entity_id, ex ) - def neato_custom_cleaning(self, mode, navigation, category, zone=None): + def neato_custom_cleaning( + self, mode: str, navigation: str, category: str, zone: str | None = None + ) -> None: """Zone cleaning service call.""" boundary_id = None if zone is not None: diff --git a/mypy.ini b/mypy.ini index 7f0a932f0af..9c54f7a043f 100644 --- a/mypy.ini +++ b/mypy.ini @@ -704,6 +704,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.neato.*] +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.nest.*] check_untyped_defs = true disallow_incomplete_defs = true @@ -1515,9 +1526,6 @@ ignore_errors = true [mypy-homeassistant.components.mullvad.*] ignore_errors = true -[mypy-homeassistant.components.neato.*] -ignore_errors = true - [mypy-homeassistant.components.ness_alarm.*] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 23967721053..e3b76747be2 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -101,7 +101,6 @@ IGNORED_MODULES: Final[list[str]] = [ "homeassistant.components.mobile_app.*", "homeassistant.components.motion_blinds.*", "homeassistant.components.mullvad.*", - "homeassistant.components.neato.*", "homeassistant.components.ness_alarm.*", "homeassistant.components.nest.legacy.*", "homeassistant.components.netio.*",