mirror of
https://github.com/home-assistant/core.git
synced 2025-07-14 08:47:10 +00:00
Neato refactor and support for sensors (#4319)
* Imporvements to neato * Review changes
This commit is contained in:
parent
613615433a
commit
679d500e61
@ -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
|
||||||
|
81
homeassistant/components/neato.py
Normal file
81
homeassistant/components/neato.py
Normal 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
|
150
homeassistant/components/sensor/neato.py
Normal file
150
homeassistant/components/sensor/neato.py
Normal 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
|
@ -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
|
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user