Finish EntityDescription implementation for RainMachine (#55180)

This commit is contained in:
Aaron Bach 2021-08-25 08:36:25 -06:00 committed by GitHub
parent 53851cb1b4
commit 6bc5c1c9af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 178 additions and 158 deletions

View File

@ -845,6 +845,7 @@ omit =
homeassistant/components/raincloud/* homeassistant/components/raincloud/*
homeassistant/components/rainmachine/__init__.py homeassistant/components/rainmachine/__init__.py
homeassistant/components/rainmachine/binary_sensor.py homeassistant/components/rainmachine/binary_sensor.py
homeassistant/components/rainmachine/model.py
homeassistant/components/rainmachine/sensor.py homeassistant/components/rainmachine/sensor.py
homeassistant/components/rainmachine/switch.py homeassistant/components/rainmachine/switch.py
homeassistant/components/raspihats/* homeassistant/components/raspihats/*

View File

@ -22,6 +22,7 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers import aiohttp_client, config_validation as cv
import homeassistant.helpers.device_registry as dr import homeassistant.helpers.device_registry as dr
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import (
CoordinatorEntity, CoordinatorEntity,
DataUpdateCoordinator, DataUpdateCoordinator,
@ -180,7 +181,7 @@ class RainMachineEntity(CoordinatorEntity):
self, self,
coordinator: DataUpdateCoordinator, coordinator: DataUpdateCoordinator,
controller: Controller, controller: Controller,
entity_type: str, description: EntityDescription,
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator) super().__init__(coordinator)
@ -200,9 +201,9 @@ class RainMachineEntity(CoordinatorEntity):
# The colons are removed from the device MAC simply because that value # The colons are removed from the device MAC simply because that value
# (unnecessarily) makes up the existing unique ID formula and we want to avoid # (unnecessarily) makes up the existing unique ID formula and we want to avoid
# a breaking change: # a breaking change:
self._attr_unique_id = f"{controller.mac.replace(':', '')}_{entity_type}" self._attr_unique_id = f"{controller.mac.replace(':', '')}_{description.key}"
self._controller = controller self._controller = controller
self._entity_type = entity_type self.entity_description = description
@callback @callback
def _handle_coordinator_update(self) -> None: def _handle_coordinator_update(self) -> None:

View File

@ -1,13 +1,14 @@
"""This platform provides binary sensors for key RainMachine data.""" """This platform provides binary sensors for key RainMachine data."""
from dataclasses import dataclass
from functools import partial from functools import partial
from regenmaschine.controller import Controller from homeassistant.components.binary_sensor import (
BinarySensorEntity,
from homeassistant.components.binary_sensor import BinarySensorEntity BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from . import RainMachineEntity from . import RainMachineEntity
from .const import ( from .const import (
@ -18,6 +19,7 @@ from .const import (
DATA_RESTRICTIONS_UNIVERSAL, DATA_RESTRICTIONS_UNIVERSAL,
DOMAIN, DOMAIN,
) )
from .model import RainMachineSensorDescriptionMixin
TYPE_FLOW_SENSOR = "flow_sensor" TYPE_FLOW_SENSOR = "flow_sensor"
TYPE_FREEZE = "freeze" TYPE_FREEZE = "freeze"
@ -29,47 +31,75 @@ TYPE_RAINDELAY = "raindelay"
TYPE_RAINSENSOR = "rainsensor" TYPE_RAINSENSOR = "rainsensor"
TYPE_WEEKDAY = "weekday" TYPE_WEEKDAY = "weekday"
BINARY_SENSORS = {
TYPE_FLOW_SENSOR: ("Flow Sensor", "mdi:water-pump", True, DATA_PROVISION_SETTINGS), @dataclass
TYPE_FREEZE: ("Freeze Restrictions", "mdi:cancel", True, DATA_RESTRICTIONS_CURRENT), class RainMachineBinarySensorDescription(
TYPE_FREEZE_PROTECTION: ( BinarySensorEntityDescription, RainMachineSensorDescriptionMixin
"Freeze Protection", ):
"mdi:weather-snowy", """Describe a RainMachine binary sensor."""
True,
DATA_RESTRICTIONS_UNIVERSAL,
BINARY_SENSOR_DESCRIPTIONS = (
RainMachineBinarySensorDescription(
key=TYPE_FLOW_SENSOR,
name="Flow Sensor",
icon="mdi:water-pump",
api_category=DATA_PROVISION_SETTINGS,
), ),
TYPE_HOT_DAYS: ( RainMachineBinarySensorDescription(
"Extra Water on Hot Days", key=TYPE_FREEZE,
"mdi:thermometer-lines", name="Freeze Restrictions",
True, icon="mdi:cancel",
DATA_RESTRICTIONS_UNIVERSAL, api_category=DATA_RESTRICTIONS_CURRENT,
), ),
TYPE_HOURLY: ( RainMachineBinarySensorDescription(
"Hourly Restrictions", key=TYPE_FREEZE_PROTECTION,
"mdi:cancel", name="Freeze Protection",
False, icon="mdi:weather-snowy",
DATA_RESTRICTIONS_CURRENT, api_category=DATA_RESTRICTIONS_UNIVERSAL,
), ),
TYPE_MONTH: ("Month Restrictions", "mdi:cancel", False, DATA_RESTRICTIONS_CURRENT), RainMachineBinarySensorDescription(
TYPE_RAINDELAY: ( key=TYPE_HOT_DAYS,
"Rain Delay Restrictions", name="Extra Water on Hot Days",
"mdi:cancel", icon="mdi:thermometer-lines",
False, api_category=DATA_RESTRICTIONS_UNIVERSAL,
DATA_RESTRICTIONS_CURRENT,
), ),
TYPE_RAINSENSOR: ( RainMachineBinarySensorDescription(
"Rain Sensor Restrictions", key=TYPE_HOURLY,
"mdi:cancel", name="Hourly Restrictions",
False, icon="mdi:cancel",
DATA_RESTRICTIONS_CURRENT, entity_registry_enabled_default=False,
api_category=DATA_RESTRICTIONS_CURRENT,
), ),
TYPE_WEEKDAY: ( RainMachineBinarySensorDescription(
"Weekday Restrictions", key=TYPE_MONTH,
"mdi:cancel", name="Month Restrictions",
False, icon="mdi:cancel",
DATA_RESTRICTIONS_CURRENT, entity_registry_enabled_default=False,
api_category=DATA_RESTRICTIONS_CURRENT,
), ),
} RainMachineBinarySensorDescription(
key=TYPE_RAINDELAY,
name="Rain Delay Restrictions",
icon="mdi:cancel",
entity_registry_enabled_default=False,
api_category=DATA_RESTRICTIONS_CURRENT,
),
RainMachineBinarySensorDescription(
key=TYPE_RAINSENSOR,
name="Rain Sensor Restrictions",
icon="mdi:cancel",
entity_registry_enabled_default=False,
api_category=DATA_RESTRICTIONS_CURRENT,
),
RainMachineBinarySensorDescription(
key=TYPE_WEEKDAY,
name="Weekday Restrictions",
icon="mdi:cancel",
entity_registry_enabled_default=False,
api_category=DATA_RESTRICTIONS_CURRENT,
),
)
async def async_setup_entry( async def async_setup_entry(
@ -101,74 +131,49 @@ async def async_setup_entry(
async_add_entities( async_add_entities(
[ [
async_get_sensor(api_category)( async_get_sensor(description.api_category)(controller, description)
controller, sensor_type, name, icon, enabled_by_default for description in BINARY_SENSOR_DESCRIPTIONS
)
for (
sensor_type,
(name, icon, enabled_by_default, api_category),
) in BINARY_SENSORS.items()
] ]
) )
class RainMachineBinarySensor(RainMachineEntity, BinarySensorEntity): class CurrentRestrictionsBinarySensor(RainMachineEntity, BinarySensorEntity):
"""Define a general RainMachine binary sensor."""
def __init__(
self,
coordinator: DataUpdateCoordinator,
controller: Controller,
sensor_type: str,
name: str,
icon: str,
enabled_by_default: bool,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator, controller, sensor_type)
self._attr_entity_registry_enabled_default = enabled_by_default
self._attr_icon = icon
self._attr_name = name
class CurrentRestrictionsBinarySensor(RainMachineBinarySensor):
"""Define a binary sensor that handles current restrictions data.""" """Define a binary sensor that handles current restrictions data."""
@callback @callback
def update_from_latest_data(self) -> None: def update_from_latest_data(self) -> None:
"""Update the state.""" """Update the state."""
if self._entity_type == TYPE_FREEZE: if self.entity_description.key == TYPE_FREEZE:
self._attr_is_on = self.coordinator.data["freeze"] self._attr_is_on = self.coordinator.data["freeze"]
elif self._entity_type == TYPE_HOURLY: elif self.entity_description.key == TYPE_HOURLY:
self._attr_is_on = self.coordinator.data["hourly"] self._attr_is_on = self.coordinator.data["hourly"]
elif self._entity_type == TYPE_MONTH: elif self.entity_description.key == TYPE_MONTH:
self._attr_is_on = self.coordinator.data["month"] self._attr_is_on = self.coordinator.data["month"]
elif self._entity_type == TYPE_RAINDELAY: elif self.entity_description.key == TYPE_RAINDELAY:
self._attr_is_on = self.coordinator.data["rainDelay"] self._attr_is_on = self.coordinator.data["rainDelay"]
elif self._entity_type == TYPE_RAINSENSOR: elif self.entity_description.key == TYPE_RAINSENSOR:
self._attr_is_on = self.coordinator.data["rainSensor"] self._attr_is_on = self.coordinator.data["rainSensor"]
elif self._entity_type == TYPE_WEEKDAY: elif self.entity_description.key == TYPE_WEEKDAY:
self._attr_is_on = self.coordinator.data["weekDay"] self._attr_is_on = self.coordinator.data["weekDay"]
class ProvisionSettingsBinarySensor(RainMachineBinarySensor): class ProvisionSettingsBinarySensor(RainMachineEntity, BinarySensorEntity):
"""Define a binary sensor that handles provisioning data.""" """Define a binary sensor that handles provisioning data."""
@callback @callback
def update_from_latest_data(self) -> None: def update_from_latest_data(self) -> None:
"""Update the state.""" """Update the state."""
if self._entity_type == TYPE_FLOW_SENSOR: if self.entity_description.key == TYPE_FLOW_SENSOR:
self._attr_is_on = self.coordinator.data["system"].get("useFlowSensor") self._attr_is_on = self.coordinator.data["system"].get("useFlowSensor")
class UniversalRestrictionsBinarySensor(RainMachineBinarySensor): class UniversalRestrictionsBinarySensor(RainMachineEntity, BinarySensorEntity):
"""Define a binary sensor that handles universal restrictions data.""" """Define a binary sensor that handles universal restrictions data."""
@callback @callback
def update_from_latest_data(self) -> None: def update_from_latest_data(self) -> None:
"""Update the state.""" """Update the state."""
if self._entity_type == TYPE_FREEZE_PROTECTION: if self.entity_description.key == TYPE_FREEZE_PROTECTION:
self._attr_is_on = self.coordinator.data["freezeProtectEnabled"] self._attr_is_on = self.coordinator.data["freezeProtectEnabled"]
elif self._entity_type == TYPE_HOT_DAYS: elif self.entity_description.key == TYPE_HOT_DAYS:
self._attr_is_on = self.coordinator.data["hotDaysExtraWatering"] self._attr_is_on = self.coordinator.data["hotDaysExtraWatering"]

View File

@ -0,0 +1,9 @@
"""Define RainMachine data models."""
from dataclasses import dataclass
@dataclass
class RainMachineSensorDescriptionMixin:
"""Define an entity description mixin for binary and regular sensors."""
api_category: str

View File

@ -4,8 +4,6 @@ from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from functools import partial from functools import partial
from regenmaschine.controller import Controller
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
@ -15,7 +13,6 @@ from homeassistant.const import (
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from . import RainMachineEntity from . import RainMachineEntity
from .const import ( from .const import (
@ -25,6 +22,7 @@ from .const import (
DATA_RESTRICTIONS_UNIVERSAL, DATA_RESTRICTIONS_UNIVERSAL,
DOMAIN, DOMAIN,
) )
from .model import RainMachineSensorDescriptionMixin
TYPE_FLOW_SENSOR_CLICK_M3 = "flow_sensor_clicks_cubic_meter" TYPE_FLOW_SENSOR_CLICK_M3 = "flow_sensor_clicks_cubic_meter"
TYPE_FLOW_SENSOR_CONSUMED_LITERS = "flow_sensor_consumed_liters" TYPE_FLOW_SENSOR_CONSUMED_LITERS = "flow_sensor_consumed_liters"
@ -34,21 +32,14 @@ TYPE_FREEZE_TEMP = "freeze_protect_temp"
@dataclass @dataclass
class RainmachineRequiredKeysMixin: class RainMachineSensorEntityDescription(
"""Mixin for required keys.""" SensorEntityDescription, RainMachineSensorDescriptionMixin
api_category: str
@dataclass
class RainmachineSensorEntityDescription(
SensorEntityDescription, RainmachineRequiredKeysMixin
): ):
"""Describes Rainmachine sensor entity.""" """Describe a RainMachine sensor."""
SENSOR_TYPES: tuple[RainmachineSensorEntityDescription, ...] = ( SENSOR_DESCRIPTIONS = (
RainmachineSensorEntityDescription( RainMachineSensorEntityDescription(
key=TYPE_FLOW_SENSOR_CLICK_M3, key=TYPE_FLOW_SENSOR_CLICK_M3,
name="Flow Sensor Clicks", name="Flow Sensor Clicks",
icon="mdi:water-pump", icon="mdi:water-pump",
@ -56,7 +47,7 @@ SENSOR_TYPES: tuple[RainmachineSensorEntityDescription, ...] = (
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
api_category=DATA_PROVISION_SETTINGS, api_category=DATA_PROVISION_SETTINGS,
), ),
RainmachineSensorEntityDescription( RainMachineSensorEntityDescription(
key=TYPE_FLOW_SENSOR_CONSUMED_LITERS, key=TYPE_FLOW_SENSOR_CONSUMED_LITERS,
name="Flow Sensor Consumed Liters", name="Flow Sensor Consumed Liters",
icon="mdi:water-pump", icon="mdi:water-pump",
@ -64,7 +55,7 @@ SENSOR_TYPES: tuple[RainmachineSensorEntityDescription, ...] = (
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
api_category=DATA_PROVISION_SETTINGS, api_category=DATA_PROVISION_SETTINGS,
), ),
RainmachineSensorEntityDescription( RainMachineSensorEntityDescription(
key=TYPE_FLOW_SENSOR_START_INDEX, key=TYPE_FLOW_SENSOR_START_INDEX,
name="Flow Sensor Start Index", name="Flow Sensor Start Index",
icon="mdi:water-pump", icon="mdi:water-pump",
@ -72,7 +63,7 @@ SENSOR_TYPES: tuple[RainmachineSensorEntityDescription, ...] = (
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
api_category=DATA_PROVISION_SETTINGS, api_category=DATA_PROVISION_SETTINGS,
), ),
RainmachineSensorEntityDescription( RainMachineSensorEntityDescription(
key=TYPE_FLOW_SENSOR_WATERING_CLICKS, key=TYPE_FLOW_SENSOR_WATERING_CLICKS,
name="Flow Sensor Clicks", name="Flow Sensor Clicks",
icon="mdi:water-pump", icon="mdi:water-pump",
@ -80,13 +71,12 @@ SENSOR_TYPES: tuple[RainmachineSensorEntityDescription, ...] = (
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
api_category=DATA_PROVISION_SETTINGS, api_category=DATA_PROVISION_SETTINGS,
), ),
RainmachineSensorEntityDescription( RainMachineSensorEntityDescription(
key=TYPE_FREEZE_TEMP, key=TYPE_FREEZE_TEMP,
name="Freeze Protect Temperature", name="Freeze Protect Temperature",
icon="mdi:thermometer", icon="mdi:thermometer",
native_unit_of_measurement=TEMP_CELSIUS, native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
entity_registry_enabled_default=True,
api_category=DATA_RESTRICTIONS_UNIVERSAL, api_category=DATA_RESTRICTIONS_UNIVERSAL,
), ),
) )
@ -116,38 +106,22 @@ async def async_setup_entry(
async_add_entities( async_add_entities(
[ [
async_get_sensor(description.api_category)(controller, description) async_get_sensor(description.api_category)(controller, description)
for description in SENSOR_TYPES for description in SENSOR_DESCRIPTIONS
] ]
) )
class RainMachineSensor(RainMachineEntity, SensorEntity): class ProvisionSettingsSensor(RainMachineEntity, SensorEntity):
"""Define a general RainMachine sensor."""
entity_description: RainmachineSensorEntityDescription
def __init__(
self,
coordinator: DataUpdateCoordinator,
controller: Controller,
description: RainmachineSensorEntityDescription,
) -> None:
"""Initialize."""
super().__init__(coordinator, controller, description.key)
self.entity_description = description
class ProvisionSettingsSensor(RainMachineSensor):
"""Define a sensor that handles provisioning data.""" """Define a sensor that handles provisioning data."""
@callback @callback
def update_from_latest_data(self) -> None: def update_from_latest_data(self) -> None:
"""Update the state.""" """Update the state."""
if self._entity_type == TYPE_FLOW_SENSOR_CLICK_M3: if self.entity_description.key == TYPE_FLOW_SENSOR_CLICK_M3:
self._attr_native_value = self.coordinator.data["system"].get( self._attr_native_value = self.coordinator.data["system"].get(
"flowSensorClicksPerCubicMeter" "flowSensorClicksPerCubicMeter"
) )
elif self._entity_type == TYPE_FLOW_SENSOR_CONSUMED_LITERS: elif self.entity_description.key == TYPE_FLOW_SENSOR_CONSUMED_LITERS:
clicks = self.coordinator.data["system"].get("flowSensorWateringClicks") clicks = self.coordinator.data["system"].get("flowSensorWateringClicks")
clicks_per_m3 = self.coordinator.data["system"].get( clicks_per_m3 = self.coordinator.data["system"].get(
"flowSensorClicksPerCubicMeter" "flowSensorClicksPerCubicMeter"
@ -157,21 +131,21 @@ class ProvisionSettingsSensor(RainMachineSensor):
self._attr_native_value = (clicks * 1000) / clicks_per_m3 self._attr_native_value = (clicks * 1000) / clicks_per_m3
else: else:
self._attr_native_value = None self._attr_native_value = None
elif self._entity_type == TYPE_FLOW_SENSOR_START_INDEX: elif self.entity_description.key == TYPE_FLOW_SENSOR_START_INDEX:
self._attr_native_value = self.coordinator.data["system"].get( self._attr_native_value = self.coordinator.data["system"].get(
"flowSensorStartIndex" "flowSensorStartIndex"
) )
elif self._entity_type == TYPE_FLOW_SENSOR_WATERING_CLICKS: elif self.entity_description.key == TYPE_FLOW_SENSOR_WATERING_CLICKS:
self._attr_native_value = self.coordinator.data["system"].get( self._attr_native_value = self.coordinator.data["system"].get(
"flowSensorWateringClicks" "flowSensorWateringClicks"
) )
class UniversalRestrictionsSensor(RainMachineSensor): class UniversalRestrictionsSensor(RainMachineEntity, SensorEntity):
"""Define a sensor that handles universal restrictions data.""" """Define a sensor that handles universal restrictions data."""
@callback @callback
def update_from_latest_data(self) -> None: def update_from_latest_data(self) -> None:
"""Update the state.""" """Update the state."""
if self._entity_type == TYPE_FREEZE_TEMP: if self.entity_description.key == TYPE_FREEZE_TEMP:
self._attr_native_value = self.coordinator.data["freezeProtectTemp"] self._attr_native_value = self.coordinator.data["freezeProtectTemp"]

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Coroutine from collections.abc import Coroutine
from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from typing import Any from typing import Any
@ -9,7 +10,7 @@ from regenmaschine.controller import Controller
from regenmaschine.errors import RequestError from regenmaschine.errors import RequestError
import voluptuous as vol import voluptuous as vol
from homeassistant.components.switch import SwitchEntity from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ID from homeassistant.const import ATTR_ID
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
@ -115,6 +116,20 @@ SWITCH_TYPE_PROGRAM = "program"
SWITCH_TYPE_ZONE = "zone" SWITCH_TYPE_ZONE = "zone"
@dataclass
class RainMachineSwitchDescriptionMixin:
"""Define an entity description mixin for switches."""
uid: int
@dataclass
class RainMachineSwitchDescription(
SwitchEntityDescription, RainMachineSwitchDescriptionMixin
):
"""Describe a RainMachine switch."""
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
@ -166,17 +181,33 @@ async def async_setup_entry(
] ]
zones_coordinator = hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id][DATA_ZONES] zones_coordinator = hass.data[DOMAIN][DATA_COORDINATOR][entry.entry_id][DATA_ZONES]
entities: list[RainMachineProgram | RainMachineZone] = [] entities: list[RainMachineProgram | RainMachineZone] = [
for uid, program in programs_coordinator.data.items():
entities.append(
RainMachineProgram( RainMachineProgram(
programs_coordinator, controller, uid, program["name"], entry programs_coordinator,
controller,
entry,
RainMachineSwitchDescription(
key=f"RainMachineProgram_{uid}",
name=program["name"],
uid=uid,
),
) )
for uid, program in programs_coordinator.data.items()
]
entities.extend(
[
RainMachineZone(
zones_coordinator,
controller,
entry,
RainMachineSwitchDescription(
key=f"RainMachineZone_{uid}",
name=zone["name"],
uid=uid,
),
) )
for uid, zone in zones_coordinator.data.items(): for uid, zone in zones_coordinator.data.items()
entities.append( ]
RainMachineZone(zones_coordinator, controller, uid, zone["name"], entry)
) )
async_add_entities(entities) async_add_entities(entities)
@ -186,35 +217,28 @@ class RainMachineSwitch(RainMachineEntity, SwitchEntity):
"""A class to represent a generic RainMachine switch.""" """A class to represent a generic RainMachine switch."""
_attr_icon = DEFAULT_ICON _attr_icon = DEFAULT_ICON
entity_description: RainMachineSwitchDescription
def __init__( def __init__(
self, self,
coordinator: DataUpdateCoordinator, coordinator: DataUpdateCoordinator,
controller: Controller, controller: Controller,
uid: int,
name: str,
entry: ConfigEntry, entry: ConfigEntry,
description: RainMachineSwitchDescription,
) -> None: ) -> None:
"""Initialize a generic RainMachine switch.""" """Initialize a generic RainMachine switch."""
super().__init__(coordinator, controller, type(self).__name__) super().__init__(coordinator, controller, description)
self._attr_is_on = False self._attr_is_on = False
self._attr_name = name self._data = coordinator.data[self.entity_description.uid]
self._data = coordinator.data[uid]
self._entry = entry self._entry = entry
self._is_active = True self._is_active = True
self._uid = uid
@property @property
def available(self) -> bool: def available(self) -> bool:
"""Return True if entity is available.""" """Return True if entity is available."""
return super().available and self._is_active return super().available and self._is_active
@property
def unique_id(self) -> str:
"""Return a unique, Home Assistant friendly identifier for this entity."""
return f"{super().unique_id}_{self._uid}"
async def _async_run_switch_coroutine(self, api_coro: Coroutine) -> None: async def _async_run_switch_coroutine(self, api_coro: Coroutine) -> None:
"""Run a coroutine to toggle the switch.""" """Run a coroutine to toggle the switch."""
try: try:
@ -222,7 +246,7 @@ class RainMachineSwitch(RainMachineEntity, SwitchEntity):
except RequestError as err: except RequestError as err:
LOGGER.error( LOGGER.error(
'Error while toggling %s "%s": %s', 'Error while toggling %s "%s": %s',
self._entity_type, self.entity_description.key,
self.unique_id, self.unique_id,
err, err,
) )
@ -231,7 +255,7 @@ class RainMachineSwitch(RainMachineEntity, SwitchEntity):
if resp["statusCode"] != 0: if resp["statusCode"] != 0:
LOGGER.error( LOGGER.error(
'Error while toggling %s "%s": %s', 'Error while toggling %s "%s": %s',
self._entity_type, self.entity_description.key,
self.unique_id, self.unique_id,
resp["message"], resp["message"],
) )
@ -301,7 +325,7 @@ class RainMachineSwitch(RainMachineEntity, SwitchEntity):
@callback @callback
def update_from_latest_data(self) -> None: def update_from_latest_data(self) -> None:
"""Update the state.""" """Update the state."""
self._data = self.coordinator.data[self._uid] self._data = self.coordinator.data[self.entity_description.uid]
self._is_active = self._data["active"] self._is_active = self._data["active"]
@ -316,13 +340,13 @@ class RainMachineProgram(RainMachineSwitch):
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the program off.""" """Turn the program off."""
await self._async_run_switch_coroutine( await self._async_run_switch_coroutine(
self._controller.programs.stop(self._uid) self._controller.programs.stop(self.entity_description.uid)
) )
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the program on.""" """Turn the program on."""
await self._async_run_switch_coroutine( await self._async_run_switch_coroutine(
self._controller.programs.start(self._uid) self._controller.programs.start(self.entity_description.uid)
) )
@callback @callback
@ -341,10 +365,14 @@ class RainMachineProgram(RainMachineSwitch):
self._attr_extra_state_attributes.update( self._attr_extra_state_attributes.update(
{ {
ATTR_ID: self._uid, ATTR_ID: self.entity_description.uid,
ATTR_NEXT_RUN: next_run, ATTR_NEXT_RUN: next_run,
ATTR_SOAK: self.coordinator.data[self._uid].get("soak"), ATTR_SOAK: self.coordinator.data[self.entity_description.uid].get(
ATTR_STATUS: RUN_STATUS_MAP[self.coordinator.data[self._uid]["status"]], "soak"
),
ATTR_STATUS: RUN_STATUS_MAP[
self.coordinator.data[self.entity_description.uid]["status"]
],
ATTR_ZONES: ", ".join(z["name"] for z in self.zones), ATTR_ZONES: ", ".join(z["name"] for z in self.zones),
} }
) )
@ -355,13 +383,15 @@ class RainMachineZone(RainMachineSwitch):
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the zone off.""" """Turn the zone off."""
await self._async_run_switch_coroutine(self._controller.zones.stop(self._uid)) await self._async_run_switch_coroutine(
self._controller.zones.stop(self.entity_description.uid)
)
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the zone on.""" """Turn the zone on."""
await self._async_run_switch_coroutine( await self._async_run_switch_coroutine(
self._controller.zones.start( self._controller.zones.start(
self._uid, self.entity_description.uid,
self._entry.options[CONF_ZONE_RUN_TIME], self._entry.options[CONF_ZONE_RUN_TIME],
) )
) )