2019-04-03 15:40:03 +00:00
|
|
|
"""This component provides support for RainMachine programs and zones."""
|
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 (
|
2019-07-31 19:25:30 +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 (
|
2019-07-31 19:25:30 +00:00
|
|
|
DATA_CLIENT,
|
|
|
|
DOMAIN as RAINMACHINE_DOMAIN,
|
|
|
|
PROGRAM_UPDATE_TOPIC,
|
|
|
|
ZONE_UPDATE_TOPIC,
|
|
|
|
RainMachineEntity,
|
|
|
|
)
|
2019-03-21 05:56:46 +00:00
|
|
|
|
2018-05-29 19:02:16 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2018-05-01 19:36:43 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
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"}
|
2018-05-08 22:10:03 +00:00
|
|
|
|
|
|
|
SOIL_TYPE_MAP = {
|
2019-07-31 19:25:30 +00:00
|
|
|
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",
|
2018-05-08 22:10:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SLOPE_TYPE_MAP = {
|
2019-07-31 19:25:30 +00:00
|
|
|
0: "Not Set",
|
|
|
|
1: "Flat",
|
|
|
|
2: "Moderate",
|
|
|
|
3: "High",
|
|
|
|
4: "Very High",
|
|
|
|
99: "Other",
|
2018-05-08 22:10:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SPRINKLER_TYPE_MAP = {
|
2019-07-31 19:25:30 +00:00
|
|
|
0: "Not Set",
|
|
|
|
1: "Popup Spray",
|
|
|
|
2: "Rotors",
|
|
|
|
3: "Surface Drip",
|
|
|
|
4: "Bubblers Drip",
|
|
|
|
99: "Other",
|
2018-05-08 22:10:03 +00:00
|
|
|
}
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
SUN_EXPOSURE_MAP = {0: "Not Set", 1: "Full Sun", 2: "Partial Shade", 3: "Full Shade"}
|
2018-05-08 22:10:03 +00:00
|
|
|
|
|
|
|
VEGETATION_MAP = {
|
2019-07-31 19:25:30 +00:00
|
|
|
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",
|
2018-05-08 22:10:03 +00:00
|
|
|
}
|
|
|
|
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
async def async_setup_platform(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(
|
2019-07-31 19:25:30 +00:00
|
|
|
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
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
self._name = obj["name"]
|
2018-05-08 22:10:03 +00:00
|
|
|
self._obj = obj
|
2019-07-31 19:25:30 +00:00
|
|
|
self._rainmachine_entity_id = obj["uid"]
|
2018-05-29 19:02:16 +00:00
|
|
|
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."""
|
2019-07-31 19:25:30 +00:00
|
|
|
return bool(self._obj.get("active"))
|
2019-03-13 14:19:26 +00:00
|
|
|
|
2018-05-29 19:02:16 +00:00
|
|
|
@property
|
|
|
|
def icon(self) -> str:
|
|
|
|
"""Return the icon."""
|
2019-07-31 19:25:30 +00:00
|
|
|
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."""
|
2019-07-31 19:25:30 +00:00
|
|
|
return "{0}_{1}_{2}".format(
|
|
|
|
self.rainmachine.device_mac.replace(":", ""),
|
|
|
|
self._switch_type,
|
|
|
|
self._rainmachine_entity_id,
|
|
|
|
)
|
2018-05-29 19:02:16 +00:00
|
|
|
|
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."""
|
2019-07-31 19:25:30 +00:00
|
|
|
super().__init__(rainmachine, "program", obj)
|
2018-05-08 22:10:03 +00:00
|
|
|
|
2017-08-08 07:49:25 +00:00
|
|
|
@property
|
|
|
|
def is_on(self) -> bool:
|
|
|
|
"""Return whether the program is running."""
|
2019-07-31 19:25:30 +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."""
|
2019-07-31 19:25:30 +00:00
|
|
|
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."""
|
2019-07-31 19:25:30 +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:
|
2019-07-31 19:25:30 +00:00
|
|
|
await self.rainmachine.client.programs.stop(self._rainmachine_entity_id)
|
2018-06-10 08:23:07 +00:00
|
|
|
async_dispatcher_send(self.hass, PROGRAM_UPDATE_TOPIC)
|
|
|
|
except RequestError as err:
|
|
|
|
_LOGGER.error(
|
2019-07-31 19:25:30 +00:00
|
|
|
'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:
|
2019-07-31 19:25:30 +00:00
|
|
|
await self.rainmachine.client.programs.start(self._rainmachine_entity_id)
|
2018-06-10 08:23:07 +00:00
|
|
|
async_dispatcher_send(self.hass, PROGRAM_UPDATE_TOPIC)
|
|
|
|
except RequestError as err:
|
|
|
|
_LOGGER.error(
|
2019-07-31 19:25:30 +00:00
|
|
|
'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(
|
2019-07-31 19:25:30 +00:00
|
|
|
self._rainmachine_entity_id
|
|
|
|
)
|
2018-05-08 22:10:03 +00:00
|
|
|
|
2019-03-13 14:19:26 +00:00
|
|
|
try:
|
|
|
|
next_run = datetime.strptime(
|
2019-07-31 19:25:30 +00:00
|
|
|
"{0} {1}".format(self._obj["nextRun"], self._obj["startTime"]),
|
|
|
|
"%Y-%m-%d %H:%M",
|
|
|
|
).isoformat()
|
2019-03-13 14:19:26 +00:00
|
|
|
except ValueError:
|
|
|
|
next_run = None
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
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),
|
|
|
|
}
|
|
|
|
)
|
2018-06-10 08:23:07 +00:00
|
|
|
except RequestError as err:
|
|
|
|
_LOGGER.error(
|
2019-07-31 19:25:30 +00:00
|
|
|
'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."""
|
2019-07-31 19:25:30 +00:00
|
|
|
super().__init__(rainmachine, "zone", obj)
|
2018-05-08 22:10:03 +00:00
|
|
|
|
|
|
|
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."""
|
2019-07-31 19:25:30 +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."""
|
2019-07-31 19:25:30 +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:
|
2019-07-31 19:25:30 +00:00
|
|
|
await self.rainmachine.client.zones.stop(self._rainmachine_entity_id)
|
2018-06-10 08:23:07 +00:00
|
|
|
except RequestError as err:
|
2019-07-31 19:25:30 +00:00
|
|
|
_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(
|
2019-07-31 19:25:30 +00:00
|
|
|
self._rainmachine_entity_id, self._run_time
|
|
|
|
)
|
2018-06-10 08:23:07 +00:00
|
|
|
except RequestError as err:
|
2019-07-31 19:25:30 +00:00
|
|
|
_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(
|
2019-07-31 19:25:30 +00:00
|
|
|
self._rainmachine_entity_id
|
|
|
|
)
|
2018-05-08 22:10:03 +00:00
|
|
|
|
2018-06-10 08:23:07 +00:00
|
|
|
self._properties_json = await self.rainmachine.client.zones.get(
|
2019-07-31 19:25:30 +00:00
|
|
|
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")),
|
|
|
|
}
|
|
|
|
)
|
2018-06-10 08:23:07 +00:00
|
|
|
except RequestError as err:
|
|
|
|
_LOGGER.error(
|
2019-07-31 19:25:30 +00:00
|
|
|
'Unable to update info for zone "%s": %s', self.unique_id, str(err)
|
|
|
|
)
|