Alter RainMachine to not create entities if the underlying data is missing (#72733)

pull/72794/head
Aaron Bach 2022-05-31 13:09:07 -06:00 committed by GitHub
parent 638992f9c4
commit a3e1b285cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 49 additions and 46 deletions

View File

@ -970,6 +970,7 @@ omit =
homeassistant/components/rainmachine/model.py
homeassistant/components/rainmachine/sensor.py
homeassistant/components/rainmachine/switch.py
homeassistant/components/rainmachine/util.py
homeassistant/components/raspyrfm/*
homeassistant/components/recollect_waste/__init__.py
homeassistant/components/recollect_waste/sensor.py

View File

@ -50,10 +50,7 @@ from .const import (
LOGGER,
)
DEFAULT_ATTRIBUTION = "Data provided by Green Electronics LLC"
DEFAULT_ICON = "mdi:water"
DEFAULT_SSL = True
DEFAULT_UPDATE_INTERVAL = timedelta(seconds=15)
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)

View File

@ -1,6 +1,5 @@
"""This platform provides binary sensors for key RainMachine data."""
from dataclasses import dataclass
from functools import partial
from homeassistant.components.binary_sensor import (
BinarySensorEntity,
@ -21,6 +20,7 @@ from .const import (
DOMAIN,
)
from .model import RainMachineDescriptionMixinApiCategory
from .util import key_exists
TYPE_FLOW_SENSOR = "flow_sensor"
TYPE_FREEZE = "freeze"
@ -46,6 +46,7 @@ BINARY_SENSOR_DESCRIPTIONS = (
name="Flow Sensor",
icon="mdi:water-pump",
api_category=DATA_PROVISION_SETTINGS,
data_key="useFlowSensor",
),
RainMachineBinarySensorDescription(
key=TYPE_FREEZE,
@ -53,6 +54,7 @@ BINARY_SENSOR_DESCRIPTIONS = (
icon="mdi:cancel",
entity_category=EntityCategory.DIAGNOSTIC,
api_category=DATA_RESTRICTIONS_CURRENT,
data_key="freeze",
),
RainMachineBinarySensorDescription(
key=TYPE_FREEZE_PROTECTION,
@ -60,6 +62,7 @@ BINARY_SENSOR_DESCRIPTIONS = (
icon="mdi:weather-snowy",
entity_category=EntityCategory.DIAGNOSTIC,
api_category=DATA_RESTRICTIONS_UNIVERSAL,
data_key="freezeProtectEnabled",
),
RainMachineBinarySensorDescription(
key=TYPE_HOT_DAYS,
@ -67,6 +70,7 @@ BINARY_SENSOR_DESCRIPTIONS = (
icon="mdi:thermometer-lines",
entity_category=EntityCategory.DIAGNOSTIC,
api_category=DATA_RESTRICTIONS_UNIVERSAL,
data_key="hotDaysExtraWatering",
),
RainMachineBinarySensorDescription(
key=TYPE_HOURLY,
@ -75,6 +79,7 @@ BINARY_SENSOR_DESCRIPTIONS = (
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
api_category=DATA_RESTRICTIONS_CURRENT,
data_key="hourly",
),
RainMachineBinarySensorDescription(
key=TYPE_MONTH,
@ -83,6 +88,7 @@ BINARY_SENSOR_DESCRIPTIONS = (
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
api_category=DATA_RESTRICTIONS_CURRENT,
data_key="month",
),
RainMachineBinarySensorDescription(
key=TYPE_RAINDELAY,
@ -91,6 +97,7 @@ BINARY_SENSOR_DESCRIPTIONS = (
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
api_category=DATA_RESTRICTIONS_CURRENT,
data_key="rainDelay",
),
RainMachineBinarySensorDescription(
key=TYPE_RAINSENSOR,
@ -99,6 +106,7 @@ BINARY_SENSOR_DESCRIPTIONS = (
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
api_category=DATA_RESTRICTIONS_CURRENT,
data_key="rainSensor",
),
RainMachineBinarySensorDescription(
key=TYPE_WEEKDAY,
@ -107,6 +115,7 @@ BINARY_SENSOR_DESCRIPTIONS = (
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
api_category=DATA_RESTRICTIONS_CURRENT,
data_key="weekDay",
),
)
@ -118,35 +127,20 @@ async def async_setup_entry(
controller = hass.data[DOMAIN][entry.entry_id][DATA_CONTROLLER]
coordinators = hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR]
@callback
def async_get_sensor_by_api_category(api_category: str) -> partial:
"""Generate the appropriate sensor object for an API category."""
if api_category == DATA_PROVISION_SETTINGS:
return partial(
ProvisionSettingsBinarySensor,
entry,
coordinators[DATA_PROVISION_SETTINGS],
)
if api_category == DATA_RESTRICTIONS_CURRENT:
return partial(
CurrentRestrictionsBinarySensor,
entry,
coordinators[DATA_RESTRICTIONS_CURRENT],
)
return partial(
UniversalRestrictionsBinarySensor,
entry,
coordinators[DATA_RESTRICTIONS_UNIVERSAL],
)
api_category_sensor_map = {
DATA_PROVISION_SETTINGS: ProvisionSettingsBinarySensor,
DATA_RESTRICTIONS_CURRENT: CurrentRestrictionsBinarySensor,
DATA_RESTRICTIONS_UNIVERSAL: UniversalRestrictionsBinarySensor,
}
async_add_entities(
[
async_get_sensor_by_api_category(description.api_category)(
controller, description
api_category_sensor_map[description.api_category](
entry, coordinator, controller, description
)
for description in BINARY_SENSOR_DESCRIPTIONS
if (coordinator := coordinators[description.api_category]) is not None
and key_exists(coordinator.data, description.data_key)
]
)

View File

@ -7,6 +7,7 @@ class RainMachineDescriptionMixinApiCategory:
"""Define an entity description mixin for binary and regular sensors."""
api_category: str
data_key: str
@dataclass

View File

@ -3,7 +3,6 @@ from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta
from functools import partial
from homeassistant.components.sensor import (
SensorDeviceClass,
@ -33,6 +32,7 @@ from .model import (
RainMachineDescriptionMixinApiCategory,
RainMachineDescriptionMixinUid,
)
from .util import key_exists
DEFAULT_ZONE_COMPLETION_TIME_WOBBLE_TOLERANCE = timedelta(seconds=5)
@ -68,6 +68,7 @@ SENSOR_DESCRIPTIONS = (
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
api_category=DATA_PROVISION_SETTINGS,
data_key="flowSensorClicksPerCubicMeter",
),
RainMachineSensorDescriptionApiCategory(
key=TYPE_FLOW_SENSOR_CONSUMED_LITERS,
@ -78,6 +79,7 @@ SENSOR_DESCRIPTIONS = (
entity_registry_enabled_default=False,
state_class=SensorStateClass.TOTAL_INCREASING,
api_category=DATA_PROVISION_SETTINGS,
data_key="flowSensorWateringClicks",
),
RainMachineSensorDescriptionApiCategory(
key=TYPE_FLOW_SENSOR_START_INDEX,
@ -87,6 +89,7 @@ SENSOR_DESCRIPTIONS = (
native_unit_of_measurement="index",
entity_registry_enabled_default=False,
api_category=DATA_PROVISION_SETTINGS,
data_key="flowSensorStartIndex",
),
RainMachineSensorDescriptionApiCategory(
key=TYPE_FLOW_SENSOR_WATERING_CLICKS,
@ -97,6 +100,7 @@ SENSOR_DESCRIPTIONS = (
entity_registry_enabled_default=False,
state_class=SensorStateClass.MEASUREMENT,
api_category=DATA_PROVISION_SETTINGS,
data_key="flowSensorWateringClicks",
),
RainMachineSensorDescriptionApiCategory(
key=TYPE_FREEZE_TEMP,
@ -107,6 +111,7 @@ SENSOR_DESCRIPTIONS = (
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
api_category=DATA_RESTRICTIONS_UNIVERSAL,
data_key="freezeProtectTemp",
),
)
@ -118,27 +123,18 @@ async def async_setup_entry(
controller = hass.data[DOMAIN][entry.entry_id][DATA_CONTROLLER]
coordinators = hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR]
@callback
def async_get_sensor_by_api_category(api_category: str) -> partial:
"""Generate the appropriate sensor object for an API category."""
if api_category == DATA_PROVISION_SETTINGS:
return partial(
ProvisionSettingsSensor,
entry,
coordinators[DATA_PROVISION_SETTINGS],
)
return partial(
UniversalRestrictionsSensor,
entry,
coordinators[DATA_RESTRICTIONS_UNIVERSAL],
)
api_category_sensor_map = {
DATA_PROVISION_SETTINGS: ProvisionSettingsSensor,
DATA_RESTRICTIONS_UNIVERSAL: UniversalRestrictionsSensor,
}
sensors = [
async_get_sensor_by_api_category(description.api_category)(
controller, description
api_category_sensor_map[description.api_category](
entry, coordinator, controller, description
)
for description in SENSOR_DESCRIPTIONS
if (coordinator := coordinators[description.api_category]) is not None
and key_exists(coordinator.data, description.data_key)
]
zone_coordinator = coordinators[DATA_ZONES]

View File

@ -0,0 +1,14 @@
"""Define RainMachine utilities."""
from __future__ import annotations
from typing import Any
def key_exists(data: dict[str, Any], search_key: str) -> bool:
"""Return whether a key exists in a nested dict."""
for key, value in data.items():
if key == search_key:
return True
if isinstance(value, dict):
return key_exists(value, search_key)
return False