Skip to content
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
b4ac588
initial commit
Santobert Sep 7, 2019
161ae33
Minor changes
Santobert Sep 8, 2019
66bbeb6
add async setup entry
Santobert Sep 8, 2019
d366279
Add translations and some other stuff
Santobert Sep 11, 2019
5be857f
add and remove entry
Santobert Sep 11, 2019
d4bb5e5
use async_setup_entry
Santobert Sep 11, 2019
86c5c9a
Update config_flows.py
Santobert Sep 11, 2019
a9be9a0
dshokouhi's changes
Santobert Sep 12, 2019
ce61769
Improve workflow
Santobert Sep 12, 2019
fbb8a74
Add valid_vendors
Santobert Sep 12, 2019
66aab5a
Add entity registry
Santobert Sep 12, 2019
fa47173
Add device registry
Santobert Sep 12, 2019
ed9c0cf
Update entry from configuration.yaml
Santobert Sep 12, 2019
403d3c8
Revert unneccesary changes
Santobert Sep 12, 2019
64661f7
Update .coveragerc
Santobert Sep 13, 2019
c53efe0
Prepared tests
Santobert Sep 13, 2019
c557c69
Add dshokouhi and Santobert as codeowners
Santobert Sep 13, 2019
b0549e9
Fix unload entry and abort when already_configured
Santobert Sep 13, 2019
c00c885
First tests
Santobert Sep 13, 2019
a7d14d8
Add test for abort cases
Santobert Sep 13, 2019
199dd8b
Add test for invalid credentials on import
Santobert Sep 13, 2019
c3fa082
Add one last test
Santobert Sep 13, 2019
a619959
Add test_init.py with some tests
Santobert Sep 13, 2019
c64439d
Merge remote-tracking branch 'upstream/dev' into neato_config_flow
Santobert Sep 28, 2019
f1b0355
Address reviews, part 1
Santobert Sep 28, 2019
bfafae3
Update outdated entry
Santobert Sep 28, 2019
9520aa1
await instead of add_job
Santobert Sep 28, 2019
a0ce9a3
run IO inside an executor
Santobert Sep 28, 2019
6cb4746
remove faulty test
Santobert Sep 28, 2019
c843f39
Fix pylint issues
Santobert Sep 28, 2019
fa61829
Move IO out of constructur
Santobert Sep 28, 2019
c901f32
Edit error translations
Santobert Sep 29, 2019
49ad8a2
Edit imports
Santobert Sep 29, 2019
1459152
Minor changes
Santobert Sep 29, 2019
af626e1
Remove test for invalid vendor
Santobert Sep 29, 2019
d710643
Async setup platform
Santobert Sep 29, 2019
f95c61a
Edit login function
Santobert Sep 29, 2019
91f7e69
Moved IO out if init
Santobert Sep 29, 2019
ec24c5b
Update switches after added to hass
Santobert Sep 29, 2019
80228bd
Revert update outdated entry
Santobert Sep 29, 2019
69209c2
Merge remote-tracking branch 'upstream/dev' into neato_config_flow
Santobert Oct 2, 2019
76f0b69
try and update new entrys from config.yaml
Santobert Oct 6, 2019
67e205d
Add test invalid vendor
Santobert Oct 6, 2019
4e30a56
Default to neato
Santobert Oct 6, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,9 @@ omit =
homeassistant/components/n26/*
homeassistant/components/nad/media_player.py
homeassistant/components/nanoleaf/light.py
homeassistant/components/neato/*
homeassistant/components/neato/camera.py
homeassistant/components/neato/vacuum.py
homeassistant/components/neato/switch.py
homeassistant/components/nederlandse_spoorwegen/sensor.py
homeassistant/components/nello/lock.py
homeassistant/components/nest/*
Expand Down
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ homeassistant/components/mpd/* @fabaff
homeassistant/components/mqtt/* @home-assistant/core
homeassistant/components/mysensors/* @MartinHjelmare
homeassistant/components/mystrom/* @fabaff
homeassistant/components/neato/* @dshokouhi @Santobert
homeassistant/components/nello/* @pschmitt
homeassistant/components/ness_alarm/* @nickw444
homeassistant/components/nest/* @awarecan
Expand Down
26 changes: 26 additions & 0 deletions homeassistant/components/neato/.translations/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"config": {
"title": "Neato",
"step": {
"user": {
"title": "Neato Account Info",
"data": {
"username": "Username",
"password": "Password",
"vendor": "Vendor"
},
"description": "See [Neato documentation]({docs_url})."
}
},
"error": {
"invalid_credentials": "Invalid credentials"
},
"create_entry": {
"default": "See [Neato documentation]({docs_url})."
},
"abort": {
"already_configured": "Already configured",
"invalid_credentials": "Invalid credentials"
Comment thread
Santobert marked this conversation as resolved.
}
}
}
259 changes: 90 additions & 169 deletions homeassistant/components/neato/__init__.py
Original file line number Diff line number Diff line change
@@ -1,194 +1,113 @@
"""Support for Neato botvac connected vacuum cleaners."""
import asyncio
import logging
from datetime import timedelta
from urllib.error import HTTPError

from requests.exceptions import HTTPError, ConnectionError as ConnError
import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import discovery
from homeassistant.helpers import config_validation as cv
from homeassistant.util import Throttle

from .const import (
CONF_VENDOR,
NEATO_CONFIG,
NEATO_DOMAIN,
NEATO_LOGIN,
NEATO_ROBOTS,
NEATO_PERSISTENT_MAPS,
NEATO_MAP_DATA,
VALID_VENDORS,
)

_LOGGER = logging.getLogger(__name__)

CONF_VENDOR = "vendor"
DOMAIN = "neato"
NEATO_ROBOTS = "neato_robots"
NEATO_LOGIN = "neato_login"
NEATO_MAP_DATA = "neato_map_data"
NEATO_PERSISTENT_MAPS = "neato_persistent_maps"

CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
NEATO_DOMAIN: vol.Schema(
{
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_VENDOR, default="neato"): vol.In(
["neato", "vorwerk"]
),
vol.Optional(CONF_VENDOR, default="neato"): vol.In(VALID_VENDORS),
}
)
},
extra=vol.ALLOW_EXTRA,
)

MODE = {1: "Eco", 2: "Turbo"}

ACTION = {
0: "Invalid",
1: "House Cleaning",
2: "Spot Cleaning",
3: "Manual Cleaning",
4: "Docking",
5: "User Menu Active",
6: "Suspended Cleaning",
7: "Updating",
8: "Copying logs",
9: "Recovering Location",
10: "IEC test",
11: "Map cleaning",
12: "Exploring map (creating a persistent map)",
13: "Acquiring Persistent Map IDs",
14: "Creating & Uploading Map",
15: "Suspended Exploration",
}

ERRORS = {
"ui_error_battery_battundervoltlithiumsafety": "Replace battery",
"ui_error_battery_critical": "Replace battery",
"ui_error_battery_invalidsensor": "Replace battery",
"ui_error_battery_lithiumadapterfailure": "Replace battery",
"ui_error_battery_mismatch": "Replace battery",
"ui_error_battery_nothermistor": "Replace battery",
"ui_error_battery_overtemp": "Replace battery",
"ui_error_battery_overvolt": "Replace battery",
"ui_error_battery_undercurrent": "Replace battery",
"ui_error_battery_undertemp": "Replace battery",
"ui_error_battery_undervolt": "Replace battery",
"ui_error_battery_unplugged": "Replace battery",
"ui_error_brush_stuck": "Brush stuck",
"ui_error_brush_overloaded": "Brush overloaded",
"ui_error_bumper_stuck": "Bumper stuck",
"ui_error_check_battery_switch": "Check battery",
"ui_error_corrupt_scb": "Call customer service corrupt board",
"ui_error_deck_debris": "Deck debris",
"ui_error_dflt_app": "Check Neato app",
"ui_error_disconnect_chrg_cable": "Disconnected charge cable",
"ui_error_disconnect_usb_cable": "Disconnected USB cable",
"ui_error_dust_bin_missing": "Dust bin missing",
"ui_error_dust_bin_full": "Dust bin full",
"ui_error_dust_bin_emptied": "Dust bin emptied",
"ui_error_hardware_failure": "Hardware failure",
"ui_error_ldrop_stuck": "Clear my path",
"ui_error_lds_jammed": "Clear my path",
"ui_error_lds_bad_packets": "Check Neato app",
"ui_error_lds_disconnected": "Check Neato app",
"ui_error_lds_missed_packets": "Check Neato app",
"ui_error_lwheel_stuck": "Clear my path",
"ui_error_navigation_backdrop_frontbump": "Clear my path",
"ui_error_navigation_backdrop_leftbump": "Clear my path",
"ui_error_navigation_backdrop_wheelextended": "Clear my path",
"ui_error_navigation_noprogress": "Clear my path",
"ui_error_navigation_origin_unclean": "Clear my path",
"ui_error_navigation_pathproblems": "Cannot return to base",
"ui_error_navigation_pinkycommsfail": "Clear my path",
"ui_error_navigation_falling": "Clear my path",
"ui_error_navigation_noexitstogo": "Clear my path",
"ui_error_navigation_nomotioncommands": "Clear my path",
"ui_error_navigation_rightdrop_leftbump": "Clear my path",
"ui_error_navigation_undockingfailed": "Clear my path",
"ui_error_picked_up": "Picked up",
"ui_error_qa_fail": "Check Neato app",
"ui_error_rdrop_stuck": "Clear my path",
"ui_error_reconnect_failed": "Reconnect failed",
"ui_error_rwheel_stuck": "Clear my path",
"ui_error_stuck": "Stuck!",
"ui_error_unable_to_return_to_base": "Unable to return to base",
"ui_error_unable_to_see": "Clean vacuum sensors",
"ui_error_vacuum_slip": "Clear my path",
"ui_error_vacuum_stuck": "Clear my path",
"ui_error_warning": "Error check app",
"batt_base_connect_fail": "Battery failed to connect to base",
"batt_base_no_power": "Battery base has no power",
"batt_low": "Battery low",
"batt_on_base": "Battery on base",
"clean_tilt_on_start": "Clean the tilt on start",
"dustbin_full": "Dust bin full",
"dustbin_missing": "Dust bin missing",
"gen_picked_up": "Picked up",
"hw_fail": "Hardware failure",
"hw_tof_sensor_sensor": "Hardware sensor disconnected",
"lds_bad_packets": "Bad packets",
"lds_deck_debris": "Debris on deck",
"lds_disconnected": "Disconnected",
"lds_jammed": "Jammed",
"lds_missed_packets": "Missed packets",
"maint_brush_stuck": "Brush stuck",
"maint_brush_overload": "Brush overloaded",
"maint_bumper_stuck": "Bumper stuck",
"maint_customer_support_qa": "Contact customer support",
"maint_vacuum_stuck": "Vacuum is stuck",
"maint_vacuum_slip": "Vacuum is stuck",
"maint_left_drop_stuck": "Vacuum is stuck",
"maint_left_wheel_stuck": "Vacuum is stuck",
"maint_right_drop_stuck": "Vacuum is stuck",
"maint_right_wheel_stuck": "Vacuum is stuck",
"not_on_charge_base": "Not on the charge base",
"nav_robot_falling": "Clear my path",
"nav_no_path": "Clear my path",
"nav_path_problem": "Clear my path",
"nav_backdrop_frontbump": "Clear my path",
"nav_backdrop_leftbump": "Clear my path",
"nav_backdrop_wheelextended": "Clear my path",
"nav_mag_sensor": "Clear my path",
"nav_no_exit": "Clear my path",
"nav_no_movement": "Clear my path",
"nav_rightdrop_leftbump": "Clear my path",
"nav_undocking_failed": "Clear my path",
}

ALERTS = {
"ui_alert_dust_bin_full": "Please empty dust bin",
"ui_alert_recovering_location": "Returning to start",
"ui_alert_battery_chargebasecommerr": "Battery error",
"ui_alert_busy_charging": "Busy charging",
"ui_alert_charging_base": "Base charging",
"ui_alert_charging_power": "Charging power",
"ui_alert_connect_chrg_cable": "Connect charge cable",
"ui_alert_info_thank_you": "Thank you",
"ui_alert_invalid": "Invalid check app",
"ui_alert_old_error": "Old error",
"ui_alert_swupdate_fail": "Update failed",
"dustbin_full": "Please empty dust bin",
"maint_brush_change": "Change the brush",
"maint_filter_change": "Change the filter",
"clean_completed_to_start": "Cleaning completed",
"nav_floorplan_not_created": "No floorplan found",
"nav_floorplan_load_fail": "Failed to load floorplan",
"nav_floorplan_localization_fail": "Failed to load floorplan",
"clean_incomplete_to_start": "Cleaning incomplete",
"log_upload_failed": "Logs failed to upload",
}


def setup(hass, config):

async def async_setup(hass, config):
"""Set up the Neato component."""

if NEATO_DOMAIN not in config:
# There is an entry and nothing in configuration.yaml
return True

entries = hass.config_entries.async_entries(NEATO_DOMAIN)
hass.data[NEATO_CONFIG] = config[NEATO_DOMAIN]

if entries:
# There is an entry and something in the configuration.yaml
entry = entries[0]
conf = config[NEATO_DOMAIN]
if (
entry.data[CONF_USERNAME] == conf[CONF_USERNAME]
and entry.data[CONF_PASSWORD] == conf[CONF_PASSWORD]
and entry.data[CONF_VENDOR] == conf[CONF_VENDOR]
):
# The entry is not outdated
return True

# The entry is outdated
hass.config_entries.async_update_entry(entry, data=config[NEATO_DOMAIN])
Comment thread
Santobert marked this conversation as resolved.
else:
# Create the new entry
hass.async_create_task(
hass.config_entries.flow.async_init(
NEATO_DOMAIN,
context={"source": SOURCE_IMPORT},
data=config[NEATO_DOMAIN],
)
)

return True


async def async_setup_entry(hass, entry):
"""Set up config entry."""
from pybotvac import Account, Neato, Vorwerk

if config[DOMAIN][CONF_VENDOR] == "neato":
hass.data[NEATO_LOGIN] = NeatoHub(hass, config[DOMAIN], Account, Neato)
elif config[DOMAIN][CONF_VENDOR] == "vorwerk":
hass.data[NEATO_LOGIN] = NeatoHub(hass, config[DOMAIN], Account, Vorwerk)
if entry.data[CONF_VENDOR] == "neato":
hass.data[NEATO_LOGIN] = NeatoHub(hass, entry.data, Account, Neato)
elif entry.data[CONF_VENDOR] == "vorwerk":
hass.data[NEATO_LOGIN] = NeatoHub(hass, entry.data, Account, Vorwerk)

hub = hass.data[NEATO_LOGIN]
if not hub.login():
await hass.async_add_executor_job(hub.login)
if not hub.logged_in:
Comment thread
Santobert marked this conversation as resolved.
_LOGGER.debug("Failed to login to Neato API")
return False
hub.update_robots()

await hass.async_add_executor_job(hub.update_robots)
for component in ("camera", "vacuum", "switch"):
discovery.load_platform(hass, component, DOMAIN, {}, config)
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
)

return True


async def async_unload_entry(hass, entry):
"""Unload config entry."""
hass.data.pop(NEATO_LOGIN)
await asyncio.gather(
hass.config_entries.async_forward_entry_unload(entry, "camera"),
hass.config_entries.async_forward_entry_unload(entry, "vacuum"),
hass.config_entries.async_forward_entry_unload(entry, "switch"),
)
return True


Expand All @@ -202,12 +121,8 @@ def __init__(self, hass, domain_config, neato, vendor):
self._hass = hass
self._vendor = vendor

self.my_neato = neato(
domain_config[CONF_USERNAME], domain_config[CONF_PASSWORD], vendor
)
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
self.my_neato = None
self.logged_in = False

def login(self):
"""Login to My Neato."""
Expand All @@ -216,10 +131,16 @@ def login(self):
self.my_neato = self._neato(
self.config[CONF_USERNAME], self.config[CONF_PASSWORD], self._vendor
)
return True
except HTTPError:
self.logged_in = True
except (HTTPError, ConnError):
_LOGGER.error("Unable to connect to Neato API")
return False
self.logged_in = False
return

_LOGGER.debug("Successfully connected to Neato API")
Comment thread
Santobert marked this conversation as resolved.
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):
Expand Down
20 changes: 17 additions & 3 deletions homeassistant/components/neato/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,30 @@

from homeassistant.components.camera import Camera

from . import NEATO_LOGIN, NEATO_MAP_DATA, NEATO_ROBOTS
from .const import NEATO_DOMAIN, NEATO_MAP_DATA, NEATO_ROBOTS, NEATO_LOGIN

_LOGGER = logging.getLogger(__name__)

SCAN_INTERVAL = timedelta(minutes=10)


def setup_platform(hass, config, add_entities, discovery_info=None):
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
Comment thread
Santobert marked this conversation as resolved.
Outdated
"""Set up the Neato Camera."""
pass


async def async_setup_entry(hass, entry, async_add_entities):
"""Set up Neato camera with config entry."""
dev = []
for robot in hass.data[NEATO_ROBOTS]:
if "maps" in robot.traits:
dev.append(NeatoCleaningMap(hass, robot))

if not dev:
return

_LOGGER.debug("Adding robots for cleaning maps %s", dev)
add_entities(dev, True)
async_add_entities(dev, True)


class NeatoCleaningMap(Camera):
Expand Down Expand Up @@ -61,3 +70,8 @@ def name(self):
def unique_id(self):
"""Return unique ID."""
return self._robot_serial

@property
def device_info(self):
"""Device info for neato robot."""
return {"identifiers": {(NEATO_DOMAIN, self._robot_serial)}}
Loading