From 62c1383720bc28989b953a4bd978eaaa770ca315 Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Mon, 4 Sep 2017 01:48:00 -0400 Subject: [PATCH 01/12] Introducing support to Melnor RainCloud sprinkler systems --- .coveragerc | 3 + .../components/binary_sensor/raincloud.py | 102 ++++++++++++++++ homeassistant/components/raincloud.py | 95 +++++++++++++++ homeassistant/components/sensor/raincloud.py | 108 +++++++++++++++++ homeassistant/components/switch/raincloud.py | 111 ++++++++++++++++++ requirements_all.txt | 3 + 6 files changed, 422 insertions(+) create mode 100644 homeassistant/components/binary_sensor/raincloud.py create mode 100644 homeassistant/components/raincloud.py create mode 100644 homeassistant/components/sensor/raincloud.py create mode 100644 homeassistant/components/switch/raincloud.py diff --git a/.coveragerc b/.coveragerc index 5e27aed0182534..da3968870772f7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -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 diff --git a/homeassistant/components/binary_sensor/raincloud.py b/homeassistant/components/binary_sensor/raincloud.py new file mode 100644 index 00000000000000..65e5b6fe1c905d --- /dev/null +++ b/homeassistant/components/binary_sensor/raincloud.py @@ -0,0 +1,102 @@ +""" +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 +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.Required(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.get('raincloud').data + + sensors = [] + for sensor_type in config.get(CONF_MONITORED_CONDITIONS): + if sensor_type == 'status': + sensors.append( + RainCloudBinarySensor(hass, + raincloud.controller, + sensor_type)) + sensors.append( + RainCloudBinarySensor(hass, + 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(hass, zone, sensor_type)) + + add_devices(sensors, True) + return True + + +class RainCloudBinarySensor(BinarySensorDevice): + """A sensor implementation for raincloud device.""" + + def __init__(self, hass, 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.""" + attrs = {} + + attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION + attrs['current_time'] = self._data.current_time + attrs['identifier'] = self._data.serial + return attrs diff --git a/homeassistant/components/raincloud.py b/homeassistant/components/raincloud.py new file mode 100644 index 00000000000000..b6121d9336edc4 --- /dev/null +++ b/homeassistant/components/raincloud.py @@ -0,0 +1,95 @@ +""" +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 +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.event import track_time_interval + +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' + +DOMAIN = 'raincloud' +DEFAULT_ENTITY_NAMESPACE = 'raincloud' +DEFAULT_WATERING_TIME = 15 + +SCAN_INTERVAL_HUB = timedelta(seconds=20) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + 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: + return False + hass.data['raincloud'] = RainCloudHub(hass, + 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: {}
' + 'You will need to restart hass after fixing.' + ''.format(ex), + title=NOTIFICATION_TITLE, + notification_id=NOTIFICATION_ID) + return False + return True + + +class RainCloudHub(Entity): + """Base class for all Raincloud entities.""" + + def __init__(self, hass, data, default_watering_timer): + """Initialize the entity.""" + self.data = data + self.default_watering_timer = default_watering_timer + + # Load data + track_time_interval(hass, self._update_hub, SCAN_INTERVAL_HUB) + self._update_hub(SCAN_INTERVAL_HUB) + + @property + def should_poll(self): + """Return false. RainCloud Hub object updates variables.""" + return False + + def _update_hub(self, now): + """Refresh data from for all child objects.""" + _LOGGER.debug("Updating RainCloud Hub component.") + self.data.update() diff --git a/homeassistant/components/sensor/raincloud.py b/homeassistant/components/sensor/raincloud.py new file mode 100644 index 00000000000000..5bb2b2f7bac73a --- /dev/null +++ b/homeassistant/components/sensor/raincloud.py @@ -0,0 +1,108 @@ +""" +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 +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_MONITORED_CONDITIONS, STATE_UNKNOWN, ATTR_ATTRIBUTION) +from homeassistant.helpers.entity import Entity + +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.Required(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.get('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: + rounded_level = round(int(self._state), -1) + if rounded_level < 10: + self._icon = 'mdi:battery-outline' + elif self._state == 100: + self._icon = 'mdi:battery' + else: + self._icon = 'mdi:battery-{}'.format(str(rounded_level)) + return self._icon + + @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.""" + attrs = {} + + attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION + attrs['identifier'] = self._data.serial + attrs['current_time'] = self._data.current_time + return attrs diff --git a/homeassistant/components/switch/raincloud.py b/homeassistant/components/switch/raincloud.py new file mode 100644 index 00000000000000..ae75ec828348d9 --- /dev/null +++ b/homeassistant/components/switch/raincloud.py @@ -0,0 +1,111 @@ +""" +Support for Melnor RainCloud sprinkler water timer. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.raincloud/ +""" +import logging + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.raincloud import CONF_ATTRIBUTION +from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION) + +DEPENDENCIES = ['raincloud'] + +_LOGGER = logging.getLogger(__name__) + +# Sensor types: label, desc, unit, icon +SENSOR_TYPES = { + 'auto_watering': ['Automatic Watering', 'mdi:autorenew'], + 'manual_watering': ['Manual Watering', 'water-pump'], +} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(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_hub = hass.data.get('raincloud') + raincloud = raincloud_hub.data + default_watering_timer = raincloud_hub.default_watering_timer + + sensors = [] + for sensor_type in config.get(CONF_MONITORED_CONDITIONS): + # create an sensor for each zone managed by faucet + for zone in raincloud.controller.faucet.zones: + sensors.append( + RainCloudSwitch(zone, sensor_type, default_watering_timer)) + + add_devices(sensors, True) + return True + + +class RainCloudSwitch(SwitchDevice): + """A switch implementation for raincloud device.""" + + def __init__(self, data, sensor_type, default_watering_timer): + """Initialize a switch for raincloud device.""" + self._sensor_type = sensor_type + self._data = data + self._default_watering_timer = default_watering_timer + self._icon = 'mdi:{}'.format(SENSOR_TYPES.get(self._sensor_type)[1]) + 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 device is on.""" + return self._state + + def turn_on(self): + """Turn the device on.""" + if self._sensor_type == 'manual_watering': + self._data.watering_time = self._default_watering_timer + elif self._sensor_type == 'auto_watering': + self._data.auto_watering = True + self._state = True + + def turn_off(self): + """Turn the device off.""" + if self._sensor_type == 'manual_watering': + self._data.watering_time = 'off' + elif self._sensor_type == 'auto_watering': + self._data.auto_watering = False + self._state = False + + def update(self): + """Update device state.""" + _LOGGER.debug("Updating RainCloud switch: %s", self._name) + if self._sensor_type == 'manual_watering': + self._state = bool(self._data.watering_time) + elif self._sensor_type == 'auto_watering': + self._state = self._data.auto_watering + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return self._icon + + @property + def device_state_attributes(self): + """Return the state attributes.""" + attrs = {} + + attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION + attrs['current_time'] = self._data.current_time + attrs['default_manual_timer'] = self._default_watering_timer + attrs['identifier'] = self._data.serial + return attrs diff --git a/requirements_all.txt b/requirements_all.txt index ae91099165b09d..2289f8fcb1f012 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -841,6 +841,9 @@ rachiopy==0.1.2 # homeassistant.components.climate.radiotherm radiotherm==1.3 +# homeassistant.components.raincloud +raincloudy==0.0.1 + # homeassistant.components.raspihats # raspihats==2.2.1 From 07bb50b12a6db197b662fcfd23e44d84beb86d70 Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Mon, 4 Sep 2017 03:27:18 -0400 Subject: [PATCH 02/12] Make monitored_conditions optional for sub-components --- homeassistant/components/binary_sensor/raincloud.py | 2 +- homeassistant/components/sensor/raincloud.py | 2 +- homeassistant/components/switch/raincloud.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/binary_sensor/raincloud.py b/homeassistant/components/binary_sensor/raincloud.py index 65e5b6fe1c905d..3faeb554184f06 100644 --- a/homeassistant/components/binary_sensor/raincloud.py +++ b/homeassistant/components/binary_sensor/raincloud.py @@ -25,7 +25,7 @@ } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), }) diff --git a/homeassistant/components/sensor/raincloud.py b/homeassistant/components/sensor/raincloud.py index 5bb2b2f7bac73a..94c2490a9e3175 100644 --- a/homeassistant/components/sensor/raincloud.py +++ b/homeassistant/components/sensor/raincloud.py @@ -28,7 +28,7 @@ } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), }) diff --git a/homeassistant/components/switch/raincloud.py b/homeassistant/components/switch/raincloud.py index ae75ec828348d9..a9153d260cd7d9 100644 --- a/homeassistant/components/switch/raincloud.py +++ b/homeassistant/components/switch/raincloud.py @@ -25,7 +25,7 @@ } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), }) From c7400816cc24e72828c96d4e5b0e9e1bd6a0f5af Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Fri, 8 Sep 2017 05:12:19 -0400 Subject: [PATCH 03/12] Part 1/2 - Modified attributes, added DATA_ constant and using battery helper --- .../components/binary_sensor/raincloud.py | 25 +++++++--------- homeassistant/components/raincloud.py | 30 ++++++++++--------- homeassistant/components/sensor/raincloud.py | 25 +++++++--------- homeassistant/components/switch/raincloud.py | 17 +++++------ 4 files changed, 45 insertions(+), 52 deletions(-) diff --git a/homeassistant/components/binary_sensor/raincloud.py b/homeassistant/components/binary_sensor/raincloud.py index 3faeb554184f06..2d20f1f9ba527d 100644 --- a/homeassistant/components/binary_sensor/raincloud.py +++ b/homeassistant/components/binary_sensor/raincloud.py @@ -9,7 +9,7 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.raincloud import CONF_ATTRIBUTION +from homeassistant.components.raincloud import CONF_ATTRIBUTION, DATA_RAINCLOUD from homeassistant.components.binary_sensor import ( BinarySensorDevice, PLATFORM_SCHEMA) from homeassistant.const import ( @@ -32,24 +32,22 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up a sensor for a raincloud device.""" - raincloud = hass.data.get('raincloud').data + raincloud = hass.data[DATA_RAINCLOUD].data sensors = [] for sensor_type in config.get(CONF_MONITORED_CONDITIONS): if sensor_type == 'status': sensors.append( - RainCloudBinarySensor(hass, - raincloud.controller, + RainCloudBinarySensor(raincloud.controller, sensor_type)) sensors.append( - RainCloudBinarySensor(hass, - raincloud.controller.faucet, + 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(hass, zone, sensor_type)) + sensors.append(RainCloudBinarySensor(zone, sensor_type)) add_devices(sensors, True) return True @@ -58,7 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class RainCloudBinarySensor(BinarySensorDevice): """A sensor implementation for raincloud device.""" - def __init__(self, hass, data, sensor_type): + def __init__(self, data, sensor_type): """Initialize a sensor for raincloud device.""" super().__init__() self._sensor_type = sensor_type @@ -94,9 +92,8 @@ def icon(self): @property def device_state_attributes(self): """Return the state attributes.""" - attrs = {} - - attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION - attrs['current_time'] = self._data.current_time - attrs['identifier'] = self._data.serial - return attrs + return { + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, + 'current_time': self._data.current_time, + 'identifier': self._data.serial + } diff --git a/homeassistant/components/raincloud.py b/homeassistant/components/raincloud.py index b6121d9336edc4..87e7a92c38cb6b 100644 --- a/homeassistant/components/raincloud.py +++ b/homeassistant/components/raincloud.py @@ -4,15 +4,18 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/raincloud/ """ +import asyncio 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 +from homeassistant.const import ( + CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL) from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_time_interval +from homeassistant.helpers.dispatcher import async_dispatcher_send from requests.exceptions import HTTPError, ConnectTimeout @@ -28,16 +31,19 @@ NOTIFICATION_ID = 'raincloud_notification' NOTIFICATION_TITLE = 'Rain Cloud Setup' +DATA_RAINCLOUD = 'raincloud' DOMAIN = 'raincloud' DEFAULT_ENTITY_NAMESPACE = 'raincloud' DEFAULT_WATERING_TIME = 15 -SCAN_INTERVAL_HUB = timedelta(seconds=20) +SCAN_INTERVAL = timedelta(seconds=20) 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)), }), @@ -57,9 +63,9 @@ def setup(hass, config): raincloud = RainCloudy(username=username, password=password) if not raincloud.is_connected: return False - hass.data['raincloud'] = RainCloudHub(hass, - raincloud, - default_watering_timer) + hass.data[DATA_RAINCLOUD] = RainCloudHub(hass, + 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( @@ -69,10 +75,11 @@ def setup(hass, config): title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID) return False + return True -class RainCloudHub(Entity): +class RainCloudHub: """Base class for all Raincloud entities.""" def __init__(self, hass, data, default_watering_timer): @@ -80,14 +87,9 @@ def __init__(self, hass, data, default_watering_timer): self.data = data self.default_watering_timer = default_watering_timer - # Load data - track_time_interval(hass, self._update_hub, SCAN_INTERVAL_HUB) - self._update_hub(SCAN_INTERVAL_HUB) - - @property - def should_poll(self): - """Return false. RainCloud Hub object updates variables.""" - return False + # needs change + track_time_interval(hass, self._update_hub, SCAN_INTERVAL) + self._update_hub(SCAN_INTERVAL) def _update_hub(self, now): """Refresh data from for all child objects.""" diff --git a/homeassistant/components/sensor/raincloud.py b/homeassistant/components/sensor/raincloud.py index 94c2490a9e3175..240bd00eb86639 100644 --- a/homeassistant/components/sensor/raincloud.py +++ b/homeassistant/components/sensor/raincloud.py @@ -9,11 +9,12 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.raincloud import CONF_ATTRIBUTION +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'] @@ -35,7 +36,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up a sensor for a raincloud device.""" - raincloud = hass.data.get('raincloud').data + raincloud = hass.data[DATA_RAINCLOUD].data sensors = [] for sensor_type in config.get(CONF_MONITORED_CONDITIONS): @@ -83,13 +84,8 @@ def state(self): def icon(self): """Icon to use in the frontend, if any.""" if self._sensor_type == 'battery' and self._state is not STATE_UNKNOWN: - rounded_level = round(int(self._state), -1) - if rounded_level < 10: - self._icon = 'mdi:battery-outline' - elif self._state == 100: - self._icon = 'mdi:battery' - else: - self._icon = 'mdi:battery-{}'.format(str(rounded_level)) + return icon_for_battery_level(battery_level=int(self._state), + charging=False) return self._icon @property @@ -100,9 +96,8 @@ def unit_of_measurement(self): @property def device_state_attributes(self): """Return the state attributes.""" - attrs = {} - - attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION - attrs['identifier'] = self._data.serial - attrs['current_time'] = self._data.current_time - return attrs + return { + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, + 'identifier': self._data.serial, + 'current_time': self._data.current_time + } diff --git a/homeassistant/components/switch/raincloud.py b/homeassistant/components/switch/raincloud.py index a9153d260cd7d9..d6a1708c0adfe5 100644 --- a/homeassistant/components/switch/raincloud.py +++ b/homeassistant/components/switch/raincloud.py @@ -9,7 +9,7 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.raincloud import CONF_ATTRIBUTION +from homeassistant.components.raincloud import CONF_ATTRIBUTION, DATA_RAINCLOUD from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA from homeassistant.const import ( CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION) @@ -32,7 +32,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up a sensor for a raincloud device.""" - raincloud_hub = hass.data.get('raincloud') + raincloud_hub = hass.data[DATA_RAINCLOUD] raincloud = raincloud_hub.data default_watering_timer = raincloud_hub.default_watering_timer @@ -102,10 +102,9 @@ def icon(self): @property def device_state_attributes(self): """Return the state attributes.""" - attrs = {} - - attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION - attrs['current_time'] = self._data.current_time - attrs['default_manual_timer'] = self._default_watering_timer - attrs['identifier'] = self._data.serial - return attrs + return { + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, + 'current_time': self._data.current_time, + 'default_manual_timer': self._default_watering_timer, + 'identifier': self._data.serial + } From faa77c96d3c3137f072edb4935d6ce2f05ae1b59 Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Sun, 17 Sep 2017 17:49:34 -0400 Subject: [PATCH 04/12] Part 2/2 - Refactored self-update hub --- homeassistant/components/raincloud.py | 36 +++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/raincloud.py b/homeassistant/components/raincloud.py index 87e7a92c38cb6b..1b038ca42e7974 100644 --- a/homeassistant/components/raincloud.py +++ b/homeassistant/components/raincloud.py @@ -4,7 +4,6 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/raincloud/ """ -import asyncio import logging from datetime import timedelta @@ -13,9 +12,8 @@ from homeassistant.const import ( CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL) -from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_time_interval -from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.dispatcher import dispatcher_send from requests.exceptions import HTTPError, ConnectTimeout @@ -33,11 +31,12 @@ DATA_RAINCLOUD = 'raincloud' DOMAIN = 'raincloud' -DEFAULT_ENTITY_NAMESPACE = '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, @@ -62,9 +61,8 @@ def setup(hass, config): raincloud = RainCloudy(username=username, password=password) if not raincloud.is_connected: - return False - hass.data[DATA_RAINCLOUD] = RainCloudHub(hass, - raincloud, + 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)) @@ -76,22 +74,24 @@ def setup(hass, config): 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) + + # Call the Raincloud API to refresh updates + track_time_interval(hass, hub_refresh, SCAN_INTERVAL) + return True -class RainCloudHub: +class RainCloudHub(object): """Base class for all Raincloud entities.""" - def __init__(self, hass, data, default_watering_timer): + def __init__(self, data, default_watering_timer): """Initialize the entity.""" self.data = data self.default_watering_timer = default_watering_timer - - # needs change - track_time_interval(hass, self._update_hub, SCAN_INTERVAL) - self._update_hub(SCAN_INTERVAL) - - def _update_hub(self, now): - """Refresh data from for all child objects.""" - _LOGGER.debug("Updating RainCloud Hub component.") - self.data.update() From 95b58b1ac620bebb7f8e8a3ced39e8d67966a0bc Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Mon, 18 Sep 2017 23:04:59 -0400 Subject: [PATCH 05/12] Fixed change requested: - Dispatcher signal connection - Don't send raincloud object via dispatcher_send() - Honoring the dynamic scan_interval value on track_time_interval() --- homeassistant/components/raincloud.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/raincloud.py b/homeassistant/components/raincloud.py index 1b038ca42e7974..00d66f42a47e42 100644 --- a/homeassistant/components/raincloud.py +++ b/homeassistant/components/raincloud.py @@ -4,6 +4,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/raincloud/ """ +import asyncio import logging from datetime import timedelta @@ -13,7 +14,8 @@ 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 homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, dispatcher_send) from requests.exceptions import HTTPError, ConnectTimeout @@ -55,6 +57,7 @@ def setup(hass, config): username = conf.get(CONF_USERNAME) password = conf.get(CONF_PASSWORD) default_watering_timer = conf.get(CONF_WATERING_TIME) + scan_interval = conf.get(CONF_SCAN_INTERVAL) try: from raincloudy.core import RainCloudy @@ -62,7 +65,8 @@ def setup(hass, config): raincloud = RainCloudy(username=username, password=password) if not raincloud.is_connected: raise HTTPError - hass.data[DATA_RAINCLOUD] = RainCloudHub(raincloud, + hass.data[DATA_RAINCLOUD] = RainCloudHub(hass, + raincloud, default_watering_timer) except (ConnectTimeout, HTTPError) as ex: _LOGGER.error("Unable to connect to Rain Cloud service: %s", str(ex)) @@ -80,10 +84,10 @@ def hub_refresh(event_time): raincloud = hass.data[DATA_RAINCLOUD] raincloud.data.update() - dispatcher_send(hass, SIGNAL_UPDATE_RAINCLOUD, raincloud) + dispatcher_send(hass, SIGNAL_UPDATE_RAINCLOUD) # Call the Raincloud API to refresh updates - track_time_interval(hass, hub_refresh, SCAN_INTERVAL) + track_time_interval(hass, hub_refresh, scan_interval) return True @@ -91,7 +95,18 @@ def hub_refresh(event_time): class RainCloudHub(object): """Base class for all Raincloud entities.""" - def __init__(self, data, default_watering_timer): + def __init__(self, hass, data, default_watering_timer): """Initialize the entity.""" + self.hass = hass self.data = data self.default_watering_timer = default_watering_timer + + @asyncio.coroutine + def async_added_to_hass(self): + """Register callbacks.""" + async_dispatcher_connect( + self.hass, SIGNAL_UPDATE_RAINCLOUD, self._update_callback) + + def _update_callback(self): + """Callback update method.""" + self.data.update() From 87b49d7edb0857b935a2dec9d453bca4f8d5e2ff Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Tue, 26 Sep 2017 22:28:37 -0400 Subject: [PATCH 06/12] Inherents async_added_to_hass() on all device classes --- .../components/binary_sensor/raincloud.py | 27 ++++++++------- homeassistant/components/raincloud.py | 7 ++-- homeassistant/components/sensor/raincloud.py | 25 +++++++------- homeassistant/components/switch/raincloud.py | 33 +++++++++++-------- 4 files changed, 52 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/binary_sensor/raincloud.py b/homeassistant/components/binary_sensor/raincloud.py index 2d20f1f9ba527d..1fd35a7b38cd6d 100644 --- a/homeassistant/components/binary_sensor/raincloud.py +++ b/homeassistant/components/binary_sensor/raincloud.py @@ -9,7 +9,8 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.raincloud import CONF_ATTRIBUTION, DATA_RAINCLOUD +from homeassistant.components.raincloud import ( + CONF_ATTRIBUTION, DATA_RAINCLOUD, RainCloudHub) from homeassistant.components.binary_sensor import ( BinarySensorDevice, PLATFORM_SCHEMA) from homeassistant.const import ( @@ -38,31 +39,33 @@ def setup_platform(hass, config, add_devices, discovery_info=None): for sensor_type in config.get(CONF_MONITORED_CONDITIONS): if sensor_type == 'status': sensors.append( - RainCloudBinarySensor(raincloud.controller, + RainCloudBinarySensor(hass, + raincloud.controller, sensor_type)) sensors.append( - RainCloudBinarySensor(raincloud.controller.faucet, + RainCloudBinarySensor(hass, + 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)) + sensors.append(RainCloudBinarySensor(hass, zone, sensor_type)) add_devices(sensors, True) return True -class RainCloudBinarySensor(BinarySensorDevice): +class RainCloudBinarySensor(RainCloudHub, BinarySensorDevice): """A sensor implementation for raincloud device.""" - def __init__(self, data, sensor_type): + def __init__(self, hass, data, sensor_type): """Initialize a sensor for raincloud device.""" - super().__init__() + self._hass = hass self._sensor_type = sensor_type - self._data = data + self.data = data self._name = "{0} {1}".format( - self._data.name, SENSOR_TYPES.get(self._sensor_type)[0]) + self.data.name, SENSOR_TYPES.get(self._sensor_type)[0]) self._state = None @property @@ -78,7 +81,7 @@ def is_on(self): 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) + self._state = getattr(self.data, self._sensor_type) @property def icon(self): @@ -94,6 +97,6 @@ def device_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: CONF_ATTRIBUTION, - 'current_time': self._data.current_time, - 'identifier': self._data.serial + 'current_time': self.data.current_time, + 'identifier': self.data.serial } diff --git a/homeassistant/components/raincloud.py b/homeassistant/components/raincloud.py index 00d66f42a47e42..c2fbf58e0c0273 100644 --- a/homeassistant/components/raincloud.py +++ b/homeassistant/components/raincloud.py @@ -14,6 +14,7 @@ from homeassistant.const import ( CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL) from homeassistant.helpers.event import track_time_interval +from homeassistant.helpers.entity import Entity from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, dispatcher_send) @@ -92,12 +93,12 @@ def hub_refresh(event_time): return True -class RainCloudHub(object): +class RainCloudHub(Entity): """Base class for all Raincloud entities.""" def __init__(self, hass, data, default_watering_timer): """Initialize the entity.""" - self.hass = hass + self._hass = hass self.data = data self.default_watering_timer = default_watering_timer @@ -105,7 +106,7 @@ def __init__(self, hass, data, default_watering_timer): def async_added_to_hass(self): """Register callbacks.""" async_dispatcher_connect( - self.hass, SIGNAL_UPDATE_RAINCLOUD, self._update_callback) + self._hass, SIGNAL_UPDATE_RAINCLOUD, self._update_callback) def _update_callback(self): """Callback update method.""" diff --git a/homeassistant/components/sensor/raincloud.py b/homeassistant/components/sensor/raincloud.py index 240bd00eb86639..f12211282245fb 100644 --- a/homeassistant/components/sensor/raincloud.py +++ b/homeassistant/components/sensor/raincloud.py @@ -9,7 +9,8 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.raincloud import CONF_ATTRIBUTION, DATA_RAINCLOUD +from homeassistant.components.raincloud import ( + CONF_ATTRIBUTION, DATA_RAINCLOUD, RainCloudHub) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( CONF_MONITORED_CONDITIONS, STATE_UNKNOWN, ATTR_ATTRIBUTION) @@ -42,27 +43,29 @@ def setup_platform(hass, config, add_devices, discovery_info=None): for sensor_type in config.get(CONF_MONITORED_CONDITIONS): if sensor_type == 'battery': sensors.append( - RainCloudSensor(raincloud.controller.faucet, + RainCloudSensor(hass, + 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)) + sensors.append(RainCloudSensor(hass, zone, sensor_type)) add_devices(sensors, True) return True -class RainCloudSensor(Entity): +class RainCloudSensor(RainCloudHub, Entity): """A sensor implementation for raincloud device.""" - def __init__(self, data, sensor_type): + def __init__(self, hass, data, sensor_type): """Initialize a sensor for raincloud device.""" - self._data = data + self._hass = hass + 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.data.name, SENSOR_TYPES.get(self._sensor_type)[0]) self._state = STATE_UNKNOWN @property @@ -75,9 +78,9 @@ 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('%') + self._state = self.data.battery.strip('%') else: - self._state = getattr(self._data, self._sensor_type) + self._state = getattr(self.data, self._sensor_type) return self._state @property @@ -98,6 +101,6 @@ def device_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: CONF_ATTRIBUTION, - 'identifier': self._data.serial, - 'current_time': self._data.current_time + 'identifier': self.data.serial, + 'current_time': self.data.current_time } diff --git a/homeassistant/components/switch/raincloud.py b/homeassistant/components/switch/raincloud.py index d6a1708c0adfe5..2ad6a146fd427b 100644 --- a/homeassistant/components/switch/raincloud.py +++ b/homeassistant/components/switch/raincloud.py @@ -9,7 +9,8 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.raincloud import CONF_ATTRIBUTION, DATA_RAINCLOUD +from homeassistant.components.raincloud import ( + CONF_ATTRIBUTION, DATA_RAINCLOUD, RainCloudHub) from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA from homeassistant.const import ( CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION) @@ -41,23 +42,27 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # create an sensor for each zone managed by faucet for zone in raincloud.controller.faucet.zones: sensors.append( - RainCloudSwitch(zone, sensor_type, default_watering_timer)) + RainCloudSwitch(hass, + zone, + sensor_type, + default_watering_timer)) add_devices(sensors, True) return True -class RainCloudSwitch(SwitchDevice): +class RainCloudSwitch(RainCloudHub, SwitchDevice): """A switch implementation for raincloud device.""" - def __init__(self, data, sensor_type, default_watering_timer): + def __init__(self, hass, data, sensor_type, default_watering_timer): """Initialize a switch for raincloud device.""" + self._hass = hass self._sensor_type = sensor_type - self._data = data + self.data = data self._default_watering_timer = default_watering_timer self._icon = 'mdi:{}'.format(SENSOR_TYPES.get(self._sensor_type)[1]) self._name = "{0} {1}".format( - self._data.name, SENSOR_TYPES.get(self._sensor_type)[0]) + self.data.name, SENSOR_TYPES.get(self._sensor_type)[0]) self._state = None @property @@ -73,26 +78,26 @@ def is_on(self): def turn_on(self): """Turn the device on.""" if self._sensor_type == 'manual_watering': - self._data.watering_time = self._default_watering_timer + self.data.watering_time = self._default_watering_timer elif self._sensor_type == 'auto_watering': - self._data.auto_watering = True + self.data.auto_watering = True self._state = True def turn_off(self): """Turn the device off.""" if self._sensor_type == 'manual_watering': - self._data.watering_time = 'off' + self.data.watering_time = 'off' elif self._sensor_type == 'auto_watering': - self._data.auto_watering = False + self.data.auto_watering = False self._state = False def update(self): """Update device state.""" _LOGGER.debug("Updating RainCloud switch: %s", self._name) if self._sensor_type == 'manual_watering': - self._state = bool(self._data.watering_time) + self._state = bool(self.data.watering_time) elif self._sensor_type == 'auto_watering': - self._state = self._data.auto_watering + self._state = self.data.auto_watering @property def icon(self): @@ -104,7 +109,7 @@ def device_state_attributes(self): """Return the state attributes.""" return { ATTR_ATTRIBUTION: CONF_ATTRIBUTION, - 'current_time': self._data.current_time, + 'current_time': self.data.current_time, 'default_manual_timer': self._default_watering_timer, - 'identifier': self._data.serial + 'identifier': self.data.serial } From 827e2fd3ae20e0b64eb8c75aea6a728fc5ebef0e Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Tue, 26 Sep 2017 23:07:20 -0400 Subject: [PATCH 07/12] Makes lint happy --- homeassistant/components/binary_sensor/raincloud.py | 1 + homeassistant/components/raincloud.py | 10 ++-------- homeassistant/components/sensor/raincloud.py | 1 + homeassistant/components/switch/raincloud.py | 8 ++++++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/binary_sensor/raincloud.py b/homeassistant/components/binary_sensor/raincloud.py index 1fd35a7b38cd6d..4093580c2ca0e1 100644 --- a/homeassistant/components/binary_sensor/raincloud.py +++ b/homeassistant/components/binary_sensor/raincloud.py @@ -61,6 +61,7 @@ class RainCloudBinarySensor(RainCloudHub, BinarySensorDevice): def __init__(self, hass, data, sensor_type): """Initialize a sensor for raincloud device.""" + super().__init__(hass, data) self._hass = hass self._sensor_type = sensor_type self.data = data diff --git a/homeassistant/components/raincloud.py b/homeassistant/components/raincloud.py index c2fbf58e0c0273..88ef61fa6ca0c5 100644 --- a/homeassistant/components/raincloud.py +++ b/homeassistant/components/raincloud.py @@ -46,8 +46,6 @@ 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) @@ -57,7 +55,6 @@ def setup(hass, config): conf = config[DOMAIN] username = conf.get(CONF_USERNAME) password = conf.get(CONF_PASSWORD) - default_watering_timer = conf.get(CONF_WATERING_TIME) scan_interval = conf.get(CONF_SCAN_INTERVAL) try: @@ -66,9 +63,7 @@ def setup(hass, config): raincloud = RainCloudy(username=username, password=password) if not raincloud.is_connected: raise HTTPError - hass.data[DATA_RAINCLOUD] = RainCloudHub(hass, - raincloud, - default_watering_timer) + hass.data[DATA_RAINCLOUD] = RainCloudHub(hass, raincloud) except (ConnectTimeout, HTTPError) as ex: _LOGGER.error("Unable to connect to Rain Cloud service: %s", str(ex)) hass.components.persistent_notification.create( @@ -96,11 +91,10 @@ def hub_refresh(event_time): class RainCloudHub(Entity): """Base class for all Raincloud entities.""" - def __init__(self, hass, data, default_watering_timer): + def __init__(self, hass, data): """Initialize the entity.""" self._hass = hass self.data = data - self.default_watering_timer = default_watering_timer @asyncio.coroutine def async_added_to_hass(self): diff --git a/homeassistant/components/sensor/raincloud.py b/homeassistant/components/sensor/raincloud.py index f12211282245fb..16748d5ebd20ac 100644 --- a/homeassistant/components/sensor/raincloud.py +++ b/homeassistant/components/sensor/raincloud.py @@ -60,6 +60,7 @@ class RainCloudSensor(RainCloudHub, Entity): def __init__(self, hass, data, sensor_type): """Initialize a sensor for raincloud device.""" + super().__init__(hass, data) self._hass = hass self.data = data self._sensor_type = sensor_type diff --git a/homeassistant/components/switch/raincloud.py b/homeassistant/components/switch/raincloud.py index 2ad6a146fd427b..83827544d4e850 100644 --- a/homeassistant/components/switch/raincloud.py +++ b/homeassistant/components/switch/raincloud.py @@ -10,7 +10,8 @@ import homeassistant.helpers.config_validation as cv from homeassistant.components.raincloud import ( - CONF_ATTRIBUTION, DATA_RAINCLOUD, RainCloudHub) + ALLOWED_WATERING_TIME, CONF_ATTRIBUTION, CONF_WATERING_TIME, + DATA_RAINCLOUD, DEFAULT_WATERING_TIME, RainCloudHub) from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA from homeassistant.const import ( CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION) @@ -28,6 +29,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), + vol.Optional(CONF_WATERING_TIME, default=DEFAULT_WATERING_TIME): + vol.All(vol.In(ALLOWED_WATERING_TIME)), }) @@ -35,7 +38,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up a sensor for a raincloud device.""" raincloud_hub = hass.data[DATA_RAINCLOUD] raincloud = raincloud_hub.data - default_watering_timer = raincloud_hub.default_watering_timer + default_watering_timer = config.get(CONF_WATERING_TIME) sensors = [] for sensor_type in config.get(CONF_MONITORED_CONDITIONS): @@ -56,6 +59,7 @@ class RainCloudSwitch(RainCloudHub, SwitchDevice): def __init__(self, hass, data, sensor_type, default_watering_timer): """Initialize a switch for raincloud device.""" + super().__init__(hass, data) self._hass = hass self._sensor_type = sensor_type self.data = data From b89aef16e69ac072bd54d44b88a6726005e12ea1 Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Thu, 28 Sep 2017 03:19:10 -0400 Subject: [PATCH 08/12] * Refactored RainCloud code to incorporate suggestions. Many thanks to @pvizelli and @martinhjelmare!! --- .../components/binary_sensor/raincloud.py | 51 ++--------- homeassistant/components/raincloud.py | 88 +++++++++++++++++-- homeassistant/components/sensor/raincloud.py | 63 +++---------- homeassistant/components/switch/raincloud.py | 40 ++------- 4 files changed, 111 insertions(+), 131 deletions(-) diff --git a/homeassistant/components/binary_sensor/raincloud.py b/homeassistant/components/binary_sensor/raincloud.py index 4093580c2ca0e1..874f7a81a1780a 100644 --- a/homeassistant/components/binary_sensor/raincloud.py +++ b/homeassistant/components/binary_sensor/raincloud.py @@ -10,24 +10,18 @@ import homeassistant.helpers.config_validation as cv from homeassistant.components.raincloud import ( - CONF_ATTRIBUTION, DATA_RAINCLOUD, RainCloudHub) + BINARY_SENSORS, DATA_RAINCLOUD, ICON_MAP, RainCloudEntity) from homeassistant.components.binary_sensor import ( BinarySensorDevice, PLATFORM_SCHEMA) -from homeassistant.const import ( - CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION) +from homeassistant.const import CONF_MONITORED_CONDITIONS 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)]), + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(BINARY_SENSORS)): + vol.All(cv.ensure_list, [vol.In(BINARY_SENSORS)]), }) @@ -39,41 +33,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None): for sensor_type in config.get(CONF_MONITORED_CONDITIONS): if sensor_type == 'status': sensors.append( - RainCloudBinarySensor(hass, - raincloud.controller, - sensor_type)) + RainCloudBinarySensor(raincloud.controller, sensor_type)) sensors.append( - RainCloudBinarySensor(hass, - raincloud.controller.faucet, + 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(hass, zone, sensor_type)) + sensors.append(RainCloudBinarySensor(zone, sensor_type)) add_devices(sensors, True) return True -class RainCloudBinarySensor(RainCloudHub, BinarySensorDevice): +class RainCloudBinarySensor(RainCloudEntity, BinarySensorDevice): """A sensor implementation for raincloud device.""" - def __init__(self, hass, data, sensor_type): - """Initialize a sensor for raincloud device.""" - super().__init__(hass, data) - self._hass = hass - 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.""" @@ -91,13 +67,4 @@ def icon(self): 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 - } + return ICON_MAP.get(self._sensor_type) diff --git a/homeassistant/components/raincloud.py b/homeassistant/components/raincloud.py index 88ef61fa6ca0c5..9d9f0e46504302 100644 --- a/homeassistant/components/raincloud.py +++ b/homeassistant/components/raincloud.py @@ -12,7 +12,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL) + ATTR_ATTRIBUTION, CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL) from homeassistant.helpers.event import track_time_interval from homeassistant.helpers.entity import Entity from homeassistant.helpers.dispatcher import ( @@ -36,6 +36,45 @@ DOMAIN = 'raincloud' DEFAULT_WATERING_TIME = 15 +KEY_MAP = { + 'auto_watering': 'Automatic Watering', + 'battery': 'Battery', + 'is_watering': 'Watering', + 'manual_watering': 'Manual Watering', + 'next_cycle': 'Next Cycle', + 'rain_delay': 'Rain Delay', + 'status': 'Status', + 'watering_time': 'Remaining Watering Time', +} + +ICON_MAP = { + 'auto_watering': 'mdi:autorenew', + 'battery': '', + 'is_watering': '', + 'manual_watering': 'mdi:water-pump', + 'next_cycle': 'mdi:calendar-clock', + 'rain_delay': 'mdi:weather-rainy', + 'status': '', + 'watering_time': 'mdi:water-pump', +} + +UNIT_OF_MEASUREMENT_MAP = { + 'auto_watering': '', + 'battery': '%', + 'is_watering': '', + 'manual_watering': '', + 'next_cycle': '', + 'rain_delay': 'days', + 'status': '', + 'watering_time': 'min', +} + +BINARY_SENSORS = ['is_watering', 'status'] + +SENSORS = ['battery', 'next_cycle', 'rain_delay', 'watering_time'] + +SWITCHES = ['auto_watering', 'manual_watering'] + SCAN_INTERVAL = timedelta(seconds=20) SIGNAL_UPDATE_RAINCLOUD = "raincloud_update" @@ -63,7 +102,7 @@ def setup(hass, config): raincloud = RainCloudy(username=username, password=password) if not raincloud.is_connected: raise HTTPError - hass.data[DATA_RAINCLOUD] = RainCloudHub(hass, raincloud) + hass.data[DATA_RAINCLOUD] = RainCloudHub(raincloud) except (ConnectTimeout, HTTPError) as ex: _LOGGER.error("Unable to connect to Rain Cloud service: %s", str(ex)) hass.components.persistent_notification.create( @@ -88,20 +127,55 @@ def hub_refresh(event_time): return True -class RainCloudHub(Entity): +class RainCloudHub(object): """Base class for all Raincloud entities.""" - def __init__(self, hass, data): + def __init__(self, data): """Initialize the entity.""" - self._hass = hass self.data = data + +class RainCloudEntity(Entity): + """Entity class for RainCloud devices.""" + + def __init__(self, data, sensor_type): + """Initialize the entity.""" + self.data = data + self._sensor_type = sensor_type + self._name = "{0} {1}".format( + self.data.name, KEY_MAP.get(self._sensor_type)) + self._state = None + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + @asyncio.coroutine def async_added_to_hass(self): """Register callbacks.""" async_dispatcher_connect( - self._hass, SIGNAL_UPDATE_RAINCLOUD, self._update_callback) + self.hass, SIGNAL_UPDATE_RAINCLOUD, self._update_callback) def _update_callback(self): """Callback update method.""" - self.data.update() + self.update() + + @property + def unit_of_measurement(self): + """Return the units of measurement.""" + return UNIT_OF_MEASUREMENT_MAP.get(self._sensor_type) + + @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, + } + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return ICON_MAP.get(self._sensor_type) diff --git a/homeassistant/components/sensor/raincloud.py b/homeassistant/components/sensor/raincloud.py index 16748d5ebd20ac..dfece7458a1830 100644 --- a/homeassistant/components/sensor/raincloud.py +++ b/homeassistant/components/sensor/raincloud.py @@ -10,10 +10,9 @@ import homeassistant.helpers.config_validation as cv from homeassistant.components.raincloud import ( - CONF_ATTRIBUTION, DATA_RAINCLOUD, RainCloudHub) + DATA_RAINCLOUD, ICON_MAP, RainCloudEntity, SENSORS) from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - CONF_MONITORED_CONDITIONS, STATE_UNKNOWN, ATTR_ATTRIBUTION) +from homeassistant.const import CONF_MONITORED_CONDITIONS from homeassistant.helpers.entity import Entity from homeassistant.util.icon import icon_for_battery_level @@ -21,17 +20,9 @@ _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)]), + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSORS)): + vol.All(cv.ensure_list, [vol.In(SENSORS)]), }) @@ -43,65 +34,37 @@ def setup_platform(hass, config, add_devices, discovery_info=None): for sensor_type in config.get(CONF_MONITORED_CONDITIONS): if sensor_type == 'battery': sensors.append( - RainCloudSensor(hass, - raincloud.controller.faucet, + 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(hass, zone, sensor_type)) + sensors.append(RainCloudSensor(zone, sensor_type)) add_devices(sensors, True) return True -class RainCloudSensor(RainCloudHub, Entity): +class RainCloudSensor(RainCloudEntity, Entity): """A sensor implementation for raincloud device.""" - def __init__(self, hass, data, sensor_type): - """Initialize a sensor for raincloud device.""" - super().__init__(hass, data) - self._hass = hass - 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.""" + return self._state + + def update(self): + """Get the latest data and updates the states.""" _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: + if self._sensor_type == 'battery' and self._state is not None: return icon_for_battery_level(battery_level=int(self._state), charging=False) - return self._icon - - @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 - } + return ICON_MAP.get(self._sensor_type) diff --git a/homeassistant/components/switch/raincloud.py b/homeassistant/components/switch/raincloud.py index 83827544d4e850..274071d2b22cad 100644 --- a/homeassistant/components/switch/raincloud.py +++ b/homeassistant/components/switch/raincloud.py @@ -11,7 +11,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.components.raincloud import ( ALLOWED_WATERING_TIME, CONF_ATTRIBUTION, CONF_WATERING_TIME, - DATA_RAINCLOUD, DEFAULT_WATERING_TIME, RainCloudHub) + DATA_RAINCLOUD, DEFAULT_WATERING_TIME, RainCloudEntity, SWITCHES) from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA from homeassistant.const import ( CONF_MONITORED_CONDITIONS, ATTR_ATTRIBUTION) @@ -20,15 +20,9 @@ _LOGGER = logging.getLogger(__name__) -# Sensor types: label, desc, unit, icon -SENSOR_TYPES = { - 'auto_watering': ['Automatic Watering', 'mdi:autorenew'], - 'manual_watering': ['Manual Watering', '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)]), + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SWITCHES)): + vol.All(cv.ensure_list, [vol.In(SWITCHES)]), vol.Optional(CONF_WATERING_TIME, default=DEFAULT_WATERING_TIME): vol.All(vol.In(ALLOWED_WATERING_TIME)), }) @@ -45,34 +39,21 @@ def setup_platform(hass, config, add_devices, discovery_info=None): # create an sensor for each zone managed by faucet for zone in raincloud.controller.faucet.zones: sensors.append( - RainCloudSwitch(hass, + RainCloudSwitch(default_watering_timer, zone, - sensor_type, - default_watering_timer)) + sensor_type)) add_devices(sensors, True) return True -class RainCloudSwitch(RainCloudHub, SwitchDevice): +class RainCloudSwitch(RainCloudEntity, SwitchDevice): """A switch implementation for raincloud device.""" - def __init__(self, hass, data, sensor_type, default_watering_timer): + def __init__(self, default_watering_timer, *args): """Initialize a switch for raincloud device.""" - super().__init__(hass, data) - self._hass = hass - self._sensor_type = sensor_type - self.data = data + super().__init__(*args) self._default_watering_timer = default_watering_timer - self._icon = 'mdi:{}'.format(SENSOR_TYPES.get(self._sensor_type)[1]) - 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): @@ -103,11 +84,6 @@ def update(self): elif self._sensor_type == 'auto_watering': self._state = self.data.auto_watering - @property - def icon(self): - """Icon to use in the frontend, if any.""" - return self._icon - @property def device_state_attributes(self): """Return the state attributes.""" From 91a3221242f19e0f3cae2644c08ac3cea0768653 Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Fri, 29 Sep 2017 00:38:24 -0400 Subject: [PATCH 09/12] Removed Entity from RainCloud sensor and fixed docstrings --- homeassistant/components/raincloud.py | 6 +++--- homeassistant/components/sensor/raincloud.py | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/raincloud.py b/homeassistant/components/raincloud.py index 9d9f0e46504302..b176da63fd834e 100644 --- a/homeassistant/components/raincloud.py +++ b/homeassistant/components/raincloud.py @@ -128,7 +128,7 @@ def hub_refresh(event_time): class RainCloudHub(object): - """Base class for all Raincloud entities.""" + """Representation of a base RainCloud device.""" def __init__(self, data): """Initialize the entity.""" @@ -139,7 +139,7 @@ class RainCloudEntity(Entity): """Entity class for RainCloud devices.""" def __init__(self, data, sensor_type): - """Initialize the entity.""" + """Initialize the RainCloud entity.""" self.data = data self._sensor_type = sensor_type self._name = "{0} {1}".format( @@ -159,7 +159,7 @@ def async_added_to_hass(self): def _update_callback(self): """Callback update method.""" - self.update() + self.schedule_update_ha_state(True) @property def unit_of_measurement(self): diff --git a/homeassistant/components/sensor/raincloud.py b/homeassistant/components/sensor/raincloud.py index dfece7458a1830..ab073917e8e43c 100644 --- a/homeassistant/components/sensor/raincloud.py +++ b/homeassistant/components/sensor/raincloud.py @@ -13,7 +13,6 @@ DATA_RAINCLOUD, ICON_MAP, RainCloudEntity, SENSORS) from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import CONF_MONITORED_CONDITIONS -from homeassistant.helpers.entity import Entity from homeassistant.util.icon import icon_for_battery_level DEPENDENCIES = ['raincloud'] @@ -45,7 +44,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return True -class RainCloudSensor(RainCloudEntity, Entity): +class RainCloudSensor(RainCloudEntity): """A sensor implementation for raincloud device.""" @property From 590906da7431d0183b7b82d8ba2a59990697c8d1 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Fri, 29 Sep 2017 08:49:20 +0200 Subject: [PATCH 10/12] Update raincloud.py --- homeassistant/components/raincloud.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/raincloud.py b/homeassistant/components/raincloud.py index b176da63fd834e..f174c0317077a4 100644 --- a/homeassistant/components/raincloud.py +++ b/homeassistant/components/raincloud.py @@ -116,7 +116,6 @@ def setup(hass, config): 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) From 354120b23085e5b8593ea1212a342dec9c9f9723 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Fri, 29 Sep 2017 08:50:47 +0200 Subject: [PATCH 11/12] Update raincloud.py --- homeassistant/components/switch/raincloud.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/switch/raincloud.py b/homeassistant/components/switch/raincloud.py index 274071d2b22cad..f373a6aad84639 100644 --- a/homeassistant/components/switch/raincloud.py +++ b/homeassistant/components/switch/raincloud.py @@ -30,8 +30,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up a sensor for a raincloud device.""" - raincloud_hub = hass.data[DATA_RAINCLOUD] - raincloud = raincloud_hub.data + raincloud = hass.data[DATA_RAINCLOUD].data default_watering_timer = config.get(CONF_WATERING_TIME) sensors = [] From d5eb1b10df21a4f8fc8c35af0b05304673ee7199 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Fri, 29 Sep 2017 09:48:44 +0200 Subject: [PATCH 12/12] fix lint --- homeassistant/components/raincloud.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/raincloud.py b/homeassistant/components/raincloud.py index f174c0317077a4..0cc91576daebd0 100644 --- a/homeassistant/components/raincloud.py +++ b/homeassistant/components/raincloud.py @@ -116,8 +116,7 @@ def setup(hass, config): def hub_refresh(event_time): """Call Raincloud hub to refresh information.""" _LOGGER.debug("Updating RainCloud Hub component.") - raincloud.data.update() - + hass.data[DATA_RAINCLOUD].data.update() dispatcher_send(hass, SIGNAL_UPDATE_RAINCLOUD) # Call the Raincloud API to refresh updates