Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
214 changes: 214 additions & 0 deletions homeassistant/components/switch/rachio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import logging
import voluptuous as vol
from datetime import timedelta

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'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you push your dependency to PyPi?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this fork is intended to stay as a permanent code, could you push it to pypi?

'/archive/edf666de8ef3f9596ddc8d3a989e8c4b829a4319.zip'
'#rachiopy==0.1.1']

_LOGGER = logging.getLogger(__name__)

DATA_RACHIO = 'rachio'

CONF_MANUAL_RUN_MINS = 'manual_run_mins'
manual_run_mins = 60

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): cv.positive_int
})


# Set up the component
# noinspection PyUnusedLocal
def setup_platform(hass, config, add_devices, discovery_info=None):
global manual_run_mins

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not use global


# Get options
manual_run_mins = config.get(CONF_MANUAL_RUN_MINS) or manual_run_mins
_LOGGER.debug("Rachio run time is " + str(manual_run_mins) + " min")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This, among other things, is one of the many lint issues that CI is reporting that need to be fixed: https://travis-ci.org/home-assistant/home-assistant/jobs/233033184#L271


# Get access token
_LOGGER.debug("Getting Rachio access token...")
access_token = config.get(CONF_ACCESS_TOKEN)
if not access_token:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is impossible, it's marked as required in your PLATFORM_SCHEMA

_LOGGER.error("Rachio API access token must be set "
"in the configuration")
return False

# Configure API
_LOGGER.debug("Configuring Rachio API...")
from rachiopy import Rachio
r = Rachio(access_token)
person = None
try:
person = _get_person(r)
except:
_LOGGER.error("Could not reach the Rachio API. "
"Is your access token valid?")
return False

# Get and persist devices
devices = _list_devices(r)
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


# Pull the account info of the person whose access token was provided
def _get_person(r):
person_id = r.person.getInfo()[1]['id']
return r.person.get(person_id)[1]


# Pull a list of devices on the account
def _list_devices(r):
return [RachioIro(r, d['id']) for d in _get_person(r)['devices']]


# Represents one Rachio Iro
class RachioIro(object):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have expected this class to live in RachioPy

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RachioPy is not my own creation, and in fact includes these classes, however, they only contain static methods and are otherwise useless. I'll add these methods to my fork of the library.

def __init__(self, r, device_id):
self.r = r
self._device_id = device_id
self._device = None
self._running = None
self._zones = None

def __str__(self):
return "Rachio Iro " + self.serial_number

@property
def device_id(self):
return self._device['id']

@property
def status(self):
return self._device['status']

@property
def serial_number(self):
return self._device['serialNumber']

@property
def is_paused(self):
return self._device['paused']

@property
def is_on(self):
return self._device['on']

@property
def current_schedule(self):
return self._running

def list_zones(self, include_disabled=False):
if not self._zones:
self._zones = [RachioZone(self.r, self, zone['id'])
for zone in self._device['zones']]

if include_disabled:
return self._zones
else:
self.update(propagate=True, no_throttle=True)
return [z for z in self._zones if z.is_enabled]

# Pull updated device info from the Rachio API
@util.Throttle(MIN_UPDATE_INTERVAL, MIN_FORCED_UPDATE_INTERVAL)
def update(self, propagate=True):
self._device = self.r.device.get(self._device_id)[1]
self._running = self.r.device.getCurrentSchedule(self._device_id)[1]

# Possibly update all zones
if propagate:
for zone in self.list_zones(include_disabled=True):
zone.update(propagate=False)

_LOGGER.debug("Updated " + str(self))


# Represents one zone of sprinklers connected to the Rachio Iro
class RachioZone(SwitchDevice):
def __init__(self, r, device, zone_id):
self.r = r
self._device = device
self._zone_id = zone_id
self._zone = None

def __str__(self):
return "Rachio Zone " + self.name

@property
def zone_id(self):
return self._zone['id']

@property
def unique_id(self):
return '{iro}-{zone}'.format(
iro=self._device.device_id,
zone=self.zone_id)

@property
def number(self):
return self._zone['zoneNumber']

@property
def name(self):
return self._zone['name'] or "Zone " + self.number

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If _zone['name'] does not exist, a KeyError will be raised. Use _zone.get('name') instead to get None back.

@Klikini Klikini May 17, 2017

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Looks like this was unnecessary anyway. The API returns a placeholder name already, so I don't need to make one.


@property
def is_enabled(self):
return self._zone['enabled']

# TODO: fix this always returning false
@property
def is_on(self):
self._device.update(propagate=False)
schedule = self._device.current_schedule
return self.zone_id == schedule.get('zoneId')

# Pull updated zone info from the Rachio API
def update(self, propagate=True):
self._zone = self.r.zone.get(self._zone_id)[1]

# Possibly update device
if propagate:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your entity is set to be polled for updates by Home Assistant (because you do not overwrite the should_poll property). Each entity will individually call _device.update(), which is fine because Throttle will take care of it only getting called once.

So you can remove the whole propagation of updates.

self._device.update()

_LOGGER.debug("Updated " + str(self))

# Start the zone and return the response headers
def turn_on(self, seconds=None):
seconds = seconds or (manual_run_mins * 60)

# Stop other zones first
self.turn_off()

_LOGGER.info("Watering {} for {} sec".format(self.name, seconds))
self.r.zone.start(self.zone_id, seconds)

# Stop all zones and return the response headers
def turn_off(self):
_LOGGER.info("Stopping watering of all zones")
self.r.device.stopWater(self._device.device_id)
3 changes: 3 additions & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,9 @@ hikvision==0.4
# homeassistant.components.binary_sensor.workday
holidays==0.8.1

# homeassistant.components.switch.rachio
https://github.com/Klikini/rachiopy/archive/edf666de8ef3f9596ddc8d3a989e8c4b829a4319.zip#rachiopy==0.1.1

# homeassistant.components.switch.dlink
https://github.com/LinuxChristian/pyW215/archive/v0.4.zip#pyW215==0.4

Expand Down
1 change: 1 addition & 0 deletions tests/testing_config/automations.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this file.