Neato refactor and support for sensors (#4319)

* Imporvements to neato

* Review changes
This commit is contained in:
John Arild Berentsen 2016-11-19 09:14:40 +01:00 committed by Paulus Schoutsen
parent 613615433a
commit 679d500e61
5 changed files with 304 additions and 109 deletions

View File

@ -101,6 +101,9 @@ omit =
homeassistant/components/netatmo.py homeassistant/components/netatmo.py
homeassistant/components/*/netatmo.py homeassistant/components/*/netatmo.py
homeassistant/components/neato.py
homeassistant/components/*/neato.py
homeassistant/components/homematic.py homeassistant/components/homematic.py
homeassistant/components/*/homematic.py homeassistant/components/*/homematic.py
@ -311,7 +314,6 @@ omit =
homeassistant/components/switch/edimax.py homeassistant/components/switch/edimax.py
homeassistant/components/switch/hikvisioncam.py homeassistant/components/switch/hikvisioncam.py
homeassistant/components/switch/mystrom.py homeassistant/components/switch/mystrom.py
homeassistant/components/switch/neato.py
homeassistant/components/switch/netio.py homeassistant/components/switch/netio.py
homeassistant/components/switch/orvibo.py homeassistant/components/switch/orvibo.py
homeassistant/components/switch/pilight.py homeassistant/components/switch/pilight.py

View File

@ -0,0 +1,81 @@
"""
Support for Neato botvac connected vacuum cleaners.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/neato/
"""
import logging
from datetime import timedelta
from urllib.error import HTTPError
import voluptuous as vol
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import discovery
from homeassistant.util import Throttle
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']
DOMAIN = 'neato'
NEATO_ROBOTS = 'neato_robots'
NEATO_LOGIN = 'neato_login'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
})
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Setup the Verisure component."""
from pybotvac import Account
hass.data[NEATO_LOGIN] = NeatoHub(hass, config[DOMAIN], Account)
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 ('sensor', 'switch'):
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True
class NeatoHub(object):
"""A My Neato hub wrapper class."""
def __init__(self, hass, domain_config, neato):
"""Initialize the Neato hub."""
self.config = domain_config
self._neato = neato
self._hass = hass
self.my_neato = neato(
domain_config[CONF_USERNAME],
domain_config[CONF_PASSWORD])
self._hass.data[NEATO_ROBOTS] = self.my_neato.robots
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])
return True
except HTTPError:
_LOGGER.error("Unable to connect to Neato API")
return False
@Throttle(timedelta(seconds=1))
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

View File

@ -0,0 +1,150 @@
"""
Support for Neato Connected Vaccums sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.neato/
"""
import logging
from homeassistant.helpers.entity import Entity
from homeassistant.components.neato import NEATO_ROBOTS, NEATO_LOGIN
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPE_STATUS = 'status'
SENSOR_TYPE_BATTERY = 'battery'
SENSOR_TYPES = {
SENSOR_TYPE_STATUS: ['Status'],
SENSOR_TYPE_BATTERY: ['Battery']
}
STATES = {
1: 'Idle',
2: 'Busy',
3: 'Pause',
4: 'Error'
}
MODE = {
1: 'Eco',
2: 'Turbo'
}
ACTION = {
0: 'No action',
1: 'House cleaning',
2: 'Spot cleaning',
3: 'Manual cleaning',
4: 'Docking',
5: 'User menu active',
6: 'Cleaning cancelled',
7: 'Updating...',
8: 'Copying logs...',
9: 'Calculating position...',
10: 'IEC test'
}
ERRORS = {
'ui_error_brush_stuck': 'Brush stuck',
'ui_error_brush_overloaded': 'Brush overloaded',
'ui_error_bumper_stuck': 'Bumper stuck',
'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_navigation_noprogress': 'Clear my path',
'ui_error_navigation_origin_unclean': 'Clear my path',
'ui_error_navigation_falling': 'Clear my path',
'ui_error_picked_up': 'Picked up',
'ui_error_stuck': 'Stuck!'
}
ALERTS = {
'ui_alert_dust_bin_full': 'Please empty dust bin',
'ui_alert_recovering_location': 'Returning to start'
}
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:
dev.append(NeatoConnectedSensor(hass, robot, type_name))
_LOGGER.debug('Adding sensors %s', dev)
add_devices(dev)
class NeatoConnectedSensor(Entity):
"""Neato Connected Sensor."""
def __init__(self, hass, robot, sensor_type):
"""Initialize the Neato Connected sensor."""
self.type = sensor_type
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
def update(self):
"""Update the properties of sensor."""
_LOGGER.debug('Update of sensor')
self.neato.update_robots()
if not self._state:
return
self._state = self.robot.state
_LOGGER.debug('self._state=%s', self._state)
if self.type == SENSOR_TYPE_STATUS:
if self._state['state'] == 1:
if self._state['details']['isCharging']:
self._status_state = 'Charging'
elif (self._state['details']['isDocked'] and
not self._state['details']['isCharging']):
self._status_state = 'Docked'
else:
self._status_state = 'Stopped'
elif self._state['state'] == 2:
if ALERTS.get(self._state['error']) is None:
self._status_state = (MODE.get(
self._state['cleaning']['mode']) + ' ' +
ACTION.get(self._state['action']))
else:
self._status_state = ALERTS.get(self._state['error'])
elif self._state['state'] == 3:
self._status_state = 'Paused'
elif self._state['state'] == 4:
self._status_state = ERRORS.get(self._state['error'])
if self.type == SENSOR_TYPE_BATTERY:
self._battery_state = self._state['details']['charge']
@property
def unit_of_measurement(self):
"""Return unit for the sensor."""
if self.type == SENSOR_TYPE_BATTERY:
return '%'
@property
def available(self):
"""Return True if sensor data is available."""
if not self._state:
return False
else:
return True
@property
def state(self):
"""Return the sensor state."""
if self.type == SENSOR_TYPE_STATUS:
return self._status_state
if self.type == SENSOR_TYPE_BATTERY:
return self._battery_state
@property
def name(self):
"""Return the name of the sensor."""
return self._robot_name

View File

@ -1,148 +1,110 @@
""" """
Support for Neato Connected Vaccums. Support for Neato Connected Vaccums switches.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.neato/ https://home-assistant.io/components/switch.neato/
""" """
import time import time
import logging import logging
from datetime import timedelta
from urllib.error import HTTPError
from requests.exceptions import HTTPError as req_HTTPError
import voluptuous as vol from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.const import (CONF_PASSWORD, CONF_USERNAME, STATE_OFF,
STATE_ON, STATE_UNAVAILABLE)
from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity import ToggleEntity
import homeassistant.helpers.config_validation as cv from homeassistant.components.neato import NEATO_ROBOTS, NEATO_LOGIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/jabesq/pybotvac/archive/v0.0.1.zip' SWITCH_TYPE_CLEAN = 'clean'
'#pybotvac==0.0.1'] SWITCH_TYPE_SCHEDULE = 'scedule'
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
MIN_TIME_TO_WAIT = timedelta(seconds=10)
MIN_TIME_TO_LOCK_UPDATE = 10
SWITCH_TYPES = { SWITCH_TYPES = {
'clean': ['Clean'] SWITCH_TYPE_CLEAN: ['Clean'],
SWITCH_TYPE_SCHEDULE: ['Schedule']
} }
DOMAIN = 'neato'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
})
}, extra=vol.ALLOW_EXTRA)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Neato platform.""" """Setup the Neato switches."""
from pybotvac import Account if not hass.data[NEATO_ROBOTS]:
try:
auth = Account(config[CONF_USERNAME], config[CONF_PASSWORD])
except HTTPError:
_LOGGER.error("Unable to connect to Neato API")
return False return False
dev = [] dev = []
for robot in auth.robots: for robot in hass.data[NEATO_ROBOTS]:
for type_name in SWITCH_TYPES: for type_name in SWITCH_TYPES:
dev.append(NeatoConnectedSwitch(robot, type_name)) dev.append(NeatoConnectedSwitch(hass, robot, type_name))
_LOGGER.debug('Adding switches %s', dev)
add_devices(dev) add_devices(dev)
class NeatoConnectedSwitch(ToggleEntity): class NeatoConnectedSwitch(ToggleEntity):
"""Neato Connected Switch (clean).""" """Neato Connected Switches."""
def __init__(self, robot, switch_type): def __init__(self, hass, robot, switch_type):
"""Initialize the Neato Connected switch.""" """Initialize the Neato Connected switches."""
self.type = switch_type self.type = switch_type
self.robot = robot self.robot = robot
self.lock = False self.neato = hass.data[NEATO_LOGIN]
self.last_lock_time = None self._robot_name = self.robot.name + ' ' + SWITCH_TYPES[self.type][0]
self.graceful_state = False self._state = self.robot.state
self._state = None self._schedule_state = None
self._clean_state = None
def lock_update(self): def update(self):
"""Lock the update since Neato clean takes some time to start.""" """Update the states of Neato switches."""
if self.is_update_locked(): _LOGGER.debug('Running switch update')
return self.neato.update_robots()
self.lock = True
self.last_lock_time = time.time()
def reset_update_lock(self):
"""Reset the update lock."""
self.lock = False
self.last_lock_time = None
def set_graceful_lock(self, state):
"""Set the graceful state."""
self.graceful_state = state
self.reset_update_lock()
self.lock_update()
def is_update_locked(self):
"""Check if the update method is locked."""
if self.last_lock_time is None:
return False
if time.time() - self.last_lock_time >= MIN_TIME_TO_LOCK_UPDATE:
self.last_lock_time = None
return False
return True
@property
def state(self):
"""Return the state."""
if not self._state: if not self._state:
return STATE_UNAVAILABLE return
if not self._state['availableCommands']['start'] and \ _LOGGER.debug('self._state=%s', self._state)
not self._state['availableCommands']['stop'] and \ if self.type == SWITCH_TYPE_CLEAN:
not self._state['availableCommands']['pause'] and \ if (self.robot.state['action'] == 1 and
not self._state['availableCommands']['resume'] and \ self.robot.state['state'] == 2):
not self._state['availableCommands']['goToBase']: self._clean_state = STATE_ON
return STATE_UNAVAILABLE else:
return STATE_ON if self.is_on else STATE_OFF self._clean_state = STATE_OFF
if self.type == SWITCH_TYPE_SCHEDULE:
_LOGGER.debug('self._state=%s', self._state)
if self.robot.schedule_enabled:
self._schedule_state = STATE_ON
else:
self._schedule_state = STATE_OFF
@property @property
def name(self): def name(self):
"""Return the name of the sensor.""" """Return the name of the switch."""
return self.robot.name + ' ' + SWITCH_TYPES[self.type][0] return self._robot_name
@property
def available(self):
"""Return True if entity is available."""
if not self._state:
return False
else:
return True
@property @property
def is_on(self): def is_on(self):
"""Return true if device is on.""" """Return true if switch is on."""
if self.is_update_locked(): if self.type == SWITCH_TYPE_CLEAN:
return self.graceful_state if self._clean_state == STATE_ON:
if self._state['action'] == 1 and self._state['state'] == 2: return True
return False
elif self.type == SWITCH_TYPE_SCHEDULE:
if self._schedule_state == STATE_ON:
return True return True
return False return False
def turn_on(self, **kwargs): def turn_on(self, **kwargs):
"""Turn the device on.""" """Turn the switch on."""
self.set_graceful_lock(True) if self.type == SWITCH_TYPE_CLEAN:
self.robot.start_cleaning() self.robot.start_cleaning()
elif self.type == SWITCH_TYPE_SCHEDULE:
self.robot.enable_schedule()
def turn_off(self, **kwargs): def turn_off(self, **kwargs):
"""Turn the device off (Return Robot to base).""" """Turn the switch off."""
if self.type == SWITCH_TYPE_CLEAN:
self.robot.pause_cleaning() self.robot.pause_cleaning()
time.sleep(1) time.sleep(1)
self.robot.send_to_base() self.robot.send_to_base()
elif self.type == SWITCH_TYPE_SCHEDULE:
def update(self): self.robot.disable_schedule()
"""Refresh Robot state from Neato API."""
try:
self._state = self.robot.state
except req_HTTPError:
_LOGGER.error("Unable to retrieve to Robot State.")
self._state = None
return False

View File

@ -192,7 +192,7 @@ https://github.com/danieljkemp/onkyo-eiscp/archive/python3.zip#onkyo-eiscp==0.9.
# homeassistant.components.netatmo # homeassistant.components.netatmo
https://github.com/jabesq/netatmo-api-python/archive/v0.7.0.zip#lnetatmo==0.7.0 https://github.com/jabesq/netatmo-api-python/archive/v0.7.0.zip#lnetatmo==0.7.0
# homeassistant.components.switch.neato # 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.1.zip#pybotvac==0.0.1
# homeassistant.components.sensor.sabnzbd # homeassistant.components.sensor.sabnzbd