Bump pybotvac and use new exceptions (#27249)

* Bump pybotvac

* Fix tests

* Remove super calls

* Surround some more statements

* Correct logger message for vacuum
This commit is contained in:
Santobert 2019-10-06 20:10:11 +02:00 committed by Martin Hjelmare
parent 1059cea28f
commit dae8cd8801
12 changed files with 178 additions and 77 deletions

View File

@ -13,7 +13,8 @@
} }
}, },
"error": { "error": {
"invalid_credentials": "Invalid credentials" "invalid_credentials": "Invalid credentials",
"unexpected_error": "Unexpected error"
}, },
"create_entry": { "create_entry": {
"default": "See [Neato documentation]({docs_url})." "default": "See [Neato documentation]({docs_url})."

View File

@ -3,7 +3,7 @@ import asyncio
import logging import logging
from datetime import timedelta from datetime import timedelta
from requests.exceptions import HTTPError, ConnectionError as ConnError from pybotvac.exceptions import NeatoException, NeatoLoginException, NeatoRobotException
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.config_entries import SOURCE_IMPORT
@ -20,6 +20,7 @@ from .const import (
NEATO_ROBOTS, NEATO_ROBOTS,
NEATO_PERSISTENT_MAPS, NEATO_PERSISTENT_MAPS,
NEATO_MAP_DATA, NEATO_MAP_DATA,
SCAN_INTERVAL_MINUTES,
VALID_VENDORS, VALID_VENDORS,
) )
@ -103,7 +104,12 @@ async def async_setup_entry(hass, entry):
_LOGGER.debug("Failed to login to Neato API") _LOGGER.debug("Failed to login to Neato API")
return False return False
await hass.async_add_executor_job(hub.update_robots) try:
await hass.async_add_executor_job(hub.update_robots)
except NeatoRobotException:
_LOGGER.debug("Failed to connect to Neato API")
return False
for component in ("camera", "vacuum", "switch"): for component in ("camera", "vacuum", "switch"):
hass.async_create_task( hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component) hass.config_entries.async_forward_entry_setup(entry, component)
@ -144,17 +150,19 @@ class NeatoHub:
self.config[CONF_USERNAME], self.config[CONF_PASSWORD], self._vendor self.config[CONF_USERNAME], self.config[CONF_PASSWORD], self._vendor
) )
self.logged_in = True self.logged_in = True
except (HTTPError, ConnError):
_LOGGER.error("Unable to connect to Neato API") _LOGGER.debug("Successfully connected to Neato API")
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
except NeatoException as ex:
if isinstance(ex, NeatoLoginException):
_LOGGER.error("Invalid credentials")
else:
_LOGGER.error("Unable to connect to Neato API")
self.logged_in = False self.logged_in = False
return
_LOGGER.debug("Successfully connected to Neato API") @Throttle(timedelta(minutes=SCAN_INTERVAL_MINUTES))
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
@Throttle(timedelta(seconds=300))
def update_robots(self): def update_robots(self):
"""Update the robot states.""" """Update the robot states."""
_LOGGER.debug("Running HUB.update_robots %s", self._hass.data[NEATO_ROBOTS]) _LOGGER.debug("Running HUB.update_robots %s", self._hass.data[NEATO_ROBOTS])

View File

@ -2,13 +2,21 @@
from datetime import timedelta from datetime import timedelta
import logging import logging
from pybotvac.exceptions import NeatoRobotException
from homeassistant.components.camera import Camera from homeassistant.components.camera import Camera
from .const import NEATO_DOMAIN, NEATO_MAP_DATA, NEATO_ROBOTS, NEATO_LOGIN from .const import (
NEATO_DOMAIN,
NEATO_MAP_DATA,
NEATO_ROBOTS,
NEATO_LOGIN,
SCAN_INTERVAL_MINUTES,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(minutes=10) SCAN_INTERVAL = timedelta(minutes=SCAN_INTERVAL_MINUTES)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
@ -37,9 +45,9 @@ class NeatoCleaningMap(Camera):
"""Initialize Neato cleaning map.""" """Initialize Neato cleaning map."""
super().__init__() super().__init__()
self.robot = robot self.robot = robot
self._robot_name = "{} {}".format(self.robot.name, "Cleaning Map") self.neato = hass.data[NEATO_LOGIN] if NEATO_LOGIN in hass.data else None
self._robot_name = f"{self.robot.name} Cleaning Map"
self._robot_serial = self.robot.serial self._robot_serial = self.robot.serial
self.neato = hass.data[NEATO_LOGIN]
self._image_url = None self._image_url = None
self._image = None self._image = None
@ -50,16 +58,31 @@ class NeatoCleaningMap(Camera):
def update(self): def update(self):
"""Check the contents of the map list.""" """Check the contents of the map list."""
self.neato.update_robots() if self.neato is None:
image_url = None _LOGGER.error("Error while updating camera")
map_data = self.hass.data[NEATO_MAP_DATA] self._image = None
image_url = map_data[self._robot_serial]["maps"][0]["url"] self._image_url = None
if image_url == self._image_url:
_LOGGER.debug("The map image_url is the same as old")
return return
image = self.neato.download_map(image_url)
self._image = image.read() _LOGGER.debug("Running camera update")
self._image_url = image_url try:
self.neato.update_robots()
image_url = None
map_data = self.hass.data[NEATO_MAP_DATA][self._robot_serial]["maps"][0]
image_url = map_data["url"]
if image_url == self._image_url:
_LOGGER.debug("The map image_url is the same as old")
return
image = self.neato.download_map(image_url)
self._image = image.read()
self._image_url = image_url
except NeatoRobotException as ex:
_LOGGER.error("Neato camera connection error: %s", ex)
self._image = None
self._image_url = None
@property @property
def name(self): def name(self):

View File

@ -3,7 +3,7 @@
import logging import logging
import voluptuous as vol import voluptuous as vol
from requests.exceptions import HTTPError, ConnectionError as ConnError from pybotvac.exceptions import NeatoLoginException, NeatoRobotException
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
@ -106,7 +106,9 @@ class NeatoConfigFlow(config_entries.ConfigFlow, domain=NEATO_DOMAIN):
try: try:
Account(username, password, this_vendor) Account(username, password, this_vendor)
except (HTTPError, ConnError): except NeatoLoginException:
return "invalid_credentials" return "invalid_credentials"
except NeatoRobotException:
return "unexpected_error"
return None return None

View File

@ -9,6 +9,8 @@ NEATO_CONFIG = "neato_config"
NEATO_MAP_DATA = "neato_map_data" NEATO_MAP_DATA = "neato_map_data"
NEATO_PERSISTENT_MAPS = "neato_persistent_maps" NEATO_PERSISTENT_MAPS = "neato_persistent_maps"
SCAN_INTERVAL_MINUTES = 5
VALID_VENDORS = ["neato", "vorwerk"] VALID_VENDORS = ["neato", "vorwerk"]
MODE = {1: "Eco", 2: "Turbo"} MODE = {1: "Eco", 2: "Turbo"}

View File

@ -4,7 +4,7 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/neato", "documentation": "https://www.home-assistant.io/integrations/neato",
"requirements": [ "requirements": [
"pybotvac==0.0.15" "pybotvac==0.0.16"
], ],
"dependencies": [], "dependencies": [],
"codeowners": [ "codeowners": [

View File

@ -13,7 +13,8 @@
} }
}, },
"error": { "error": {
"invalid_credentials": "Invalid credentials" "invalid_credentials": "Invalid credentials",
"unexpected_error": "Unexpected error"
}, },
"create_entry": { "create_entry": {
"default": "See [Neato documentation]({docs_url})." "default": "See [Neato documentation]({docs_url})."

View File

@ -2,16 +2,16 @@
from datetime import timedelta from datetime import timedelta
import logging import logging
import requests from pybotvac.exceptions import NeatoRobotException
from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
from .const import NEATO_DOMAIN, NEATO_LOGIN, NEATO_ROBOTS from .const import NEATO_DOMAIN, NEATO_LOGIN, NEATO_ROBOTS, SCAN_INTERVAL_MINUTES
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(minutes=10) SCAN_INTERVAL = timedelta(minutes=SCAN_INTERVAL_MINUTES)
SWITCH_TYPE_SCHEDULE = "schedule" SWITCH_TYPE_SCHEDULE = "schedule"
@ -44,8 +44,8 @@ class NeatoConnectedSwitch(ToggleEntity):
"""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.neato = hass.data[NEATO_LOGIN] self.neato = hass.data[NEATO_LOGIN] if NEATO_LOGIN in hass.data else None
self._robot_name = "{} {}".format(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 = None
self._schedule_state = None self._schedule_state = None
self._clean_state = None self._clean_state = None
@ -53,17 +53,20 @@ class NeatoConnectedSwitch(ToggleEntity):
def update(self): def update(self):
"""Update the states of Neato switches.""" """Update the states of Neato switches."""
_LOGGER.debug("Running switch update") if self.neato is None:
self.neato.update_robots() _LOGGER.error("Error while updating switches")
try:
self._state = self.robot.state
except (
requests.exceptions.ConnectionError,
requests.exceptions.HTTPError,
) as ex:
_LOGGER.warning("Neato connection error: %s", ex)
self._state = None self._state = None
return return
_LOGGER.debug("Running switch update")
try:
self.neato.update_robots()
self._state = self.robot.state
except NeatoRobotException as ex:
_LOGGER.error("Neato switch connection error: %s", ex)
self._state = None
return
_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)
@ -104,9 +107,15 @@ class NeatoConnectedSwitch(ToggleEntity):
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Turn the switch on.""" """Turn the switch on."""
if self.type == SWITCH_TYPE_SCHEDULE: if self.type == SWITCH_TYPE_SCHEDULE:
self.robot.enable_schedule() try:
self.robot.enable_schedule()
except NeatoRobotException as ex:
_LOGGER.error("Neato switch connection error: %s", ex)
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
"""Turn the switch off.""" """Turn the switch off."""
if self.type == SWITCH_TYPE_SCHEDULE: if self.type == SWITCH_TYPE_SCHEDULE:
self.robot.disable_schedule() try:
self.robot.disable_schedule()
except NeatoRobotException as ex:
_LOGGER.error("Neato switch connection error: %s", ex)

View File

@ -2,7 +2,8 @@
from datetime import timedelta from datetime import timedelta
import logging import logging
import requests from pybotvac.exceptions import NeatoRobotException
import voluptuous as vol import voluptuous as vol
from homeassistant.components.vacuum import ( from homeassistant.components.vacuum import (
@ -41,11 +42,12 @@ from .const import (
NEATO_MAP_DATA, NEATO_MAP_DATA,
NEATO_PERSISTENT_MAPS, NEATO_PERSISTENT_MAPS,
NEATO_ROBOTS, NEATO_ROBOTS,
SCAN_INTERVAL_MINUTES,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(minutes=5) SCAN_INTERVAL = timedelta(minutes=SCAN_INTERVAL_MINUTES)
SUPPORT_NEATO = ( SUPPORT_NEATO = (
SUPPORT_BATTERY SUPPORT_BATTERY
@ -154,22 +156,26 @@ class NeatoConnectedVacuum(StateVacuumDevice):
def update(self): def update(self):
"""Update the states of Neato Vacuums.""" """Update the states of Neato Vacuums."""
_LOGGER.debug("Running Neato Vacuums update") if self.neato is None:
if self._robot_stats is None: _LOGGER.error("Error while updating vacuum")
self._robot_stats = self.robot.get_robot_info().json()
self.neato.update_robots()
try:
self._state = self.robot.state
self._available = True
except (
requests.exceptions.ConnectionError,
requests.exceptions.HTTPError,
) as ex:
_LOGGER.warning("Neato connection error: %s", ex)
self._state = None self._state = None
self._available = False self._available = False
return return
try:
_LOGGER.debug("Running Neato Vacuums update")
if self._robot_stats is None:
self._robot_stats = self.robot.get_robot_info().json()
self.neato.update_robots()
self._state = self.robot.state
self._available = True
except NeatoRobotException as ex:
if self._available: # print only once when available
_LOGGER.error("Neato vacuum connection error: %s", ex)
self._state = None
self._available = False
return
_LOGGER.debug("self._state=%s", self._state) _LOGGER.debug("self._state=%s", self._state)
if "alert" in self._state: if "alert" in self._state:
robot_alert = ALERTS.get(self._state["alert"]) robot_alert = ALERTS.get(self._state["alert"])
@ -313,33 +319,51 @@ class NeatoConnectedVacuum(StateVacuumDevice):
def start(self): def start(self):
"""Start cleaning or resume cleaning.""" """Start cleaning or resume cleaning."""
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:
self.robot.resume_cleaning()
except NeatoRobotException as ex:
_LOGGER.error("Neato vacuum connection error: %s", ex)
def pause(self): def pause(self):
"""Pause the vacuum.""" """Pause the vacuum."""
self.robot.pause_cleaning() try:
self.robot.pause_cleaning()
except NeatoRobotException as ex:
_LOGGER.error("Neato vacuum connection error: %s", ex)
def return_to_base(self, **kwargs): def return_to_base(self, **kwargs):
"""Set the vacuum cleaner to return to the dock.""" """Set the vacuum cleaner to return to the dock."""
if self._clean_state == STATE_CLEANING: try:
self.robot.pause_cleaning() if self._clean_state == STATE_CLEANING:
self._clean_state = STATE_RETURNING self.robot.pause_cleaning()
self.robot.send_to_base() self._clean_state = STATE_RETURNING
self.robot.send_to_base()
except NeatoRobotException as ex:
_LOGGER.error("Neato vacuum connection error: %s", ex)
def stop(self, **kwargs): def stop(self, **kwargs):
"""Stop the vacuum cleaner.""" """Stop the vacuum cleaner."""
self.robot.stop_cleaning() try:
self.robot.stop_cleaning()
except NeatoRobotException as ex:
_LOGGER.error("Neato vacuum connection error: %s", ex)
def locate(self, **kwargs): def locate(self, **kwargs):
"""Locate the robot by making it emit a sound.""" """Locate the robot by making it emit a sound."""
self.robot.locate() try:
self.robot.locate()
except NeatoRobotException as ex:
_LOGGER.error("Neato vacuum connection error: %s", ex)
def clean_spot(self, **kwargs): def clean_spot(self, **kwargs):
"""Run a spot cleaning starting from the base.""" """Run a spot cleaning starting from the base."""
self.robot.start_spot_cleaning() try:
self.robot.start_spot_cleaning()
except NeatoRobotException as ex:
_LOGGER.error("Neato vacuum connection error: %s", ex)
def neato_custom_cleaning(self, mode, navigation, category, zone=None, **kwargs): def neato_custom_cleaning(self, mode, navigation, category, zone=None, **kwargs):
"""Zone cleaning service call.""" """Zone cleaning service call."""
@ -355,4 +379,7 @@ class NeatoConnectedVacuum(StateVacuumDevice):
return return
self._clean_state = STATE_CLEANING self._clean_state = STATE_CLEANING
self.robot.start_cleaning(mode, navigation, category, boundary_id) try:
self.robot.start_cleaning(mode, navigation, category, boundary_id)
except NeatoRobotException as ex:
_LOGGER.error("Neato vacuum connection error: %s", ex)

View File

@ -1102,7 +1102,7 @@ pyblackbird==0.5
# pybluez==0.22 # pybluez==0.22
# homeassistant.components.neato # homeassistant.components.neato
pybotvac==0.0.15 pybotvac==0.0.16
# homeassistant.components.nissan_leaf # homeassistant.components.nissan_leaf
pycarwings2==2.9 pycarwings2==2.9

View File

@ -297,7 +297,7 @@ pyMetno==0.4.6
pyblackbird==0.5 pyblackbird==0.5
# homeassistant.components.neato # homeassistant.components.neato
pybotvac==0.0.15 pybotvac==0.0.16
# homeassistant.components.cast # homeassistant.components.cast
pychromecast==4.0.1 pychromecast==4.0.1

View File

@ -103,11 +103,11 @@ async def test_abort_if_already_setup(hass, account):
async def test_abort_on_invalid_credentials(hass): async def test_abort_on_invalid_credentials(hass):
"""Test when we have invalid credentials.""" """Test when we have invalid credentials."""
from requests.exceptions import HTTPError from pybotvac.exceptions import NeatoLoginException
flow = init_config_flow(hass) flow = init_config_flow(hass)
with patch("pybotvac.Account", side_effect=HTTPError()): with patch("pybotvac.Account", side_effect=NeatoLoginException()):
result = await flow.async_step_user( result = await flow.async_step_user(
{ {
CONF_USERNAME: USERNAME, CONF_USERNAME: USERNAME,
@ -127,3 +127,31 @@ async def test_abort_on_invalid_credentials(hass):
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "invalid_credentials" assert result["reason"] == "invalid_credentials"
async def test_abort_on_unexpected_error(hass):
"""Test when we have an unexpected error."""
from pybotvac.exceptions import NeatoRobotException
flow = init_config_flow(hass)
with patch("pybotvac.Account", side_effect=NeatoRobotException()):
result = await flow.async_step_user(
{
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
CONF_VENDOR: VENDOR_NEATO,
}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["errors"] == {"base": "unexpected_error"}
result = await flow.async_step_import(
{
CONF_USERNAME: USERNAME,
CONF_PASSWORD: PASSWORD,
CONF_VENDOR: VENDOR_NEATO,
}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result["reason"] == "unexpected_error"