From ca885f3fab9d58adcb87935ed1e1137d9f537439 Mon Sep 17 00:00:00 2001 From: Jack Boswell Date: Thu, 19 Jan 2023 13:57:48 +1300 Subject: [PATCH] Add a switch to Starlink for stow/unstow (#85730) Co-authored-by: J. Nick Koston --- homeassistant/components/starlink/__init__.py | 7 +- homeassistant/components/starlink/button.py | 2 +- .../components/starlink/coordinator.py | 13 +++- homeassistant/components/starlink/switch.py | 78 +++++++++++++++++++ 4 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/starlink/switch.py diff --git a/homeassistant/components/starlink/__init__.py b/homeassistant/components/starlink/__init__.py index acd85c3595a..ceb962c88cd 100644 --- a/homeassistant/components/starlink/__init__.py +++ b/homeassistant/components/starlink/__init__.py @@ -8,7 +8,12 @@ from homeassistant.core import HomeAssistant from .const import DOMAIN from .coordinator import StarlinkUpdateCoordinator -PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR] +PLATFORMS: list[Platform] = [ + Platform.BINARY_SENSOR, + Platform.BUTTON, + Platform.SENSOR, + Platform.SWITCH, +] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/starlink/button.py b/homeassistant/components/starlink/button.py index e9613cb817e..4c57bac261d 100644 --- a/homeassistant/components/starlink/button.py +++ b/homeassistant/components/starlink/button.py @@ -61,6 +61,6 @@ BUTTONS = [ name="Reboot", device_class=ButtonDeviceClass.RESTART, entity_category=EntityCategory.DIAGNOSTIC, - press_fn=lambda coordinator: coordinator.reboot_starlink(), + press_fn=lambda coordinator: coordinator.async_reboot_starlink(), ) ] diff --git a/homeassistant/components/starlink/coordinator.py b/homeassistant/components/starlink/coordinator.py index 6e63f84b067..56d25bf2d1a 100644 --- a/homeassistant/components/starlink/coordinator.py +++ b/homeassistant/components/starlink/coordinator.py @@ -13,6 +13,7 @@ from starlink_grpc import ( ObstructionDict, StatusDict, reboot, + set_stow_state, status_data, ) @@ -56,7 +57,17 @@ class StarlinkUpdateCoordinator(DataUpdateCoordinator[StarlinkData]): except GrpcError as exc: raise UpdateFailed from exc - async def reboot_starlink(self): + async def async_stow_starlink(self, stow: bool): + """Set whether Starlink system tied to this coordinator should be stowed.""" + async with async_timeout.timeout(4): + try: + await self.hass.async_add_executor_job( + set_stow_state, not stow, self.channel_context + ) + except GrpcError as exc: + raise HomeAssistantError from exc + + async def async_reboot_starlink(self): """Reboot the Starlink system tied to this coordinator.""" async with async_timeout.timeout(4): try: diff --git a/homeassistant/components/starlink/switch.py b/homeassistant/components/starlink/switch.py new file mode 100644 index 00000000000..daa7b45b305 --- /dev/null +++ b/homeassistant/components/starlink/switch.py @@ -0,0 +1,78 @@ +"""Contains switches exposed by the Starlink integration.""" + +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from typing import Any + +from homeassistant.components.switch import ( + SwitchDeviceClass, + SwitchEntity, + SwitchEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import StarlinkData, StarlinkUpdateCoordinator +from .entity import StarlinkEntity + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up all binary sensors for this entry.""" + coordinator = hass.data[DOMAIN][entry.entry_id] + + async_add_entities( + StarlinkSwitchEntity(coordinator, description) for description in SWITCHES + ) + + +@dataclass +class StarlinkSwitchEntityDescriptionMixin: + """Mixin for required keys.""" + + value_fn: Callable[[StarlinkData], bool | None] + turn_on_fn: Callable[[StarlinkUpdateCoordinator], Awaitable[None]] + turn_off_fn: Callable[[StarlinkUpdateCoordinator], Awaitable[None]] + + +@dataclass +class StarlinkSwitchEntityDescription( + SwitchEntityDescription, StarlinkSwitchEntityDescriptionMixin +): + """Describes a Starlink switch entity.""" + + +class StarlinkSwitchEntity(StarlinkEntity, SwitchEntity): + """A SwitchEntity for Starlink devices. Handles creating unique IDs.""" + + entity_description: StarlinkSwitchEntityDescription + + @property + def is_on(self) -> bool | None: + """Return True if entity is on.""" + return self.entity_description.value_fn(self.coordinator.data) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the entity on.""" + return await self.entity_description.turn_on_fn(self.coordinator) + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the entity off.""" + return await self.entity_description.turn_off_fn(self.coordinator) + + +SWITCHES = [ + StarlinkSwitchEntityDescription( + key="stowed", + name="Stowed", + device_class=SwitchDeviceClass.SWITCH, + value_fn=lambda data: data.status["state"] == "STOWED", + turn_on_fn=lambda coordinator: coordinator.async_stow_starlink(True), + turn_off_fn=lambda coordinator: coordinator.async_stow_starlink(False), + ) +]