Rachio (Sprinklers) (#7600)

* Rachio platform started

* Rachio tests

* detect bad api token

* Documentation, Code cleanup

* Docstrings end with a period, log uses %

* Fix arguments, default run time is now 10 minutes

* Fix typo, remove todo (GH issue exists)

* Revert polymer submodule commit

* Use a RachioPy version with SSL cert validation

* Update requirements
pull/6477/merge
Andy Castille 2017-05-29 04:15:27 -05:00 committed by Fabian Affolter
parent c12b8f763c
commit fc1bb58247
3 changed files with 238 additions and 0 deletions

View File

@ -89,6 +89,9 @@ omit =
homeassistant/components/qwikswitch.py
homeassistant/components/*/qwikswitch.py
homeassistant/components/rachio.py
homeassistant/components/*/rachio.py
homeassistant/components/raspihats.py
homeassistant/components/*/raspihats.py

View File

@ -0,0 +1,232 @@
"""Integration with the Rachio Iro sprinkler system controller."""
import logging
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
import homeassistant.util as util
from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['https://github.com/Klikini/rachiopy'
'/archive/2c8996fcfa97a9f361a789e0c998797ed2805281.zip'
'#rachiopy==0.1.1']
_LOGGER = logging.getLogger(__name__)
DATA_RACHIO = 'rachio'
CONF_MANUAL_RUN_MINS = 'manual_run_mins'
DEFAULT_MANUAL_RUN_MINS = 10
MIN_UPDATE_INTERVAL = timedelta(minutes=5)
MIN_FORCED_UPDATE_INTERVAL = timedelta(seconds=1)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ACCESS_TOKEN): cv.string,
vol.Optional(CONF_MANUAL_RUN_MINS, default=DEFAULT_MANUAL_RUN_MINS):
cv.positive_int
})
# noinspection PyUnusedLocal
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the component."""
# Get options
manual_run_mins = config.get(CONF_MANUAL_RUN_MINS)
_LOGGER.debug("Rachio run time is %d min", manual_run_mins)
# Get access token
_LOGGER.debug("Getting Rachio access token...")
access_token = config.get(CONF_ACCESS_TOKEN)
# Configure API
_LOGGER.debug("Configuring Rachio API...")
from rachiopy import Rachio
rachio = Rachio(access_token)
person = None
try:
person = _get_person(rachio)
except KeyError:
_LOGGER.error("Could not reach the Rachio API. "
"Is your access token valid?")
return False
# Get and persist devices
devices = _list_devices(rachio, manual_run_mins)
if len(devices) == 0:
_LOGGER.error("No Rachio devices found in account " +
person['username'])
return False
else:
hass.data[DATA_RACHIO] = devices[0]
if len(devices) > 1:
_LOGGER.warning("Multiple Rachio devices found in account, "
"using " + hass.data[DATA_RACHIO].device_id)
else:
_LOGGER.info("Found Rachio device")
hass.data[DATA_RACHIO].update()
add_devices(hass.data[DATA_RACHIO].list_zones())
return True
def _get_person(rachio):
"""Pull the account info of the person whose access token was provided."""
person_id = rachio.person.getInfo()[1]['id']
return rachio.person.get(person_id)[1]
def _list_devices(rachio, manual_run_mins):
"""Pull a list of devices on the account."""
return [RachioIro(rachio, d['id'], manual_run_mins)
for d in _get_person(rachio)['devices']]
class RachioIro(object):
"""Represents one Rachio Iro."""
def __init__(self, rachio, device_id, manual_run_mins):
"""Initialize a new device."""
self.rachio = rachio
self._device_id = device_id
self.manual_run_mins = manual_run_mins
self._device = None
self._running = None
self._zones = None
def __str__(self):
"""Display the device as a string."""
return "Rachio Iro " + self.serial_number
@property
def device_id(self):
"""How the Rachio API refers to the device."""
return self._device['id']
@property
def status(self):
"""The current status of the device."""
return self._device['status']
@property
def serial_number(self):
"""The serial number of the device."""
return self._device['serialNumber']
@property
def is_paused(self):
"""Whether the device is temporarily disabled."""
return self._device['paused']
@property
def is_on(self):
"""Whether the device is powered on and connected."""
return self._device['on']
@property
def current_schedule(self):
"""The schedule that the device is running right now."""
return self._running
def list_zones(self, include_disabled=False):
"""A list of the zones connected to the device and their data."""
if not self._zones:
self._zones = [RachioZone(self.rachio, self, zone['id'],
self.manual_run_mins)
for zone in self._device['zones']]
if include_disabled:
return self._zones
else:
self.update(no_throttle=True)
return [z for z in self._zones if z.is_enabled]
@util.Throttle(MIN_UPDATE_INTERVAL, MIN_FORCED_UPDATE_INTERVAL)
def update(self, **kwargs):
"""Pull updated device info from the Rachio API."""
self._device = self.rachio.device.get(self._device_id)[1]
self._running = self.rachio.device\
.getCurrentSchedule(self._device_id)[1]
# Possibly update all zones
for zone in self.list_zones(include_disabled=True):
zone.update()
_LOGGER.debug("Updated %s", str(self))
class RachioZone(SwitchDevice):
"""Represents one zone of sprinklers connected to the Rachio Iro."""
def __init__(self, rachio, device, zone_id, manual_run_mins):
"""Initialize a new Rachio Zone."""
self.rachio = rachio
self._device = device
self._zone_id = zone_id
self._zone = None
self._manual_run_secs = manual_run_mins * 60
def __str__(self):
"""Display the zone as a string."""
return "Rachio Zone " + self.name
@property
def zone_id(self):
"""How the Rachio API refers to the zone."""
return self._zone['id']
@property
def unique_id(self):
"""Generate a unique string ID for the zone."""
return '{iro}-{zone}'.format(
iro=self._device.device_id,
zone=self.zone_id)
@property
def number(self):
"""The physical connection of the zone pump."""
return self._zone['zoneNumber']
@property
def name(self):
"""The friendly name of the zone."""
return self._zone['name']
@property
def is_enabled(self):
"""Whether the zone is allowed to run."""
return self._zone['enabled']
@property
def is_on(self):
"""Whether the zone is currently running."""
self._device.update()
schedule = self._device.current_schedule
return self.zone_id == schedule.get('zoneId')
def update(self):
"""Pull updated zone info from the Rachio API."""
self._zone = self.rachio.zone.get(self._zone_id)[1]
# Possibly update device
self._device.update()
_LOGGER.debug("Updated %s", str(self))
def turn_on(self):
"""Start the zone."""
# Convert minutes to seconds
seconds = self._manual_run_secs * 60
# Stop other zones first
self.turn_off()
_LOGGER.info("Watering %s for %d sec", self.name, seconds)
self.rachio.zone.start(self.zone_id, seconds)
def turn_off(self):
"""Stop all zones."""
_LOGGER.info("Stopping watering of all zones")
self.rachio.device.stopWater(self._device.device_id)

View File

@ -261,6 +261,9 @@ hikvision==0.4
# homeassistant.components.binary_sensor.workday
holidays==0.8.1
# homeassistant.components.switch.rachio
https://github.com/Klikini/rachiopy/archive/2c8996fcfa97a9f361a789e0c998797ed2805281.zip#rachiopy==0.1.1
# homeassistant.components.switch.dlink
https://github.com/LinuxChristian/pyW215/archive/v0.4.zip#pyW215==0.4