2023-03-03 10:26:13 +00:00
|
|
|
"""Component providing support for RainMachine programs and zones."""
|
2021-04-20 15:40:41 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2021-11-23 03:47:01 +00:00
|
|
|
import asyncio
|
2022-08-07 20:50:49 +00:00
|
|
|
from collections.abc import Awaitable, Callable, Coroutine
|
2021-08-25 14:36:25 +00:00
|
|
|
from dataclasses import dataclass
|
2019-03-13 14:19:26 +00:00
|
|
|
from datetime import datetime
|
2023-01-23 06:28:43 +00:00
|
|
|
from typing import Any, Concatenate, ParamSpec, TypeVar
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2022-08-07 20:50:49 +00:00
|
|
|
from regenmaschine.errors import RainMachineError
|
2021-01-04 19:01:14 +00:00
|
|
|
import voluptuous as vol
|
2019-12-05 05:12:44 +00:00
|
|
|
|
2021-08-25 14:36:25 +00:00
|
|
|
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
2020-11-06 09:58:50 +00:00
|
|
|
from homeassistant.config_entries import ConfigEntry
|
2023-02-09 19:15:37 +00:00
|
|
|
from homeassistant.const import ATTR_ID, EntityCategory
|
2020-11-06 09:58:50 +00:00
|
|
|
from homeassistant.core import HomeAssistant, callback
|
2021-11-23 03:47:01 +00:00
|
|
|
from homeassistant.exceptions import HomeAssistantError
|
2021-01-04 19:01:14 +00:00
|
|
|
from homeassistant.helpers import config_validation as cv, entity_platform
|
2021-04-30 18:38:59 +00:00
|
|
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2022-08-03 22:23:42 +00:00
|
|
|
from . import RainMachineData, RainMachineEntity, async_update_programs_and_zones
|
2020-01-26 03:27:35 +00:00
|
|
|
from .const import (
|
2022-10-27 04:27:08 +00:00
|
|
|
CONF_DEFAULT_ZONE_RUN_TIME,
|
|
|
|
CONF_DURATION,
|
|
|
|
CONF_USE_APP_RUN_TIMES,
|
2020-01-26 03:27:35 +00:00
|
|
|
DATA_PROGRAMS,
|
2022-10-27 04:27:08 +00:00
|
|
|
DATA_PROVISION_SETTINGS,
|
2022-09-23 23:05:07 +00:00
|
|
|
DATA_RESTRICTIONS_UNIVERSAL,
|
2020-01-26 03:27:35 +00:00
|
|
|
DATA_ZONES,
|
2021-01-04 19:01:14 +00:00
|
|
|
DEFAULT_ZONE_RUN,
|
2020-11-06 09:58:50 +00:00
|
|
|
DOMAIN,
|
2019-07-31 19:25:30 +00:00
|
|
|
)
|
2022-09-23 23:05:07 +00:00
|
|
|
from .model import (
|
|
|
|
RainMachineEntityDescription,
|
|
|
|
RainMachineEntityDescriptionMixinDataKey,
|
|
|
|
RainMachineEntityDescriptionMixinUid,
|
|
|
|
)
|
2022-10-18 08:40:49 +00:00
|
|
|
from .util import RUN_STATE_MAP, key_exists
|
2019-03-21 05:56:46 +00:00
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_AREA = "area"
|
|
|
|
ATTR_CS_ON = "cs_on"
|
|
|
|
ATTR_CURRENT_CYCLE = "current_cycle"
|
|
|
|
ATTR_CYCLES = "cycles"
|
2022-10-27 04:27:08 +00:00
|
|
|
ATTR_ZONE_RUN_TIME = "zone_run_time_from_app"
|
2019-07-31 19:25:30 +00:00
|
|
|
ATTR_DELAY = "delay"
|
|
|
|
ATTR_DELAY_ON = "delay_on"
|
|
|
|
ATTR_FIELD_CAPACITY = "field_capacity"
|
2020-06-02 03:49:02 +00:00
|
|
|
ATTR_NEXT_RUN = "next_run"
|
2019-07-31 19:25:30 +00:00
|
|
|
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"]
|
|
|
|
|
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",
|
2021-06-29 17:57:28 +00:00
|
|
|
2: "Rotors Low Rate",
|
2019-07-31 19:25:30 +00:00
|
|
|
3: "Surface Drip",
|
|
|
|
4: "Bubblers Drip",
|
2021-06-29 08:20:11 +00:00
|
|
|
5: "Rotors High Rate",
|
2019-07-31 19:25:30 +00:00
|
|
|
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",
|
2021-06-29 08:20:11 +00:00
|
|
|
1: "Not Set",
|
2019-07-31 19:25:30 +00:00
|
|
|
2: "Cool Season Grass",
|
|
|
|
3: "Fruit Trees",
|
|
|
|
4: "Flowers",
|
|
|
|
5: "Vegetables",
|
|
|
|
6: "Citrus",
|
2021-06-29 17:57:28 +00:00
|
|
|
7: "Bushes",
|
2019-07-31 19:25:30 +00:00
|
|
|
9: "Drought Tolerant Plants",
|
|
|
|
10: "Warm Season Grass",
|
2021-06-29 17:57:28 +00:00
|
|
|
11: "Trees",
|
2019-07-31 19:25:30 +00:00
|
|
|
99: "Other",
|
2018-05-08 22:10:03 +00:00
|
|
|
}
|
|
|
|
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2022-08-07 20:50:49 +00:00
|
|
|
_T = TypeVar("_T", bound="RainMachineBaseSwitch")
|
|
|
|
_P = ParamSpec("_P")
|
|
|
|
|
|
|
|
|
|
|
|
def raise_on_request_error(
|
|
|
|
func: Callable[Concatenate[_T, _P], Awaitable[None]]
|
|
|
|
) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, None]]:
|
|
|
|
"""Define a decorator to raise on a request error."""
|
|
|
|
|
|
|
|
async def decorator(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None:
|
|
|
|
"""Decorate."""
|
|
|
|
try:
|
|
|
|
await func(self, *args, **kwargs)
|
|
|
|
except RainMachineError as err:
|
|
|
|
raise HomeAssistantError(
|
|
|
|
f"Error while executing {func.__name__}: {err}",
|
|
|
|
) from err
|
|
|
|
|
|
|
|
return decorator
|
|
|
|
|
|
|
|
|
2021-08-25 14:36:25 +00:00
|
|
|
@dataclass
|
|
|
|
class RainMachineSwitchDescription(
|
2022-08-03 22:23:42 +00:00
|
|
|
SwitchEntityDescription,
|
|
|
|
RainMachineEntityDescription,
|
2021-08-25 14:36:25 +00:00
|
|
|
):
|
|
|
|
"""Describe a RainMachine switch."""
|
|
|
|
|
|
|
|
|
2022-09-23 23:05:07 +00:00
|
|
|
@dataclass
|
|
|
|
class RainMachineActivitySwitchDescription(
|
|
|
|
RainMachineSwitchDescription, RainMachineEntityDescriptionMixinUid
|
|
|
|
):
|
|
|
|
"""Describe a RainMachine activity (program/zone) switch."""
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class RainMachineRestrictionSwitchDescription(
|
|
|
|
RainMachineSwitchDescription, RainMachineEntityDescriptionMixinDataKey
|
|
|
|
):
|
|
|
|
"""Describe a RainMachine restriction switch."""
|
|
|
|
|
|
|
|
|
|
|
|
TYPE_RESTRICTIONS_FREEZE_PROTECT_ENABLED = "freeze_protect_enabled"
|
|
|
|
TYPE_RESTRICTIONS_HOT_DAYS_EXTRA_WATERING = "hot_days_extra_watering"
|
|
|
|
|
|
|
|
RESTRICTIONS_SWITCH_DESCRIPTIONS = (
|
|
|
|
RainMachineRestrictionSwitchDescription(
|
|
|
|
key=TYPE_RESTRICTIONS_FREEZE_PROTECT_ENABLED,
|
|
|
|
name="Freeze protection",
|
|
|
|
icon="mdi:snowflake-alert",
|
|
|
|
api_category=DATA_RESTRICTIONS_UNIVERSAL,
|
|
|
|
data_key="freezeProtectEnabled",
|
|
|
|
),
|
|
|
|
RainMachineRestrictionSwitchDescription(
|
|
|
|
key=TYPE_RESTRICTIONS_HOT_DAYS_EXTRA_WATERING,
|
|
|
|
name="Extra water on hot days",
|
|
|
|
icon="mdi:heat-wave",
|
|
|
|
api_category=DATA_RESTRICTIONS_UNIVERSAL,
|
|
|
|
data_key="hotDaysExtraWatering",
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-11-06 09:58:50 +00:00
|
|
|
async def async_setup_entry(
|
2021-04-30 18:38:59 +00:00
|
|
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
2020-11-06 09:58:50 +00:00
|
|
|
) -> None:
|
2018-11-14 20:23:49 +00:00
|
|
|
"""Set up RainMachine switches based on a config entry."""
|
2021-05-03 16:34:28 +00:00
|
|
|
platform = entity_platform.async_get_current_platform()
|
2021-01-04 19:01:14 +00:00
|
|
|
|
2021-07-19 13:57:06 +00:00
|
|
|
for service_name, schema, method in (
|
2021-10-08 18:03:47 +00:00
|
|
|
("start_program", {}, "async_start_program"),
|
2021-01-04 19:01:14 +00:00
|
|
|
(
|
|
|
|
"start_zone",
|
|
|
|
{
|
|
|
|
vol.Optional(
|
2022-10-27 04:27:08 +00:00
|
|
|
CONF_DEFAULT_ZONE_RUN_TIME, default=DEFAULT_ZONE_RUN
|
2021-10-08 18:03:47 +00:00
|
|
|
): cv.positive_int
|
2021-01-04 19:01:14 +00:00
|
|
|
},
|
|
|
|
"async_start_zone",
|
|
|
|
),
|
2021-10-08 18:03:47 +00:00
|
|
|
("stop_program", {}, "async_stop_program"),
|
|
|
|
("stop_zone", {}, "async_stop_zone"),
|
2021-07-19 13:57:06 +00:00
|
|
|
):
|
2021-01-04 19:01:14 +00:00
|
|
|
platform.async_register_entity_service(service_name, schema, method)
|
|
|
|
|
2022-08-03 22:23:42 +00:00
|
|
|
data: RainMachineData = hass.data[DOMAIN][entry.entry_id]
|
2022-09-23 23:05:07 +00:00
|
|
|
entities: list[RainMachineBaseSwitch] = []
|
2021-11-23 03:47:01 +00:00
|
|
|
|
2022-08-03 22:23:42 +00:00
|
|
|
for kind, api_category, switch_class, switch_enabled_class in (
|
|
|
|
("program", DATA_PROGRAMS, RainMachineProgram, RainMachineProgramEnabled),
|
|
|
|
("zone", DATA_ZONES, RainMachineZone, RainMachineZoneEnabled),
|
2021-11-23 03:47:01 +00:00
|
|
|
):
|
2022-08-03 22:23:42 +00:00
|
|
|
coordinator = data.coordinators[api_category]
|
|
|
|
for uid, activity in coordinator.data.items():
|
|
|
|
name = activity["name"].capitalize()
|
2022-07-10 19:27:01 +00:00
|
|
|
|
2021-11-23 03:47:01 +00:00
|
|
|
# Add a switch to start/stop the program or zone:
|
|
|
|
entities.append(
|
|
|
|
switch_class(
|
|
|
|
entry,
|
2022-08-03 22:23:42 +00:00
|
|
|
data,
|
2022-09-23 23:05:07 +00:00
|
|
|
RainMachineActivitySwitchDescription(
|
2021-11-23 03:47:01 +00:00
|
|
|
key=f"{kind}_{uid}",
|
2022-07-10 19:27:01 +00:00
|
|
|
name=name,
|
2022-08-03 22:23:42 +00:00
|
|
|
api_category=api_category,
|
2021-11-23 03:47:01 +00:00
|
|
|
uid=uid,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
# Add a switch to enabled/disable the program or zone:
|
|
|
|
entities.append(
|
|
|
|
switch_enabled_class(
|
|
|
|
entry,
|
2022-08-03 22:23:42 +00:00
|
|
|
data,
|
2022-09-23 23:05:07 +00:00
|
|
|
RainMachineActivitySwitchDescription(
|
2021-11-23 03:47:01 +00:00
|
|
|
key=f"{kind}_{uid}_enabled",
|
2022-07-10 19:27:01 +00:00
|
|
|
name=f"{name} enabled",
|
2022-08-03 22:23:42 +00:00
|
|
|
api_category=api_category,
|
2021-11-23 03:47:01 +00:00
|
|
|
uid=uid,
|
|
|
|
),
|
|
|
|
)
|
2021-08-25 14:36:25 +00:00
|
|
|
)
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2022-09-23 23:05:07 +00:00
|
|
|
# Add switches to control restrictions:
|
|
|
|
for description in RESTRICTIONS_SWITCH_DESCRIPTIONS:
|
2022-11-06 19:38:55 +00:00
|
|
|
coordinator = data.coordinators[description.api_category]
|
2022-10-18 08:40:49 +00:00
|
|
|
if not key_exists(coordinator.data, description.data_key):
|
|
|
|
continue
|
2022-09-23 23:05:07 +00:00
|
|
|
entities.append(RainMachineRestrictionSwitch(entry, data, description))
|
|
|
|
|
2020-11-06 09:58:50 +00:00
|
|
|
async_add_entities(entities)
|
2017-08-08 07:49:25 +00:00
|
|
|
|
|
|
|
|
2021-11-23 03:47:01 +00:00
|
|
|
class RainMachineBaseSwitch(RainMachineEntity, SwitchEntity):
|
|
|
|
"""Define a base RainMachine switch."""
|
2018-05-29 19:02:16 +00:00
|
|
|
|
2021-08-25 14:36:25 +00:00
|
|
|
entity_description: RainMachineSwitchDescription
|
2021-07-03 16:23:52 +00:00
|
|
|
|
2020-11-06 09:58:50 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
2021-10-16 03:33:26 +00:00
|
|
|
entry: ConfigEntry,
|
2022-08-03 22:23:42 +00:00
|
|
|
data: RainMachineData,
|
2021-08-25 14:36:25 +00:00
|
|
|
description: RainMachineSwitchDescription,
|
2020-11-06 09:58:50 +00:00
|
|
|
) -> None:
|
2021-11-23 03:47:01 +00:00
|
|
|
"""Initialize."""
|
2022-08-03 22:23:42 +00:00
|
|
|
super().__init__(entry, data, description)
|
2021-07-03 16:23:52 +00:00
|
|
|
|
|
|
|
self._attr_is_on = False
|
2020-11-06 09:58:50 +00:00
|
|
|
self._entry = entry
|
2020-01-26 03:27:35 +00:00
|
|
|
|
2022-08-07 20:50:49 +00:00
|
|
|
@callback
|
|
|
|
def _update_activities(self) -> None:
|
|
|
|
"""Update all activity data."""
|
2020-11-06 09:58:50 +00:00
|
|
|
self.hass.async_create_task(
|
|
|
|
async_update_programs_and_zones(self.hass, self._entry)
|
|
|
|
)
|
|
|
|
|
2021-10-08 18:03:47 +00:00
|
|
|
async def async_start_program(self) -> None:
|
2021-11-23 03:47:01 +00:00
|
|
|
"""Execute the start_program entity service."""
|
2021-10-08 18:03:47 +00:00
|
|
|
raise NotImplementedError("Service not implemented for this entity")
|
2021-01-04 19:01:14 +00:00
|
|
|
|
2021-10-08 18:03:47 +00:00
|
|
|
async def async_start_zone(self, *, zone_run_time: int) -> None:
|
2021-11-23 03:47:01 +00:00
|
|
|
"""Execute the start_zone entity service."""
|
2021-10-08 18:03:47 +00:00
|
|
|
raise NotImplementedError("Service not implemented for this entity")
|
2021-01-04 19:01:14 +00:00
|
|
|
|
2021-10-08 18:03:47 +00:00
|
|
|
async def async_stop_program(self) -> None:
|
2021-11-23 03:47:01 +00:00
|
|
|
"""Execute the stop_program entity service."""
|
2021-10-08 18:03:47 +00:00
|
|
|
raise NotImplementedError("Service not implemented for this entity")
|
2021-01-04 19:01:14 +00:00
|
|
|
|
2021-10-08 18:03:47 +00:00
|
|
|
async def async_stop_zone(self) -> None:
|
2021-11-23 03:47:01 +00:00
|
|
|
"""Execute the stop_zone entity service."""
|
2021-10-08 18:03:47 +00:00
|
|
|
raise NotImplementedError("Service not implemented for this entity")
|
2021-01-04 19:01:14 +00:00
|
|
|
|
2018-06-10 08:23:07 +00:00
|
|
|
|
2021-11-23 03:47:01 +00:00
|
|
|
class RainMachineActivitySwitch(RainMachineBaseSwitch):
|
|
|
|
"""Define a RainMachine switch to start/stop an activity (program or zone)."""
|
|
|
|
|
2022-09-23 23:05:07 +00:00
|
|
|
_attr_icon = "mdi:water"
|
|
|
|
entity_description: RainMachineActivitySwitchDescription
|
|
|
|
|
2021-11-23 03:47:01 +00:00
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
|
|
"""Turn the switch off.
|
|
|
|
|
|
|
|
The only way this could occur is if someone rapidly turns a disabled activity
|
|
|
|
off right after turning it on.
|
|
|
|
"""
|
|
|
|
if not self.coordinator.data[self.entity_description.uid]["active"]:
|
|
|
|
raise HomeAssistantError(
|
|
|
|
f"Cannot turn off an inactive program/zone: {self.name}"
|
|
|
|
)
|
|
|
|
|
|
|
|
await self.async_turn_off_when_active(**kwargs)
|
|
|
|
|
2022-08-07 20:50:49 +00:00
|
|
|
@raise_on_request_error
|
2021-11-23 03:47:01 +00:00
|
|
|
async def async_turn_off_when_active(self, **kwargs: Any) -> None:
|
|
|
|
"""Turn the switch off when its associated activity is active."""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
|
|
"""Turn the switch on."""
|
|
|
|
if not self.coordinator.data[self.entity_description.uid]["active"]:
|
2021-12-04 04:05:01 +00:00
|
|
|
self._attr_is_on = False
|
|
|
|
self.async_write_ha_state()
|
2021-11-23 03:47:01 +00:00
|
|
|
raise HomeAssistantError(
|
|
|
|
f"Cannot turn on an inactive program/zone: {self.name}"
|
|
|
|
)
|
|
|
|
|
|
|
|
await self.async_turn_on_when_active(**kwargs)
|
|
|
|
|
2022-08-07 20:50:49 +00:00
|
|
|
@raise_on_request_error
|
2021-11-23 03:47:01 +00:00
|
|
|
async def async_turn_on_when_active(self, **kwargs: Any) -> None:
|
|
|
|
"""Turn the switch on when its associated activity is active."""
|
|
|
|
raise NotImplementedError
|
2017-08-08 07:49:25 +00:00
|
|
|
|
|
|
|
|
2021-11-23 03:47:01 +00:00
|
|
|
class RainMachineEnabledSwitch(RainMachineBaseSwitch):
|
|
|
|
"""Define a RainMachine switch to enable/disable an activity (program or zone)."""
|
|
|
|
|
2022-09-23 23:05:07 +00:00
|
|
|
_attr_entity_category = EntityCategory.CONFIG
|
|
|
|
_attr_icon = "mdi:cog"
|
|
|
|
entity_description: RainMachineActivitySwitchDescription
|
|
|
|
|
2021-11-23 03:47:01 +00:00
|
|
|
@callback
|
|
|
|
def update_from_latest_data(self) -> None:
|
|
|
|
"""Update the entity when new data is received."""
|
|
|
|
self._attr_is_on = self.coordinator.data[self.entity_description.uid]["active"]
|
2018-11-14 20:23:49 +00:00
|
|
|
|
2021-10-08 18:03:47 +00:00
|
|
|
|
2021-11-23 03:47:01 +00:00
|
|
|
class RainMachineProgram(RainMachineActivitySwitch):
|
|
|
|
"""Define a RainMachine program."""
|
2021-10-08 18:03:47 +00:00
|
|
|
|
|
|
|
async def async_start_program(self) -> None:
|
2021-11-23 03:47:01 +00:00
|
|
|
"""Start the program."""
|
2021-10-08 18:03:47 +00:00
|
|
|
await self.async_turn_on()
|
|
|
|
|
|
|
|
async def async_stop_program(self) -> None:
|
2021-11-23 03:47:01 +00:00
|
|
|
"""Stop the program."""
|
2021-10-08 18:03:47 +00:00
|
|
|
await self.async_turn_off()
|
|
|
|
|
2022-08-07 20:50:49 +00:00
|
|
|
@raise_on_request_error
|
2021-11-23 03:47:01 +00:00
|
|
|
async def async_turn_off_when_active(self, **kwargs: Any) -> None:
|
|
|
|
"""Turn the switch off when its associated activity is active."""
|
2022-08-07 20:50:49 +00:00
|
|
|
await self._data.controller.programs.stop(self.entity_description.uid)
|
|
|
|
self._update_activities()
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2022-08-07 20:50:49 +00:00
|
|
|
@raise_on_request_error
|
2021-11-23 03:47:01 +00:00
|
|
|
async def async_turn_on_when_active(self, **kwargs: Any) -> None:
|
|
|
|
"""Turn the switch on when its associated activity is active."""
|
2022-08-07 20:50:49 +00:00
|
|
|
await self._data.controller.programs.start(self.entity_description.uid)
|
|
|
|
self._update_activities()
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2020-03-17 11:00:54 +00:00
|
|
|
@callback
|
|
|
|
def update_from_latest_data(self) -> None:
|
2021-11-23 03:47:01 +00:00
|
|
|
"""Update the entity when new data is received."""
|
|
|
|
data = self.coordinator.data[self.entity_description.uid]
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2021-11-23 03:47:01 +00:00
|
|
|
self._attr_is_on = bool(data["status"])
|
2018-05-08 22:10:03 +00:00
|
|
|
|
2021-11-23 03:47:01 +00:00
|
|
|
next_run: str | None
|
|
|
|
if data.get("nextRun") is None:
|
|
|
|
next_run = None
|
|
|
|
else:
|
2020-01-26 03:27:35 +00:00
|
|
|
next_run = datetime.strptime(
|
2021-11-23 03:47:01 +00:00
|
|
|
f"{data['nextRun']} {data['startTime']}",
|
2020-01-26 03:27:35 +00:00
|
|
|
"%Y-%m-%d %H:%M",
|
|
|
|
).isoformat()
|
|
|
|
|
2021-07-03 16:23:52 +00:00
|
|
|
self._attr_extra_state_attributes.update(
|
2020-01-26 03:27:35 +00:00
|
|
|
{
|
2021-08-25 14:36:25 +00:00
|
|
|
ATTR_ID: self.entity_description.uid,
|
2020-01-26 03:27:35 +00:00
|
|
|
ATTR_NEXT_RUN: next_run,
|
2021-11-23 03:47:01 +00:00
|
|
|
ATTR_SOAK: data.get("soak"),
|
2022-04-13 22:26:30 +00:00
|
|
|
ATTR_STATUS: RUN_STATE_MAP[data["status"]],
|
2021-11-23 03:47:01 +00:00
|
|
|
ATTR_ZONES: [z for z in data["wateringTimes"] if z["active"]],
|
2020-01-26 03:27:35 +00:00
|
|
|
}
|
|
|
|
)
|
2017-08-08 07:49:25 +00:00
|
|
|
|
|
|
|
|
2021-11-23 03:47:01 +00:00
|
|
|
class RainMachineProgramEnabled(RainMachineEnabledSwitch):
|
|
|
|
"""Define a switch to enable/disable a RainMachine program."""
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2022-08-07 20:50:49 +00:00
|
|
|
@raise_on_request_error
|
2021-11-23 03:47:01 +00:00
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
|
|
"""Disable the program."""
|
|
|
|
tasks = [
|
2022-08-07 20:50:49 +00:00
|
|
|
self._data.controller.programs.stop(self.entity_description.uid),
|
|
|
|
self._data.controller.programs.disable(self.entity_description.uid),
|
2021-11-23 03:47:01 +00:00
|
|
|
]
|
|
|
|
await asyncio.gather(*tasks)
|
2022-08-07 20:50:49 +00:00
|
|
|
self._update_activities()
|
2021-11-23 03:47:01 +00:00
|
|
|
|
2022-08-07 20:50:49 +00:00
|
|
|
@raise_on_request_error
|
2021-11-23 03:47:01 +00:00
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
|
|
"""Enable the program."""
|
2022-08-07 20:50:49 +00:00
|
|
|
await self._data.controller.programs.enable(self.entity_description.uid)
|
|
|
|
self._update_activities()
|
2021-10-08 18:03:47 +00:00
|
|
|
|
2021-11-23 03:47:01 +00:00
|
|
|
|
2022-09-23 23:05:07 +00:00
|
|
|
class RainMachineRestrictionSwitch(RainMachineBaseSwitch):
|
|
|
|
"""Define a RainMachine restriction setting."""
|
|
|
|
|
|
|
|
_attr_entity_category = EntityCategory.CONFIG
|
|
|
|
entity_description: RainMachineRestrictionSwitchDescription
|
|
|
|
|
|
|
|
@raise_on_request_error
|
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
|
|
"""Disable the restriction."""
|
|
|
|
await self._data.controller.restrictions.set_universal(
|
|
|
|
{self.entity_description.data_key: False}
|
|
|
|
)
|
|
|
|
self._attr_is_on = False
|
|
|
|
self.async_write_ha_state()
|
|
|
|
|
|
|
|
@raise_on_request_error
|
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
|
|
"""Enable the restriction."""
|
|
|
|
await self._data.controller.restrictions.set_universal(
|
|
|
|
{self.entity_description.data_key: True}
|
|
|
|
)
|
|
|
|
self._attr_is_on = True
|
|
|
|
self.async_write_ha_state()
|
|
|
|
|
|
|
|
@callback
|
|
|
|
def update_from_latest_data(self) -> None:
|
|
|
|
"""Update the entity when new data is received."""
|
|
|
|
self._attr_is_on = self.coordinator.data[self.entity_description.data_key]
|
|
|
|
|
|
|
|
|
2021-11-23 03:47:01 +00:00
|
|
|
class RainMachineZone(RainMachineActivitySwitch):
|
|
|
|
"""Define a RainMachine zone."""
|
2021-10-08 18:03:47 +00:00
|
|
|
|
|
|
|
async def async_start_zone(self, *, zone_run_time: int) -> None:
|
|
|
|
"""Start a particular zone for a certain amount of time."""
|
2022-01-16 23:38:23 +00:00
|
|
|
await self.async_turn_on(duration=zone_run_time)
|
2021-10-08 18:03:47 +00:00
|
|
|
|
|
|
|
async def async_stop_zone(self) -> None:
|
|
|
|
"""Stop a zone."""
|
|
|
|
await self.async_turn_off()
|
|
|
|
|
2022-08-07 20:50:49 +00:00
|
|
|
@raise_on_request_error
|
2021-11-23 03:47:01 +00:00
|
|
|
async def async_turn_off_when_active(self, **kwargs: Any) -> None:
|
|
|
|
"""Turn the switch off when its associated activity is active."""
|
2022-08-07 20:50:49 +00:00
|
|
|
await self._data.controller.zones.stop(self.entity_description.uid)
|
|
|
|
self._update_activities()
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2022-08-07 20:50:49 +00:00
|
|
|
@raise_on_request_error
|
2021-11-23 03:47:01 +00:00
|
|
|
async def async_turn_on_when_active(self, **kwargs: Any) -> None:
|
|
|
|
"""Turn the switch on when its associated activity is active."""
|
2022-10-27 04:27:08 +00:00
|
|
|
# 1. Use duration parameter if provided from service call
|
|
|
|
duration = kwargs.get(CONF_DURATION)
|
|
|
|
if not duration:
|
|
|
|
if (
|
|
|
|
self._entry.options[CONF_USE_APP_RUN_TIMES]
|
|
|
|
and ATTR_ZONE_RUN_TIME in self._attr_extra_state_attributes
|
|
|
|
):
|
|
|
|
# 2. Use app's zone-specific default, if enabled and available
|
|
|
|
duration = self._attr_extra_state_attributes[ATTR_ZONE_RUN_TIME]
|
|
|
|
else:
|
|
|
|
# 3. Fall back to global zone default duration
|
|
|
|
duration = self._entry.options[CONF_DEFAULT_ZONE_RUN_TIME]
|
2022-08-07 20:50:49 +00:00
|
|
|
await self._data.controller.zones.start(
|
|
|
|
self.entity_description.uid,
|
2022-10-27 04:27:08 +00:00
|
|
|
duration,
|
2020-01-26 03:27:35 +00:00
|
|
|
)
|
2022-08-07 20:50:49 +00:00
|
|
|
self._update_activities()
|
2017-08-08 07:49:25 +00:00
|
|
|
|
2020-03-17 11:00:54 +00:00
|
|
|
@callback
|
|
|
|
def update_from_latest_data(self) -> None:
|
2021-11-23 03:47:01 +00:00
|
|
|
"""Update the entity when new data is received."""
|
|
|
|
data = self.coordinator.data[self.entity_description.uid]
|
2020-11-06 09:58:50 +00:00
|
|
|
|
2021-11-23 03:47:01 +00:00
|
|
|
self._attr_is_on = bool(data["state"])
|
2020-01-26 03:27:35 +00:00
|
|
|
|
2022-05-30 21:36:58 +00:00
|
|
|
attrs = {
|
|
|
|
ATTR_CURRENT_CYCLE: data["cycle"],
|
|
|
|
ATTR_ID: data["uid"],
|
|
|
|
ATTR_NO_CYCLES: data["noOfCycles"],
|
2022-05-30 23:48:42 +00:00
|
|
|
ATTR_RESTRICTIONS: data["restriction"],
|
2022-05-30 21:36:58 +00:00
|
|
|
ATTR_SLOPE: SLOPE_TYPE_MAP.get(data["slope"], 99),
|
|
|
|
ATTR_SOIL_TYPE: SOIL_TYPE_MAP.get(data["soil"], 99),
|
|
|
|
ATTR_SPRINKLER_TYPE: SPRINKLER_TYPE_MAP.get(data["group_id"], 99),
|
|
|
|
ATTR_STATUS: RUN_STATE_MAP[data["state"]],
|
|
|
|
ATTR_SUN_EXPOSURE: SUN_EXPOSURE_MAP.get(data.get("sun")),
|
|
|
|
ATTR_VEGETATION_TYPE: VEGETATION_MAP.get(data["type"], 99),
|
|
|
|
}
|
|
|
|
|
|
|
|
if "waterSense" in data:
|
|
|
|
if "area" in data["waterSense"]:
|
|
|
|
attrs[ATTR_AREA] = round(data["waterSense"]["area"], 2)
|
|
|
|
if "fieldCapacity" in data["waterSense"]:
|
|
|
|
attrs[ATTR_FIELD_CAPACITY] = round(
|
|
|
|
data["waterSense"]["fieldCapacity"], 2
|
|
|
|
)
|
|
|
|
if "precipitationRate" in data["waterSense"]:
|
|
|
|
attrs[ATTR_PRECIP_RATE] = round(
|
|
|
|
data["waterSense"]["precipitationRate"], 2
|
|
|
|
)
|
|
|
|
|
2022-10-27 04:27:08 +00:00
|
|
|
if self._entry.options[CONF_USE_APP_RUN_TIMES]:
|
|
|
|
provision_data = self._data.coordinators[DATA_PROVISION_SETTINGS].data
|
|
|
|
if zone_durations := provision_data.get("system", {}).get("zoneDuration"):
|
|
|
|
attrs[ATTR_ZONE_RUN_TIME] = zone_durations[
|
|
|
|
list(self.coordinator.data).index(self.entity_description.uid)
|
|
|
|
]
|
|
|
|
|
2022-05-30 21:36:58 +00:00
|
|
|
self._attr_extra_state_attributes.update(attrs)
|
2021-11-23 03:47:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
class RainMachineZoneEnabled(RainMachineEnabledSwitch):
|
|
|
|
"""Define a switch to enable/disable a RainMachine zone."""
|
|
|
|
|
2022-08-07 20:50:49 +00:00
|
|
|
@raise_on_request_error
|
2021-11-23 03:47:01 +00:00
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
|
|
"""Disable the zone."""
|
|
|
|
tasks = [
|
2022-08-07 20:50:49 +00:00
|
|
|
self._data.controller.zones.stop(self.entity_description.uid),
|
|
|
|
self._data.controller.zones.disable(self.entity_description.uid),
|
2021-11-23 03:47:01 +00:00
|
|
|
]
|
|
|
|
await asyncio.gather(*tasks)
|
2022-08-07 20:50:49 +00:00
|
|
|
self._update_activities()
|
2021-11-23 03:47:01 +00:00
|
|
|
|
2022-08-07 20:50:49 +00:00
|
|
|
@raise_on_request_error
|
2021-11-23 03:47:01 +00:00
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
|
|
"""Enable the zone."""
|
2022-08-07 20:50:49 +00:00
|
|
|
await self._data.controller.zones.enable(self.entity_description.uid)
|
|
|
|
self._update_activities()
|