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

Introducing support to Melnor RainCloud sprinkler systems #9287

Merged
merged 12 commits into from
Sep 29, 2017
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ omit =
homeassistant/components/rachio.py
homeassistant/components/*/rachio.py

homeassistant/components/raincloud.py
homeassistant/components/*/raincloud.py

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

Expand Down
99 changes: 99 additions & 0 deletions homeassistant/components/binary_sensor/raincloud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""
Support for Melnor RainCloud sprinkler water timer.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.raincloud/
"""
import logging

import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant.components.raincloud import CONF_ATTRIBUTION, DATA_RAINCLOUD
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION)

DEPENDENCIES = ['raincloud']

_LOGGER = logging.getLogger(__name__)

SENSOR_TYPES = {
'is_watering': ['Watering', ''],
'status': ['Status', ''],
}

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a sensor for a raincloud device."""
raincloud = hass.data[DATA_RAINCLOUD].data

sensors = []
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
if sensor_type == 'status':
sensors.append(
RainCloudBinarySensor(raincloud.controller,
sensor_type))
sensors.append(
RainCloudBinarySensor(raincloud.controller.faucet,
sensor_type))

else:
# create an sensor for each zone managed by faucet
for zone in raincloud.controller.faucet.zones:
sensors.append(RainCloudBinarySensor(zone, sensor_type))

add_devices(sensors, True)
return True


class RainCloudBinarySensor(BinarySensorDevice):
"""A sensor implementation for raincloud device."""

def __init__(self, data, sensor_type):
"""Initialize a sensor for raincloud device."""
super().__init__()
self._sensor_type = sensor_type
self._data = data
self._name = "{0} {1}".format(
self._data.name, SENSOR_TYPES.get(self._sensor_type)[0])
self._state = None

@property
def name(self):
"""Return the name of the sensor."""
return self._name

@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self._state

def update(self):
"""Get the latest data and updates the state."""
_LOGGER.debug("Updating RainCloud sensor: %s", self._name)
self._state = getattr(self._data, self._sensor_type)

@property
def icon(self):
"""Return the icon of this device."""
if self._sensor_type == 'is_watering':
return 'mdi:water' if self.is_on else 'mdi:water-off'
elif self._sensor_type == 'status':
return 'mdi:pipe' if self.is_on else 'mdi:pipe-disconnected'
return SENSOR_TYPES.get(self._sensor_type)[1]

@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
'current_time': self._data.current_time,
'identifier': self._data.serial
}
97 changes: 97 additions & 0 deletions homeassistant/components/raincloud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""
Support for Melnor RainCloud sprinkler water timer.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/raincloud/
"""
import logging
from datetime import timedelta

import voluptuous as vol
import homeassistant.helpers.config_validation as cv

from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL)
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.dispatcher import dispatcher_send

from requests.exceptions import HTTPError, ConnectTimeout

REQUIREMENTS = ['raincloudy==0.0.1']

_LOGGER = logging.getLogger(__name__)

ALLOWED_WATERING_TIME = [5, 10, 15, 30, 45, 60]

CONF_ATTRIBUTION = "Data provided by Melnor Aquatimer.com"
CONF_WATERING_TIME = 'watering_minutes'

NOTIFICATION_ID = 'raincloud_notification'
NOTIFICATION_TITLE = 'Rain Cloud Setup'

DATA_RAINCLOUD = 'raincloud'
DOMAIN = 'raincloud'
DEFAULT_WATERING_TIME = 15

SCAN_INTERVAL = timedelta(seconds=20)

SIGNAL_UPDATE_RAINCLOUD = "raincloud_update"

CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
cv.time_period,
vol.Optional(CONF_WATERING_TIME, default=DEFAULT_WATERING_TIME):
vol.All(vol.In(ALLOWED_WATERING_TIME)),
}),
}, extra=vol.ALLOW_EXTRA)


def setup(hass, config):
"""Set up the Melnor RainCloud component."""
conf = config[DOMAIN]
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
default_watering_timer = conf.get(CONF_WATERING_TIME)

try:
from raincloudy.core import RainCloudy

raincloud = RainCloudy(username=username, password=password)
if not raincloud.is_connected:
raise HTTPError
hass.data[DATA_RAINCLOUD] = RainCloudHub(raincloud,
default_watering_timer)
except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Rain Cloud service: %s", str(ex))
hass.components.persistent_notification.create(
'Error: {}<br />'
'You will need to restart hass after fixing.'
''.format(ex),
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
return False

def hub_refresh(event_time):
"""Call Raincloud hub to refresh information."""
_LOGGER.debug("Updating RainCloud Hub component.")
raincloud = hass.data[DATA_RAINCLOUD]
raincloud.data.update()

dispatcher_send(hass, SIGNAL_UPDATE_RAINCLOUD, raincloud)
Copy link
Member

Choose a reason for hiding this comment

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

You need not send the hub object, there is allready availaible.


# Call the Raincloud API to refresh updates
track_time_interval(hass, hub_refresh, SCAN_INTERVAL)
Copy link
Member

Choose a reason for hiding this comment

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

Use the config value for the interval instead the statit SCAN_INTERVAL


return True


class RainCloudHub(object):
"""Base class for all Raincloud entities."""
Copy link
Member

Choose a reason for hiding this comment

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

Stale docstring.


def __init__(self, data, default_watering_timer):
"""Initialize the entity."""
self.data = data
self.default_watering_timer = default_watering_timer
103 changes: 103 additions & 0 deletions homeassistant/components/sensor/raincloud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""
Support for Melnor RainCloud sprinkler water timer.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.raincloud/
"""
import logging

import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant.components.raincloud import CONF_ATTRIBUTION, DATA_RAINCLOUD
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_MONITORED_CONDITIONS, STATE_UNKNOWN, ATTR_ATTRIBUTION)
from homeassistant.helpers.entity import Entity
from homeassistant.util.icon import icon_for_battery_level

DEPENDENCIES = ['raincloud']

_LOGGER = logging.getLogger(__name__)

# Sensor types: label, desc, unit, icon
SENSOR_TYPES = {
'battery': ['Battery', '%', ''],
'next_cycle': ['Next Cycle', '', 'calendar-clock'],
'rain_delay': ['Rain Delay', 'days', 'weather-rainy'],
'watering_time': ['Remaining Watering Time', 'min', 'water-pump'],
}

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a sensor for a raincloud device."""
raincloud = hass.data[DATA_RAINCLOUD].data

sensors = []
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
if sensor_type == 'battery':
sensors.append(
RainCloudSensor(raincloud.controller.faucet,
sensor_type))
else:
# create an sensor for each zone managed by a faucet
for zone in raincloud.controller.faucet.zones:
sensors.append(RainCloudSensor(zone, sensor_type))

add_devices(sensors, True)
return True


class RainCloudSensor(Entity):
"""A sensor implementation for raincloud device."""

def __init__(self, data, sensor_type):
"""Initialize a sensor for raincloud device."""
self._data = data
self._sensor_type = sensor_type
self._icon = 'mdi:{}'.format(SENSOR_TYPES.get(self._sensor_type)[2])
self._name = "{0} {1}".format(
self._data.name, SENSOR_TYPES.get(self._sensor_type)[0])
self._state = STATE_UNKNOWN

@property
def name(self):
"""Return the name of the sensor."""
return self._name

@property
def state(self):
"""Return the state of the sensor."""
_LOGGER.debug("Updating RainCloud sensor: %s", self._name)
if self._sensor_type == 'battery':
self._state = self._data.battery.strip('%')
else:
self._state = getattr(self._data, self._sensor_type)
return self._state

@property
def icon(self):
"""Icon to use in the frontend, if any."""
if self._sensor_type == 'battery' and self._state is not STATE_UNKNOWN:
return icon_for_battery_level(battery_level=int(self._state),
charging=False)
return self._icon
Copy link
Member

Choose a reason for hiding this comment

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

Use the battery helper from utils/icon.py


@property
def unit_of_measurement(self):
"""Return the units of measurement."""
return SENSOR_TYPES.get(self._sensor_type)[1]

@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
'identifier': self._data.serial,
'current_time': self._data.current_time
}
Loading