"""Init file for Supervisor Audio RESTful API.""" import asyncio from collections.abc import Awaitable from dataclasses import asdict import logging from typing import Any from aiohttp import web import voluptuous as vol from ..const import ( ATTR_ACTIVE, ATTR_APPLICATION, ATTR_AUDIO, ATTR_BLK_READ, ATTR_BLK_WRITE, ATTR_CARD, ATTR_CPU_PERCENT, ATTR_HOST, ATTR_INDEX, ATTR_INPUT, ATTR_MEMORY_LIMIT, ATTR_MEMORY_PERCENT, ATTR_MEMORY_USAGE, ATTR_NAME, ATTR_NETWORK_RX, ATTR_NETWORK_TX, ATTR_OUTPUT, ATTR_UPDATE_AVAILABLE, ATTR_VERSION, ATTR_VERSION_LATEST, ATTR_VOLUME, ) from ..coresys import CoreSysAttributes from ..exceptions import APIError from ..host.sound import StreamType from ..validate import version_tag from .utils import api_process, api_validate _LOGGER: logging.Logger = logging.getLogger(__name__) SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): version_tag}) SCHEMA_VOLUME = vol.Schema( { vol.Required(ATTR_INDEX): vol.Coerce(int), vol.Required(ATTR_VOLUME): vol.Coerce(float), } ) # pylint: disable=no-value-for-parameter SCHEMA_MUTE = vol.Schema( { vol.Required(ATTR_INDEX): vol.Coerce(int), vol.Required(ATTR_ACTIVE): vol.Boolean(), } ) SCHEMA_DEFAULT = vol.Schema({vol.Required(ATTR_NAME): str}) SCHEMA_PROFILE = vol.Schema( {vol.Required(ATTR_CARD): str, vol.Required(ATTR_NAME): str} ) class APIAudio(CoreSysAttributes): """Handle RESTful API for Audio functions.""" @api_process async def info(self, request: web.Request) -> dict[str, Any]: """Return Audio information.""" return { ATTR_VERSION: self.sys_plugins.audio.version, ATTR_VERSION_LATEST: self.sys_plugins.audio.latest_version, ATTR_UPDATE_AVAILABLE: self.sys_plugins.audio.need_update, ATTR_HOST: str(self.sys_docker.network.audio), ATTR_AUDIO: { ATTR_CARD: [asdict(card) for card in self.sys_host.sound.cards], ATTR_INPUT: [asdict(stream) for stream in self.sys_host.sound.inputs], ATTR_OUTPUT: [asdict(stream) for stream in self.sys_host.sound.outputs], ATTR_APPLICATION: [ asdict(stream) for stream in self.sys_host.sound.applications ], }, } @api_process async def stats(self, request: web.Request) -> dict[str, Any]: """Return resource information.""" stats = await self.sys_plugins.audio.stats() return { ATTR_CPU_PERCENT: stats.cpu_percent, ATTR_MEMORY_USAGE: stats.memory_usage, ATTR_MEMORY_LIMIT: stats.memory_limit, ATTR_MEMORY_PERCENT: stats.memory_percent, ATTR_NETWORK_RX: stats.network_rx, ATTR_NETWORK_TX: stats.network_tx, ATTR_BLK_READ: stats.blk_read, ATTR_BLK_WRITE: stats.blk_write, } @api_process async def update(self, request: web.Request) -> None: """Update Audio plugin.""" body = await api_validate(SCHEMA_VERSION, request) version = body.get(ATTR_VERSION, self.sys_plugins.audio.latest_version) if version == self.sys_plugins.audio.version: raise APIError(f"Version {version} is already in use") await asyncio.shield(self.sys_plugins.audio.update(version)) @api_process def restart(self, request: web.Request) -> Awaitable[None]: """Restart Audio plugin.""" return asyncio.shield(self.sys_plugins.audio.restart()) @api_process def reload(self, request: web.Request) -> Awaitable[None]: """Reload Audio information.""" return asyncio.shield(self.sys_host.sound.update()) @api_process async def set_volume(self, request: web.Request) -> None: """Set audio volume on stream.""" source: StreamType = StreamType(request.match_info["source"]) application: bool = request.path.endswith("application") body = await api_validate(SCHEMA_VOLUME, request) await asyncio.shield( self.sys_host.sound.set_volume( source, body[ATTR_INDEX], body[ATTR_VOLUME], application ) ) @api_process async def set_mute(self, request: web.Request) -> None: """Mute audio volume on stream.""" source: StreamType = StreamType(request.match_info["source"]) application: bool = request.path.endswith("application") body = await api_validate(SCHEMA_MUTE, request) await asyncio.shield( self.sys_host.sound.set_mute( source, body[ATTR_INDEX], body[ATTR_ACTIVE], application ) ) @api_process async def set_default(self, request: web.Request) -> None: """Set audio default stream.""" source: StreamType = StreamType(request.match_info["source"]) body = await api_validate(SCHEMA_DEFAULT, request) await asyncio.shield(self.sys_host.sound.set_default(source, body[ATTR_NAME])) @api_process async def set_profile(self, request: web.Request) -> None: """Set audio default sources.""" body = await api_validate(SCHEMA_PROFILE, request) await asyncio.shield( self.sys_host.sound.ativate_profile(body[ATTR_CARD], body[ATTR_NAME]) )