Rachio webhooks (#15111)

* Make fewer requests to the Rachio API

* BREAKING: Rewrite Rachio component
pull/15251/head
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
https://home-assistant.io/components/switch.rachio/
"""
from abc import abstractmethod
from datetime import timedelta
import logging
import voluptuous as vol
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.util as util
from homeassistant.helpers.dispatcher import dispatcher_connect
REQUIREMENTS = ['rachiopy==0.1.2']
DEPENDENCIES = ['rachio']
_LOGGER = logging.getLogger(__name__)
# Manual run length
CONF_MANUAL_RUN_MINS = 'manual_run_mins'
DATA_RACHIO = 'rachio'
DEFAULT_MANUAL_RUN_MINS = 10
MIN_UPDATE_INTERVAL = timedelta(seconds=30)
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,
})
ATTR_ZONE_SUMMARY = 'Summary'
ATTR_ZONE_NUMBER = 'Zone number'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""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
manual_run_mins = config.get(CONF_MANUAL_RUN_MINS)
_LOGGER.debug("Rachio run time is %d min", manual_run_mins)
# Add all zones from all controllers as switches
devices = []
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
_LOGGER.debug("Configuring Rachio API")
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())
add_devices(devices)
_LOGGER.info("%d Rachio switch(es) added", len(devices))
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]
class RachioSwitch(SwitchDevice):
"""Represent a Rachio state that can be toggled."""
def __init__(self, controller, poll=True):
"""Initialize a new Rachio switch."""
self._controller = controller
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):
"""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)
if poll:
self._state = self._poll_update()
else:
self._state = None
@property
def device_id(self):
"""Return the Rachio API device ID."""
return self._device['id']
def should_poll(self) -> bool:
"""Declare that this entity pushes its state to HA."""
return False
@property
def status(self):
"""Return the current status of the device."""
return self._device['status']
def name(self) -> str:
"""Get a name for this switch."""
return "Switch on {}".format(self._controller.name)
@property
def serial_number(self):
"""Return the serial number of the device."""
return self._device['serialNumber']
def is_on(self) -> bool:
"""Return whether the switch is currently on."""
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
def is_paused(self):
"""Return whether the device is temporarily disabled."""
return self._device['paused']
def name(self) -> str:
"""Return the name of the standby switch."""
return "{} in standby mode".format(self._controller.name)
@property
def is_on(self):
"""Return whether the device is powered on and connected."""
return self._device['on']
def icon(self) -> str:
"""Return an icon for the standby switch."""
return "mdi:power"
@property
def current_schedule(self):
"""Return the schedule that the device is running right now."""
return self._running
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]
def list_zones(self, include_disabled=False):
"""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']]
return not data[KEY_ON]
if include_disabled:
return self._zones
def _handle_update(self, *args, **kwargs) -> None:
"""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)
return [z for z in self._zones if z.is_enabled]
self.schedule_update_ha_state()
@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]
def turn_on(self, **kwargs) -> None:
"""Put the controller in standby mode."""
self._controller.rachio.device.off(self._controller.controller_id)
# Possibly update all zones
for zone in self.list_zones(include_disabled=True):
zone.update()
_LOGGER.debug("Updated %s", str(self))
def turn_off(self, **kwargs) -> None:
"""Resume controller functionality."""
self._controller.rachio.device.on(self._controller.controller_id)
class RachioZone(SwitchDevice):
class RachioZone(RachioSwitch):
"""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."""
self.rachio = rachio
self._device = device
self._zone_id = zone_id
self._zone = None
self._manual_run_secs = manual_run_mins * 60
self._id = data[KEY_ID]
self._zone_name = data[KEY_NAME]
self._zone_number = data[KEY_ZONE_NUMBER]
self._zone_enabled = data[KEY_ENABLED]
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):
"""Display the zone as a string."""
return "Rachio Zone {}".format(self.name)
return 'Rachio Zone "{}" on {}'.format(self.name,
str(self._controller))
@property
def zone_id(self):
def zone_id(self) -> str:
"""How the Rachio API refers to the zone."""
return self._zone['id']
return self._id
@property
def unique_id(self):
"""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):
def name(self) -> str:
"""Return the friendly name of the zone."""
return self._zone['name']
return self._zone_name
@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 self._zone['enabled']
return self._zone_enabled
@property
def is_on(self):
"""Return whether the zone is currently running."""
schedule = self._device.current_schedule
return self.zone_id == schedule.get('zoneId')
def state_attributes(self) -> dict:
"""Return the optional state attributes."""
return {
ATTR_ZONE_NUMBER: self._zone_number,
ATTR_ZONE_SUMMARY: self._summary,
}
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, **kwargs):
"""Start the zone."""
def turn_on(self, **kwargs) -> None:
"""Start watering this zone."""
# Stop other zones first
self.turn_off()
_LOGGER.info("Watering %s for %d s", self.name, self._manual_run_secs)
self.rachio.zone.start(self.zone_id, self._manual_run_secs)
# Start this zone
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):
"""Stop all zones."""
_LOGGER.info("Stopping watering of all zones")
self.rachio.device.stopWater(self._device.device_id)
def turn_off(self, **kwargs) -> None:
"""Stop watering all zones."""
self._controller.stop_watering()
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
qnapstats==0.2.6
# homeassistant.components.switch.rachio
rachiopy==0.1.2
# homeassistant.components.rachio
rachiopy==0.1.3
# homeassistant.components.climate.radiotherm
radiotherm==1.3