Add rainbird rain delay number entity, deprecating the sensor and service (#86208)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
pull/86569/head
Allen Porter 2023-01-24 12:16:52 -08:00 committed by GitHub
parent da390dbd9a
commit 09891ead8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 205 additions and 3 deletions

View File

@ -16,6 +16,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
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
@ -24,7 +25,7 @@ from homeassistant.helpers.typing import ConfigType
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]
PLATFORMS = [Platform.SWITCH, Platform.SENSOR, Platform.BINARY_SENSOR, Platform.NUMBER]
_LOGGER = logging.getLogger(__name__)
@ -117,11 +118,33 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
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]
entity_registry = er.async_get(hass)
entity_ids = (
entry.entity_id
for entry in er.async_entries_for_config_entry(entity_registry, entry_id)
if entry.unique_id == f"{coordinator.serial_number}-rain-delay"
)
async_create_issue(
hass,
DOMAIN,
"deprecated_raindelay",
breaks_in_ha_version="2023.4.0",
is_fixable=True,
is_persistent=True,
severity=IssueSeverity.WARNING,
translation_key="deprecated_raindelay",
translation_placeholders={
"alternate_target": next(entity_ids, "unknown"),
},
)
await coordinator.controller.set_rain_delay(duration)
hass.services.async_register(

View File

@ -0,0 +1,61 @@
"""The number platform for rainbird."""
from __future__ import annotations
import logging
from homeassistant.components.number import NumberEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfTime
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import RainbirdUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up entry for a Rain Bird number platform."""
async_add_entities(
[
RainDelayNumber(
hass.data[DOMAIN][config_entry.entry_id],
)
]
)
class RainDelayNumber(CoordinatorEntity[RainbirdUpdateCoordinator], NumberEntity):
"""A number implemnetaiton for the rain delay."""
_attr_native_min_value = 0
_attr_native_max_value = 14
_attr_native_step = 1
_attr_native_unit_of_measurement = UnitOfTime.DAYS
_attr_icon = "mdi:water-off"
_attr_name = "Rain delay"
_attr_has_entity_name = True
def __init__(
self,
coordinator: RainbirdUpdateCoordinator,
) -> None:
"""Initialize the Rain Bird sensor."""
super().__init__(coordinator)
self._attr_unique_id = f"{coordinator.serial_number}-rain-delay"
self._attr_device_info = coordinator.device_info
@property
def native_value(self) -> float | None:
"""Return the value reported by the sensor."""
return self.coordinator.data.rain_delay
async def async_set_native_value(self, value: float) -> None:
"""Update the current value."""
await self.coordinator.controller.set_rain_delay(value)

View File

@ -32,6 +32,17 @@
"deprecated_yaml": {
"title": "The Rain Bird YAML configuration is being removed",
"description": "Configuring Rain Bird in configuration.yaml is being removed in Home Assistant 2023.4.\n\nYour configuration has been imported into the UI automatically, however default per-zone irrigation times are no longer supported. Remove the Rain Bird YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue."
},
"deprecated_raindelay": {
"title": "The Rain Bird Rain Delay Service is being removed",
"fix_flow": {
"step": {
"confirm": {
"title": "The Rain Bird Rain Delay Service is being removed",
"description": "The Rain Bird service `rainbird.set_rain_delay` is being removed and replaced by a Number entity for managing the rain delay. Any existing automations or scripts will need to be updated to use `number.set_value` with a target of `{alternate_target}` instead."
}
}
}
}
}
}

View File

@ -19,6 +19,17 @@
}
},
"issues": {
"deprecated_raindelay": {
"fix_flow": {
"step": {
"confirm": {
"description": "The Rain Bird service `rainbird.set_rain_delay` is being removed and replaced by a Number entity for managing the rain delay. Any existing automations or scripts will need to be updated to use `number.set_value` with a target of `{alternate_target}` instead.",
"title": "The Rain Bird Rain Delay Service is being removed"
}
}
},
"title": "The Rain Bird Rain Delay Service is being removed"
},
"deprecated_yaml": {
"description": "Configuring Rain Bird in configuration.yaml is being removed in Home Assistant 2023.4.\n\nYour configuration has been imported into the UI automatically, however default per-zone irrigation times are no longer supported. Remove the Rain Bird YAML configuration from your configuration.yaml file and restart Home Assistant to fix this issue.",
"title": "The Rain Bird YAML configuration is being removed"

View File

@ -10,7 +10,7 @@ from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers import device_registry as dr, issue_registry as ir
from .conftest import (
ACK_ECHO,
@ -102,7 +102,7 @@ async def test_communication_failure(
] == config_entry_states
@pytest.mark.parametrize("platforms", [[Platform.SENSOR]])
@pytest.mark.parametrize("platforms", [[Platform.NUMBER, Platform.SENSOR]])
async def test_rain_delay_service(
hass: HomeAssistant,
setup_integration: ComponentSetup,
@ -131,6 +131,15 @@ async def test_rain_delay_service(
assert len(aioclient_mock.mock_calls) == 1
issue_registry: ir.IssueRegistry = ir.async_get(hass)
issue = issue_registry.async_get_issue(
domain=DOMAIN, issue_id="deprecated_raindelay"
)
assert issue
assert issue.translation_placeholders == {
"alternate_target": "number.rain_bird_controller_rain_delay"
}
async def test_rain_delay_invalid_config_entry(
hass: HomeAssistant,

View File

@ -0,0 +1,87 @@
"""Tests for rainbird number platform."""
import pytest
from homeassistant.components import number
from homeassistant.components.rainbird import DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from .conftest import (
ACK_ECHO,
RAIN_DELAY,
RAIN_DELAY_OFF,
SERIAL_NUMBER,
ComponentSetup,
mock_response,
)
from tests.test_util.aiohttp import AiohttpClientMocker
@pytest.fixture
def platforms() -> list[str]:
"""Fixture to specify platforms to test."""
return [Platform.NUMBER]
@pytest.mark.parametrize(
"rain_delay_response,expected_state",
[(RAIN_DELAY, "16"), (RAIN_DELAY_OFF, "0")],
)
async def test_number_values(
hass: HomeAssistant,
setup_integration: ComponentSetup,
expected_state: str,
) -> None:
"""Test sensor platform."""
assert await setup_integration()
raindelay = hass.states.get("number.rain_bird_controller_rain_delay")
assert raindelay is not None
assert raindelay.state == expected_state
assert raindelay.attributes == {
"friendly_name": "Rain Bird Controller Rain delay",
"icon": "mdi:water-off",
"min": 0,
"max": 14,
"mode": "auto",
"step": 1,
"unit_of_measurement": "d",
}
async def test_set_value(
hass: HomeAssistant,
setup_integration: ComponentSetup,
aioclient_mock: AiohttpClientMocker,
responses: list[str],
config_entry: ConfigEntry,
) -> None:
"""Test setting the rain delay number."""
assert await setup_integration()
device_registry = dr.async_get(hass)
device = device_registry.async_get_device({(DOMAIN, SERIAL_NUMBER)})
assert device
assert device.name == "Rain Bird Controller"
aioclient_mock.mock_calls.clear()
responses.append(mock_response(ACK_ECHO))
await hass.services.async_call(
number.DOMAIN,
number.SERVICE_SET_VALUE,
{
ATTR_ENTITY_ID: "number.rain_bird_controller_rain_delay",
number.ATTR_VALUE: 3,
},
blocking=True,
)
assert len(aioclient_mock.mock_calls) == 1