core/homeassistant/components/rainmachine/switch.py

322 lines
10 KiB
Python

"""This component provides support for RainMachine programs and zones."""
from datetime import datetime
import logging
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import ATTR_ID
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_send)
from . import (
DATA_CLIENT, DOMAIN as RAINMACHINE_DOMAIN, PROGRAM_UPDATE_TOPIC,
ZONE_UPDATE_TOPIC, RainMachineEntity)
_LOGGER = logging.getLogger(__name__)
ATTR_NEXT_RUN = 'next_run'
ATTR_AREA = 'area'
ATTR_CS_ON = 'cs_on'
ATTR_CURRENT_CYCLE = 'current_cycle'
ATTR_CYCLES = 'cycles'
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'
DAYS = [
'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
'Sunday'
]
PROGRAM_STATUS_MAP = {0: 'Not Running', 1: 'Running', 2: 'Queued'}
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',
4: 'Bubblers Drip',
99: 'Other'
}
SUN_EXPOSURE_MAP = {
0: 'Not Set',
1: 'Full Sun',
2: 'Partial Shade',
3: 'Full Shade'
}
VEGETATION_MAP = {
0: 'Not Set',
2: 'Cool Season Grass',
3: 'Fruit Trees',
4: 'Flowers',
5: 'Vegetables',
6: 'Citrus',
7: 'Trees and Bushes',
9: 'Drought Tolerant Plants',
10: 'Warm Season Grass',
99: 'Other'
}
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up RainMachine switches sensor based on the old way."""
pass
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]
entities = []
programs = await rainmachine.client.programs.all(include_inactive=True)
for program in programs:
entities.append(RainMachineProgram(rainmachine, program))
zones = await rainmachine.client.zones.all(include_inactive=True)
for zone in zones:
entities.append(
RainMachineZone(
rainmachine, zone, rainmachine.default_zone_runtime))
async_add_entities(entities, True)
class RainMachineSwitch(RainMachineEntity, SwitchDevice):
"""A class to represent a generic RainMachine switch."""
def __init__(self, rainmachine, switch_type, obj):
"""Initialize a generic RainMachine switch."""
super().__init__(rainmachine)
self._name = obj['name']
self._obj = obj
self._rainmachine_entity_id = obj['uid']
self._switch_type = switch_type
@property
def available(self) -> bool:
"""Return True if entity is available."""
return bool(self._obj.get('active'))
@property
def icon(self) -> str:
"""Return the icon."""
return 'mdi:water'
@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)
@callback
def _program_updated(self):
"""Update state, trigger updates."""
self.async_schedule_update_ha_state(True)
class RainMachineProgram(RainMachineSwitch):
"""A RainMachine program."""
def __init__(self, rainmachine, obj):
"""Initialize a generic RainMachine switch."""
super().__init__(rainmachine, 'program', obj)
@property
def is_on(self) -> bool:
"""Return whether the program is running."""
return bool(self._obj.get('status'))
@property
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']]
async def async_added_to_hass(self):
"""Register callbacks."""
self._dispatcher_handlers.append(async_dispatcher_connect(
self.hass, PROGRAM_UPDATE_TOPIC, self._program_updated))
async def async_turn_off(self, **kwargs) -> None:
"""Turn the program off."""
from regenmaschine.errors import RequestError
try:
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))
async def async_turn_on(self, **kwargs) -> None:
"""Turn the program on."""
from regenmaschine.errors import RequestError
try:
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))
async def async_update(self) -> None:
"""Update info for the program."""
from regenmaschine.errors import RequestError
try:
self._obj = await self.rainmachine.client.programs.get(
self._rainmachine_entity_id)
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
self._attrs.update({
ATTR_ID: self._obj['uid'],
ATTR_NEXT_RUN: next_run,
ATTR_SOAK: self._obj.get('soak'),
ATTR_STATUS: PROGRAM_STATUS_MAP[self._obj.get('status')],
ATTR_ZONES: ', '.join(z['name'] for z in self.zones)
})
except RequestError as err:
_LOGGER.error(
'Unable to update info for program "%s": %s', self.unique_id,
str(err))
class RainMachineZone(RainMachineSwitch):
"""A RainMachine zone."""
def __init__(self, rainmachine, obj, zone_run_time):
"""Initialize a RainMachine zone."""
super().__init__(rainmachine, 'zone', obj)
self._properties_json = {}
self._run_time = zone_run_time
@property
def is_on(self) -> bool:
"""Return whether the zone is running."""
return bool(self._obj.get('state'))
async def async_added_to_hass(self):
"""Register callbacks."""
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))
async def async_turn_off(self, **kwargs) -> None:
"""Turn the zone off."""
from regenmaschine.errors import RequestError
try:
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))
async def async_turn_on(self, **kwargs) -> None:
"""Turn the zone on."""
from regenmaschine.errors import RequestError
try:
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))
async def async_update(self) -> None:
"""Update info for the zone."""
from regenmaschine.errors import RequestError
try:
self._obj = await self.rainmachine.client.zones.get(
self._rainmachine_entity_id)
self._properties_json = await self.rainmachine.client.zones.get(
self._rainmachine_entity_id, details=True)
self._attrs.update({
ATTR_ID:
self._obj['uid'],
ATTR_AREA:
self._properties_json.get('waterSense').get('area'),
ATTR_CURRENT_CYCLE:
self._obj.get('cycle'),
ATTR_FIELD_CAPACITY:
self._properties_json.get('waterSense').get(
'fieldCapacity'),
ATTR_NO_CYCLES:
self._obj.get('noOfCycles'),
ATTR_PRECIP_RATE:
self._properties_json.get('waterSense').get(
'precipitationRate'),
ATTR_RESTRICTIONS:
self._obj.get('restriction'),
ATTR_SLOPE:
SLOPE_TYPE_MAP.get(self._properties_json.get('slope')),
ATTR_SOIL_TYPE:
SOIL_TYPE_MAP.get(self._properties_json.get('sun')),
ATTR_SPRINKLER_TYPE:
SPRINKLER_TYPE_MAP.get(
self._properties_json.get('group_id')),
ATTR_SUN_EXPOSURE:
SUN_EXPOSURE_MAP.get(self._properties_json.get('sun')),
ATTR_VEGETATION_TYPE:
VEGETATION_MAP.get(self._obj.get('type')),
})
except RequestError as err:
_LOGGER.error(
'Unable to update info for zone "%s": %s', self.unique_id,
str(err))