Remove Melnor RainCloud integration (#183505)

This commit is contained in:
Franck Nijhof
2026-09-29 00:13:50 +02:00
committed by GitHub
parent a46aea73ba
commit 8dd5fc39d9
12 changed files with 4 additions and 477 deletions
Generated
-1
View File
@@ -1539,7 +1539,6 @@ CLAUDE.md @home-assistant/core
/tests/components/radiotherm/ @vinnyfuria
/homeassistant/components/rainbird/ @konikvranik @allenporter
/tests/components/rainbird/ @konikvranik @allenporter
/homeassistant/components/raincloud/ @vanstinator
/homeassistant/components/rainforest_eagle/ @gtdiehl @jcalbert @hastarin
/tests/components/rainforest_eagle/ @gtdiehl @jcalbert @hastarin
/homeassistant/components/rainforest_raven/ @cottsay
-5
View File
@@ -1,5 +0,0 @@
{
"domain": "melnor",
"name": "Melnor",
"integrations": ["melnor", "raincloud"]
}
@@ -1,84 +0,0 @@
"""Support for Melnor RainCloud sprinkler water timer."""
from datetime import timedelta
import logging
import probatio
from raincloudy.core import RainCloudy
from requests.exceptions import ConnectTimeout, HTTPError
from homeassistant.components import persistent_notification
from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.typing import ConfigType
from .const import DATA_RAINCLOUD, SIGNAL_UPDATE_RAINCLOUD
_LOGGER = logging.getLogger(__name__)
NOTIFICATION_ID = "raincloud_notification"
NOTIFICATION_TITLE = "Rain Cloud Setup"
DOMAIN = "raincloud"
SCAN_INTERVAL = timedelta(seconds=20)
CONFIG_SCHEMA = probatio.Schema(
{
DOMAIN: probatio.Schema(
{
probatio.Required(CONF_USERNAME): cv.string,
probatio.Required(CONF_PASSWORD): cv.string,
probatio.Optional(
CONF_SCAN_INTERVAL, default=SCAN_INTERVAL
): cv.time_period,
}
)
},
extra=probatio.ALLOW_EXTRA,
)
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Melnor RainCloud component."""
conf = config[DOMAIN]
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
scan_interval = conf.get(CONF_SCAN_INTERVAL)
try:
raincloud = RainCloudy(username=username, password=password)
if not raincloud.is_connected:
raise HTTPError # noqa: TRY301
hass.data[DATA_RAINCLOUD] = RainCloudHub(raincloud)
except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Rain Cloud service: %s", str(ex))
persistent_notification.create(
hass,
f"Error: {ex}<br />You will need to restart hass after fixing.",
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID,
)
return False
def hub_refresh(event_time):
"""Call Raincloud hub to refresh information."""
_LOGGER.debug("Updating RainCloud Hub component")
hass.data[DATA_RAINCLOUD].data.update()
dispatcher_send(hass, SIGNAL_UPDATE_RAINCLOUD)
# Call the Raincloud API to refresh updates
track_time_interval(hass, hub_refresh, scan_interval)
return True
class RainCloudHub:
"""Representation of a base RainCloud device."""
def __init__(self, data):
"""Initialize the entity."""
self.data = data
@@ -1,81 +0,0 @@
"""Support for Melnor RainCloud sprinkler water timer."""
import logging
from typing import override
import probatio
from homeassistant.components.binary_sensor import (
PLATFORM_SCHEMA as BINARY_SENSOR_PLATFORM_SCHEMA,
BinarySensorEntity,
)
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import DATA_RAINCLOUD
from .entity import RainCloudEntity
_LOGGER = logging.getLogger(__name__)
BINARY_SENSORS = ["is_watering", "status"]
PLATFORM_SCHEMA = BINARY_SENSOR_PLATFORM_SCHEMA.extend(
{
probatio.Optional(
CONF_MONITORED_CONDITIONS, default=list(BINARY_SENSORS)
): probatio.All(cv.ensure_list, [probatio.In(BINARY_SENSORS)])
}
)
def setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up a sensor for a raincloud device."""
raincloud = hass.data[DATA_RAINCLOUD].data
sensors = []
for sensor_type in config[CONF_MONITORED_CONDITIONS]:
if sensor_type == "status":
sensors.append(RainCloudBinarySensor(raincloud.controller, sensor_type))
sensors.append(
RainCloudBinarySensor(raincloud.controller.faucet, sensor_type)
)
else:
# create a sensor for each zone managed by faucet
sensors.extend(
RainCloudBinarySensor(zone, sensor_type)
for zone in raincloud.controller.faucet.zones
)
add_entities(sensors, True)
class RainCloudBinarySensor(RainCloudEntity, BinarySensorEntity):
"""A sensor implementation for raincloud device."""
def update(self) -> None:
"""Get the latest data and updates the state."""
_LOGGER.debug("Updating RainCloud sensor: %s", self.name)
state = getattr(self.data, self._sensor_type)
if self._sensor_type == "status":
self._attr_is_on = state == "Online"
else:
self._attr_is_on = state
@property
@override
def icon(self) -> str | None:
"""Return the icon of this device."""
if self._sensor_type == "is_watering":
return "mdi:water" if self.is_on else "mdi:water-off"
if self._sensor_type == "status":
return "mdi:pipe" if self.is_on else "mdi:pipe-disconnected"
return super().icon
@@ -1,17 +0,0 @@
"""Support for Melnor RainCloud sprinkler water timer."""
DATA_RAINCLOUD = "raincloud"
ICON_MAP = {
"auto_watering": "mdi:autorenew",
"battery": "",
"is_watering": "",
"manual_watering": "mdi:water-pump",
"next_cycle": "mdi:calendar-clock",
"rain_delay": "mdi:weather-rainy",
"status": "",
"watering_time": "mdi:water-pump",
}
SIGNAL_UPDATE_RAINCLOUD = "raincloud_update"
@@ -1,62 +0,0 @@
"""Support for Melnor RainCloud sprinkler water timer."""
from typing import Any, override
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from .const import SIGNAL_UPDATE_RAINCLOUD
KEY_MAP = {
"auto_watering": "Automatic Watering",
"battery": "Battery",
"is_watering": "Watering",
"manual_watering": "Manual Watering",
"next_cycle": "Next Cycle",
"rain_delay": "Rain Delay",
"status": "Status",
"watering_time": "Remaining Watering Time",
}
ICON_MAP = {
"auto_watering": "mdi:autorenew",
"battery": "",
"is_watering": "",
"manual_watering": "mdi:water-pump",
"next_cycle": "mdi:calendar-clock",
"rain_delay": "mdi:weather-rainy",
"status": "",
"watering_time": "mdi:water-pump",
}
class RainCloudEntity(Entity):
"""Entity class for RainCloud devices."""
_attr_attribution = "Data provided by Melnor Aquatimer.com"
def __init__(self, data, sensor_type):
"""Initialize the RainCloud entity."""
self.data = data
self._sensor_type = sensor_type
self._attr_name = f"{self.data.name} {KEY_MAP.get(self._sensor_type)}"
self._attr_icon = ICON_MAP.get(self._sensor_type)
@override
async def async_added_to_hass(self) -> None:
"""Register callbacks."""
self.async_on_remove(
async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_RAINCLOUD, self._update_callback
)
)
def _update_callback(self):
"""Call update method."""
self.schedule_update_ha_state(True)
@property
@override
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes."""
return {"identifier": self.data.serial}
@@ -1,10 +0,0 @@
{
"domain": "raincloud",
"name": "Melnor RainCloud",
"codeowners": ["@vanstinator"],
"documentation": "https://www.home-assistant.io/integrations/raincloud",
"iot_class": "cloud_polling",
"loggers": ["raincloudy"],
"quality_scale": "legacy",
"requirements": ["raincloudy==0.0.7"]
}
@@ -1,95 +0,0 @@
"""Support for Melnor RainCloud sprinkler water timer."""
import logging
from typing import cast, override
import probatio
from homeassistant.components.sensor import (
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
SensorEntity,
)
from homeassistant.const import CONF_MONITORED_CONDITIONS, PERCENTAGE, UnitOfTime
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.icon import icon_for_battery_level
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import DATA_RAINCLOUD
from .entity import RainCloudEntity
_LOGGER = logging.getLogger(__name__)
SENSORS = ["battery", "next_cycle", "rain_delay", "watering_time"]
PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
{
probatio.Optional(
CONF_MONITORED_CONDITIONS, default=list(SENSORS)
): probatio.All(cv.ensure_list, [probatio.In(SENSORS)])
}
)
UNIT_OF_MEASUREMENT_MAP: dict[str, str] = {
"auto_watering": "",
"battery": PERCENTAGE,
"is_watering": "",
"manual_watering": "",
"next_cycle": "",
"rain_delay": UnitOfTime.DAYS,
"status": "",
"watering_time": UnitOfTime.MINUTES,
}
def setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up a sensor for a raincloud device."""
raincloud = hass.data[DATA_RAINCLOUD].data
sensors = []
for sensor_type in config[CONF_MONITORED_CONDITIONS]:
if sensor_type == "battery":
sensors.append(RainCloudSensor(raincloud.controller.faucet, sensor_type))
else:
# create a sensor for each zone managed by a faucet
sensors.extend(
RainCloudSensor(zone, sensor_type)
for zone in raincloud.controller.faucet.zones
)
add_entities(sensors, True)
class RainCloudSensor(RainCloudEntity, SensorEntity):
"""A sensor implementation for raincloud device."""
@property
@override
def native_unit_of_measurement(self) -> str | None:
"""Return the units of measurement."""
return UNIT_OF_MEASUREMENT_MAP.get(self._sensor_type)
def update(self) -> None:
"""Get the latest data and updates the states."""
_LOGGER.debug("Updating RainCloud sensor: %s", self.name)
if self._sensor_type == "battery":
self._attr_native_value = self.data.battery
else:
self._attr_native_value = getattr(self.data, self._sensor_type)
@property
@override
def icon(self) -> str | None:
"""Icon to use in the frontend, if any."""
if self._sensor_type == "battery" and self.native_value is not None:
return icon_for_battery_level(
battery_level=int(cast(float, self.native_value)),
charging=False,
)
return super().icon
@@ -1,102 +0,0 @@
"""Support for Melnor RainCloud sprinkler water timer."""
import logging
from typing import Any, override
import probatio
from homeassistant.components.switch import (
PLATFORM_SCHEMA as SWITCH_PLATFORM_SCHEMA,
SwitchEntity,
)
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import DATA_RAINCLOUD
from .entity import RainCloudEntity
_LOGGER = logging.getLogger(__name__)
ALLOWED_WATERING_TIME = [5, 10, 15, 30, 45, 60]
CONF_WATERING_TIME = "watering_minutes"
DEFAULT_WATERING_TIME = 15
SWITCHES = ["auto_watering", "manual_watering"]
PLATFORM_SCHEMA = SWITCH_PLATFORM_SCHEMA.extend(
{
probatio.Optional(
CONF_MONITORED_CONDITIONS, default=list(SWITCHES)
): probatio.All(cv.ensure_list, [probatio.In(SWITCHES)]),
probatio.Optional(
CONF_WATERING_TIME, default=DEFAULT_WATERING_TIME
): probatio.All(probatio.In(ALLOWED_WATERING_TIME)),
}
)
def setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up a sensor for a raincloud device."""
raincloud = hass.data[DATA_RAINCLOUD].data
default_watering_timer = config[CONF_WATERING_TIME]
add_entities(
(
RainCloudSwitch(default_watering_timer, zone, sensor_type)
for zone in raincloud.controller.faucet.zones
for sensor_type in config[CONF_MONITORED_CONDITIONS]
),
True,
)
class RainCloudSwitch(RainCloudEntity, SwitchEntity):
"""A switch implementation for raincloud device."""
def __init__(self, default_watering_timer, *args):
"""Initialize a switch for raincloud device."""
super().__init__(*args)
self._default_watering_timer = default_watering_timer
@override
def turn_on(self, **kwargs: Any) -> None:
"""Turn the device on."""
if self._sensor_type == "manual_watering":
self.data.watering_time = self._default_watering_timer
elif self._sensor_type == "auto_watering":
self.data.auto_watering = True
self._attr_is_on = True
@override
def turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""
if self._sensor_type == "manual_watering":
self.data.watering_time = "off"
elif self._sensor_type == "auto_watering":
self.data.auto_watering = False
self._attr_is_on = False
def update(self) -> None:
"""Update device state."""
_LOGGER.debug("Updating RainCloud switch: %s", self.name)
if self._sensor_type == "manual_watering":
self._attr_is_on = bool(self.data.watering_time)
elif self._sensor_type == "auto_watering":
self._attr_is_on = self.data.auto_watering
@property
@override
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes."""
return {
"default_manual_timer": self._default_watering_timer,
"identifier": self.data.serial,
}
+4 -15
View File
@@ -4339,21 +4339,10 @@
"iot_class": "cloud_polling"
},
"melnor": {
"name": "Melnor",
"integrations": {
"melnor": {
"integration_type": "device",
"config_flow": true,
"iot_class": "local_polling",
"name": "Melnor Bluetooth"
},
"raincloud": {
"integration_type": "hub",
"config_flow": false,
"iot_class": "cloud_polling",
"name": "Melnor RainCloud"
}
}
"name": "Melnor Bluetooth",
"integration_type": "device",
"config_flow": true,
"iot_class": "local_polling"
},
"meraki": {
"name": "Meraki",
-3
View File
@@ -2952,9 +2952,6 @@ radios==0.3.2
# homeassistant.components.radiotherm
radiotherm==2.1.0
# homeassistant.components.raincloud
raincloudy==0.0.7
# homeassistant.components.rapt_ble
rapt-ble==2.0.0
-2
View File
@@ -730,7 +730,6 @@ INTEGRATIONS_WITHOUT_QUALITY_SCALE_FILE = [
"radarr",
"radio_browser",
"radiotherm",
"raincloud",
"rainforest_eagle",
"rainforest_raven",
"rainmachine",
@@ -1667,7 +1666,6 @@ INTEGRATIONS_WITHOUT_SCALE = [
"radarr",
"radio_browser",
"radiotherm",
"raincloud",
"rainforest_eagle",
"rainforest_raven",
"rainmachine",