2019-02-14 04:35:12 +00:00
|
|
|
"""Support for Neato Connected Vacuums."""
|
2017-11-03 13:25:26 +00:00
|
|
|
import logging
|
2018-06-21 01:46:15 +00:00
|
|
|
from datetime import timedelta
|
2017-11-03 13:25:26 +00:00
|
|
|
import requests
|
|
|
|
|
|
|
|
from homeassistant.components.vacuum import (
|
2018-08-18 18:20:32 +00:00
|
|
|
StateVacuumDevice, SUPPORT_BATTERY, SUPPORT_PAUSE, SUPPORT_RETURN_HOME,
|
|
|
|
SUPPORT_STATE, SUPPORT_STOP, SUPPORT_START, STATE_IDLE,
|
|
|
|
STATE_PAUSED, STATE_CLEANING, STATE_DOCKED, STATE_RETURNING, STATE_ERROR,
|
2018-07-18 10:19:38 +00:00
|
|
|
SUPPORT_MAP, ATTR_STATUS, ATTR_BATTERY_LEVEL, ATTR_BATTERY_ICON,
|
2019-01-07 19:03:22 +00:00
|
|
|
SUPPORT_LOCATE, SUPPORT_CLEAN_SPOT)
|
2017-11-03 13:25:26 +00:00
|
|
|
from homeassistant.components.neato import (
|
|
|
|
NEATO_ROBOTS, NEATO_LOGIN, NEATO_MAP_DATA, ACTION, ERRORS, MODE, ALERTS)
|
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
DEPENDENCIES = ['neato']
|
|
|
|
|
2018-06-27 20:55:27 +00:00
|
|
|
SCAN_INTERVAL = timedelta(minutes=5)
|
|
|
|
|
2017-11-03 13:25:26 +00:00
|
|
|
SUPPORT_NEATO = SUPPORT_BATTERY | SUPPORT_PAUSE | SUPPORT_RETURN_HOME | \
|
2019-01-08 20:12:35 +00:00
|
|
|
SUPPORT_STOP | SUPPORT_START | SUPPORT_CLEAN_SPOT | \
|
2018-08-18 18:20:32 +00:00
|
|
|
SUPPORT_STATE | SUPPORT_MAP | SUPPORT_LOCATE
|
2017-11-03 13:25:26 +00:00
|
|
|
|
|
|
|
ATTR_CLEAN_START = 'clean_start'
|
|
|
|
ATTR_CLEAN_STOP = 'clean_stop'
|
|
|
|
ATTR_CLEAN_AREA = 'clean_area'
|
|
|
|
ATTR_CLEAN_BATTERY_START = 'battery_level_at_clean_start'
|
|
|
|
ATTR_CLEAN_BATTERY_END = 'battery_level_at_clean_end'
|
|
|
|
ATTR_CLEAN_SUSP_COUNT = 'clean_suspension_count'
|
|
|
|
ATTR_CLEAN_SUSP_TIME = 'clean_suspension_time'
|
|
|
|
|
|
|
|
|
2018-08-24 14:37:30 +00:00
|
|
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
2017-11-03 13:25:26 +00:00
|
|
|
"""Set up the Neato vacuum."""
|
|
|
|
dev = []
|
|
|
|
for robot in hass.data[NEATO_ROBOTS]:
|
|
|
|
dev.append(NeatoConnectedVacuum(hass, robot))
|
|
|
|
_LOGGER.debug("Adding vacuums %s", dev)
|
2018-08-24 14:37:30 +00:00
|
|
|
add_entities(dev, True)
|
2017-11-03 13:25:26 +00:00
|
|
|
|
|
|
|
|
2018-08-18 18:20:32 +00:00
|
|
|
class NeatoConnectedVacuum(StateVacuumDevice):
|
2018-01-21 06:35:38 +00:00
|
|
|
"""Representation of a Neato Connected Vacuum."""
|
2017-11-03 13:25:26 +00:00
|
|
|
|
|
|
|
def __init__(self, hass, robot):
|
2018-01-21 06:35:38 +00:00
|
|
|
"""Initialize the Neato Connected Vacuum."""
|
2017-11-03 13:25:26 +00:00
|
|
|
self.robot = robot
|
|
|
|
self.neato = hass.data[NEATO_LOGIN]
|
|
|
|
self._name = '{}'.format(self.robot.name)
|
|
|
|
self._status_state = None
|
|
|
|
self._clean_state = None
|
|
|
|
self._state = None
|
|
|
|
self._mapdata = hass.data[NEATO_MAP_DATA]
|
|
|
|
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_suspension_charge_count = None
|
|
|
|
self.clean_suspension_time = None
|
2018-10-12 06:40:45 +00:00
|
|
|
self._available = False
|
|
|
|
self._battery_level = None
|
2018-10-12 22:33:13 +00:00
|
|
|
self._robot_serial = self.robot.serial
|
2017-11-03 13:25:26 +00:00
|
|
|
|
|
|
|
def update(self):
|
|
|
|
"""Update the states of Neato Vacuums."""
|
2018-01-21 06:35:38 +00:00
|
|
|
_LOGGER.debug("Running Neato Vacuums update")
|
2017-11-03 13:25:26 +00:00
|
|
|
self.neato.update_robots()
|
|
|
|
try:
|
|
|
|
self._state = self.robot.state
|
2018-10-12 06:40:45 +00:00
|
|
|
self._available = True
|
2017-11-03 13:25:26 +00:00
|
|
|
except (requests.exceptions.ConnectionError,
|
|
|
|
requests.exceptions.HTTPError) as ex:
|
|
|
|
_LOGGER.warning("Neato connection error: %s", ex)
|
|
|
|
self._state = None
|
2018-10-12 06:40:45 +00:00
|
|
|
self._available = False
|
2017-11-03 13:25:26 +00:00
|
|
|
return
|
|
|
|
_LOGGER.debug('self._state=%s', self._state)
|
2019-01-10 21:56:10 +00:00
|
|
|
if 'alert' in self._state:
|
|
|
|
robot_alert = ALERTS.get(self._state['alert'])
|
|
|
|
else:
|
|
|
|
robot_alert = None
|
2017-11-03 13:25:26 +00:00
|
|
|
if self._state['state'] == 1:
|
|
|
|
if self._state['details']['isCharging']:
|
2018-08-18 18:20:32 +00:00
|
|
|
self._clean_state = STATE_DOCKED
|
2017-11-03 13:25:26 +00:00
|
|
|
self._status_state = 'Charging'
|
|
|
|
elif (self._state['details']['isDocked'] and
|
|
|
|
not self._state['details']['isCharging']):
|
2018-08-18 18:20:32 +00:00
|
|
|
self._clean_state = STATE_DOCKED
|
2017-11-03 13:25:26 +00:00
|
|
|
self._status_state = 'Docked'
|
|
|
|
else:
|
2018-08-18 18:20:32 +00:00
|
|
|
self._clean_state = STATE_IDLE
|
2017-11-03 13:25:26 +00:00
|
|
|
self._status_state = 'Stopped'
|
2018-12-27 17:57:38 +00:00
|
|
|
|
|
|
|
if robot_alert is not None:
|
|
|
|
self._status_state = robot_alert
|
2017-11-03 13:25:26 +00:00
|
|
|
elif self._state['state'] == 2:
|
2018-12-27 17:57:38 +00:00
|
|
|
if robot_alert is None:
|
2018-08-18 18:20:32 +00:00
|
|
|
self._clean_state = STATE_CLEANING
|
2017-11-03 13:25:26 +00:00
|
|
|
self._status_state = (
|
|
|
|
MODE.get(self._state['cleaning']['mode'])
|
|
|
|
+ ' ' + ACTION.get(self._state['action']))
|
|
|
|
else:
|
2018-12-27 17:57:38 +00:00
|
|
|
self._status_state = robot_alert
|
2017-11-03 13:25:26 +00:00
|
|
|
elif self._state['state'] == 3:
|
2018-08-18 18:20:32 +00:00
|
|
|
self._clean_state = STATE_PAUSED
|
2017-11-03 13:25:26 +00:00
|
|
|
self._status_state = 'Paused'
|
|
|
|
elif self._state['state'] == 4:
|
2018-08-18 18:20:32 +00:00
|
|
|
self._clean_state = STATE_ERROR
|
2017-11-03 13:25:26 +00:00
|
|
|
self._status_state = ERRORS.get(self._state['error'])
|
|
|
|
|
2018-10-20 12:28:30 +00:00
|
|
|
if not self._mapdata.get(self._robot_serial, {}).get('maps', []):
|
2017-11-03 13:25:26 +00:00
|
|
|
return
|
|
|
|
self.clean_time_start = (
|
2018-10-20 12:28:30 +00:00
|
|
|
(self._mapdata[self._robot_serial]['maps'][0]['start_at']
|
2017-11-03 13:25:26 +00:00
|
|
|
.strip('Z'))
|
|
|
|
.replace('T', ' '))
|
|
|
|
self.clean_time_stop = (
|
2018-10-20 12:28:30 +00:00
|
|
|
(self._mapdata[self._robot_serial]['maps'][0]['end_at'].strip('Z'))
|
2017-11-03 13:25:26 +00:00
|
|
|
.replace('T', ' '))
|
|
|
|
self.clean_area = (
|
2018-10-20 12:28:30 +00:00
|
|
|
self._mapdata[self._robot_serial]['maps'][0]['cleaned_area'])
|
2017-11-03 13:25:26 +00:00
|
|
|
self.clean_suspension_charge_count = (
|
2018-10-20 12:28:30 +00:00
|
|
|
self._mapdata[self._robot_serial]['maps'][0]
|
2017-11-03 13:25:26 +00:00
|
|
|
['suspended_cleaning_charging_count'])
|
|
|
|
self.clean_suspension_time = (
|
2018-10-20 12:28:30 +00:00
|
|
|
self._mapdata[self._robot_serial]['maps'][0]
|
2017-11-03 13:25:26 +00:00
|
|
|
['time_in_suspended_cleaning'])
|
|
|
|
self.clean_battery_start = (
|
2018-10-20 12:28:30 +00:00
|
|
|
self._mapdata[self._robot_serial]['maps'][0]['run_charge_at_start']
|
|
|
|
)
|
2017-11-03 13:25:26 +00:00
|
|
|
self.clean_battery_end = (
|
2018-10-20 12:28:30 +00:00
|
|
|
self._mapdata[self._robot_serial]['maps'][0]['run_charge_at_end'])
|
2017-11-03 13:25:26 +00:00
|
|
|
|
2018-10-12 06:40:45 +00:00
|
|
|
self._battery_level = self._state['details']['charge']
|
|
|
|
|
2017-11-03 13:25:26 +00:00
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Return the name of the device."""
|
|
|
|
return self._name
|
|
|
|
|
|
|
|
@property
|
|
|
|
def supported_features(self):
|
|
|
|
"""Flag vacuum cleaner robot features that are supported."""
|
|
|
|
return SUPPORT_NEATO
|
|
|
|
|
|
|
|
@property
|
|
|
|
def battery_level(self):
|
|
|
|
"""Return the battery level of the vacuum cleaner."""
|
2018-10-12 06:40:45 +00:00
|
|
|
return self._battery_level
|
|
|
|
|
|
|
|
@property
|
|
|
|
def available(self):
|
|
|
|
"""Return if the robot is available."""
|
|
|
|
return self._available
|
2017-11-03 13:25:26 +00:00
|
|
|
|
|
|
|
@property
|
2018-08-18 18:20:32 +00:00
|
|
|
def state(self):
|
2017-11-03 13:25:26 +00:00
|
|
|
"""Return the status of the vacuum cleaner."""
|
2018-08-18 18:20:32 +00:00
|
|
|
return self._clean_state
|
2017-11-03 13:25:26 +00:00
|
|
|
|
2018-10-12 22:33:13 +00:00
|
|
|
@property
|
|
|
|
def unique_id(self):
|
|
|
|
"""Return a unique ID."""
|
|
|
|
return self._robot_serial
|
|
|
|
|
2017-11-03 13:25:26 +00:00
|
|
|
@property
|
2018-08-18 18:20:32 +00:00
|
|
|
def device_state_attributes(self):
|
2017-11-03 13:25:26 +00:00
|
|
|
"""Return the state attributes of the vacuum cleaner."""
|
|
|
|
data = {}
|
|
|
|
|
2018-08-18 18:20:32 +00:00
|
|
|
if self._status_state is not None:
|
|
|
|
data[ATTR_STATUS] = self._status_state
|
2017-11-03 13:25:26 +00:00
|
|
|
|
|
|
|
if self.battery_level is not None:
|
|
|
|
data[ATTR_BATTERY_LEVEL] = self.battery_level
|
|
|
|
data[ATTR_BATTERY_ICON] = self.battery_icon
|
|
|
|
|
|
|
|
if self.clean_time_start is not None:
|
|
|
|
data[ATTR_CLEAN_START] = self.clean_time_start
|
|
|
|
if self.clean_time_stop is not None:
|
|
|
|
data[ATTR_CLEAN_STOP] = self.clean_time_stop
|
|
|
|
if self.clean_area is not None:
|
|
|
|
data[ATTR_CLEAN_AREA] = self.clean_area
|
|
|
|
if self.clean_suspension_charge_count is not None:
|
|
|
|
data[ATTR_CLEAN_SUSP_COUNT] = (
|
|
|
|
self.clean_suspension_charge_count)
|
|
|
|
if self.clean_suspension_time is not None:
|
|
|
|
data[ATTR_CLEAN_SUSP_TIME] = self.clean_suspension_time
|
|
|
|
if self.clean_battery_start is not None:
|
|
|
|
data[ATTR_CLEAN_BATTERY_START] = self.clean_battery_start
|
|
|
|
if self.clean_battery_end is not None:
|
|
|
|
data[ATTR_CLEAN_BATTERY_END] = self.clean_battery_end
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
2018-08-18 18:20:32 +00:00
|
|
|
def start(self):
|
|
|
|
"""Start cleaning or resume cleaning."""
|
|
|
|
if self._state['state'] == 1:
|
|
|
|
self.robot.start_cleaning()
|
|
|
|
elif self._state['state'] == 3:
|
|
|
|
self.robot.resume_cleaning()
|
2017-11-03 13:25:26 +00:00
|
|
|
|
2018-08-18 18:20:32 +00:00
|
|
|
def pause(self):
|
|
|
|
"""Pause the vacuum."""
|
2017-11-03 13:25:26 +00:00
|
|
|
self.robot.pause_cleaning()
|
|
|
|
|
|
|
|
def return_to_base(self, **kwargs):
|
|
|
|
"""Set the vacuum cleaner to return to the dock."""
|
2018-09-22 09:34:46 +00:00
|
|
|
if self._clean_state == STATE_CLEANING:
|
|
|
|
self.robot.pause_cleaning()
|
2018-08-18 18:20:32 +00:00
|
|
|
self._clean_state = STATE_RETURNING
|
2017-11-03 13:25:26 +00:00
|
|
|
self.robot.send_to_base()
|
|
|
|
|
|
|
|
def stop(self, **kwargs):
|
|
|
|
"""Stop the vacuum cleaner."""
|
|
|
|
self.robot.stop_cleaning()
|
|
|
|
|
2018-07-18 10:19:38 +00:00
|
|
|
def locate(self, **kwargs):
|
|
|
|
"""Locate the robot by making it emit a sound."""
|
|
|
|
self.robot.locate()
|
2019-01-07 19:03:22 +00:00
|
|
|
|
|
|
|
def clean_spot(self, **kwargs):
|
|
|
|
"""Run a spot cleaning starting from the base."""
|
|
|
|
self.robot.start_spot_cleaning()
|