diff --git a/.coveragerc b/.coveragerc
index 3d1bbab8456..8d884dc53e6 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -219,7 +219,7 @@ omit =
homeassistant/components/raincloud.py
homeassistant/components/*/raincloud.py
- homeassistant/components/rainmachine.py
+ homeassistant/components/rainmachine/*
homeassistant/components/*/rainmachine.py
homeassistant/components/raspihats.py
diff --git a/CODEOWNERS b/CODEOWNERS
index 32639fed43c..0da8353e5aa 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -78,7 +78,6 @@ homeassistant/components/sensor/sytadin.py @gautric
homeassistant/components/sensor/tibber.py @danielhiversen
homeassistant/components/sensor/upnp.py @dgomes
homeassistant/components/sensor/waqi.py @andrey-git
-homeassistant/components/switch/rainmachine.py @bachya
homeassistant/components/switch/tplink.py @rytilahti
homeassistant/components/vacuum/roomba.py @pschmitt
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
@@ -100,6 +99,8 @@ homeassistant/components/matrix.py @tinloaf
homeassistant/components/*/matrix.py @tinloaf
homeassistant/components/qwikswitch.py @kellerza
homeassistant/components/*/qwikswitch.py @kellerza
+homeassistant/components/rainmachine/* @bachya
+homeassistant/components/*/rainmachine.py @bachya
homeassistant/components/*/rfxtrx.py @danielhiversen
homeassistant/components/tahoma.py @philklei
homeassistant/components/*/tahoma.py @philklei
diff --git a/homeassistant/components/binary_sensor/rainmachine.py b/homeassistant/components/binary_sensor/rainmachine.py
new file mode 100644
index 00000000000..601a73298af
--- /dev/null
+++ b/homeassistant/components/binary_sensor/rainmachine.py
@@ -0,0 +1,102 @@
+"""
+This platform provides binary sensors for key RainMachine data.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/binary_sensor.rainmachine/
+"""
+import logging
+
+from homeassistant.components.binary_sensor import BinarySensorDevice
+from homeassistant.components.rainmachine import (
+ BINARY_SENSORS, DATA_RAINMACHINE, DATA_UPDATE_TOPIC, TYPE_FREEZE,
+ TYPE_FREEZE_PROTECTION, TYPE_HOT_DAYS, TYPE_HOURLY, TYPE_MONTH,
+ TYPE_RAINDELAY, TYPE_RAINSENSOR, TYPE_WEEKDAY, RainMachineEntity)
+from homeassistant.const import CONF_MONITORED_CONDITIONS
+from homeassistant.core import callback
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
+
+DEPENDENCIES = ['rainmachine']
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up the RainMachine Switch platform."""
+ if discovery_info is None:
+ return
+
+ rainmachine = hass.data[DATA_RAINMACHINE]
+
+ binary_sensors = []
+ for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
+ name, icon = BINARY_SENSORS[sensor_type]
+ binary_sensors.append(
+ RainMachineBinarySensor(rainmachine, sensor_type, name, icon))
+
+ add_devices(binary_sensors, True)
+
+
+class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice):
+ """A sensor implementation for raincloud device."""
+
+ def __init__(self, rainmachine, sensor_type, name, icon):
+ """Initialize the sensor."""
+ super().__init__(rainmachine)
+
+ self._icon = icon
+ self._name = name
+ self._sensor_type = sensor_type
+ self._state = None
+
+ @property
+ def icon(self) -> str:
+ """Return the icon."""
+ return self._icon
+
+ @property
+ def is_on(self):
+ """Return the status of the sensor."""
+ return self._state
+
+ @property
+ def should_poll(self):
+ """Disable polling."""
+ return False
+
+ @property
+ def unique_id(self) -> str:
+ """Return a unique, HASS-friendly identifier for this entity."""
+ return '{0}_{1}'.format(
+ self.rainmachine.device_mac.replace(':', ''), self._sensor_type)
+
+ @callback
+ def update_data(self):
+ """Update the state."""
+ self.async_schedule_update_ha_state(True)
+
+ async def async_added_to_hass(self):
+ """Register callbacks."""
+ async_dispatcher_connect(self.hass, DATA_UPDATE_TOPIC,
+ self.update_data)
+
+ def update(self):
+ """Update the state."""
+ if self._sensor_type == TYPE_FREEZE:
+ self._state = self.rainmachine.restrictions['current']['freeze']
+ elif self._sensor_type == TYPE_FREEZE_PROTECTION:
+ self._state = self.rainmachine.restrictions['global'][
+ 'freezeProtectEnabled']
+ elif self._sensor_type == TYPE_HOT_DAYS:
+ self._state = self.rainmachine.restrictions['global'][
+ 'hotDaysExtraWatering']
+ elif self._sensor_type == TYPE_HOURLY:
+ self._state = self.rainmachine.restrictions['current']['hourly']
+ elif self._sensor_type == TYPE_MONTH:
+ self._state = self.rainmachine.restrictions['current']['month']
+ elif self._sensor_type == TYPE_RAINDELAY:
+ self._state = self.rainmachine.restrictions['current']['rainDelay']
+ elif self._sensor_type == TYPE_RAINSENSOR:
+ self._state = self.rainmachine.restrictions['current'][
+ 'rainSensor']
+ elif self._sensor_type == TYPE_WEEKDAY:
+ self._state = self.rainmachine.restrictions['current']['weekDay']
diff --git a/homeassistant/components/rainmachine.py b/homeassistant/components/rainmachine.py
deleted file mode 100644
index f2d5893d60b..00000000000
--- a/homeassistant/components/rainmachine.py
+++ /dev/null
@@ -1,132 +0,0 @@
-"""
-This component provides support for RainMachine sprinkler controllers.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/rainmachine/
-"""
-import logging
-
-import voluptuous as vol
-
-from homeassistant.const import (
- ATTR_ATTRIBUTION, CONF_IP_ADDRESS, CONF_PASSWORD, CONF_PORT, CONF_SSL,
- CONF_SWITCHES)
-from homeassistant.helpers import config_validation as cv, discovery
-from homeassistant.helpers.entity import Entity
-
-REQUIREMENTS = ['regenmaschine==0.4.1']
-
-_LOGGER = logging.getLogger(__name__)
-
-DATA_RAINMACHINE = 'data_rainmachine'
-DOMAIN = 'rainmachine'
-
-NOTIFICATION_ID = 'rainmachine_notification'
-NOTIFICATION_TITLE = 'RainMachine Component Setup'
-
-CONF_ZONE_RUN_TIME = 'zone_run_time'
-
-DEFAULT_ATTRIBUTION = 'Data provided by Green Electronics LLC'
-DEFAULT_ICON = 'mdi:water'
-DEFAULT_PORT = 8080
-DEFAULT_SSL = True
-
-PROGRAM_UPDATE_TOPIC = '{0}_program_update'.format(DOMAIN)
-
-SWITCH_SCHEMA = vol.Schema({
- vol.Optional(CONF_ZONE_RUN_TIME):
- cv.positive_int
-})
-
-CONFIG_SCHEMA = vol.Schema(
- {
- DOMAIN: vol.Schema({
- vol.Required(CONF_IP_ADDRESS): cv.string,
- vol.Required(CONF_PASSWORD): cv.string,
- vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
- vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
- vol.Optional(CONF_SWITCHES): SWITCH_SCHEMA,
- })
- },
- extra=vol.ALLOW_EXTRA)
-
-
-def setup(hass, config):
- """Set up the RainMachine component."""
- from regenmaschine import Authenticator, Client
- from regenmaschine.exceptions import HTTPError
- from requests.exceptions import ConnectTimeout
-
- conf = config[DOMAIN]
- ip_address = conf[CONF_IP_ADDRESS]
- password = conf[CONF_PASSWORD]
- port = conf[CONF_PORT]
- ssl = conf[CONF_SSL]
-
- _LOGGER.debug('Setting up RainMachine client')
-
- try:
- auth = Authenticator.create_local(
- ip_address, password, port=port, https=ssl)
- client = Client(auth)
- hass.data[DATA_RAINMACHINE] = RainMachine(client)
- except (HTTPError, ConnectTimeout, UnboundLocalError) as exc_info:
- _LOGGER.error('An error occurred: %s', str(exc_info))
- hass.components.persistent_notification.create(
- 'Error: {0}
'
- 'You will need to restart hass after fixing.'
- ''.format(exc_info),
- title=NOTIFICATION_TITLE,
- notification_id=NOTIFICATION_ID)
- return False
-
- _LOGGER.debug('Setting up switch platform')
- switch_config = conf.get(CONF_SWITCHES, {})
- discovery.load_platform(hass, 'switch', DOMAIN, switch_config, config)
-
- _LOGGER.debug('Setup complete')
-
- return True
-
-
-class RainMachine(object):
- """Define a generic RainMachine object."""
-
- def __init__(self, client):
- """Initialize."""
- self.client = client
- self.device_mac = self.client.provision.wifi()['macAddress']
-
-
-class RainMachineEntity(Entity):
- """Define a generic RainMachine entity."""
-
- def __init__(self,
- rainmachine,
- rainmachine_type,
- rainmachine_entity_id,
- icon=DEFAULT_ICON):
- """Initialize."""
- self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
- self._icon = icon
- self._rainmachine_type = rainmachine_type
- self._rainmachine_entity_id = rainmachine_entity_id
- self.rainmachine = rainmachine
-
- @property
- def device_state_attributes(self) -> dict:
- """Return the state attributes."""
- return self._attrs
-
- @property
- def icon(self) -> str:
- """Return the icon."""
- return self._icon
-
- @property
- def unique_id(self) -> str:
- """Return a unique, HASS-friendly identifier for this entity."""
- return '{0}_{1}_{2}'.format(
- self.rainmachine.device_mac.replace(
- ':', ''), self._rainmachine_type,
- self._rainmachine_entity_id)
diff --git a/homeassistant/components/rainmachine/__init__.py b/homeassistant/components/rainmachine/__init__.py
new file mode 100644
index 00000000000..7ee6b063720
--- /dev/null
+++ b/homeassistant/components/rainmachine/__init__.py
@@ -0,0 +1,226 @@
+"""
+Support for RainMachine devices.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/rainmachine/
+"""
+import logging
+from datetime import timedelta
+
+import voluptuous as vol
+
+from homeassistant.const import (
+ ATTR_ATTRIBUTION, CONF_BINARY_SENSORS, CONF_IP_ADDRESS, CONF_PASSWORD,
+ CONF_PORT, CONF_SENSORS, CONF_SSL, CONF_MONITORED_CONDITIONS,
+ CONF_SWITCHES)
+from homeassistant.helpers import config_validation as cv, discovery
+from homeassistant.helpers.dispatcher import dispatcher_send
+from homeassistant.helpers.entity import Entity
+from homeassistant.helpers.event import track_time_interval
+
+REQUIREMENTS = ['regenmaschine==0.4.2']
+
+_LOGGER = logging.getLogger(__name__)
+
+DATA_RAINMACHINE = 'data_rainmachine'
+DOMAIN = 'rainmachine'
+
+NOTIFICATION_ID = 'rainmachine_notification'
+NOTIFICATION_TITLE = 'RainMachine Component Setup'
+
+DATA_UPDATE_TOPIC = '{0}_data_update'.format(DOMAIN)
+PROGRAM_UPDATE_TOPIC = '{0}_program_update'.format(DOMAIN)
+
+CONF_PROGRAM_ID = 'program_id'
+CONF_ZONE_ID = 'zone_id'
+CONF_ZONE_RUN_TIME = 'zone_run_time'
+
+DEFAULT_ATTRIBUTION = 'Data provided by Green Electronics LLC'
+DEFAULT_ICON = 'mdi:water'
+DEFAULT_PORT = 8080
+DEFAULT_SCAN_INTERVAL = timedelta(seconds=60)
+DEFAULT_SSL = True
+DEFAULT_ZONE_RUN = 60 * 10
+
+TYPE_FREEZE = 'freeze'
+TYPE_FREEZE_PROTECTION = 'freeze_protection'
+TYPE_FREEZE_TEMP = 'freeze_protect_temp'
+TYPE_HOT_DAYS = 'extra_water_on_hot_days'
+TYPE_HOURLY = 'hourly'
+TYPE_MONTH = 'month'
+TYPE_RAINDELAY = 'raindelay'
+TYPE_RAINSENSOR = 'rainsensor'
+TYPE_WEEKDAY = 'weekday'
+
+BINARY_SENSORS = {
+ TYPE_FREEZE: ('Freeze Restrictions', 'mdi:cancel'),
+ TYPE_FREEZE_PROTECTION: ('Freeze Protection', 'mdi:weather-snowy'),
+ TYPE_HOT_DAYS: ('Extra Water on Hot Days', 'mdi:thermometer-lines'),
+ TYPE_HOURLY: ('Hourly Restrictions', 'mdi:cancel'),
+ TYPE_MONTH: ('Month Restrictions', 'mdi:cancel'),
+ TYPE_RAINDELAY: ('Rain Delay Restrictions', 'mdi:cancel'),
+ TYPE_RAINSENSOR: ('Rain Sensor Restrictions', 'mdi:cancel'),
+ TYPE_WEEKDAY: ('Weekday Restrictions', 'mdi:cancel'),
+}
+
+SENSORS = {
+ TYPE_FREEZE_TEMP: ('Freeze Protect Temperature', 'mdi:thermometer', '°C'),
+}
+
+BINARY_SENSOR_SCHEMA = vol.Schema({
+ vol.Optional(CONF_MONITORED_CONDITIONS, default=list(BINARY_SENSORS)):
+ vol.All(cv.ensure_list, [vol.In(BINARY_SENSORS)])
+})
+
+SENSOR_SCHEMA = vol.Schema({
+ vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSORS)):
+ vol.All(cv.ensure_list, [vol.In(SENSORS)])
+})
+
+SERVICE_START_PROGRAM_SCHEMA = vol.Schema({
+ vol.Required(CONF_PROGRAM_ID): cv.positive_int,
+})
+
+SERVICE_START_ZONE_SCHEMA = vol.Schema({
+ vol.Required(CONF_ZONE_ID): cv.positive_int,
+ vol.Optional(CONF_ZONE_RUN_TIME, default=DEFAULT_ZONE_RUN):
+ cv.positive_int,
+})
+
+SERVICE_STOP_PROGRAM_SCHEMA = vol.Schema({
+ vol.Required(CONF_PROGRAM_ID): cv.positive_int,
+})
+
+SERVICE_STOP_ZONE_SCHEMA = vol.Schema({
+ vol.Required(CONF_ZONE_ID): cv.positive_int,
+})
+
+SWITCH_SCHEMA = vol.Schema({vol.Optional(CONF_ZONE_RUN_TIME): cv.positive_int})
+
+CONFIG_SCHEMA = vol.Schema(
+ {
+ DOMAIN:
+ vol.Schema({
+ vol.Required(CONF_IP_ADDRESS): cv.string,
+ vol.Required(CONF_PASSWORD): cv.string,
+ vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
+ vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
+ vol.Optional(CONF_BINARY_SENSORS, default={}):
+ BINARY_SENSOR_SCHEMA,
+ vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA,
+ vol.Optional(CONF_SWITCHES, default={}): SWITCH_SCHEMA,
+ })
+ },
+ extra=vol.ALLOW_EXTRA)
+
+
+def setup(hass, config):
+ """Set up the RainMachine component."""
+ from regenmaschine import Authenticator, Client
+ from regenmaschine.exceptions import RainMachineError
+
+ conf = config[DOMAIN]
+ ip_address = conf[CONF_IP_ADDRESS]
+ password = conf[CONF_PASSWORD]
+ port = conf[CONF_PORT]
+ ssl = conf[CONF_SSL]
+
+ try:
+ auth = Authenticator.create_local(
+ ip_address, password, port=port, https=ssl)
+ rainmachine = RainMachine(hass, Client(auth))
+ rainmachine.update()
+ hass.data[DATA_RAINMACHINE] = rainmachine
+ except RainMachineError as exc:
+ _LOGGER.error('An error occurred: %s', str(exc))
+ hass.components.persistent_notification.create(
+ 'Error: {0}
'
+ 'You will need to restart hass after fixing.'
+ ''.format(exc),
+ title=NOTIFICATION_TITLE,
+ notification_id=NOTIFICATION_ID)
+ return False
+
+ for component, schema in [
+ ('binary_sensor', conf[CONF_BINARY_SENSORS]),
+ ('sensor', conf[CONF_SENSORS]),
+ ('switch', conf[CONF_SWITCHES]),
+ ]:
+ discovery.load_platform(hass, component, DOMAIN, schema, config)
+
+ def refresh(event_time):
+ """Refresh RainMachine data."""
+ _LOGGER.debug('Updating RainMachine data')
+ hass.data[DATA_RAINMACHINE].update()
+ dispatcher_send(hass, DATA_UPDATE_TOPIC)
+
+ track_time_interval(hass, refresh, DEFAULT_SCAN_INTERVAL)
+
+ def start_program(service):
+ """Start a particular program."""
+ rainmachine.client.programs.start(service.data[CONF_PROGRAM_ID])
+
+ def start_zone(service):
+ """Start a particular zone for a certain amount of time."""
+ rainmachine.client.zones.start(service.data[CONF_ZONE_ID],
+ service.data[CONF_ZONE_RUN_TIME])
+
+ def stop_all(service):
+ """Stop all watering."""
+ rainmachine.client.watering.stop_all()
+
+ def stop_program(service):
+ """Stop a program."""
+ rainmachine.client.programs.stop(service.data[CONF_PROGRAM_ID])
+
+ def stop_zone(service):
+ """Stop a zone."""
+ rainmachine.client.zones.stop(service.data[CONF_ZONE_ID])
+
+ for service, method, schema in [
+ ('start_program', start_program, SERVICE_START_PROGRAM_SCHEMA),
+ ('start_zone', start_zone, SERVICE_START_ZONE_SCHEMA),
+ ('stop_all', stop_all, {}),
+ ('stop_program', stop_program, SERVICE_STOP_PROGRAM_SCHEMA),
+ ('stop_zone', stop_zone, SERVICE_STOP_ZONE_SCHEMA)
+ ]:
+ hass.services.register(DOMAIN, service, method, schema=schema)
+
+ return True
+
+
+class RainMachine(object):
+ """Define a generic RainMachine object."""
+
+ def __init__(self, hass, client):
+ """Initialize."""
+ self.client = client
+ self.device_mac = self.client.provision.wifi()['macAddress']
+ self.restrictions = {}
+
+ def update(self):
+ """Update sensor/binary sensor data."""
+ self.restrictions.update({
+ 'current': self.client.restrictions.current(),
+ 'global': self.client.restrictions.universal()
+ })
+
+
+class RainMachineEntity(Entity):
+ """Define a generic RainMachine entity."""
+
+ def __init__(self, rainmachine):
+ """Initialize."""
+ self._attrs = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
+ self._name = None
+ self.rainmachine = rainmachine
+
+ @property
+ def device_state_attributes(self) -> dict:
+ """Return the state attributes."""
+ return self._attrs
+
+ @property
+ def name(self) -> str:
+ """Return the name of the entity."""
+ return self._name
diff --git a/homeassistant/components/rainmachine/services.yaml b/homeassistant/components/rainmachine/services.yaml
new file mode 100644
index 00000000000..a8c77628c8f
--- /dev/null
+++ b/homeassistant/components/rainmachine/services.yaml
@@ -0,0 +1,32 @@
+# Describes the format for available RainMachine services
+
+---
+start_program:
+ description: Start a program.
+ fields:
+ program_id:
+ description: The program to start.
+ example: 3
+start_zone:
+ description: Start a zone for a set number of seconds.
+ fields:
+ zone_id:
+ description: The zone to start.
+ example: 3
+ zone_run_time:
+ description: The number of seconds to run the zone.
+ example: 120
+stop_all:
+ description: Stop all watering activities.
+stop_program:
+ description: Stop a program.
+ fields:
+ program_id:
+ description: The program to stop.
+ example: 3
+stop_zone:
+ description: Stop a zone.
+ fields:
+ zone_id:
+ description: The zone to stop.
+ example: 3
diff --git a/homeassistant/components/sensor/rainmachine.py b/homeassistant/components/sensor/rainmachine.py
new file mode 100644
index 00000000000..8faf30acc38
--- /dev/null
+++ b/homeassistant/components/sensor/rainmachine.py
@@ -0,0 +1,88 @@
+"""
+This platform provides support for sensor data from RainMachine.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/sensor.rainmachine/
+"""
+import logging
+
+from homeassistant.components.rainmachine import (
+ DATA_RAINMACHINE, DATA_UPDATE_TOPIC, SENSORS, RainMachineEntity)
+from homeassistant.const import CONF_MONITORED_CONDITIONS
+from homeassistant.core import callback
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
+
+DEPENDENCIES = ['rainmachine']
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up the RainMachine Switch platform."""
+ if discovery_info is None:
+ return
+
+ rainmachine = hass.data[DATA_RAINMACHINE]
+
+ sensors = []
+ for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
+ name, icon, unit = SENSORS[sensor_type]
+ sensors.append(
+ RainMachineSensor(rainmachine, sensor_type, name, icon, unit))
+
+ add_devices(sensors, True)
+
+
+class RainMachineSensor(RainMachineEntity):
+ """A sensor implementation for raincloud device."""
+
+ def __init__(self, rainmachine, sensor_type, name, icon, unit):
+ """Initialize."""
+ super().__init__(rainmachine)
+
+ self._icon = icon
+ self._name = name
+ self._sensor_type = sensor_type
+ self._state = None
+ self._unit = unit
+
+ @property
+ def icon(self) -> str:
+ """Return the icon."""
+ return self._icon
+
+ @property
+ def should_poll(self):
+ """Disable polling."""
+ return False
+
+ @property
+ def state(self) -> str:
+ """Return the name of the entity."""
+ return self._state
+
+ @property
+ def unique_id(self) -> str:
+ """Return a unique, HASS-friendly identifier for this entity."""
+ return '{0}_{1}'.format(
+ self.rainmachine.device_mac.replace(':', ''), self._sensor_type)
+
+ @property
+ def unit_of_measurement(self):
+ """Return the unit the value is expressed in."""
+ return self._unit
+
+ @callback
+ def update_data(self):
+ """Update the state."""
+ self.async_schedule_update_ha_state(True)
+
+ async def async_added_to_hass(self):
+ """Register callbacks."""
+ async_dispatcher_connect(self.hass, DATA_UPDATE_TOPIC,
+ self.update_data)
+
+ def update(self):
+ """Update the sensor's state."""
+ self._state = self.rainmachine.restrictions['global'][
+ 'freezeProtectTemp']
diff --git a/homeassistant/components/switch/rainmachine.py b/homeassistant/components/switch/rainmachine.py
index beb00eeca44..f4b2d780a9a 100644
--- a/homeassistant/components/switch/rainmachine.py
+++ b/homeassistant/components/switch/rainmachine.py
@@ -4,12 +4,11 @@ This component provides support for RainMachine programs and zones.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/switch.rainmachine/
"""
-
-from logging import getLogger
+import logging
from homeassistant.components.rainmachine import (
- CONF_ZONE_RUN_TIME, DATA_RAINMACHINE, PROGRAM_UPDATE_TOPIC,
- RainMachineEntity)
+ CONF_ZONE_RUN_TIME, DATA_RAINMACHINE, DEFAULT_ZONE_RUN,
+ PROGRAM_UPDATE_TOPIC, RainMachineEntity)
from homeassistant.const import ATTR_ID
from homeassistant.components.switch import SwitchDevice
from homeassistant.core import callback
@@ -18,7 +17,7 @@ from homeassistant.helpers.dispatcher import (
DEPENDENCIES = ['rainmachine']
-_LOGGER = getLogger(__name__)
+_LOGGER = logging.getLogger(__name__)
ATTR_AREA = 'area'
ATTR_CS_ON = 'cs_on'
@@ -39,8 +38,6 @@ ATTR_SUN_EXPOSURE = 'sun_exposure'
ATTR_VEGETATION_TYPE = 'vegetation_type'
ATTR_ZONES = 'zones'
-DEFAULT_ZONE_RUN = 60 * 10
-
DAYS = [
'Monday',
'Tuesday',
@@ -141,26 +138,41 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class RainMachineSwitch(RainMachineEntity, SwitchDevice):
- """A class to represent a generic RainMachine entity."""
+ """A class to represent a generic RainMachine switch."""
- def __init__(self, rainmachine, rainmachine_type, obj):
- """Initialize a generic RainMachine entity."""
+ def __init__(self, rainmachine, switch_type, obj):
+ """Initialize a generic RainMachine switch."""
+ super().__init__(rainmachine)
+
+ self._name = obj['name']
self._obj = obj
- self._type = rainmachine_type
+ self._rainmachine_entity_id = obj['uid']
+ self._switch_type = switch_type
- super().__init__(rainmachine, rainmachine_type, obj.get('uid'))
+ @property
+ def icon(self) -> str:
+ """Return the icon."""
+ return 'mdi:water'
@property
def is_enabled(self) -> bool:
"""Return whether the entity is enabled."""
return self._obj.get('active')
+ @property
+ def unique_id(self) -> str:
+ """Return a unique, HASS-friendly identifier for this entity."""
+ return '{0}_{1}_{2}'.format(
+ self.rainmachine.device_mac.replace(':', ''),
+ self._switch_type,
+ self._rainmachine_entity_id)
+
class RainMachineProgram(RainMachineSwitch):
"""A RainMachine program."""
def __init__(self, rainmachine, obj):
- """Initialize."""
+ """Initialize a generic RainMachine switch."""
super().__init__(rainmachine, 'program', obj)
@property
@@ -168,11 +180,6 @@ class RainMachineProgram(RainMachineSwitch):
"""Return whether the program is running."""
return bool(self._obj.get('status'))
- @property
- def name(self) -> str:
- """Return the name of the program."""
- return 'Program: {0}'.format(self._obj.get('name'))
-
@property
def zones(self) -> list:
"""Return a list of active zones associated with this program."""
@@ -180,29 +187,29 @@ class RainMachineProgram(RainMachineSwitch):
def turn_off(self, **kwargs) -> None:
"""Turn the program off."""
- from regenmaschine.exceptions import HTTPError
+ from regenmaschine.exceptions import RainMachineError
try:
self.rainmachine.client.programs.stop(self._rainmachine_entity_id)
dispatcher_send(self.hass, PROGRAM_UPDATE_TOPIC)
- except HTTPError as exc_info:
+ except RainMachineError as exc_info:
_LOGGER.error('Unable to turn off program "%s"', self.unique_id)
_LOGGER.debug(exc_info)
def turn_on(self, **kwargs) -> None:
"""Turn the program on."""
- from regenmaschine.exceptions import HTTPError
+ from regenmaschine.exceptions import RainMachineError
try:
self.rainmachine.client.programs.start(self._rainmachine_entity_id)
dispatcher_send(self.hass, PROGRAM_UPDATE_TOPIC)
- except HTTPError as exc_info:
+ except RainMachineError as exc_info:
_LOGGER.error('Unable to turn on program "%s"', self.unique_id)
_LOGGER.debug(exc_info)
def update(self) -> None:
"""Update info for the program."""
- from regenmaschine.exceptions import HTTPError
+ from regenmaschine.exceptions import RainMachineError
try:
self._obj = self.rainmachine.client.programs.get(
@@ -210,16 +217,11 @@ class RainMachineProgram(RainMachineSwitch):
self._attrs.update({
ATTR_ID: self._obj['uid'],
- ATTR_CS_ON: self._obj.get('cs_on'),
- ATTR_CYCLES: self._obj.get('cycles'),
- ATTR_DELAY: self._obj.get('delay'),
- ATTR_DELAY_ON: self._obj.get('delay_on'),
ATTR_SOAK: self._obj.get('soak'),
- ATTR_STATUS:
- PROGRAM_STATUS_MAP[self._obj.get('status')],
+ ATTR_STATUS: PROGRAM_STATUS_MAP[self._obj.get('status')],
ATTR_ZONES: ', '.join(z['name'] for z in self.zones)
})
- except HTTPError as exc_info:
+ except RainMachineError as exc_info:
_LOGGER.error('Unable to update info for program "%s"',
self.unique_id)
_LOGGER.debug(exc_info)
@@ -240,11 +242,6 @@ class RainMachineZone(RainMachineSwitch):
"""Return whether the zone is running."""
return bool(self._obj.get('state'))
- @property
- def name(self) -> str:
- """Return the name of the zone."""
- return 'Zone: {0}'.format(self._obj.get('name'))
-
@callback
def _program_updated(self):
"""Update state, trigger updates."""
@@ -257,28 +254,28 @@ class RainMachineZone(RainMachineSwitch):
def turn_off(self, **kwargs) -> None:
"""Turn the zone off."""
- from regenmaschine.exceptions import HTTPError
+ from regenmaschine.exceptions import RainMachineError
try:
self.rainmachine.client.zones.stop(self._rainmachine_entity_id)
- except HTTPError as exc_info:
+ except RainMachineError as exc_info:
_LOGGER.error('Unable to turn off zone "%s"', self.unique_id)
_LOGGER.debug(exc_info)
def turn_on(self, **kwargs) -> None:
"""Turn the zone on."""
- from regenmaschine.exceptions import HTTPError
+ from regenmaschine.exceptions import RainMachineError
try:
self.rainmachine.client.zones.start(self._rainmachine_entity_id,
self._run_time)
- except HTTPError as exc_info:
+ except RainMachineError as exc_info:
_LOGGER.error('Unable to turn on zone "%s"', self.unique_id)
_LOGGER.debug(exc_info)
def update(self) -> None:
"""Update info for the zone."""
- from regenmaschine.exceptions import HTTPError
+ from regenmaschine.exceptions import RainMachineError
try:
self._obj = self.rainmachine.client.zones.get(
@@ -309,7 +306,7 @@ class RainMachineZone(RainMachineSwitch):
ATTR_VEGETATION_TYPE:
VEGETATION_MAP[self._obj.get('type')],
})
- except HTTPError as exc_info:
+ except RainMachineError as exc_info:
_LOGGER.error('Unable to update info for zone "%s"',
self.unique_id)
_LOGGER.debug(exc_info)
diff --git a/requirements_all.txt b/requirements_all.txt
index 958b0f1027e..1b1db52daef 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -1147,7 +1147,7 @@ raincloudy==0.0.4
# raspihats==2.2.3
# homeassistant.components.rainmachine
-regenmaschine==0.4.1
+regenmaschine==0.4.2
# homeassistant.components.python_script
restrictedpython==4.0b4