Add support fo map data from Neato (#6939)

* Responsiveness

* Delay was not needed as commands does not return until done.

* Add support for cleaning maps and cleaning data

* Hound

* Docstring

* Update requirements

* Review changes

* External lib now returns the raw data.

* debug

* Sensor did not refresh

* Error handling

* Issue warning on connection error

* Update requirements

* Review changes
This commit is contained in:
John Arild Berentsen 2017-04-13 16:41:25 +02:00 committed by Paulus Schoutsen
parent 01c7616147
commit 38ad5714cd
5 changed files with 164 additions and 22 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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