Add reboot button to RainMachine (#75227)
This commit is contained in:
parent
0df4642b62
commit
3d42c4ca87
7 changed files with 212 additions and 36 deletions
|
@ -976,6 +976,7 @@ omit =
|
|||
homeassistant/components/raincloud/*
|
||||
homeassistant/components/rainmachine/__init__.py
|
||||
homeassistant/components/rainmachine/binary_sensor.py
|
||||
homeassistant/components/rainmachine/button.py
|
||||
homeassistant/components/rainmachine/model.py
|
||||
homeassistant/components/rainmachine/sensor.py
|
||||
homeassistant/components/rainmachine/switch.py
|
||||
|
|
|
@ -30,11 +30,7 @@ from homeassistant.helpers import (
|
|||
entity_registry as er,
|
||||
)
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
UpdateFailed,
|
||||
)
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity, UpdateFailed
|
||||
from homeassistant.util.network import is_ip_address
|
||||
|
||||
from .config_flow import get_client_controller
|
||||
|
@ -49,20 +45,13 @@ from .const import (
|
|||
LOGGER,
|
||||
)
|
||||
from .model import RainMachineEntityDescription
|
||||
from .util import RainMachineDataUpdateCoordinator
|
||||
|
||||
DEFAULT_SSL = True
|
||||
|
||||
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
|
||||
|
||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH]
|
||||
|
||||
UPDATE_INTERVALS = {
|
||||
DATA_PROVISION_SETTINGS: timedelta(minutes=1),
|
||||
DATA_PROGRAMS: timedelta(seconds=30),
|
||||
DATA_RESTRICTIONS_CURRENT: timedelta(minutes=1),
|
||||
DATA_RESTRICTIONS_UNIVERSAL: timedelta(minutes=1),
|
||||
DATA_ZONES: timedelta(seconds=15),
|
||||
}
|
||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR, Platform.SWITCH]
|
||||
|
||||
CONF_CONDITION = "condition"
|
||||
CONF_DEWPOINT = "dewpoint"
|
||||
|
@ -134,13 +123,21 @@ SERVICE_RESTRICT_WATERING_SCHEMA = SERVICE_SCHEMA.extend(
|
|||
}
|
||||
)
|
||||
|
||||
COORDINATOR_UPDATE_INTERVAL_MAP = {
|
||||
DATA_PROVISION_SETTINGS: timedelta(minutes=1),
|
||||
DATA_PROGRAMS: timedelta(seconds=30),
|
||||
DATA_RESTRICTIONS_CURRENT: timedelta(minutes=1),
|
||||
DATA_RESTRICTIONS_UNIVERSAL: timedelta(minutes=1),
|
||||
DATA_ZONES: timedelta(seconds=15),
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class RainMachineData:
|
||||
"""Define an object to be stored in `hass.data`."""
|
||||
|
||||
controller: Controller
|
||||
coordinators: dict[str, DataUpdateCoordinator]
|
||||
coordinators: dict[str, RainMachineDataUpdateCoordinator]
|
||||
|
||||
|
||||
@callback
|
||||
|
@ -233,24 +230,25 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
|
||||
return data
|
||||
|
||||
async def async_init_coordinator(
|
||||
coordinator: RainMachineDataUpdateCoordinator,
|
||||
) -> None:
|
||||
"""Initialize a RainMachineDataUpdateCoordinator."""
|
||||
await coordinator.async_initialize()
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
controller_init_tasks = []
|
||||
coordinators = {}
|
||||
|
||||
for api_category in (
|
||||
DATA_PROGRAMS,
|
||||
DATA_PROVISION_SETTINGS,
|
||||
DATA_RESTRICTIONS_CURRENT,
|
||||
DATA_RESTRICTIONS_UNIVERSAL,
|
||||
DATA_ZONES,
|
||||
):
|
||||
coordinator = coordinators[api_category] = DataUpdateCoordinator(
|
||||
for api_category, update_interval in COORDINATOR_UPDATE_INTERVAL_MAP.items():
|
||||
coordinator = coordinators[api_category] = RainMachineDataUpdateCoordinator(
|
||||
hass,
|
||||
LOGGER,
|
||||
entry=entry,
|
||||
name=f'{controller.name} ("{api_category}")',
|
||||
update_interval=UPDATE_INTERVALS[api_category],
|
||||
api_category=api_category,
|
||||
update_interval=update_interval,
|
||||
update_method=partial(async_update, api_category),
|
||||
)
|
||||
controller_init_tasks.append(coordinator.async_refresh())
|
||||
controller_init_tasks.append(async_init_coordinator(coordinator))
|
||||
|
||||
await asyncio.gather(*controller_init_tasks)
|
||||
|
||||
|
@ -439,12 +437,6 @@ class RainMachineEntity(CoordinatorEntity):
|
|||
self.update_from_latest_data()
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Handle entity which will be added."""
|
||||
await super().async_added_to_hass()
|
||||
self.update_from_latest_data()
|
||||
|
||||
@callback
|
||||
def update_from_latest_data(self) -> None:
|
||||
"""Update the state."""
|
||||
raise NotImplementedError
|
||||
|
|
90
homeassistant/components/rainmachine/button.py
Normal file
90
homeassistant/components/rainmachine/button.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
"""Buttons for the RainMachine integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from regenmaschine.controller import Controller
|
||||
from regenmaschine.errors import RainMachineError
|
||||
|
||||
from homeassistant.components.button import (
|
||||
ButtonDeviceClass,
|
||||
ButtonEntity,
|
||||
ButtonEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import RainMachineData, RainMachineEntity
|
||||
from .const import DATA_PROVISION_SETTINGS, DOMAIN
|
||||
from .model import RainMachineEntityDescription
|
||||
|
||||
|
||||
@dataclass
|
||||
class RainMachineButtonDescriptionMixin:
|
||||
"""Define an entity description mixin for RainMachine buttons."""
|
||||
|
||||
push_action: Callable[[Controller], Awaitable]
|
||||
|
||||
|
||||
@dataclass
|
||||
class RainMachineButtonDescription(
|
||||
ButtonEntityDescription,
|
||||
RainMachineEntityDescription,
|
||||
RainMachineButtonDescriptionMixin,
|
||||
):
|
||||
"""Describe a RainMachine button description."""
|
||||
|
||||
|
||||
BUTTON_KIND_REBOOT = "reboot"
|
||||
|
||||
|
||||
async def _async_reboot(controller: Controller) -> None:
|
||||
"""Reboot the RainMachine."""
|
||||
await controller.machine.reboot()
|
||||
|
||||
|
||||
BUTTON_DESCRIPTIONS = (
|
||||
RainMachineButtonDescription(
|
||||
key=BUTTON_KIND_REBOOT,
|
||||
name="Reboot",
|
||||
api_category=DATA_PROVISION_SETTINGS,
|
||||
push_action=_async_reboot,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up RainMachine buttons based on a config entry."""
|
||||
data: RainMachineData = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
async_add_entities(
|
||||
RainMachineButton(entry, data, description)
|
||||
for description in BUTTON_DESCRIPTIONS
|
||||
)
|
||||
|
||||
|
||||
class RainMachineButton(RainMachineEntity, ButtonEntity):
|
||||
"""Define a RainMachine button."""
|
||||
|
||||
_attr_device_class = ButtonDeviceClass.RESTART
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
|
||||
entity_description: RainMachineButtonDescription
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Send out a restart command."""
|
||||
try:
|
||||
await self.entity_description.push_action(self._data.controller)
|
||||
except RainMachineError as err:
|
||||
raise HomeAssistantError(
|
||||
f'Error while pressing button "{self.entity_id}": {err}'
|
||||
) from err
|
||||
|
||||
async_dispatcher_send(self.hass, self.coordinator.signal_reboot_requested)
|
|
@ -3,7 +3,7 @@
|
|||
"name": "RainMachine",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/rainmachine",
|
||||
"requirements": ["regenmaschine==2022.07.1"],
|
||||
"requirements": ["regenmaschine==2022.07.3"],
|
||||
"codeowners": ["@bachya"],
|
||||
"iot_class": "local_polling",
|
||||
"homekit": {
|
||||
|
|
|
@ -1,9 +1,23 @@
|
|||
"""Define RainMachine utilities."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.backports.enum import StrEnum
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import (
|
||||
async_dispatcher_connect,
|
||||
async_dispatcher_send,
|
||||
)
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import LOGGER
|
||||
|
||||
SIGNAL_REBOOT_COMPLETED = "rainmachine_reboot_completed_{0}"
|
||||
SIGNAL_REBOOT_REQUESTED = "rainmachine_reboot_requested_{0}"
|
||||
|
||||
|
||||
class RunStates(StrEnum):
|
||||
|
@ -29,3 +43,82 @@ def key_exists(data: dict[str, Any], search_key: str) -> bool:
|
|||
if isinstance(value, dict):
|
||||
return key_exists(value, search_key)
|
||||
return False
|
||||
|
||||
|
||||
class RainMachineDataUpdateCoordinator(DataUpdateCoordinator[dict]):
|
||||
"""Define an extended DataUpdateCoordinator."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
*,
|
||||
entry: ConfigEntry,
|
||||
name: str,
|
||||
api_category: str,
|
||||
update_interval: timedelta,
|
||||
update_method: Callable[..., Awaitable],
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(
|
||||
hass,
|
||||
LOGGER,
|
||||
name=name,
|
||||
update_interval=update_interval,
|
||||
update_method=update_method,
|
||||
)
|
||||
|
||||
self._rebooting = False
|
||||
self._signal_handler_unsubs: list[Callable[..., None]] = []
|
||||
self.config_entry = entry
|
||||
self.signal_reboot_completed = SIGNAL_REBOOT_COMPLETED.format(
|
||||
self.config_entry.entry_id
|
||||
)
|
||||
self.signal_reboot_requested = SIGNAL_REBOOT_REQUESTED.format(
|
||||
self.config_entry.entry_id
|
||||
)
|
||||
|
||||
async def async_initialize(self) -> None:
|
||||
"""Initialize the coordinator."""
|
||||
|
||||
@callback
|
||||
def async_reboot_completed() -> None:
|
||||
"""Respond to a reboot completed notification."""
|
||||
LOGGER.debug("%s responding to reboot complete", self.name)
|
||||
self._rebooting = False
|
||||
self.last_update_success = True
|
||||
self.async_update_listeners()
|
||||
|
||||
@callback
|
||||
def async_reboot_requested() -> None:
|
||||
"""Respond to a reboot request."""
|
||||
LOGGER.debug("%s responding to reboot request", self.name)
|
||||
self._rebooting = True
|
||||
self.last_update_success = False
|
||||
self.async_update_listeners()
|
||||
|
||||
for signal, func in (
|
||||
(self.signal_reboot_completed, async_reboot_completed),
|
||||
(self.signal_reboot_requested, async_reboot_requested),
|
||||
):
|
||||
self._signal_handler_unsubs.append(
|
||||
async_dispatcher_connect(self.hass, signal, func)
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_check_reboot_complete() -> None:
|
||||
"""Check whether an active reboot has been completed."""
|
||||
if self._rebooting and self.last_update_success:
|
||||
LOGGER.debug("%s discovered reboot complete", self.name)
|
||||
async_dispatcher_send(self.hass, self.signal_reboot_completed)
|
||||
|
||||
self.async_add_listener(async_check_reboot_complete)
|
||||
|
||||
@callback
|
||||
def async_teardown() -> None:
|
||||
"""Tear the coordinator down appropriately."""
|
||||
for unsub in self._signal_handler_unsubs:
|
||||
unsub()
|
||||
|
||||
self.config_entry.async_on_unload(async_teardown)
|
||||
|
|
|
@ -2081,7 +2081,7 @@ raincloudy==0.0.7
|
|||
raspyrfm-client==1.2.8
|
||||
|
||||
# homeassistant.components.rainmachine
|
||||
regenmaschine==2022.07.1
|
||||
regenmaschine==2022.07.3
|
||||
|
||||
# homeassistant.components.renault
|
||||
renault-api==0.1.11
|
||||
|
|
|
@ -1402,7 +1402,7 @@ radios==0.1.1
|
|||
radiotherm==2.1.0
|
||||
|
||||
# homeassistant.components.rainmachine
|
||||
regenmaschine==2022.07.1
|
||||
regenmaschine==2022.07.3
|
||||
|
||||
# homeassistant.components.renault
|
||||
renault-api==0.1.11
|
||||
|
|
Loading…
Add table
Reference in a new issue