Rachio webhooks (#15111)

* Make fewer requests to the Rachio API

* BREAKING: Rewrite Rachio component
This commit is contained in:
Andy Castille 2018-07-01 10:54:51 -05:00 committed by Paulus Schoutsen
parent 136cc1d44d
commit 9db8759317
4 changed files with 586 additions and 158 deletions

View File

@ -0,0 +1,127 @@
"""
Integration with the Rachio Iro sprinkler system controller.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.rachio/
"""
from abc import abstractmethod
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.rachio import (DOMAIN as DOMAIN_RACHIO,
KEY_DEVICE_ID,
KEY_STATUS,
KEY_SUBTYPE,
SIGNAL_RACHIO_CONTROLLER_UPDATE,
STATUS_OFFLINE,
STATUS_ONLINE,
SUBTYPE_OFFLINE,
SUBTYPE_ONLINE,)
from homeassistant.helpers.dispatcher import dispatcher_connect
DEPENDENCIES = ['rachio']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Rachio binary sensors."""
devices = []
for controller in hass.data[DOMAIN_RACHIO].controllers:
devices.append(RachioControllerOnlineBinarySensor(hass, controller))
add_devices(devices)
_LOGGER.info("%d Rachio binary sensor(s) added", len(devices))
class RachioControllerBinarySensor(BinarySensorDevice):
"""Represent a binary sensor that reflects a Rachio state."""
def __init__(self, hass, controller, poll=True):
"""Set up a new Rachio controller binary sensor."""
self._controller = controller
if poll:
self._state = self._poll_update()
else:
self._state = None
dispatcher_connect(hass, SIGNAL_RACHIO_CONTROLLER_UPDATE,
self._handle_any_update)
@property
def should_poll(self) -> bool:
"""Declare that this entity pushes its state to HA."""
return False
@property
def is_on(self) -> bool:
"""Return whether the sensor has a 'true' value."""
return self._state
def _handle_any_update(self, *args, **kwargs) -> None:
"""Determine whether an update event applies to this device."""
if args[0][KEY_DEVICE_ID] != self._controller.controller_id:
# For another device
return
# For this device
self._handle_update()
@abstractmethod
def _poll_update(self, data=None) -> bool:
"""Request the state from the API."""
pass
@abstractmethod
def _handle_update(self, *args, **kwargs) -> None:
"""Handle an update to the state of this sensor."""
pass
class RachioControllerOnlineBinarySensor(RachioControllerBinarySensor):
"""Represent a binary sensor that reflects if the controller is online."""
def __init__(self, hass, controller):
"""Set up a new Rachio controller online binary sensor."""
super().__init__(hass, controller, poll=False)
self._state = self._poll_update(controller.init_data)
@property
def name(self) -> str:
"""Return the name of this sensor including the controller name."""
return "{} online".format(self._controller.name)
@property
def device_class(self) -> str:
"""Return the class of this device, from component DEVICE_CLASSES."""
return 'connectivity'
@property
def icon(self) -> str:
"""Return the name of an icon for this sensor."""
return 'mdi:wifi-strength-4' if self.is_on\
else 'mdi:wifi-strength-off-outline'
def _poll_update(self, data=None) -> bool:
"""Request the state from the API."""
if data is None:
data = self._controller.rachio.device.get(
self._controller.controller_id)[1]
if data[KEY_STATUS] == STATUS_ONLINE:
return True
elif data[KEY_STATUS] == STATUS_OFFLINE:
return False
else:
_LOGGER.warning('"%s" reported in unknown state "%s"', self.name,
data[KEY_STATUS])
def _handle_update(self, *args, **kwargs) -> None:
"""Handle an update to the state of this sensor."""
if args[0][KEY_SUBTYPE] == SUBTYPE_ONLINE:
self._state = True
elif args[0][KEY_SUBTYPE] == SUBTYPE_OFFLINE:
self._state = False
self.schedule_update_ha_state()

View File

@ -0,0 +1,289 @@
"""
Integration with the Rachio Iro sprinkler system controller.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/rachio/
"""
import asyncio
import logging
from aiohttp import web
import voluptuous as vol
from homeassistant.auth import generate_secret
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import CONF_API_KEY, EVENT_HOMEASSISTANT_STOP, URL_API
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
REQUIREMENTS = ['rachiopy==0.1.3']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'rachio'
CONF_CUSTOM_URL = 'hass_url_override'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_CUSTOM_URL): cv.string,
})
}, extra=vol.ALLOW_EXTRA)
# Keys used in the API JSON
KEY_DEVICE_ID = 'deviceId'
KEY_DEVICES = 'devices'
KEY_ENABLED = 'enabled'
KEY_EXTERNAL_ID = 'externalId'
KEY_ID = 'id'
KEY_NAME = 'name'
KEY_ON = 'on'
KEY_STATUS = 'status'
KEY_SUBTYPE = 'subType'
KEY_SUMMARY = 'summary'
KEY_TYPE = 'type'
KEY_URL = 'url'
KEY_USERNAME = 'username'
KEY_ZONE_ID = 'zoneId'
KEY_ZONE_NUMBER = 'zoneNumber'
KEY_ZONES = 'zones'
STATUS_ONLINE = 'ONLINE'
STATUS_OFFLINE = 'OFFLINE'
# Device webhook values
TYPE_CONTROLLER_STATUS = 'DEVICE_STATUS'
SUBTYPE_OFFLINE = 'OFFLINE'
SUBTYPE_ONLINE = 'ONLINE'
SUBTYPE_OFFLINE_NOTIFICATION = 'OFFLINE_NOTIFICATION'
SUBTYPE_COLD_REBOOT = 'COLD_REBOOT'
SUBTYPE_SLEEP_MODE_ON = 'SLEEP_MODE_ON'
SUBTYPE_SLEEP_MODE_OFF = 'SLEEP_MODE_OFF'
SUBTYPE_BROWNOUT_VALVE = 'BROWNOUT_VALVE'
SUBTYPE_RAIN_SENSOR_DETECTION_ON = 'RAIN_SENSOR_DETECTION_ON'
SUBTYPE_RAIN_SENSOR_DETECTION_OFF = 'RAIN_SENSOR_DETECTION_OFF'
SUBTYPE_RAIN_DELAY_ON = 'RAIN_DELAY_ON'
SUBTYPE_RAIN_DELAY_OFF = 'RAIN_DELAY_OFF'
# Schedule webhook values
TYPE_SCHEDULE_STATUS = 'SCHEDULE_STATUS'
SUBTYPE_SCHEDULE_STARTED = 'SCHEDULE_STARTED'
SUBTYPE_SCHEDULE_STOPPED = 'SCHEDULE_STOPPED'
SUBTYPE_SCHEDULE_COMPLETED = 'SCHEDULE_COMPLETED'
SUBTYPE_WEATHER_NO_SKIP = 'WEATHER_INTELLIGENCE_NO_SKIP'
SUBTYPE_WEATHER_SKIP = 'WEATHER_INTELLIGENCE_SKIP'
SUBTYPE_WEATHER_CLIMATE_SKIP = 'WEATHER_INTELLIGENCE_CLIMATE_SKIP'
SUBTYPE_WEATHER_FREEZE = 'WEATHER_INTELLIGENCE_FREEZE'
# Zone webhook values
TYPE_ZONE_STATUS = 'ZONE_STATUS'
SUBTYPE_ZONE_STARTED = 'ZONE_STARTED'
SUBTYPE_ZONE_STOPPED = 'ZONE_STOPPED'
SUBTYPE_ZONE_COMPLETED = 'ZONE_COMPLETED'
SUBTYPE_ZONE_CYCLING = 'ZONE_CYCLING'
SUBTYPE_ZONE_CYCLING_COMPLETED = 'ZONE_CYCLING_COMPLETED'
# Webhook callbacks
LISTEN_EVENT_TYPES = ['DEVICE_STATUS_EVENT', 'ZONE_STATUS_EVENT']
WEBHOOK_CONST_ID = 'homeassistant.rachio:'
WEBHOOK_PATH = URL_API + DOMAIN
SIGNAL_RACHIO_UPDATE = DOMAIN + '_update'
SIGNAL_RACHIO_CONTROLLER_UPDATE = SIGNAL_RACHIO_UPDATE + '_controller'
SIGNAL_RACHIO_ZONE_UPDATE = SIGNAL_RACHIO_UPDATE + '_zone'
SIGNAL_RACHIO_SCHEDULE_UPDATE = SIGNAL_RACHIO_UPDATE + '_schedule'
def setup(hass, config) -> bool:
"""Set up the Rachio component."""
from rachiopy import Rachio
# Listen for incoming webhook connections
hass.http.register_view(RachioWebhookView())
# Configure API
api_key = config[DOMAIN].get(CONF_API_KEY)
rachio = Rachio(api_key)
# Get the URL of this server
custom_url = config[DOMAIN].get(CONF_CUSTOM_URL)
hass_url = hass.config.api.base_url if custom_url is None else custom_url
rachio.webhook_auth = generate_secret()
rachio.webhook_url = hass_url + WEBHOOK_PATH
# Get the API user
try:
person = RachioPerson(hass, rachio)
except AssertionError as error:
_LOGGER.error("Could not reach the Rachio API: %s", error)
return False
# Check for Rachio controller devices
if not person.controllers:
_LOGGER.error("No Rachio devices found in account %s",
person.username)
return False
else:
_LOGGER.info("%d Rachio device(s) found", len(person.controllers))
# Enable component
hass.data[DOMAIN] = person
return True
class RachioPerson(object):
"""Represent a Rachio user."""
def __init__(self, hass, rachio):
"""Create an object from the provided API instance."""
# Use API token to get user ID
self._hass = hass
self.rachio = rachio
response = rachio.person.getInfo()
assert int(response[0][KEY_STATUS]) == 200, "API key error"
self._id = response[1][KEY_ID]
# Use user ID to get user data
data = rachio.person.get(self._id)
assert int(data[0][KEY_STATUS]) == 200, "User ID error"
self.username = data[1][KEY_USERNAME]
self._controllers = [RachioIro(self._hass, self.rachio, controller)
for controller in data[1][KEY_DEVICES]]
_LOGGER.info('Using Rachio API as user "%s"', self.username)
@property
def user_id(self) -> str:
"""Get the user ID as defined by the Rachio API."""
return self._id
@property
def controllers(self) -> list:
"""Get a list of controllers managed by this account."""
return self._controllers
class RachioIro(object):
"""Represent a Rachio Iro."""
def __init__(self, hass, rachio, data):
"""Initialize a Rachio device."""
self.hass = hass
self.rachio = rachio
self._id = data[KEY_ID]
self._name = data[KEY_NAME]
self._zones = data[KEY_ZONES]
self._init_data = data
_LOGGER.debug('%s has ID "%s"', str(self), self.controller_id)
# Listen for all updates
self._init_webhooks()
def _init_webhooks(self) -> None:
"""Start getting updates from the Rachio API."""
current_webhook_id = None
# First delete any old webhooks that may have stuck around
def _deinit_webhooks(event) -> None:
"""Stop getting updates from the Rachio API."""
webhooks = self.rachio.notification.getDeviceWebhook(
self.controller_id)[1]
for webhook in webhooks:
if webhook[KEY_EXTERNAL_ID].startswith(WEBHOOK_CONST_ID) or\
webhook[KEY_ID] == current_webhook_id:
self.rachio.notification.deleteWebhook(webhook[KEY_ID])
_deinit_webhooks(None)
# Choose which events to listen for and get their IDs
event_types = []
for event_type in self.rachio.notification.getWebhookEventType()[1]:
if event_type[KEY_NAME] in LISTEN_EVENT_TYPES:
event_types.append({"id": event_type[KEY_ID]})
# Register to listen to these events from the device
url = self.rachio.webhook_url
auth = WEBHOOK_CONST_ID + self.rachio.webhook_auth
new_webhook = self.rachio.notification.postWebhook(self.controller_id,
auth, url,
event_types)
# Save ID for deletion at shutdown
current_webhook_id = new_webhook[1][KEY_ID]
self.hass.bus.listen(EVENT_HOMEASSISTANT_STOP, _deinit_webhooks)
def __str__(self) -> str:
"""Display the controller as a string."""
return 'Rachio controller "{}"'.format(self.name)
@property
def controller_id(self) -> str:
"""Return the Rachio API controller ID."""
return self._id
@property
def name(self) -> str:
"""Return the user-defined name of the controller."""
return self._name
@property
def current_schedule(self) -> str:
"""Return the schedule that the device is running right now."""
return self.rachio.device.getCurrentSchedule(self.controller_id)[1]
@property
def init_data(self) -> dict:
"""Return the information used to set up the controller."""
return self._init_data
def list_zones(self, include_disabled=False) -> list:
"""Return a list of the zone dicts connected to the device."""
# All zones
if include_disabled:
return self._zones
# Only enabled zones
return [z for z in self._zones if z[KEY_ENABLED]]
def get_zone(self, zone_id) -> dict or None:
"""Return the zone with the given ID."""
for zone in self.list_zones(include_disabled=True):
if zone[KEY_ID] == zone_id:
return zone
return None
def stop_watering(self) -> None:
"""Stop watering all zones connected to this controller."""
self.rachio.device.stopWater(self.controller_id)
_LOGGER.info("Stopped watering of all zones on %s", str(self))
class RachioWebhookView(HomeAssistantView):
"""Provide a page for the server to call."""
SIGNALS = {
TYPE_CONTROLLER_STATUS: SIGNAL_RACHIO_CONTROLLER_UPDATE,
TYPE_SCHEDULE_STATUS: SIGNAL_RACHIO_SCHEDULE_UPDATE,
TYPE_ZONE_STATUS: SIGNAL_RACHIO_ZONE_UPDATE,
}
requires_auth = False # Handled separately
url = WEBHOOK_PATH
name = url[1:].replace('/', ':')
# pylint: disable=no-self-use
@asyncio.coroutine
async def post(self, request) -> web.Response:
"""Handle webhook calls from the server."""
hass = request.app['hass']
data = await request.json()
try:
auth = data.get(KEY_EXTERNAL_ID, str()).split(':')[1]
assert auth == hass.data[DOMAIN].rachio.webhook_auth
except (AssertionError, IndexError):
return web.Response(status=web.HTTPForbidden.status_code)
update_type = data[KEY_TYPE]
if update_type in self.SIGNALS:
async_dispatcher_send(hass, self.SIGNALS[update_type], data)
return web.Response(status=web.HTTPNoContent.status_code)

View File

@ -4,227 +4,239 @@ Integration with the Rachio Iro sprinkler system controller.
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.rachio/ https://home-assistant.io/components/switch.rachio/
""" """
from abc import abstractmethod
from datetime import timedelta from datetime import timedelta
import logging import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice
from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.components.rachio import (DOMAIN as DOMAIN_RACHIO,
KEY_DEVICE_ID,
KEY_ENABLED,
KEY_ID,
KEY_NAME,
KEY_ON,
KEY_SUBTYPE,
KEY_SUMMARY,
KEY_ZONE_ID,
KEY_ZONE_NUMBER,
SIGNAL_RACHIO_CONTROLLER_UPDATE,
SIGNAL_RACHIO_ZONE_UPDATE,
SUBTYPE_ZONE_STARTED,
SUBTYPE_ZONE_STOPPED,
SUBTYPE_ZONE_COMPLETED,
SUBTYPE_SLEEP_MODE_ON,
SUBTYPE_SLEEP_MODE_OFF)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
import homeassistant.util as util from homeassistant.helpers.dispatcher import dispatcher_connect
REQUIREMENTS = ['rachiopy==0.1.2'] DEPENDENCIES = ['rachio']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# Manual run length
CONF_MANUAL_RUN_MINS = 'manual_run_mins' CONF_MANUAL_RUN_MINS = 'manual_run_mins'
DATA_RACHIO = 'rachio'
DEFAULT_MANUAL_RUN_MINS = 10 DEFAULT_MANUAL_RUN_MINS = 10
MIN_UPDATE_INTERVAL = timedelta(seconds=30)
MIN_FORCED_UPDATE_INTERVAL = timedelta(seconds=1)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ACCESS_TOKEN): cv.string,
vol.Optional(CONF_MANUAL_RUN_MINS, default=DEFAULT_MANUAL_RUN_MINS): vol.Optional(CONF_MANUAL_RUN_MINS, default=DEFAULT_MANUAL_RUN_MINS):
cv.positive_int, cv.positive_int,
}) })
ATTR_ZONE_SUMMARY = 'Summary'
ATTR_ZONE_NUMBER = 'Zone number'
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Rachio switches.""" """Set up the Rachio switches."""
from rachiopy import Rachio manual_run_time = timedelta(minutes=config.get(CONF_MANUAL_RUN_MINS))
_LOGGER.info("Rachio run time is %s", str(manual_run_time))
# Get options # Add all zones from all controllers as switches
manual_run_mins = config.get(CONF_MANUAL_RUN_MINS) devices = []
_LOGGER.debug("Rachio run time is %d min", manual_run_mins) for controller in hass.data[DOMAIN_RACHIO].controllers:
devices.append(RachioStandbySwitch(hass, controller))
access_token = config.get(CONF_ACCESS_TOKEN) for zone in controller.list_zones():
devices.append(RachioZone(hass, controller, zone, manual_run_time))
# Configure API add_devices(devices)
_LOGGER.debug("Configuring Rachio API") _LOGGER.info("%d Rachio switch(es) added", len(devices))
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
# Get and persist devices
devices = _list_devices(rachio, manual_run_mins)
if not devices:
_LOGGER.error(
"No Rachio devices found in account %s", person['username'])
return
hass.data[DATA_RACHIO] = devices[0]
if len(devices) > 1:
_LOGGER.warning("Multiple Rachio devices found in account, "
"using %s", hass.data[DATA_RACHIO].device_id)
else:
_LOGGER.debug("Found Rachio device")
hass.data[DATA_RACHIO].update()
add_devices(hass.data[DATA_RACHIO].list_zones())
def _get_person(rachio): class RachioSwitch(SwitchDevice):
"""Pull the account info of the person whose access token was provided.""" """Represent a Rachio state that can be toggled."""
person_id = rachio.person.getInfo()[1]['id']
return rachio.person.get(person_id)[1]
def __init__(self, controller, poll=True):
"""Initialize a new Rachio switch."""
self._controller = controller
def _list_devices(rachio, manual_run_mins): if poll:
"""Pull a list of devices on the account.""" self._state = self._poll_update()
return [RachioIro(rachio, d['id'], manual_run_mins) else:
for d in _get_person(rachio)['devices']] self._state = None
class RachioIro(object):
"""Representation of a Rachio Iro."""
def __init__(self, rachio, device_id, manual_run_mins):
"""Initialize a Rachio 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 {}".format(self.serial_number)
@property @property
def device_id(self): def should_poll(self) -> bool:
"""Return the Rachio API device ID.""" """Declare that this entity pushes its state to HA."""
return self._device['id'] return False
@property @property
def status(self): def name(self) -> str:
"""Return the current status of the device.""" """Get a name for this switch."""
return self._device['status'] return "Switch on {}".format(self._controller.name)
@property @property
def serial_number(self): def is_on(self) -> bool:
"""Return the serial number of the device.""" """Return whether the switch is currently on."""
return self._device['serialNumber'] return self._state
@abstractmethod
def _poll_update(self, data=None) -> bool:
"""Poll the API."""
pass
def _handle_any_update(self, *args, **kwargs) -> None:
"""Determine whether an update event applies to this device."""
if args[0][KEY_DEVICE_ID] != self._controller.controller_id:
# For another device
return
# For this device
self._handle_update(args, kwargs)
@abstractmethod
def _handle_update(self, *args, **kwargs) -> None:
"""Handle incoming webhook data."""
pass
class RachioStandbySwitch(RachioSwitch):
"""Representation of a standby status/button."""
def __init__(self, hass, controller):
"""Instantiate a new Rachio standby mode switch."""
dispatcher_connect(hass, SIGNAL_RACHIO_CONTROLLER_UPDATE,
self._handle_any_update)
super().__init__(controller, poll=False)
self._poll_update(controller.init_data)
@property @property
def is_paused(self): def name(self) -> str:
"""Return whether the device is temporarily disabled.""" """Return the name of the standby switch."""
return self._device['paused'] return "{} in standby mode".format(self._controller.name)
@property @property
def is_on(self): def icon(self) -> str:
"""Return whether the device is powered on and connected.""" """Return an icon for the standby switch."""
return self._device['on'] return "mdi:power"
@property def _poll_update(self, data=None) -> bool:
def current_schedule(self): """Request the state from the API."""
"""Return the schedule that the device is running right now.""" if data is None:
return self._running data = self._controller.rachio.device.get(
self._controller.controller_id)[1]
def list_zones(self, include_disabled=False): return not data[KEY_ON]
"""Return a list of the zones connected to the device, incl. 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: def _handle_update(self, *args, **kwargs) -> None:
return self._zones """Update the state using webhook data."""
if args[0][KEY_SUBTYPE] == SUBTYPE_SLEEP_MODE_ON:
self._state = True
elif args[0][KEY_SUBTYPE] == SUBTYPE_SLEEP_MODE_OFF:
self._state = False
self.update(no_throttle=True) self.schedule_update_ha_state()
return [z for z in self._zones if z.is_enabled]
@util.Throttle(MIN_UPDATE_INTERVAL, MIN_FORCED_UPDATE_INTERVAL) def turn_on(self, **kwargs) -> None:
def update(self, **kwargs): """Put the controller in standby mode."""
"""Pull updated device info from the Rachio API.""" self._controller.rachio.device.off(self._controller.controller_id)
self._device = self.rachio.device.get(self._device_id)[1]
self._running = self.rachio.device\
.getCurrentSchedule(self._device_id)[1]
# Possibly update all zones def turn_off(self, **kwargs) -> None:
for zone in self.list_zones(include_disabled=True): """Resume controller functionality."""
zone.update() self._controller.rachio.device.on(self._controller.controller_id)
_LOGGER.debug("Updated %s", str(self))
class RachioZone(SwitchDevice): class RachioZone(RachioSwitch):
"""Representation of one zone of sprinklers connected to the Rachio Iro.""" """Representation of one zone of sprinklers connected to the Rachio Iro."""
def __init__(self, rachio, device, zone_id, manual_run_mins): def __init__(self, hass, controller, data, manual_run_time):
"""Initialize a new Rachio Zone.""" """Initialize a new Rachio Zone."""
self.rachio = rachio self._id = data[KEY_ID]
self._device = device self._zone_name = data[KEY_NAME]
self._zone_id = zone_id self._zone_number = data[KEY_ZONE_NUMBER]
self._zone = None self._zone_enabled = data[KEY_ENABLED]
self._manual_run_secs = manual_run_mins * 60 self._manual_run_time = manual_run_time
self._summary = str()
super().__init__(controller)
# Listen for all zone updates
dispatcher_connect(hass, SIGNAL_RACHIO_ZONE_UPDATE,
self._handle_update)
def __str__(self): def __str__(self):
"""Display the zone as a string.""" """Display the zone as a string."""
return "Rachio Zone {}".format(self.name) return 'Rachio Zone "{}" on {}'.format(self.name,
str(self._controller))
@property @property
def zone_id(self): def zone_id(self) -> str:
"""How the Rachio API refers to the zone.""" """How the Rachio API refers to the zone."""
return self._zone['id'] return self._id
@property @property
def unique_id(self): def name(self) -> str:
"""Return the unique string ID for the zone."""
return '{iro}-{zone}'.format(
iro=self._device.device_id, zone=self.zone_id)
@property
def number(self):
"""Return the physical connection of the zone pump."""
return self._zone['zoneNumber']
@property
def name(self):
"""Return the friendly name of the zone.""" """Return the friendly name of the zone."""
return self._zone['name'] return self._zone_name
@property @property
def is_enabled(self): def icon(self) -> str:
"""Return the icon to display."""
return "mdi:water"
@property
def zone_is_enabled(self) -> bool:
"""Return whether the zone is allowed to run.""" """Return whether the zone is allowed to run."""
return self._zone['enabled'] return self._zone_enabled
@property @property
def is_on(self): def state_attributes(self) -> dict:
"""Return whether the zone is currently running.""" """Return the optional state attributes."""
schedule = self._device.current_schedule return {
return self.zone_id == schedule.get('zoneId') ATTR_ZONE_NUMBER: self._zone_number,
ATTR_ZONE_SUMMARY: self._summary,
}
def update(self): def turn_on(self, **kwargs) -> None:
"""Pull updated zone info from the Rachio API.""" """Start watering this zone."""
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, **kwargs):
"""Start the zone."""
# Stop other zones first # Stop other zones first
self.turn_off() self.turn_off()
_LOGGER.info("Watering %s for %d s", self.name, self._manual_run_secs) # Start this zone
self.rachio.zone.start(self.zone_id, self._manual_run_secs) self._controller.rachio.zone.start(self.zone_id,
self._manual_run_time.seconds)
_LOGGER.debug("Watering %s on %s", self.name, self._controller.name)
def turn_off(self, **kwargs): def turn_off(self, **kwargs) -> None:
"""Stop all zones.""" """Stop watering all zones."""
_LOGGER.info("Stopping watering of all zones") self._controller.stop_watering()
self.rachio.device.stopWater(self._device.device_id)
def _poll_update(self, data=None) -> bool:
"""Poll the API to check whether the zone is running."""
schedule = self._controller.current_schedule
return self.zone_id == schedule.get(KEY_ZONE_ID)
def _handle_update(self, *args, **kwargs) -> None:
"""Handle incoming webhook zone data."""
if args[0][KEY_ZONE_ID] != self.zone_id:
return
self._summary = kwargs.get(KEY_SUMMARY, str())
if args[0][KEY_SUBTYPE] == SUBTYPE_ZONE_STARTED:
self._state = True
elif args[0][KEY_SUBTYPE] in [SUBTYPE_ZONE_STOPPED,
SUBTYPE_ZONE_COMPLETED]:
self._state = False
self.schedule_update_ha_state()

View File

@ -1163,8 +1163,8 @@ pyzabbix==0.7.4
# homeassistant.components.sensor.qnap # homeassistant.components.sensor.qnap
qnapstats==0.2.6 qnapstats==0.2.6
# homeassistant.components.switch.rachio # homeassistant.components.rachio
rachiopy==0.1.2 rachiopy==0.1.3
# homeassistant.components.climate.radiotherm # homeassistant.components.climate.radiotherm
radiotherm==1.3 radiotherm==1.3