core/homeassistant/components/rainmachine/switch.py

318 lines
9.7 KiB
Python
Raw Normal View History

"""This component provides support for RainMachine programs and zones."""
from datetime import datetime
import logging
from regenmaschine.errors import RequestError
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import ATTR_ID
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import (
2019-07-31 19:25:30 +00:00
async_dispatcher_connect,
async_dispatcher_send,
)
from . import (
2019-07-31 19:25:30 +00:00
DATA_CLIENT,
DOMAIN as RAINMACHINE_DOMAIN,
PROGRAM_UPDATE_TOPIC,
ZONE_UPDATE_TOPIC,
RainMachineEntity,
)
_LOGGER = logging.getLogger(__name__)
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"}
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",
}
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",
}
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",
}
2019-07-31 19:25:30 +00:00
SUN_EXPOSURE_MAP = {0: "Not Set", 1: "Full Sun", 2: "Partial Shade", 3: "Full Shade"}
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",
}
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(
2019-07-31 19:25:30 +00:00
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)
2019-07-31 19:25:30 +00:00
self._name = obj["name"]
self._obj = obj
2019-07-31 19:25:30 +00:00
self._rainmachine_entity_id = obj["uid"]
self._switch_type = switch_type
@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"))
@property
def icon(self) -> str:
"""Return the icon."""
2019-07-31 19:25:30 +00:00
return "mdi:water"
@property
def unique_id(self) -> str:
"""Return a unique, Home Assistant 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,
)
@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."""
2019-07-31 19:25:30 +00:00
super().__init__(rainmachine, "program", obj)
@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"))
@property
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"]]
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
)
)
async def async_turn_off(self, **kwargs) -> None:
"""Turn the program off."""
try:
2019-07-31 19:25:30 +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(
2019-07-31 19:25:30 +00:00
'Unable to turn off program "%s": %s', self.unique_id, str(err)
)
async def async_turn_on(self, **kwargs) -> None:
"""Turn the program on."""
try:
2019-07-31 19:25:30 +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(
2019-07-31 19:25:30 +00:00
'Unable to turn on program "%s": %s', self.unique_id, str(err)
)
async def async_update(self) -> None:
"""Update info for the program."""
try:
self._obj = await self.rainmachine.client.programs.get(
2019-07-31 19:25:30 +00:00
self._rainmachine_entity_id
)
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()
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),
}
)
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)
)
class RainMachineZone(RainMachineSwitch):
"""A RainMachine zone."""
def __init__(self, rainmachine, obj, zone_run_time):
"""Initialize a RainMachine zone."""
2019-07-31 19:25:30 +00:00
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."""
2019-07-31 19:25:30 +00:00
return bool(self._obj.get("state"))
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
)
)
async def async_turn_off(self, **kwargs) -> None:
"""Turn the zone off."""
try:
2019-07-31 19:25:30 +00:00
await self.rainmachine.client.zones.stop(self._rainmachine_entity_id)
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))
async def async_turn_on(self, **kwargs) -> None:
"""Turn the zone on."""
try:
await self.rainmachine.client.zones.start(
2019-07-31 19:25:30 +00:00
self._rainmachine_entity_id, self._run_time
)
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))
async def async_update(self) -> None:
"""Update info for the zone."""
try:
self._obj = await self.rainmachine.client.zones.get(
2019-07-31 19:25:30 +00:00
self._rainmachine_entity_id
)
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")),
}
)
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)
)