Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
48bbb21
Rainbird config flow
allenporter Dec 24, 2022
ffd2b5f
Add options for irrigation time and deprecate yaml
allenporter Jan 6, 2023
e16b0c6
Combine exception handling paths to get 100% test coverage
allenporter Jan 6, 2023
49dcb7c
Bump the rainird config deprecation release
allenporter Jan 6, 2023
797cf9e
Apply suggestions from code review
allenporter Jan 6, 2023
fb8ce86
Merge branch 'rainbird-config-entry' of github.com:allenporter/home-a…
allenporter Jan 6, 2023
cf019cc
Remove unnecessary sensor/binary sensor and address some PR feedback
allenporter Jan 6, 2023
868a2de
Simplify configuration flow and options based on PR feedback
allenporter Jan 6, 2023
8c1aa07
Consolidate data update coordinators to simplify overall integration
allenporter Jan 6, 2023
1e019a6
Fix type error on python3.9
allenporter Jan 6, 2023
f82a533
Handle yaml name import
allenporter Jan 6, 2023
9b12dee
Fix naming import post serialization
allenporter Jan 6, 2023
0cc4ec9
Parallelize requests to the device
allenporter Jan 6, 2023
9f8e2d7
Complete conversion to entity service
allenporter Jan 6, 2023
d8eb12e
Update homeassistant/components/rainbird/switch.py
allenporter Jan 6, 2023
63f43fb
Update homeassistant/components/rainbird/config_flow.py
allenporter Jan 6, 2023
009c08d
Remove unused import
allenporter Jan 7, 2023
b91275a
Set default duration in options used in tests
allenporter Jan 7, 2023
15edee1
Add separate devices for each sprinkler zone and update service to us…
allenporter Jan 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 91 additions & 47 deletions homeassistant/components/rainbird/__init__.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,27 @@
"""Support for Rain Bird Irrigation system LNK WiFi Module."""
from __future__ import annotations

import asyncio
import logging

from pyrainbird.async_client import (
AsyncRainbirdClient,
AsyncRainbirdController,
RainbirdApiException,
)
from pyrainbird.async_client import AsyncRainbirdClient, AsyncRainbirdController
import voluptuous as vol

from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry, ConfigEntryState
from homeassistant.const import (
CONF_FRIENDLY_NAME,
CONF_HOST,
CONF_PASSWORD,
CONF_TRIGGER_TIME,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import discovery
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.typing import ConfigType

from .const import (
CONF_ZONES,
RAINBIRD_CONTROLLER,
SENSOR_TYPE_RAINDELAY,
SENSOR_TYPE_RAINSENSOR,
)
from .const import ATTR_CONFIG_ENTRY_ID, ATTR_DURATION, CONF_SERIAL_NUMBER, CONF_ZONES
from .coordinator import RainbirdUpdateCoordinator

PLATFORMS = [Platform.SWITCH, Platform.SENSOR, Platform.BINARY_SENSOR]
Expand Down Expand Up @@ -61,47 +53,99 @@
extra=vol.ALLOW_EXTRA,
)

SERVICE_SET_RAIN_DELAY = "set_rain_delay"
SERVICE_SCHEMA_RAIN_DELAY = vol.All(
vol.Schema(
{
vol.Required(ATTR_CONFIG_ENTRY_ID): cv.string,
vol.Required(ATTR_DURATION): cv.positive_float,
}
),
)


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Rain Bird component."""
return all(
await asyncio.gather(
*[
_setup_controller(hass, controller_config, config)
for controller_config in config[DOMAIN]
]
if DOMAIN not in config:
return True

for controller_config in config[DOMAIN]:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=controller_config,
)
)

async_create_issue(
hass,
DOMAIN,
"deprecated_yaml",
breaks_in_ha_version="2023.4.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml",
)

return True

async def _setup_controller(hass, controller_config, config):
"""Set up a controller."""
server = controller_config[CONF_HOST]
password = controller_config[CONF_PASSWORD]
client = AsyncRainbirdClient(async_get_clientsession(hass), server, password)
controller = AsyncRainbirdController(client)
try:
await controller.get_serial_number()
except RainbirdApiException as exc:
_LOGGER.error("Unable to setup controller: %s", exc)
return False

rain_coordinator = RainbirdUpdateCoordinator(hass, controller.get_rain_sensor_state)
delay_coordinator = RainbirdUpdateCoordinator(hass, controller.get_rain_delay)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up the config entry for Rain Bird."""

for platform in PLATFORMS:
hass.async_create_task(
discovery.async_load_platform(
hass,
platform,
DOMAIN,
{
RAINBIRD_CONTROLLER: controller,
SENSOR_TYPE_RAINSENSOR: rain_coordinator,
SENSOR_TYPE_RAINDELAY: delay_coordinator,
**controller_config,
},
config,
)
hass.data.setdefault(DOMAIN, {})

controller = AsyncRainbirdController(
AsyncRainbirdClient(
async_get_clientsession(hass),
entry.data[CONF_HOST],
entry.data[CONF_PASSWORD],
)
)
coordinator = RainbirdUpdateCoordinator(
hass,
name=entry.title,
controller=controller,
serial_number=entry.data[CONF_SERIAL_NUMBER],
)
await coordinator.async_config_entry_first_refresh()

hass.data[DOMAIN][entry.entry_id] = coordinator

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

async def set_rain_delay(call: ServiceCall) -> None:
"""Service call to delay automatic irrigigation."""
entry_id = call.data[ATTR_CONFIG_ENTRY_ID]
duration = call.data[ATTR_DURATION]
if entry_id not in hass.data[DOMAIN]:
raise HomeAssistantError(f"Config entry id does not exist: {entry_id}")
coordinator = hass.data[DOMAIN][entry_id]
await coordinator.controller.set_rain_delay(duration)

hass.services.async_register(
DOMAIN,
SERVICE_SET_RAIN_DELAY,
set_rain_delay,
schema=SERVICE_SCHEMA_RAIN_DELAY,
)

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""

if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)

loaded_entries = [
entry
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.state == ConfigEntryState.LOADED
]
if len(loaded_entries) == 1:
hass.services.async_remove(DOMAIN, SERVICE_SET_RAIN_DELAY)

return unload_ok
49 changes: 16 additions & 33 deletions homeassistant/components/rainbird/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,71 +2,54 @@
from __future__ import annotations

import logging
from typing import Union

from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import SENSOR_TYPE_RAINDELAY, SENSOR_TYPE_RAINSENSOR
from .const import DOMAIN
from .coordinator import RainbirdUpdateCoordinator

_LOGGER = logging.getLogger(__name__)


BINARY_SENSOR_TYPES: tuple[BinarySensorEntityDescription, ...] = (
BinarySensorEntityDescription(
key=SENSOR_TYPE_RAINSENSOR,
name="Rainsensor",
icon="mdi:water",
),
BinarySensorEntityDescription(
key=SENSOR_TYPE_RAINDELAY,
name="Raindelay",
icon="mdi:water-off",
),
RAIN_SENSOR_ENTITY_DESCRIPTION = BinarySensorEntityDescription(
key="rainsensor",
name="Rainsensor",
icon="mdi:water",
)


async def async_setup_platform(
async def async_setup_entry(
hass: HomeAssistant,
config: ConfigType,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up a Rain Bird sensor."""
if discovery_info is None:
return
"""Set up entry for a Rain Bird binary_sensor."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
async_add_entities([RainBirdSensor(coordinator, RAIN_SENSOR_ENTITY_DESCRIPTION)])

async_add_entities(
[
RainBirdSensor(discovery_info[description.key], description)
for description in BINARY_SENSOR_TYPES
],
True,
)


class RainBirdSensor(
CoordinatorEntity[RainbirdUpdateCoordinator[Union[int, bool]]], BinarySensorEntity
):
class RainBirdSensor(CoordinatorEntity[RainbirdUpdateCoordinator], BinarySensorEntity):
"""A sensor implementation for Rain Bird device."""

def __init__(
self,
coordinator: RainbirdUpdateCoordinator[int | bool],
coordinator: RainbirdUpdateCoordinator,
description: BinarySensorEntityDescription,
) -> None:
"""Initialize the Rain Bird sensor."""
super().__init__(coordinator)
self.entity_description = description
self._attr_unique_id = f"{coordinator.serial_number}-{description.key}"
self._attr_device_info = coordinator.device_info

@property
def is_on(self) -> bool | None:
"""Return True if entity is on."""
return None if self.coordinator.data is None else bool(self.coordinator.data)
return self.coordinator.data.rain
Loading