diff --git a/homeassistant/components/camera/neato.py b/homeassistant/components/camera/neato.py new file mode 100644 index 00000000000..d6eafc36859 --- /dev/null +++ b/homeassistant/components/camera/neato.py @@ -0,0 +1,65 @@ +""" +Camera that loads a picture from a local file. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/camera.neato/ +""" +import logging + +from datetime import timedelta +from homeassistant.components.camera import Camera +from homeassistant.components.neato import ( + NEATO_MAP_DATA, NEATO_ROBOTS, NEATO_LOGIN) +from homeassistant.util import Throttle + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['neato'] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Camera.""" + dev = [] + for robot in hass.data[NEATO_ROBOTS]: + if 'maps' in robot.traits: + dev.append(NeatoCleaningMap(hass, robot)) + _LOGGER.debug('Adding robots for cleaning maps %s', dev) + add_devices(dev, True) + + +class NeatoCleaningMap(Camera): + """Neato cleaning map for last clean.""" + + def __init__(self, hass, robot): + """Initialize Neato cleaning map.""" + super().__init__() + self.robot = robot + self._robot_name = self.robot.name + ' Cleaning Map' + self._robot_serial = self.robot.serial + self.neato = hass.data[NEATO_LOGIN] + self._image_url = None + self._image = None + + def camera_image(self): + """Return image response.""" + self.update() + return self._image + + @Throttle(timedelta(seconds=10)) + def update(self): + """Check the contents of the map list.""" + self.neato.update_robots() + image_url = None + map_data = self.hass.data[NEATO_MAP_DATA] + image_url = map_data[self._robot_serial]['maps'][0]['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 + + @property + def name(self): + """Return the name of this camera.""" + return self._robot_name diff --git a/homeassistant/components/neato.py b/homeassistant/components/neato.py index e0b36721f74..7bc4724e18c 100644 --- a/homeassistant/components/neato.py +++ b/homeassistant/components/neato.py @@ -17,12 +17,13 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['https://github.com/jabesq/pybotvac/archive/v0.0.1.zip' - '#pybotvac==0.0.1'] +REQUIREMENTS = ['https://github.com/jabesq/pybotvac/archive/v0.0.3.zip' + '#pybotvac==0.0.3'] DOMAIN = 'neato' NEATO_ROBOTS = 'neato_robots' NEATO_LOGIN = 'neato_login' +NEATO_MAP_DATA = 'neato_map_data' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -89,7 +90,7 @@ def setup(hass, config): _LOGGER.debug('Failed to login to Neato API') return False hub.update_robots() - for component in ('sensor', 'switch'): + for component in ('camera', 'sensor', 'switch'): discovery.load_platform(hass, component, DOMAIN, {}, config) return True @@ -108,6 +109,7 @@ class NeatoHub(object): domain_config[CONF_USERNAME], domain_config[CONF_PASSWORD]) self._hass.data[NEATO_ROBOTS] = self.my_neato.robots + self._hass.data[NEATO_MAP_DATA] = self.my_neato.maps def login(self): """Login to My Neato.""" @@ -126,3 +128,9 @@ class NeatoHub(object): _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_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 diff --git a/homeassistant/components/sensor/neato.py b/homeassistant/components/sensor/neato.py index ca5cff1d24a..7c33e481069 100644 --- a/homeassistant/components/sensor/neato.py +++ b/homeassistant/components/sensor/neato.py @@ -8,9 +8,12 @@ import logging import requests from homeassistant.helpers.entity import Entity from homeassistant.components.neato import ( - NEATO_ROBOTS, NEATO_LOGIN, ACTION, ERRORS, MODE, ALERTS) + NEATO_ROBOTS, NEATO_LOGIN, NEATO_MAP_DATA, ACTION, ERRORS, MODE, ALERTS) _LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['neato'] + SENSOR_TYPE_STATUS = 'status' SENSOR_TYPE_BATTERY = 'battery' @@ -19,12 +22,17 @@ SENSOR_TYPES = { SENSOR_TYPE_BATTERY: ['Battery'] } +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' + def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Neato sensor platform.""" - if not hass.data['neato_robots']: - return False - dev = [] for robot in hass.data[NEATO_ROBOTS]: for type_name in SENSOR_TYPES: @@ -42,22 +50,37 @@ class NeatoConnectedSensor(Entity): self.robot = robot self.neato = hass.data[NEATO_LOGIN] self._robot_name = self.robot.name + ' ' + SENSOR_TYPES[self.type][0] - self._state = self.robot.state - self._battery_state = None self._status_state = None + try: + self._state = self.robot.state + except (requests.exceptions.ConnectionError, + requests.exceptions.HTTPError) as ex: + self._state = None + _LOGGER.warning('Neato connection error: %s', ex) + 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 + self._battery_state = None def update(self): """Update the properties of sensor.""" _LOGGER.debug('Update of sensor') self.neato.update_robots() - if not self._state: - return + self._mapdata = self.hass.data[NEATO_MAP_DATA] try: self._state = self.robot.state - except requests.exceptions.HTTPError as ex: + except (requests.exceptions.ConnectionError, + requests.exceptions.HTTPError) as ex: self._state = None self._status_state = 'Offline' - _LOGGER.debug('Neato connection issue: %s', ex) + _LOGGER.warning('Neato connection error: %s', ex) + return + if not self._state: return _LOGGER.debug('self._state=%s', self._state) if self.type == SENSOR_TYPE_STATUS: @@ -82,6 +105,27 @@ class NeatoConnectedSensor(Entity): self._status_state = ERRORS.get(self._state['error']) if self.type == SENSOR_TYPE_BATTERY: self._battery_state = self._state['details']['charge'] + if self._mapdata is None: + return + self.clean_time_start = ( + (self._mapdata[self.robot.serial]['maps'][0]['start_at'] + .strip('Z')) + .replace('T', ' ')) + self.clean_time_stop = ( + (self._mapdata[self.robot.serial]['maps'][0]['end_at'].strip('Z')) + .replace('T', ' ')) + self.clean_area = ( + self._mapdata[self.robot.serial]['maps'][0]['cleaned_area']) + self.clean_suspension_charge_count = ( + self._mapdata[self.robot.serial]['maps'][0] + ['suspended_cleaning_charging_count']) + self.clean_suspension_time = ( + self._mapdata[self.robot.serial]['maps'][0] + ['time_in_suspended_cleaning']) + self.clean_battery_start = ( + self._mapdata[self.robot.serial]['maps'][0]['run_charge_at_start']) + self.clean_battery_end = ( + self._mapdata[self.robot.serial]['maps'][0]['run_charge_at_end']) @property def unit_of_measurement(self): @@ -109,3 +153,25 @@ class NeatoConnectedSensor(Entity): def name(self): """Return the name of the sensor.""" return self._robot_name + + @property + def device_state_attributes(self): + """Return the device specific attributes.""" + data = {} + if self.type is SENSOR_TYPE_STATUS: + if self.clean_time_start: + data[ATTR_CLEAN_START] = self.clean_time_start + if self.clean_time_stop: + data[ATTR_CLEAN_STOP] = self.clean_time_stop + if self.clean_area: + data[ATTR_CLEAN_AREA] = self.clean_area + if self.clean_suspension_charge_count: + data[ATTR_CLEAN_SUSP_COUNT] = ( + self.clean_suspension_charge_count) + if self.clean_suspension_time: + data[ATTR_CLEAN_SUSP_TIME] = self.clean_suspension_time + if self.clean_battery_start: + data[ATTR_CLEAN_BATTERY_START] = self.clean_battery_start + if self.clean_battery_end: + data[ATTR_CLEAN_BATTERY_END] = self.clean_battery_end + return data diff --git a/homeassistant/components/switch/neato.py b/homeassistant/components/switch/neato.py index 6cd5c5088dc..b6cf6549cae 100644 --- a/homeassistant/components/switch/neato.py +++ b/homeassistant/components/switch/neato.py @@ -12,6 +12,8 @@ from homeassistant.components.neato import NEATO_ROBOTS, NEATO_LOGIN _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['neato'] + SWITCH_TYPE_CLEAN = 'clean' SWITCH_TYPE_SCHEDULE = 'scedule' @@ -23,9 +25,6 @@ SWITCH_TYPES = { def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Neato switches.""" - if not hass.data[NEATO_ROBOTS]: - return False - dev = [] for robot in hass.data[NEATO_ROBOTS]: for type_name in SWITCH_TYPES: @@ -43,7 +42,12 @@ class NeatoConnectedSwitch(ToggleEntity): self.robot = robot self.neato = hass.data[NEATO_LOGIN] self._robot_name = self.robot.name + ' ' + SWITCH_TYPES[self.type][0] - self._state = self.robot.state + 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._schedule_state = None self._clean_state = None @@ -51,14 +55,13 @@ class NeatoConnectedSwitch(ToggleEntity): """Update the states of Neato switches.""" _LOGGER.debug('Running switch update') self.neato.update_robots() - if not self._state: - return try: self._state = self.robot.state - except requests.exceptions.HTTPError: + except (requests.exceptions.ConnectionError, + requests.exceptions.HTTPError) as ex: + _LOGGER.warning('Neato connection error: %s', ex) self._state = None return - self._state = self.robot.state _LOGGER.debug('self._state=%s', self._state) if self.type == SWITCH_TYPE_CLEAN: if (self.robot.state['action'] == 1 or diff --git a/requirements_all.txt b/requirements_all.txt index 0fd90e12d34..defefb66aee 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -269,7 +269,7 @@ https://github.com/gurumitts/pylutron-caseta/archive/v0.2.5.zip#pylutron-caseta= https://github.com/jabesq/netatmo-api-python/archive/v0.9.1.zip#lnetatmo==0.9.1 # homeassistant.components.neato -https://github.com/jabesq/pybotvac/archive/v0.0.1.zip#pybotvac==0.0.1 +https://github.com/jabesq/pybotvac/archive/v0.0.3.zip#pybotvac==0.0.3 # homeassistant.components.sensor.sabnzbd https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1