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