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/model.py
homeassistant/components/rainmachine/sensor.py homeassistant/components/rainmachine/sensor.py
homeassistant/components/rainmachine/switch.py homeassistant/components/rainmachine/switch.py
homeassistant/components/rainmachine/util.py
homeassistant/components/raspyrfm/* homeassistant/components/raspyrfm/*
homeassistant/components/recollect_waste/__init__.py homeassistant/components/recollect_waste/__init__.py
homeassistant/components/recollect_waste/sensor.py homeassistant/components/recollect_waste/sensor.py

View File

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

View File

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

View File

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