2018-05-08 22:10:03 +00:00
|
|
|
"""
|
|
|
|
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/
|
|
|
|
"""
|
2019-03-13 14:19:26 +00:00
|
|
|
from datetime import datetime
|
2019-03-21 05:56:46 +00:00
|
|
|
import logging
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2018-05-01 19:36:43 +00:00
|
|
|
from homeassistant.components.switch import SwitchDevice
|
2019-03-21 05:56:46 +00:00
|
|
|
from homeassistant.const import ATTR_ID
|
2018-05-08 22:10:03 +00:00
|
|
|
from homeassistant.core import callback
|
|
|
|
from homeassistant.helpers.dispatcher import (
|
2018-06-10 08:23:07 +00:00
|
|
|
async_dispatcher_connect, async_dispatcher_send)
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2019-03-21 05:56:46 +00:00
|
|
|
from . import (
|
|
|
|
DATA_CLIENT, DOMAIN as RAINMACHINE_DOMAIN, PROGRAM_UPDATE_TOPIC,
|
|
|
|
ZONE_UPDATE_TOPIC, RainMachineEntity)
|
|
|
|
|
2018-04-28 13:46:58 +00:00
|
|
|
DEPENDENCIES = ['rainmachine']
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2018-05-29 19:02:16 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2018-05-01 19:36:43 +00:00
|
|
|
|
2019-03-13 14:19:26 +00:00
|
|
|
ATTR_NEXT_RUN = 'next_run'
|
2018-05-08 22:10:03 +00:00
|
|
|
ATTR_AREA = 'area'
|
|
|
|
ATTR_CS_ON = 'cs_on'
|
|
|
|
ATTR_CURRENT_CYCLE = 'current_cycle'
|
2017-08-08 07:49:25 +00:00
|
|
|
ATTR_CYCLES = 'cycles'
|
2018-05-08 22:10:03 +00:00
|
|
|
ATTR_DELAY = 'delay'
|
|
|
|
ATTR_DELAY_ON = 'delay_on'
|
|
|
|
ATTR_FIELD_CAPACITY = 'field_capacity'
|
|
|
|
ATTR_NO_CYCLES = 'number_of_cycles'
|
|
|
|
ATTR_PRECIP_RATE = 'sprinkler_head_precipitation_rate'
|
|
|
|
ATTR_RESTRICTIONS = 'restrictions'
|
|
|
|
ATTR_SLOPE = 'slope'
|
|
|
|
ATTR_SOAK = 'soak'
|
|
|
|
ATTR_SOIL_TYPE = 'soil_type'
|
|
|
|
ATTR_SPRINKLER_TYPE = 'sprinkler_head_type'
|
|
|
|
ATTR_STATUS = 'status'
|
|
|
|
ATTR_SUN_EXPOSURE = 'sun_exposure'
|
|
|
|
ATTR_VEGETATION_TYPE = 'vegetation_type'
|
|
|
|
ATTR_ZONES = 'zones'
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2018-05-08 22:10:03 +00:00
|
|
|
DAYS = [
|
2018-06-10 08:23:07 +00:00
|
|
|
'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
|
2018-05-08 22:10:03 +00:00
|
|
|
'Sunday'
|
|
|
|
]
|
|
|
|
|
2018-06-10 08:23:07 +00:00
|
|
|
PROGRAM_STATUS_MAP = {0: 'Not Running', 1: 'Running', 2: 'Queued'}
|
2018-05-08 22:10:03 +00:00
|
|
|
|
|
|
|
SOIL_TYPE_MAP = {
|
|
|
|
0: 'Not Set',
|
|
|
|
1: 'Clay Loam',
|
|
|
|
2: 'Silty Clay',
|
|
|
|
3: 'Clay',
|
|
|
|
4: 'Loam',
|
|
|
|
5: 'Sandy Loam',
|
|
|
|
6: 'Loamy Sand',
|
|
|
|
7: 'Sand',
|
|
|
|
8: 'Sandy Clay',
|
|
|
|
9: 'Silt Loam',
|
|
|
|
10: 'Silt',
|
|
|
|
99: 'Other'
|
|
|
|
}
|
|
|
|
|
|
|
|
SLOPE_TYPE_MAP = {
|
|
|
|
0: 'Not Set',
|
|
|
|
1: 'Flat',
|
|
|
|
2: 'Moderate',
|
|
|
|
3: 'High',
|
|
|
|
4: 'Very High',
|
|
|
|
99: 'Other'
|
|
|
|
}
|
|
|
|
|
|
|
|
SPRINKLER_TYPE_MAP = {
|
|
|
|
0: 'Not Set',
|
|
|
|
1: 'Popup Spray',
|
|
|
|
2: 'Rotors',
|
|
|
|
3: 'Surface Drip',
|
2018-05-31 16:55:50 +00:00
|
|
|
4: 'Bubblers Drip',
|
2018-05-08 22:10:03 +00:00
|
|
|
99: 'Other'
|
|
|
|
}
|
|
|
|
|
|
|
|
SUN_EXPOSURE_MAP = {
|
|
|
|
0: 'Not Set',
|
|
|
|
1: 'Full Sun',
|
|
|
|
2: 'Partial Shade',
|
|
|
|
3: 'Full Shade'
|
|
|
|
}
|
|
|
|
|
|
|
|
VEGETATION_MAP = {
|
|
|
|
0: 'Not Set',
|
2018-05-31 16:55:50 +00:00
|
|
|
2: 'Cool Season Grass',
|
2018-05-08 22:10:03 +00:00
|
|
|
3: 'Fruit Trees',
|
|
|
|
4: 'Flowers',
|
|
|
|
5: 'Vegetables',
|
|
|
|
6: 'Citrus',
|
2018-05-31 16:55:50 +00:00
|
|
|
7: 'Trees and Bushes',
|
|
|
|
9: 'Drought Tolerant Plants',
|
|
|
|
10: 'Warm Season Grass',
|
2018-05-08 22:10:03 +00:00
|
|
|
99: 'Other'
|
|
|
|
}
|
|
|
|
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2018-06-10 08:23:07 +00:00
|
|
|
async def async_setup_platform(
|
2018-08-24 14:37:30 +00:00
|
|
|
hass, config, async_add_entities, discovery_info=None):
|
2018-11-14 20:23:49 +00:00
|
|
|
"""Set up RainMachine switches sensor based on the old way."""
|
|
|
|
pass
|
2018-05-01 19:36:43 +00:00
|
|
|
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2018-11-14 20:23:49 +00:00
|
|
|
async def async_setup_entry(hass, entry, async_add_entities):
|
|
|
|
"""Set up RainMachine switches based on a config entry."""
|
|
|
|
rainmachine = hass.data[RAINMACHINE_DOMAIN][DATA_CLIENT][entry.entry_id]
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2018-04-28 13:46:58 +00:00
|
|
|
entities = []
|
2018-06-10 08:23:07 +00:00
|
|
|
|
2019-03-13 14:19:26 +00:00
|
|
|
programs = await rainmachine.client.programs.all(include_inactive=True)
|
2018-06-10 08:23:07 +00:00
|
|
|
for program in programs:
|
2018-05-08 22:10:03 +00:00
|
|
|
entities.append(RainMachineProgram(rainmachine, program))
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2019-03-13 14:19:26 +00:00
|
|
|
zones = await rainmachine.client.zones.all(include_inactive=True)
|
2018-06-10 08:23:07 +00:00
|
|
|
for zone in zones:
|
2018-11-14 20:23:49 +00:00
|
|
|
entities.append(
|
|
|
|
RainMachineZone(
|
|
|
|
rainmachine, zone, rainmachine.default_zone_runtime))
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2018-08-24 14:37:30 +00:00
|
|
|
async_add_entities(entities, True)
|
2017-08-08 07:49:25 +00:00
|
|
|
|
|
|
|
|
2018-05-08 22:10:03 +00:00
|
|
|
class RainMachineSwitch(RainMachineEntity, SwitchDevice):
|
2018-05-29 19:02:16 +00:00
|
|
|
"""A class to represent a generic RainMachine switch."""
|
|
|
|
|
|
|
|
def __init__(self, rainmachine, switch_type, obj):
|
|
|
|
"""Initialize a generic RainMachine switch."""
|
|
|
|
super().__init__(rainmachine)
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2018-05-29 19:02:16 +00:00
|
|
|
self._name = obj['name']
|
2018-05-08 22:10:03 +00:00
|
|
|
self._obj = obj
|
2018-05-29 19:02:16 +00:00
|
|
|
self._rainmachine_entity_id = obj['uid']
|
|
|
|
self._switch_type = switch_type
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2019-03-13 14:19:26 +00:00
|
|
|
@property
|
|
|
|
def available(self) -> bool:
|
|
|
|
"""Return True if entity is available."""
|
|
|
|
return bool(self._obj.get('active'))
|
|
|
|
|
2018-05-29 19:02:16 +00:00
|
|
|
@property
|
|
|
|
def icon(self) -> str:
|
|
|
|
"""Return the icon."""
|
|
|
|
return 'mdi:water'
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2018-05-29 19:02:16 +00:00
|
|
|
@property
|
|
|
|
def unique_id(self) -> str:
|
|
|
|
"""Return a unique, HASS-friendly identifier for this entity."""
|
|
|
|
return '{0}_{1}_{2}'.format(
|
2018-06-10 08:23:07 +00:00
|
|
|
self.rainmachine.device_mac.replace(':', ''), self._switch_type,
|
2018-05-29 19:02:16 +00:00
|
|
|
self._rainmachine_entity_id)
|
|
|
|
|
2018-06-10 08:23:07 +00:00
|
|
|
@callback
|
|
|
|
def _program_updated(self):
|
|
|
|
"""Update state, trigger updates."""
|
|
|
|
self.async_schedule_update_ha_state(True)
|
|
|
|
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2018-05-08 22:10:03 +00:00
|
|
|
class RainMachineProgram(RainMachineSwitch):
|
2017-08-08 07:49:25 +00:00
|
|
|
"""A RainMachine program."""
|
|
|
|
|
2018-05-08 22:10:03 +00:00
|
|
|
def __init__(self, rainmachine, obj):
|
2018-05-29 19:02:16 +00:00
|
|
|
"""Initialize a generic RainMachine switch."""
|
2018-05-08 22:10:03 +00:00
|
|
|
super().__init__(rainmachine, 'program', obj)
|
|
|
|
|
2017-08-08 07:49:25 +00:00
|
|
|
@property
|
|
|
|
def is_on(self) -> bool:
|
|
|
|
"""Return whether the program is running."""
|
2018-05-08 22:10:03 +00:00
|
|
|
return bool(self._obj.get('status'))
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2018-02-12 03:55:51 +00:00
|
|
|
@property
|
2018-05-08 22:10:03 +00:00
|
|
|
def zones(self) -> list:
|
|
|
|
"""Return a list of active zones associated with this program."""
|
|
|
|
return [z for z in self._obj['wateringTimes'] if z['active']]
|
2018-02-12 03:55:51 +00:00
|
|
|
|
2018-06-10 08:23:07 +00:00
|
|
|
async def async_added_to_hass(self):
|
|
|
|
"""Register callbacks."""
|
2018-11-17 09:42:50 +00:00
|
|
|
self._dispatcher_handlers.append(async_dispatcher_connect(
|
|
|
|
self.hass, PROGRAM_UPDATE_TOPIC, self._program_updated))
|
2018-11-14 20:23:49 +00:00
|
|
|
|
2018-06-10 08:23:07 +00:00
|
|
|
async def async_turn_off(self, **kwargs) -> None:
|
2017-08-08 07:49:25 +00:00
|
|
|
"""Turn the program off."""
|
2018-06-10 08:23:07 +00:00
|
|
|
from regenmaschine.errors import RequestError
|
2017-08-08 07:49:25 +00:00
|
|
|
|
|
|
|
try:
|
2018-06-10 08:23:07 +00:00
|
|
|
await self.rainmachine.client.programs.stop(
|
|
|
|
self._rainmachine_entity_id)
|
|
|
|
async_dispatcher_send(self.hass, PROGRAM_UPDATE_TOPIC)
|
|
|
|
except RequestError as err:
|
|
|
|
_LOGGER.error(
|
|
|
|
'Unable to turn off program "%s": %s', self.unique_id,
|
|
|
|
str(err))
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2018-06-10 08:23:07 +00:00
|
|
|
async def async_turn_on(self, **kwargs) -> None:
|
2017-08-08 07:49:25 +00:00
|
|
|
"""Turn the program on."""
|
2018-06-10 08:23:07 +00:00
|
|
|
from regenmaschine.errors import RequestError
|
2017-08-08 07:49:25 +00:00
|
|
|
|
|
|
|
try:
|
2018-06-10 08:23:07 +00:00
|
|
|
await self.rainmachine.client.programs.start(
|
|
|
|
self._rainmachine_entity_id)
|
|
|
|
async_dispatcher_send(self.hass, PROGRAM_UPDATE_TOPIC)
|
|
|
|
except RequestError as err:
|
|
|
|
_LOGGER.error(
|
|
|
|
'Unable to turn on program "%s": %s', self.unique_id, str(err))
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2018-06-10 08:23:07 +00:00
|
|
|
async def async_update(self) -> None:
|
2017-08-08 07:49:25 +00:00
|
|
|
"""Update info for the program."""
|
2018-06-10 08:23:07 +00:00
|
|
|
from regenmaschine.errors import RequestError
|
2017-08-08 07:49:25 +00:00
|
|
|
|
|
|
|
try:
|
2018-06-10 08:23:07 +00:00
|
|
|
self._obj = await self.rainmachine.client.programs.get(
|
2018-05-08 22:10:03 +00:00
|
|
|
self._rainmachine_entity_id)
|
|
|
|
|
2019-03-13 14:19:26 +00:00
|
|
|
try:
|
|
|
|
next_run = datetime.strptime(
|
|
|
|
'{0} {1}'.format(
|
|
|
|
self._obj['nextRun'], self._obj['startTime']),
|
|
|
|
'%Y-%m-%d %H:%M').isoformat()
|
|
|
|
except ValueError:
|
|
|
|
next_run = None
|
|
|
|
|
2018-05-08 22:10:03 +00:00
|
|
|
self._attrs.update({
|
|
|
|
ATTR_ID: self._obj['uid'],
|
2019-03-13 14:19:26 +00:00
|
|
|
ATTR_NEXT_RUN: next_run,
|
2018-05-08 22:10:03 +00:00
|
|
|
ATTR_SOAK: self._obj.get('soak'),
|
2018-05-29 19:02:16 +00:00
|
|
|
ATTR_STATUS: PROGRAM_STATUS_MAP[self._obj.get('status')],
|
2018-05-08 22:10:03 +00:00
|
|
|
ATTR_ZONES: ', '.join(z['name'] for z in self.zones)
|
|
|
|
})
|
2018-06-10 08:23:07 +00:00
|
|
|
except RequestError as err:
|
|
|
|
_LOGGER.error(
|
|
|
|
'Unable to update info for program "%s": %s', self.unique_id,
|
|
|
|
str(err))
|
2017-08-08 07:49:25 +00:00
|
|
|
|
|
|
|
|
2018-05-08 22:10:03 +00:00
|
|
|
class RainMachineZone(RainMachineSwitch):
|
2017-08-08 07:49:25 +00:00
|
|
|
"""A RainMachine zone."""
|
|
|
|
|
2018-05-08 22:10:03 +00:00
|
|
|
def __init__(self, rainmachine, obj, zone_run_time):
|
2017-08-08 07:49:25 +00:00
|
|
|
"""Initialize a RainMachine zone."""
|
2018-05-08 22:10:03 +00:00
|
|
|
super().__init__(rainmachine, 'zone', obj)
|
|
|
|
|
|
|
|
self._properties_json = {}
|
2017-08-08 07:49:25 +00:00
|
|
|
self._run_time = zone_run_time
|
|
|
|
|
|
|
|
@property
|
|
|
|
def is_on(self) -> bool:
|
|
|
|
"""Return whether the zone is running."""
|
2018-05-08 22:10:03 +00:00
|
|
|
return bool(self._obj.get('state'))
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2018-05-08 22:10:03 +00:00
|
|
|
async def async_added_to_hass(self):
|
|
|
|
"""Register callbacks."""
|
2018-11-17 09:42:50 +00:00
|
|
|
self._dispatcher_handlers.append(async_dispatcher_connect(
|
|
|
|
self.hass, PROGRAM_UPDATE_TOPIC, self._program_updated))
|
|
|
|
self._dispatcher_handlers.append(async_dispatcher_connect(
|
|
|
|
self.hass, ZONE_UPDATE_TOPIC, self._program_updated))
|
2018-02-12 03:55:51 +00:00
|
|
|
|
2018-06-10 08:23:07 +00:00
|
|
|
async def async_turn_off(self, **kwargs) -> None:
|
2017-08-08 07:49:25 +00:00
|
|
|
"""Turn the zone off."""
|
2018-06-10 08:23:07 +00:00
|
|
|
from regenmaschine.errors import RequestError
|
2017-08-08 07:49:25 +00:00
|
|
|
|
|
|
|
try:
|
2018-06-10 08:23:07 +00:00
|
|
|
await self.rainmachine.client.zones.stop(
|
|
|
|
self._rainmachine_entity_id)
|
|
|
|
except RequestError as err:
|
|
|
|
_LOGGER.error(
|
|
|
|
'Unable to turn off zone "%s": %s', self.unique_id, str(err))
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2018-06-10 08:23:07 +00:00
|
|
|
async def async_turn_on(self, **kwargs) -> None:
|
2017-08-08 07:49:25 +00:00
|
|
|
"""Turn the zone on."""
|
2018-06-10 08:23:07 +00:00
|
|
|
from regenmaschine.errors import RequestError
|
2017-08-08 07:49:25 +00:00
|
|
|
|
|
|
|
try:
|
2018-06-10 08:23:07 +00:00
|
|
|
await self.rainmachine.client.zones.start(
|
|
|
|
self._rainmachine_entity_id, self._run_time)
|
|
|
|
except RequestError as err:
|
|
|
|
_LOGGER.error(
|
|
|
|
'Unable to turn on zone "%s": %s', self.unique_id, str(err))
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2018-06-10 08:23:07 +00:00
|
|
|
async def async_update(self) -> None:
|
2017-08-08 07:49:25 +00:00
|
|
|
"""Update info for the zone."""
|
2018-06-10 08:23:07 +00:00
|
|
|
from regenmaschine.errors import RequestError
|
2017-08-08 07:49:25 +00:00
|
|
|
|
|
|
|
try:
|
2018-06-10 08:23:07 +00:00
|
|
|
self._obj = await self.rainmachine.client.zones.get(
|
2018-05-08 22:10:03 +00:00
|
|
|
self._rainmachine_entity_id)
|
|
|
|
|
2018-06-10 08:23:07 +00:00
|
|
|
self._properties_json = await self.rainmachine.client.zones.get(
|
|
|
|
self._rainmachine_entity_id, details=True)
|
2018-05-08 22:10:03 +00:00
|
|
|
|
|
|
|
self._attrs.update({
|
2018-06-10 08:23:07 +00:00
|
|
|
ATTR_ID:
|
|
|
|
self._obj['uid'],
|
|
|
|
ATTR_AREA:
|
|
|
|
self._properties_json.get('waterSense').get('area'),
|
|
|
|
ATTR_CURRENT_CYCLE:
|
|
|
|
self._obj.get('cycle'),
|
2018-05-08 22:10:03 +00:00
|
|
|
ATTR_FIELD_CAPACITY:
|
2019-03-13 14:19:26 +00:00
|
|
|
self._properties_json.get('waterSense').get(
|
|
|
|
'fieldCapacity'),
|
2018-06-10 08:23:07 +00:00
|
|
|
ATTR_NO_CYCLES:
|
|
|
|
self._obj.get('noOfCycles'),
|
2018-05-08 22:10:03 +00:00
|
|
|
ATTR_PRECIP_RATE:
|
2019-03-13 14:19:26 +00:00
|
|
|
self._properties_json.get('waterSense').get(
|
|
|
|
'precipitationRate'),
|
2018-06-10 08:23:07 +00:00
|
|
|
ATTR_RESTRICTIONS:
|
|
|
|
self._obj.get('restriction'),
|
|
|
|
ATTR_SLOPE:
|
|
|
|
SLOPE_TYPE_MAP.get(self._properties_json.get('slope')),
|
2018-05-08 22:10:03 +00:00
|
|
|
ATTR_SOIL_TYPE:
|
2018-05-31 16:55:50 +00:00
|
|
|
SOIL_TYPE_MAP.get(self._properties_json.get('sun')),
|
2018-05-08 22:10:03 +00:00
|
|
|
ATTR_SPRINKLER_TYPE:
|
2018-05-31 16:55:50 +00:00
|
|
|
SPRINKLER_TYPE_MAP.get(
|
|
|
|
self._properties_json.get('group_id')),
|
2018-05-08 22:10:03 +00:00
|
|
|
ATTR_SUN_EXPOSURE:
|
2018-05-31 16:55:50 +00:00
|
|
|
SUN_EXPOSURE_MAP.get(self._properties_json.get('sun')),
|
2018-05-08 22:10:03 +00:00
|
|
|
ATTR_VEGETATION_TYPE:
|
2018-05-31 16:55:50 +00:00
|
|
|
VEGETATION_MAP.get(self._obj.get('type')),
|
2018-05-08 22:10:03 +00:00
|
|
|
})
|
2018-06-10 08:23:07 +00:00
|
|
|
except RequestError as err:
|
|
|
|
_LOGGER.error(
|
|
|
|
'Unable to update info for zone "%s": %s', self.unique_id,
|
|
|
|
str(err))
|