Skip to content

Commit

Permalink
Add support for green LEDs to API (#4556)
Browse files Browse the repository at this point in the history
* Add support for green LEDs to API

* Save board config in supervisor and post on start

* Ignore no-value-for-parameter in validate
  • Loading branch information
mdegat01 authored Sep 14, 2023
1 parent d96598b commit e1232bc
Show file tree
Hide file tree
Showing 16 changed files with 424 additions and 21 deletions.
2 changes: 2 additions & 0 deletions supervisor/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ def _register_os(self) -> None:
# Boards endpoints
self.webapp.add_routes(
[
web.get("/os/boards/green", api_os.boards_green_info),
web.post("/os/boards/green", api_os.boards_green_options),
web.get("/os/boards/yellow", api_os.boards_yellow_info),
web.post("/os/boards/yellow", api_os.boards_yellow_options),
web.get("/os/boards/{board}", api_os.boards_other_info),
Expand Down
3 changes: 0 additions & 3 deletions supervisor/api/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,13 @@
ATTR_DATA_DISK = "data_disk"
ATTR_DEVICE = "device"
ATTR_DEV_PATH = "dev_path"
ATTR_DISK_LED = "disk_led"
ATTR_DISKS = "disks"
ATTR_DRIVES = "drives"
ATTR_DT_SYNCHRONIZED = "dt_synchronized"
ATTR_DT_UTC = "dt_utc"
ATTR_EJECTABLE = "ejectable"
ATTR_FALLBACK = "fallback"
ATTR_FILESYSTEMS = "filesystems"
ATTR_HEARTBEAT_LED = "heartbeat_led"
ATTR_IDENTIFIERS = "identifiers"
ATTR_JOBS = "jobs"
ATTR_LLMNR = "llmnr"
Expand All @@ -41,7 +39,6 @@
ATTR_MOUNTS = "mounts"
ATTR_MOUNT_POINTS = "mount_points"
ATTR_PANEL_PATH = "panel_path"
ATTR_POWER_LED = "power_led"
ATTR_REMOVABLE = "removable"
ATTR_REVISION = "revision"
ATTR_SEAT = "seat"
Expand Down
44 changes: 40 additions & 4 deletions supervisor/api/os.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,19 @@
import voluptuous as vol

from ..const import (
ATTR_ACTIVITY_LED,
ATTR_BOARD,
ATTR_BOOT,
ATTR_DEVICES,
ATTR_DISK_LED,
ATTR_HEARTBEAT_LED,
ATTR_ID,
ATTR_NAME,
ATTR_POWER_LED,
ATTR_SERIAL,
ATTR_SIZE,
ATTR_UPDATE_AVAILABLE,
ATTR_USER_LED,
ATTR_VERSION,
ATTR_VERSION_LATEST,
)
Expand All @@ -27,28 +32,33 @@
ATTR_DATA_DISK,
ATTR_DEV_PATH,
ATTR_DEVICE,
ATTR_DISK_LED,
ATTR_DISKS,
ATTR_HEARTBEAT_LED,
ATTR_MODEL,
ATTR_POWER_LED,
ATTR_VENDOR,
)
from .utils import api_process, api_validate

_LOGGER: logging.Logger = logging.getLogger(__name__)

# pylint: disable=no-value-for-parameter
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): version_tag})
SCHEMA_DISK = vol.Schema({vol.Required(ATTR_DEVICE): str})

# pylint: disable=no-value-for-parameter
SCHEMA_YELLOW_OPTIONS = vol.Schema(
{
vol.Optional(ATTR_DISK_LED): vol.Boolean(),
vol.Optional(ATTR_HEARTBEAT_LED): vol.Boolean(),
vol.Optional(ATTR_POWER_LED): vol.Boolean(),
}
)
SCHEMA_GREEN_OPTIONS = vol.Schema(
{
vol.Optional(ATTR_ACTIVITY_LED): vol.Boolean(),
vol.Optional(ATTR_POWER_LED): vol.Boolean(),
vol.Optional(ATTR_USER_LED): vol.Boolean(),
}
)
# pylint: enable=no-value-for-parameter


class APIOS(CoreSysAttributes):
Expand Down Expand Up @@ -105,6 +115,31 @@ async def list_data(self, request: web.Request) -> dict[str, Any]:
],
}

@api_process
async def boards_green_info(self, request: web.Request) -> dict[str, Any]:
"""Get green board settings."""
return {
ATTR_ACTIVITY_LED: self.sys_dbus.agent.board.green.activity_led,
ATTR_POWER_LED: self.sys_dbus.agent.board.green.power_led,
ATTR_USER_LED: self.sys_dbus.agent.board.green.user_led,
}

@api_process
async def boards_green_options(self, request: web.Request) -> None:
"""Update green board settings."""
body = await api_validate(SCHEMA_GREEN_OPTIONS, request)

if ATTR_ACTIVITY_LED in body:
self.sys_dbus.agent.board.green.activity_led = body[ATTR_ACTIVITY_LED]

if ATTR_POWER_LED in body:
self.sys_dbus.agent.board.green.power_led = body[ATTR_POWER_LED]

if ATTR_USER_LED in body:
self.sys_dbus.agent.board.green.user_led = body[ATTR_USER_LED]

self.sys_dbus.agent.board.green.save_data()

@api_process
async def boards_yellow_info(self, request: web.Request) -> dict[str, Any]:
"""Get yellow board settings."""
Expand All @@ -128,6 +163,7 @@ async def boards_yellow_options(self, request: web.Request) -> None:
if ATTR_POWER_LED in body:
self.sys_dbus.agent.board.yellow.power_led = body[ATTR_POWER_LED]

self.sys_dbus.agent.board.yellow.save_data()
self.sys_resolution.create_issue(
IssueType.REBOOT_REQUIRED,
ContextType.SYSTEM,
Expand Down
6 changes: 6 additions & 0 deletions supervisor/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
FILE_HASSIO_ADDONS = Path(SUPERVISOR_DATA, "addons.json")
FILE_HASSIO_AUTH = Path(SUPERVISOR_DATA, "auth.json")
FILE_HASSIO_BACKUPS = Path(SUPERVISOR_DATA, "backups.json")
FILE_HASSIO_BOARD = Path(SUPERVISOR_DATA, "board.json")
FILE_HASSIO_CONFIG = Path(SUPERVISOR_DATA, "config.json")
FILE_HASSIO_DISCOVERY = Path(SUPERVISOR_DATA, "discovery.json")
FILE_HASSIO_DOCKER = Path(SUPERVISOR_DATA, "docker.json")
Expand Down Expand Up @@ -88,6 +89,7 @@
ATTR_ACCESS_TOKEN = "access_token"
ATTR_ACCESSPOINTS = "accesspoints"
ATTR_ACTIVE = "active"
ATTR_ACTIVITY_LED = "activity_led"
ATTR_ADDON = "addon"
ATTR_ADDONS = "addons"
ATTR_ADDONS_CUSTOM_LIST = "addons_custom_list"
Expand Down Expand Up @@ -152,6 +154,7 @@
ATTR_DISCOVERY = "discovery"
ATTR_DISK = "disk"
ATTR_DISK_FREE = "disk_free"
ATTR_DISK_LED = "disk_led"
ATTR_DISK_LIFE_TIME = "disk_life_time"
ATTR_DISK_TOTAL = "disk_total"
ATTR_DISK_USED = "disk_used"
Expand All @@ -177,6 +180,7 @@
ATTR_HASSIO_ROLE = "hassio_role"
ATTR_HASSOS = "hassos"
ATTR_HEALTHY = "healthy"
ATTR_HEARTBEAT_LED = "heartbeat_led"
ATTR_HOMEASSISTANT = "homeassistant"
ATTR_HOMEASSISTANT_API = "homeassistant_api"
ATTR_HOST = "host"
Expand Down Expand Up @@ -252,6 +256,7 @@
ATTR_PORT = "port"
ATTR_PORTS = "ports"
ATTR_PORTS_DESCRIPTION = "ports_description"
ATTR_POWER_LED = "power_led"
ATTR_PREFIX = "prefix"
ATTR_PRIMARY = "primary"
ATTR_PRIORITY = "priority"
Expand Down Expand Up @@ -315,6 +320,7 @@
ATTR_URL = "url"
ATTR_USB = "usb"
ATTR_USER = "user"
ATTR_USER_LED = "user_led"
ATTR_USERNAME = "username"
ATTR_UUID = "uuid"
ATTR_VALID = "valid"
Expand Down
13 changes: 12 additions & 1 deletion supervisor/dbus/agent/boards/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
DBUS_OBJECT_HAOS_BOARDS,
)
from ...interface import DBusInterfaceProxy, dbus_property
from .const import BOARD_NAME_SUPERVISED, BOARD_NAME_YELLOW
from .const import BOARD_NAME_GREEN, BOARD_NAME_SUPERVISED, BOARD_NAME_YELLOW
from .green import Green
from .interface import BoardProxy
from .supervised import Supervised
from .yellow import Yellow
Expand Down Expand Up @@ -39,6 +40,14 @@ def board(self) -> str:
"""Get board name."""
return self.properties[DBUS_ATTR_BOARD]

@property
def green(self) -> Green:
"""Get Green board."""
if self.board != BOARD_NAME_GREEN:
raise BoardInvalidError("Green board is not in use", _LOGGER.error)

return self._board_proxy

@property
def supervised(self) -> Supervised:
"""Get Supervised board."""
Expand All @@ -61,6 +70,8 @@ async def connect(self, bus: MessageBus) -> None:

if self.board == BOARD_NAME_YELLOW:
self._board_proxy = Yellow()
elif self.board == BOARD_NAME_GREEN:
self._board_proxy = Green()
elif self.board == BOARD_NAME_SUPERVISED:
self._board_proxy = Supervised()

Expand Down
1 change: 1 addition & 0 deletions supervisor/dbus/agent/boards/const.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Constants for boards."""

BOARD_NAME_GREEN = "Green"
BOARD_NAME_SUPERVISED = "Supervised"
BOARD_NAME_YELLOW = "Yellow"
65 changes: 65 additions & 0 deletions supervisor/dbus/agent/boards/green.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Green board management."""

import asyncio

from dbus_fast.aio.message_bus import MessageBus

from ....const import ATTR_ACTIVITY_LED, ATTR_POWER_LED, ATTR_USER_LED
from ...const import DBUS_ATTR_ACTIVITY_LED, DBUS_ATTR_POWER_LED, DBUS_ATTR_USER_LED
from ...interface import dbus_property
from .const import BOARD_NAME_GREEN
from .interface import BoardProxy
from .validate import SCHEMA_GREEN_BOARD


class Green(BoardProxy):
"""Green board manager object."""

def __init__(self) -> None:
"""Initialize properties."""
super().__init__(BOARD_NAME_GREEN, SCHEMA_GREEN_BOARD)

@property
@dbus_property
def activity_led(self) -> bool:
"""Get activity LED enabled."""
return self.properties[DBUS_ATTR_ACTIVITY_LED]

@activity_led.setter
def activity_led(self, enabled: bool) -> None:
"""Enable/disable activity LED."""
self._data[ATTR_ACTIVITY_LED] = enabled
asyncio.create_task(self.dbus.Boards.Green.set_activity_led(enabled))

@property
@dbus_property
def power_led(self) -> bool:
"""Get power LED enabled."""
return self.properties[DBUS_ATTR_POWER_LED]

@power_led.setter
def power_led(self, enabled: bool) -> None:
"""Enable/disable power LED."""
self._data[ATTR_POWER_LED] = enabled
asyncio.create_task(self.dbus.Boards.Green.set_power_led(enabled))

@property
@dbus_property
def user_led(self) -> bool:
"""Get user LED enabled."""
return self.properties[DBUS_ATTR_USER_LED]

@user_led.setter
def user_led(self, enabled: bool) -> None:
"""Enable/disable disk LED."""
self._data[ATTR_USER_LED] = enabled
asyncio.create_task(self.dbus.Boards.Green.set_user_led(enabled))

async def connect(self, bus: MessageBus) -> None:
"""Connect to D-Bus."""
await super().connect(bus)

# Set LEDs based on settings on connect
self.activity_led = self._data[ATTR_ACTIVITY_LED]
self.power_led = self._data[ATTR_POWER_LED]
self.user_led = self._data[ATTR_USER_LED]
12 changes: 9 additions & 3 deletions supervisor/dbus/agent/boards/interface.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
"""Board dbus proxy interface."""

from voluptuous import Schema

from ....const import FILE_HASSIO_BOARD
from ....utils.common import FileConfiguration
from ...const import DBUS_IFACE_HAOS_BOARDS, DBUS_NAME_HAOS, DBUS_OBJECT_HAOS_BOARDS
from ...interface import DBusInterfaceProxy
from .validate import SCHEMA_BASE_BOARD


class BoardProxy(DBusInterfaceProxy):
class BoardProxy(FileConfiguration, DBusInterfaceProxy):
"""DBus interface proxy for os board."""

bus_name: str = DBUS_NAME_HAOS

def __init__(self, name: str) -> None:
def __init__(self, name: str, file_schema: Schema | None = None) -> None:
"""Initialize properties."""
super().__init__()
super().__init__(FILE_HASSIO_BOARD, file_schema or SCHEMA_BASE_BOARD)
super(FileConfiguration, self).__init__()

self._name: str = name
self.object_path: str = f"{DBUS_OBJECT_HAOS_BOARDS}/{name}"
Expand Down
32 changes: 32 additions & 0 deletions supervisor/dbus/agent/boards/validate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Validation for board config."""

import voluptuous as vol

from ....const import (
ATTR_ACTIVITY_LED,
ATTR_DISK_LED,
ATTR_HEARTBEAT_LED,
ATTR_POWER_LED,
ATTR_USER_LED,
)

# pylint: disable=no-value-for-parameter
SCHEMA_BASE_BOARD = vol.Schema({}, extra=vol.REMOVE_EXTRA)

SCHEMA_GREEN_BOARD = vol.Schema(
{
vol.Optional(ATTR_ACTIVITY_LED, default=True): vol.Boolean(),
vol.Optional(ATTR_POWER_LED, default=True): vol.Boolean(),
vol.Optional(ATTR_USER_LED, default=True): vol.Boolean(),
},
extra=vol.REMOVE_EXTRA,
)

SCHEMA_YELLOW_BOARD = vol.Schema(
{
vol.Optional(ATTR_DISK_LED, default=True): vol.Boolean(),
vol.Optional(ATTR_HEARTBEAT_LED, default=True): vol.Boolean(),
vol.Optional(ATTR_POWER_LED, default=True): vol.Boolean(),
},
extra=vol.REMOVE_EXTRA,
)
18 changes: 17 additions & 1 deletion supervisor/dbus/agent/boards/yellow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@

import asyncio

from dbus_fast.aio.message_bus import MessageBus

from ....const import ATTR_DISK_LED, ATTR_HEARTBEAT_LED, ATTR_POWER_LED
from ...const import DBUS_ATTR_DISK_LED, DBUS_ATTR_HEARTBEAT_LED, DBUS_ATTR_POWER_LED
from ...interface import dbus_property
from .const import BOARD_NAME_YELLOW
from .interface import BoardProxy
from .validate import SCHEMA_YELLOW_BOARD


class Yellow(BoardProxy):
"""Yellow board manager object."""

def __init__(self) -> None:
"""Initialize properties."""
super().__init__(BOARD_NAME_YELLOW)
super().__init__(BOARD_NAME_YELLOW, SCHEMA_YELLOW_BOARD)

@property
@dbus_property
Expand All @@ -24,6 +28,7 @@ def heartbeat_led(self) -> bool:
@heartbeat_led.setter
def heartbeat_led(self, enabled: bool) -> None:
"""Enable/disable heartbeat LED."""
self._data[ATTR_HEARTBEAT_LED] = enabled
asyncio.create_task(self.dbus.Boards.Yellow.set_heartbeat_led(enabled))

@property
Expand All @@ -35,6 +40,7 @@ def power_led(self) -> bool:
@power_led.setter
def power_led(self, enabled: bool) -> None:
"""Enable/disable power LED."""
self._data[ATTR_POWER_LED] = enabled
asyncio.create_task(self.dbus.Boards.Yellow.set_power_led(enabled))

@property
Expand All @@ -46,4 +52,14 @@ def disk_led(self) -> bool:
@disk_led.setter
def disk_led(self, enabled: bool) -> None:
"""Enable/disable disk LED."""
self._data[ATTR_DISK_LED] = enabled
asyncio.create_task(self.dbus.Boards.Yellow.set_disk_led(enabled))

async def connect(self, bus: MessageBus) -> None:
"""Connect to D-Bus."""
await super().connect(bus)

# Set LEDs based on settings on connect
self.disk_led = self._data[ATTR_DISK_LED]
self.heartbeat_led = self._data[ATTR_HEARTBEAT_LED]
self.power_led = self._data[ATTR_POWER_LED]
Loading

0 comments on commit e1232bc

Please sign in to comment.