Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RainMachine switch platform #8827

Merged
merged 11 commits into from
Aug 8, 2017
4 changes: 1 addition & 3 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,6 @@ omit =
homeassistant/components/rachio.py
homeassistant/components/*/rachio.py

homeassistant/components/rainmachine.py
homeassistant/components/*/rainmachine.py

homeassistant/components/raspihats.py
homeassistant/components/*/raspihats.py

Expand Down Expand Up @@ -542,6 +539,7 @@ omit =
homeassistant/components/switch/orvibo.py
homeassistant/components/switch/pilight.py
homeassistant/components/switch/pulseaudio_loopback.py
homeassistant/components/switch/rainmachine.py
homeassistant/components/switch/rest.py
homeassistant/components/switch/rpi_rf.py
homeassistant/components/switch/tplink.py
Expand Down
205 changes: 101 additions & 104 deletions homeassistant/components/switch/rainmachine.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import (ATTR_ATTRIBUTION, ATTR_DEVICE_CLASS,
CONF_EMAIL, CONF_IP_ADDRESS, CONF_PASSWORD)
CONF_EMAIL, CONF_IP_ADDRESS, CONF_PASSWORD,
CONF_PLATFORM, CONF_SCAN_INTERVAL)
from homeassistant.util import Throttle

_LOGGER = getLogger(__name__)
Expand All @@ -27,18 +28,92 @@
MIN_SCAN_TIME_REMOTE = timedelta(seconds=5)
MIN_SCAN_TIME_FORCED = timedelta(milliseconds=100)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_IP_ADDRESS):
cv.string,
vol.Optional(CONF_EMAIL):
cv.string,
vol.Required(CONF_PASSWORD):
cv.string,
vol.Optional(CONF_ZONE_RUN_TIME, default=DEFAULT_ZONE_RUN_SECONDS):
cv.positive_int,
vol.Optional(CONF_HIDE_DISABLED_ENTITIES, default=True):
cv.boolean
})
PLATFORM_SCHEMA = vol.Schema(

Choose a reason for hiding this comment

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

redefinition of unused 'PLATFORM_SCHEMA' from line 10

vol.All(
cv.has_at_least_one_key(CONF_IP_ADDRESS, CONF_EMAIL), {
vol.Required(CONF_PLATFORM):
cv.string,
vol.Optional(CONF_SCAN_INTERVAL):
cv.time_period,
vol.Exclusive(CONF_IP_ADDRESS, 'auth_type'):
cv.string,
vol.Exclusive(CONF_EMAIL, 'auth_type'):
vol.Email(),
vol.Required(CONF_PASSWORD):
cv.string,
vol.Optional(CONF_ZONE_RUN_TIME, default=DEFAULT_ZONE_RUN_SECONDS):
cv.positive_int,
vol.Optional(CONF_HIDE_DISABLED_ENTITIES, default=True):
cv.boolean
}),
extra=vol.ALLOW_EXTRA)


@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set this component up under its platform."""
import regenmaschine as rm

ip_address = config.get(CONF_IP_ADDRESS)
_LOGGER.debug('IP address: %s', ip_address)

email_address = config.get(CONF_EMAIL)
_LOGGER.debug('Email address: %s', email_address)

password = config.get(CONF_PASSWORD)
_LOGGER.debug('Password: %s', password)

hide_disabled_entities = config.get(CONF_HIDE_DISABLED_ENTITIES)
_LOGGER.debug('Show disabled entities: %s', hide_disabled_entities)

zone_run_time = config.get(CONF_ZONE_RUN_TIME)
_LOGGER.debug('Zone run time: %s', zone_run_time)

try:
if ip_address:
_LOGGER.debug('Configuring local API...')
auth = rm.Authenticator.create_local(ip_address, password)
elif email_address:
_LOGGER.debug('Configuring remote API...')
auth = rm.Authenticator.create_remote(email_address, password)

_LOGGER.debug('Instantiating RainMachine client...')
client = rm.Client(auth)

rainmachine_device_name = client.provision.device_name().get('name')

entities = []
for program in client.programs.all().get('programs'):
if hide_disabled_entities and program.get('active') is False:
continue

_LOGGER.debug('Adding program: %s', program)
entities.append(
RainMachineProgram(
client, program, device_name=rainmachine_device_name))

for zone in client.zones.all().get('zones'):
if hide_disabled_entities and zone.get('active') is False:
continue

_LOGGER.debug('Adding zone: %s', zone)
entities.append(
RainMachineZone(
client,
zone,
zone_run_time,
device_name=rainmachine_device_name,
))

async_add_devices(entities)
except rm.exceptions.HTTPError as exc_info:
_LOGGER.error('An HTTP error occurred while talking with RainMachine')
_LOGGER.debug(exc_info)
return False
except UnboundLocalError as exc_info:
_LOGGER.error('Could not authenticate against RainMachine')
_LOGGER.debug(exc_info)
return False


def aware_throttle(api_type):
Expand Down Expand Up @@ -95,11 +170,6 @@ def rainmachine_id(self) -> int:
"""Return the RainMachine ID for this entity."""
return self._entity_json.get('uid')

@property
def should_poll(self) -> bool:
"""Return the polling state."""
return True

@property
def unique_id(self) -> str:
"""Return a unique, HASS-friendly identifier for this entity."""
Expand All @@ -118,7 +188,7 @@ def _remote_update(self) -> None:

def _update(self) -> None: # pylint: disable=no-self-use
"""Logic for update method, regardless of API type."""
_LOGGER.warning('Update method not defined for base class')
raise NotImplementedError()

def update(self) -> None:
"""Determine how the entity updates itself."""
Expand Down Expand Up @@ -152,7 +222,7 @@ def turn_off(self, **kwargs) -> None:
except exceptions.HTTPError as exc_info:
_LOGGER.error('Unable to turn off program "%s"',
self.rainmachine_id)
_LOGGER.debug(str(exc_info))
_LOGGER.debug(exc_info)

def turn_on(self, **kwargs) -> None:
"""Turn the program on."""
Expand All @@ -165,7 +235,7 @@ def turn_on(self, **kwargs) -> None:
except exceptions.HTTPError as exc_info:
_LOGGER.error('Unable to turn on program "%s"',
self.rainmachine_id)
_LOGGER.debug(str(exc_info))
_LOGGER.debug(exc_info)

def _update(self) -> None:
"""Update info for the program."""
Expand All @@ -176,16 +246,16 @@ def _update(self) -> None:
except exceptions.HTTPError as exc_info:
_LOGGER.error('Unable to update info for program "%s"',
self.rainmachine_id)
_LOGGER.debug(str(exc_info))
_LOGGER.debug(exc_info)


class RainMachineZone(RainMachineEntity):
"""A RainMachine zone."""

def __init__(self, client, zone_json, **kwargs):
def __init__(self, client, zone_json, zone_run_time, **kwargs):
"""Initialize a RainMachine zone."""
super(RainMachineZone, self).__init__(client, zone_json, **kwargs)
self._run_time = kwargs.get(CONF_ZONE_RUN_TIME)
super().__init__(client, zone_json, **kwargs)
self._run_time = kwargs.get(zone_run_time)
Copy link
Member

Choose a reason for hiding this comment

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

The argument is already available now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change is already pushed:

screen shot 2017-08-07 at 1 26 53 pm

Are you not seeing that? Wondering if I messed up the branch further...

Copy link
Member

Choose a reason for hiding this comment

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

That looks like what I have. But I think this line should be changed to this:

self._run_time = zone_run_time

Cause zone_run_time is now available as a positional argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, great point. Updating shortly.

self._attrs.update({
ATTR_CYCLES:
self._entity_json.get('noOfCycles'),
Expand All @@ -210,9 +280,8 @@ def turn_off(self, **kwargs) -> None:
try:
self._client.zones.stop(self.rainmachine_id)
except exceptions.HTTPError as exc_info:
_LOGGER.error('Unable to turn off zone "%s"',
self.rainmachine_id)
_LOGGER.debug(str(exc_info))
_LOGGER.error('Unable to turn off zone "%s"', self.rainmachine_id)
_LOGGER.debug(exc_info)

def turn_on(self, **kwargs) -> None:
"""Turn the zone on."""
Expand All @@ -221,9 +290,8 @@ def turn_on(self, **kwargs) -> None:
try:
self._client.zones.start(self.rainmachine_id, self._run_time)
except exceptions.HTTPError as exc_info:
_LOGGER.error('Unable to turn on zone "%s"',
self.rainmachine_id)
_LOGGER.debug(str(exc_info))
_LOGGER.error('Unable to turn on zone "%s"', self.rainmachine_id)
_LOGGER.debug(exc_info)

def _update(self) -> None:
"""Update info for the zone."""
Expand All @@ -234,75 +302,4 @@ def _update(self) -> None:
except exceptions.HTTPError as exc_info:
_LOGGER.error('Unable to update info for zone "%s"',
self.rainmachine_id)
_LOGGER.debug(str(exc_info))


@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set this component up under its platform."""
import regenmaschine as rm

ip_address = config.get(CONF_IP_ADDRESS)
_LOGGER.debug('IP address: %s', ip_address)

email_address = config.get(CONF_EMAIL)
_LOGGER.debug('Email address: %s', email_address)

password = config.get(CONF_PASSWORD)
_LOGGER.debug('Password: %s', password)

hide_disabled_entities = config.get(CONF_HIDE_DISABLED_ENTITIES)
_LOGGER.debug('Show disabled entities: %s', hide_disabled_entities)

zone_run_time = config.get(CONF_ZONE_RUN_TIME)
_LOGGER.debug('Zone run time: %s', zone_run_time)

try:
if ip_address:
_LOGGER.debug('Configuring local API...')
auth = rm.Authenticator.create_local(ip_address, password)
elif email_address:
_LOGGER.debug('Configuring remote API...')
auth = rm.Authenticator.create_remote(email_address, password)
else:
_LOGGER.error('Neither IP address nor email address given')
return False
except rm.exceptions.HTTPError as exec_info:
_LOGGER.error('HTTP error during authentication: %s', str(exec_info))
return False

try:
_LOGGER.debug('Instantiating RainMachine client...')
client = rm.Client(auth)

rainmachine_device_name = client.provision.device_name().get('name')

entities = []
for program in client.programs.all().get('programs'):
if hide_disabled_entities and program.get('active') is False:
continue

_LOGGER.debug('Adding program: %s', program)
entities.append(
RainMachineProgram(
client, program, device_name=rainmachine_device_name))

for zone in client.zones.all().get('zones'):
if hide_disabled_entities and zone.get('active') is False:
continue

_LOGGER.debug('Adding zone: %s', zone)
entities.append(
RainMachineZone(
client,
zone,
device_name=rainmachine_device_name,
zone_run_time=zone_run_time))

async_add_devices(entities)
except rm.exceptions.HTTPError as exec_info:
_LOGGER.error('Request failed: %s', str(exec_info))
return False
except Exception as exec_info: # pylint: disable=broad-except
_LOGGER.error('Unknown error: %s', str(exec_info))
return False
_LOGGER.debug(exc_info)