"""Support for Neato botvac connected vacuum cleaners.""" import logging from datetime import timedelta from urllib.error import HTTPError import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import discovery from homeassistant.util import Throttle _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( { vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Optional(CONF_VENDOR, default="neato"): vol.In( ["neato", "vorwerk"] ), } ) }, 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): """Set up the Neato component.""" 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) hub = hass.data[NEATO_LOGIN] if not hub.login(): _LOGGER.debug("Failed to login to Neato API") return False hub.update_robots() for component in ("camera", "vacuum", "switch"): discovery.load_platform(hass, component, DOMAIN, {}, config) return True class NeatoHub: """A My Neato hub wrapper class.""" def __init__(self, hass, domain_config, neato, vendor): """Initialize the Neato hub.""" self.config = domain_config self._neato = neato 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 def login(self): """Login to My Neato.""" try: _LOGGER.debug("Trying to connect to Neato API") self.my_neato = self._neato( self.config[CONF_USERNAME], self.config[CONF_PASSWORD], self._vendor ) return True except HTTPError: _LOGGER.error("Unable to connect to Neato API") return False @Throttle(timedelta(seconds=300)) def update_robots(self): """Update the robot states.""" _LOGGER.debug("Running HUB.update_robots %s", self._hass.data[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): """Download a new map image.""" map_image_data = self.my_neato.get_map_image(url) return map_image_data