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

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
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.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue 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 .const import ATTR_CONFIG_ENTRY_ID, ATTR_DURATION, CONF_SERIAL_NUMBER, CONF_ZONES
from .coordinator import RainbirdUpdateCoordinator 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__) _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: async def set_rain_delay(call: ServiceCall) -> None:
"""Service call to delay automatic irrigigation.""" """Service call to delay automatic irrigigation."""
entry_id = call.data[ATTR_CONFIG_ENTRY_ID] entry_id = call.data[ATTR_CONFIG_ENTRY_ID]
duration = call.data[ATTR_DURATION] duration = call.data[ATTR_DURATION]
if entry_id not in hass.data[DOMAIN]: if entry_id not in hass.data[DOMAIN]:
raise HomeAssistantError(f"Config entry id does not exist: {entry_id}") raise HomeAssistantError(f"Config entry id does not exist: {entry_id}")
coordinator = hass.data[DOMAIN][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) await coordinator.controller.set_rain_delay(duration)
hass.services.async_register( 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": { "deprecated_yaml": {
"title": "The Rain Bird YAML configuration is being removed", "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." "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": { "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": { "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.", "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" "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.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError 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 ( from .conftest import (
ACK_ECHO, ACK_ECHO,
@ -102,7 +102,7 @@ async def test_communication_failure(
] == config_entry_states ] == config_entry_states
@pytest.mark.parametrize("platforms", [[Platform.SENSOR]]) @pytest.mark.parametrize("platforms", [[Platform.NUMBER, Platform.SENSOR]])
async def test_rain_delay_service( async def test_rain_delay_service(
hass: HomeAssistant, hass: HomeAssistant,
setup_integration: ComponentSetup, setup_integration: ComponentSetup,
@ -131,6 +131,15 @@ async def test_rain_delay_service(
assert len(aioclient_mock.mock_calls) == 1 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( async def test_rain_delay_invalid_config_entry(
hass: HomeAssistant, 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