Alter RainMachine to not create entities if the underlying data is missing (#72733)
parent
638992f9c4
commit
a3e1b285cf
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue