diff --git a/API.md b/API.md index 4823cf74b..58e5d136c 100644 --- a/API.md +++ b/API.md @@ -236,15 +236,6 @@ return: } ``` -- POST `/host/options` - -```json -{ - "audio_input": "0,0", - "audio_output": "0,0" -} -``` - - POST `/host/update` Optional: @@ -255,7 +246,11 @@ Optional: } ``` -- GET `/host/hardware` +- POST `/host/reload` + +### Hardware + +- GET `/hardware/info` ```json { "serial": ["/dev/xy"], @@ -274,7 +269,20 @@ Optional: } ``` -- POST `/host/reload` +- GET `/hardware/audio` +```json +{ + "audio": { + "input": { + "0,0": "Mic" + }, + "output": { + "1,0": "Jack", + "1,1": "HDMI" + } + } +} +``` ### Network diff --git a/hassio/addons/addon.py b/hassio/addons/addon.py index b3d6660d8..e17f3f7a5 100644 --- a/hassio/addons/addon.py +++ b/hassio/addons/addon.py @@ -1,4 +1,5 @@ """Init file for HassIO addons.""" +from contextlib import suppress from copy import deepcopy import logging import json @@ -372,15 +373,14 @@ class Addon(CoreSysAttributes): if not self.with_audio: return None - setting = self._config.audio_output if self.is_installed and \ ATTR_AUDIO_OUTPUT in self._data.user[self._id]: - setting = self._data.user[self._id][ATTR_AUDIO_OUTPUT] - return setting + return self._data.user[self._id][ATTR_AUDIO_OUTPUT] + return self._alsa.default.output @audio_output.setter def audio_output(self, value): - """Set/remove custom audio output settings.""" + """Set/reset audio output settings.""" if value is None: self._data.user[self._id].pop(ATTR_AUDIO_OUTPUT, None) else: @@ -392,14 +392,13 @@ class Addon(CoreSysAttributes): if not self.with_audio: return None - setting = self._config.audio_input if self.is_installed and ATTR_AUDIO_INPUT in self._data.user[self._id]: - setting = self._data.user[self._id][ATTR_AUDIO_INPUT] - return setting + return self._data.user[self._id][ATTR_AUDIO_INPUT] + return self._alsa.default.input @audio_input.setter def audio_input(self, value): - """Set/remove custom audio input settings.""" + """Set/reset audio input settings.""" if value is None: self._data.user[self._id].pop(ATTR_AUDIO_INPUT, None) else: @@ -504,6 +503,16 @@ class Addon(CoreSysAttributes): """Return path to custom AppArmor profile.""" return Path(self.path_location, 'apparmor') + @property + def path_asound(self): + """Return path to asound config.""" + return Path(self._config.path_tmp, f"{self.slug}_asound") + + @property + def path_extern_asound(self): + """Return path to asound config for docker.""" + return Path(self._config.path_extern_tmp, f"{self.slug}_asound") + def save_data(self): """Save data of addon.""" self._addons.data.save_data() @@ -526,6 +535,20 @@ class Addon(CoreSysAttributes): return False + def write_asound(self): + """Write asound config to file and return True on success.""" + asound_config = self._alsa.asound( + alsa_input=self.audio_input, alsa_output=self.audio_output) + + try: + with self.path_asound.open('w') as config_file: + config_file.write(asound_config) + except OSError as err: + _LOGGER.error("Addon %s can't write asound: %s", self._id, err) + return False + + return True + @property def schema(self): """Create a schema for addon options.""" @@ -598,6 +621,11 @@ class Addon(CoreSysAttributes): "Remove Home-Assistant addon data folder %s", self.path_data) shutil.rmtree(str(self.path_data)) + # Cleanup audio settings + if self.path_asound.exists(): + with suppress(OSError): + self.path_asound.unlink() + self._set_uninstall() return True @@ -613,9 +641,14 @@ class Addon(CoreSysAttributes): @check_installed async def start(self): """Set options and start addon.""" + # Options if not self.write_options(): return False + # Sound + if self.with_audio and not self.write_asound(): + return False + return await self.instance.run() @check_installed diff --git a/hassio/addons/validate.py b/hassio/addons/validate.py index c5ed81d28..cc0fde405 100644 --- a/hassio/addons/validate.py +++ b/hassio/addons/validate.py @@ -19,7 +19,7 @@ from ..const import ( ATTR_ARGS, ATTR_GPIO, ATTR_HOMEASSISTANT_API, ATTR_STDIN, ATTR_LEGACY, ATTR_HOST_DBUS, ATTR_AUTO_UART, ATTR_SERVICES, ATTR_DISCOVERY, ATTR_SECCOMP, ATTR_APPARMOR) -from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_CHANNEL +from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_DEVICE _LOGGER = logging.getLogger(__name__) @@ -165,8 +165,8 @@ SCHEMA_ADDON_USER = vol.Schema({ vol.Optional(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]), vol.Optional(ATTR_NETWORK): DOCKER_PORTS, - vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_CHANNEL, - vol.Optional(ATTR_AUDIO_INPUT): ALSA_CHANNEL, + vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_DEVICE, + vol.Optional(ATTR_AUDIO_INPUT): ALSA_DEVICE, }, extra=vol.REMOVE_EXTRA) diff --git a/hassio/api/__init__.py b/hassio/api/__init__.py index c3d878751..b91d79485 100644 --- a/hassio/api/__init__.py +++ b/hassio/api/__init__.py @@ -7,6 +7,7 @@ from aiohttp import web from .addons import APIAddons from .discovery import APIDiscovery from .homeassistant import APIHomeAssistant +from .hardware import APIHardware from .host import APIHost from .network import APINetwork from .proxy import APIProxy @@ -37,6 +38,7 @@ class RestAPI(CoreSysAttributes): """Register REST API Calls.""" self._register_supervisor() self._register_host() + self._register_hardware() self._register_homeassistant() self._register_proxy() self._register_panel() @@ -53,11 +55,9 @@ class RestAPI(CoreSysAttributes): self.webapp.add_routes([ web.get('/host/info', api_host.info), - web.get('/host/hardware', api_host.hardware), web.post('/host/reboot', api_host.reboot), web.post('/host/shutdown', api_host.shutdown), web.post('/host/update', api_host.update), - web.post('/host/options', api_host.options), web.post('/host/reload', api_host.reload), ]) @@ -71,6 +71,16 @@ class RestAPI(CoreSysAttributes): web.post('/network/options', api_net.options), ]) + def _register_hardware(self): + """Register hardware function.""" + api_hardware = APIHardware() + api_hardware.coresys = self.coresys + + self.webapp.add_routes([ + web.get('/hardware/info', api_hardware.info), + web.get('/hardware/audio', api_hardware.audio), + ]) + def _register_supervisor(self): """Register supervisor function.""" api_supervisor = APISupervisor() diff --git a/hassio/api/addons.py b/hassio/api/addons.py index d16ec10c4..a4d5b4593 100644 --- a/hassio/api/addons.py +++ b/hassio/api/addons.py @@ -20,7 +20,7 @@ from ..const import ( ATTR_DISCOVERY, ATTR_SECCOMP, ATTR_APPARMOR, CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY, CONTENT_TYPE_TEXT) from ..coresys import CoreSysAttributes -from ..validate import DOCKER_PORTS +from ..validate import DOCKER_PORTS, ALSA_DEVICE _LOGGER = logging.getLogger(__name__) @@ -33,6 +33,8 @@ SCHEMA_OPTIONS = vol.Schema({ vol.Optional(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]), vol.Optional(ATTR_NETWORK): vol.Any(None, DOCKER_PORTS), vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(), + vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_DEVICE, + vol.Optional(ATTR_AUDIO_INPUT): ALSA_DEVICE, }) diff --git a/hassio/api/hardware.py b/hassio/api/hardware.py new file mode 100644 index 000000000..e5e22a35f --- /dev/null +++ b/hassio/api/hardware.py @@ -0,0 +1,34 @@ +"""Init file for HassIO hardware rest api.""" +import logging + +from .utils import api_process +from ..const import ( + ATTR_SERIAL, ATTR_DISK, ATTR_GPIO, ATTR_AUDIO, ATTR_INPUT, ATTR_OUTPUT) +from ..coresys import CoreSysAttributes + +_LOGGER = logging.getLogger(__name__) + + +class APIHardware(CoreSysAttributes): + """Handle rest api for hardware functions.""" + + @api_process + async def info(self, request): + """Show hardware info.""" + return { + ATTR_SERIAL: list(self._hardware.serial_devices), + ATTR_INPUT: list(self._hardware.input_devices), + ATTR_DISK: list(self._hardware.disk_devices), + ATTR_GPIO: list(self._hardware.gpio_devices), + ATTR_AUDIO: self._hardware.audio_devices, + } + + @api_process + async def audio(self, request): + """Show ALSA audio devices.""" + return { + ATTR_AUDIO: { + ATTR_INPUT: self._alsa.input_devices, + ATTR_OUTPUT: self._alsa.output_devices, + } + } diff --git a/hassio/api/host.py b/hassio/api/host.py index 7bc69826e..a0b63f2c9 100644 --- a/hassio/api/host.py +++ b/hassio/api/host.py @@ -7,10 +7,8 @@ import voluptuous as vol from .utils import api_process_hostcontrol, api_process, api_validate from ..const import ( ATTR_VERSION, ATTR_LAST_VERSION, ATTR_TYPE, ATTR_HOSTNAME, ATTR_FEATURES, - ATTR_OS, ATTR_SERIAL, ATTR_INPUT, ATTR_DISK, ATTR_AUDIO, ATTR_AUDIO_INPUT, - ATTR_AUDIO_OUTPUT, ATTR_GPIO) + ATTR_OS) from ..coresys import CoreSysAttributes -from ..validate import ALSA_CHANNEL _LOGGER = logging.getLogger(__name__) @@ -18,11 +16,6 @@ SCHEMA_VERSION = vol.Schema({ vol.Optional(ATTR_VERSION): vol.Coerce(str), }) -SCHEMA_OPTIONS = vol.Schema({ - vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_CHANNEL, - vol.Optional(ATTR_AUDIO_INPUT): ALSA_CHANNEL, -}) - class APIHost(CoreSysAttributes): """Handle rest api for host functions.""" @@ -39,19 +32,6 @@ class APIHost(CoreSysAttributes): ATTR_OS: self._host_control.os_info, } - @api_process - async def options(self, request): - """Process host options.""" - body = await api_validate(SCHEMA_OPTIONS, request) - - if ATTR_AUDIO_OUTPUT in body: - self._config.audio_output = body[ATTR_AUDIO_OUTPUT] - if ATTR_AUDIO_INPUT in body: - self._config.audio_input = body[ATTR_AUDIO_INPUT] - - self._config.save_data() - return True - @api_process_hostcontrol def reboot(self, request): """Reboot host.""" @@ -79,14 +59,3 @@ class APIHost(CoreSysAttributes): return await asyncio.shield( self._host_control.update(version=version), loop=self._loop) - - @api_process - async def hardware(self, request): - """Return local hardware infos.""" - return { - ATTR_SERIAL: list(self._hardware.serial_devices), - ATTR_INPUT: list(self._hardware.input_devices), - ATTR_DISK: list(self._hardware.disk_devices), - ATTR_GPIO: list(self._hardware.gpio_devices), - ATTR_AUDIO: self._hardware.audio_devices, - } diff --git a/hassio/bootstrap.py b/hassio/bootstrap.py index 82ece0f13..8ce5f2f41 100644 --- a/hassio/bootstrap.py +++ b/hassio/bootstrap.py @@ -17,6 +17,7 @@ from .snapshots import SnapshotManager from .tasks import Tasks from .updater import Updater from .services import ServiceManager +from .host import AlsaAudio _LOGGER = logging.getLogger(__name__) @@ -28,6 +29,7 @@ def initialize_coresys(loop): # Initialize core objects coresys.updater = Updater(coresys) coresys.api = RestAPI(coresys) + coresys.alsa = AlsaAudio(coresys) coresys.supervisor = Supervisor(coresys) coresys.homeassistant = HomeAssistant(coresys) coresys.addons = AddonManager(coresys) diff --git a/hassio/config.py b/hassio/config.py index baf31fb27..2f86ad5ed 100644 --- a/hassio/config.py +++ b/hassio/config.py @@ -6,7 +6,7 @@ from pathlib import Path, PurePath from .const import ( FILE_HASSIO_CONFIG, HASSIO_DATA, ATTR_TIMEZONE, ATTR_ADDONS_CUSTOM_LIST, - ATTR_AUDIO_INPUT, ATTR_AUDIO_OUTPUT, ATTR_LAST_BOOT, ATTR_WAIT_BOOT) + ATTR_LAST_BOOT, ATTR_WAIT_BOOT) from .utils.dt import parse_datetime from .utils.json import JsonConfig from .validate import SCHEMA_HASSIO_CONFIG @@ -136,6 +136,11 @@ class CoreConfig(JsonConfig): """Return hass.io temp folder.""" return Path(HASSIO_DATA, TMP_DATA) + @property + def path_extern_tmp(self): + """Return hass.io temp folder for docker.""" + return PurePath(self.path_extern_hassio, TMP_DATA) + @property def path_backup(self): """Return root backup data folder.""" @@ -174,23 +179,3 @@ class CoreConfig(JsonConfig): return self._data[ATTR_ADDONS_CUSTOM_LIST].remove(repo) - - @property - def audio_output(self): - """Return ALSA audio output card,dev.""" - return self._data.get(ATTR_AUDIO_OUTPUT) - - @audio_output.setter - def audio_output(self, value): - """Set ALSA audio output card,dev.""" - self._data[ATTR_AUDIO_OUTPUT] = value - - @property - def audio_input(self): - """Return ALSA audio input card,dev.""" - return self._data.get(ATTR_AUDIO_INPUT) - - @audio_input.setter - def audio_input(self, value): - """Set ALSA audio input card,dev.""" - self._data[ATTR_AUDIO_INPUT] = value diff --git a/hassio/const.py b/hassio/const.py index 795cbd327..926e5d940 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -27,6 +27,7 @@ DOCKER_NETWORK_RANGE = ip_network('172.30.33.0/24') LABEL_VERSION = 'io.hass.version' LABEL_ARCH = 'io.hass.arch' LABEL_TYPE = 'io.hass.type' +LABEL_MACHINE = 'io.hass.machine' META_ADDON = 'addon' META_SUPERVISOR = 'supervisor' diff --git a/hassio/coresys.py b/hassio/coresys.py index 79a9a9047..f17889d3a 100644 --- a/hassio/coresys.py +++ b/hassio/coresys.py @@ -42,6 +42,7 @@ class CoreSys(object): self._snapshots = None self._tasks = None self._services = None + self._alsa = None @property def arch(self): @@ -50,6 +51,13 @@ class CoreSys(object): return self._supervisor.arch return None + @property + def machine(self): + """Return running machine type of hass.io system.""" + if self._homeassistant: + return self._homeassistant.machine + return None + @property def dev(self): """Return True if we run dev modus.""" @@ -196,6 +204,18 @@ class CoreSys(object): raise RuntimeError("Services already set!") self._services = value + @property + def alsa(self): + """Return ALSA Audio object.""" + return self._alsa + + @alsa.setter + def alsa(self, value): + """Set a ALSA Audio object.""" + if self._alsa: + raise RuntimeError("ALSA already set!") + self._alsa = value + class CoreSysAttributes(object): """Inheret basic CoreSysAttributes.""" diff --git a/hassio/docker/__init__.py b/hassio/docker/__init__.py index 58bff4657..a5733ddbc 100644 --- a/hassio/docker/__init__.py +++ b/hassio/docker/__init__.py @@ -1,8 +1,8 @@ """Init file for HassIO docker object.""" from contextlib import suppress -from collections import namedtuple import logging +import attr import docker from .network import DockerNetwork @@ -10,7 +10,8 @@ from ..const import SOCKET_DOCKER _LOGGER = logging.getLogger(__name__) -CommandReturn = namedtuple('CommandReturn', ['exit_code', 'output']) +# pylint: disable=invalid-name +CommandReturn = attr.make_class('CommandReturn', ['exit_code', 'output']) class DockerAPI(object): diff --git a/hassio/docker/addon.py b/hassio/docker/addon.py index e766be5e0..9dc363235 100644 --- a/hassio/docker/addon.py +++ b/hassio/docker/addon.py @@ -201,7 +201,7 @@ class DockerAddon(DockerInterface): 'bind': "/share", 'mode': addon_mapping[MAP_SHARE] }}) - # init other hardware mappings + # Init other hardware mappings if self.addon.with_gpio: volumes.update({ "/sys/class/gpio": { @@ -212,13 +212,20 @@ class DockerAddon(DockerInterface): }, }) - # host dbus system + # Host dbus system if self.addon.host_dbus: volumes.update({ "/var/run/dbus": { 'bind': "/var/run/dbus", 'mode': 'rw' }}) + # ALSA configuration + if self.addon.with_audio: + volumes.update({ + str(self.addon.path_extern_asound): { + 'bind': "/etc/asound.conf", 'mode': 'ro' + }}) + return volumes def _run(self): diff --git a/hassio/docker/homeassistant.py b/hassio/docker/homeassistant.py index 48d0c9aa0..1ac05b18c 100644 --- a/hassio/docker/homeassistant.py +++ b/hassio/docker/homeassistant.py @@ -4,7 +4,7 @@ import logging import docker from .interface import DockerInterface -from ..const import ENV_TOKEN, ENV_TIME +from ..const import ENV_TOKEN, ENV_TIME, LABEL_MACHINE _LOGGER = logging.getLogger(__name__) @@ -14,6 +14,13 @@ HASS_DOCKER_NAME = 'homeassistant' class DockerHomeAssistant(DockerInterface): """Docker hassio wrapper for HomeAssistant.""" + @property + def machine(self): + """Return machine of Home-Assistant docker image.""" + if self._meta and LABEL_MACHINE in self._meta['Config']['Labels']: + return self._meta['Config']['Labels'][LABEL_MACHINE] + return None + @property def image(self): """Return name of docker image.""" diff --git a/hassio/homeassistant.py b/hassio/homeassistant.py index 21916cfdc..899050b83 100644 --- a/hassio/homeassistant.py +++ b/hassio/homeassistant.py @@ -1,6 +1,5 @@ """HomeAssistant control object.""" import asyncio -from collections import namedtuple import logging import os import re @@ -9,6 +8,7 @@ import time import aiohttp from aiohttp.hdrs import CONTENT_TYPE +import attr from .const import ( FILE_HASSIO_HOMEASSISTANT, ATTR_IMAGE, ATTR_LAST_VERSION, ATTR_UUID, @@ -24,7 +24,8 @@ _LOGGER = logging.getLogger(__name__) RE_YAML_ERROR = re.compile(r"homeassistant\.util\.yaml") -ConfigResult = namedtuple('ConfigResult', ['valid', 'log']) +# pylint: disable=invalid-name +ConfigResult = attr.make_class('ConfigResult', ['valid', 'log']) class HomeAssistant(JsonConfig, CoreSysAttributes): @@ -45,6 +46,11 @@ class HomeAssistant(JsonConfig, CoreSysAttributes): _LOGGER.info("No HomeAssistant docker %s found.", self.image) await self.install_landingpage() + @property + def machine(self): + """Return System Machines.""" + return self.instance.machine + @property def api_ip(self): """Return IP of HomeAssistant instance.""" diff --git a/hassio/host/__init__.py b/hassio/host/__init__.py new file mode 100644 index 000000000..16b30ad18 --- /dev/null +++ b/hassio/host/__init__.py @@ -0,0 +1,2 @@ +"""Host function like audio/dbus/systemd.""" +from .alsa import AlsaAudio # noqa diff --git a/hassio/host/alsa.py b/hassio/host/alsa.py new file mode 100644 index 000000000..6baca457c --- /dev/null +++ b/hassio/host/alsa.py @@ -0,0 +1,137 @@ +"""Host Audio-support.""" +import logging +import json +from pathlib import Path +from string import Template + +import attr + +from ..const import ATTR_INPUT, ATTR_OUTPUT, ATTR_DEVICES, ATTR_NAME +from ..coresys import CoreSysAttributes + +_LOGGER = logging.getLogger(__name__) + +# pylint: disable=invalid-name +DefaultConfig = attr.make_class('DefaultConfig', ['input', 'output']) + + +class AlsaAudio(CoreSysAttributes): + """Handle Audio ALSA host data.""" + + def __init__(self, coresys): + """Initialize Alsa audio system.""" + self.coresys = coresys + self._data = { + ATTR_INPUT: {}, + ATTR_OUTPUT: {}, + } + self._cache = 0 + self._default = None + + @property + def input_devices(self): + """Return list of ALSA input devices.""" + self._update_device() + return self._data[ATTR_INPUT] + + @property + def output_devices(self): + """Return list of ALSA output devices.""" + self._update_device() + return self._data[ATTR_OUTPUT] + + def _update_device(self): + """Update Internal device DB.""" + current_id = hash(frozenset(self._hardware.audio_devices)) + + # Need rebuild? + if current_id == self._cache: + return + + # Clean old stuff + self._data[ATTR_INPUT].clear() + self._data[ATTR_OUTPUT].clear() + + # Init database + _LOGGER.info("Update ALSA device list") + database = self._audio_database() + + # Process devices + for dev_id, dev_data in self._hardware.audio_devices.items(): + for chan_id, chan_type in dev_data[ATTR_DEVICES].items(): + alsa_id = f"{dev_id},{chan_id}" + dev_name = dev_data[ATTR_NAME] + + # Lookup type + if chan_type.endswith('playback'): + key = ATTR_OUTPUT + elif chan_type.endswith('capture'): + key = ATTR_INPUT + else: + _LOGGER.warning("Unknown channel type: %s", chan_type) + continue + + # Use name from DB or a generic name + self._data[key][alsa_id] = database.get( + self._machine, {}).get( + dev_name, {}).get(alsa_id, f"{dev_name}: {chan_id}") + + self._cache = current_id + + @staticmethod + def _audio_database(): + """Read local json audio data into dict.""" + json_file = Path(__file__).parent.joinpath('audiodb.json') + + try: + # pylint: disable=no-member + with json_file.open('r') as database: + return json.loads(database.read()) + except (ValueError, OSError) as err: + _LOGGER.warning("Can't read audio DB: %s", err) + + return {} + + @property + def default(self): + """Generate ALSA default setting.""" + # Init defaults + if self._default is None: + database = self._audio_database() + alsa_input = database.get(self._machine, {}).get(ATTR_INPUT) + alsa_output = database.get(self._machine, {}).get(ATTR_OUTPUT) + + self._default = DefaultConfig(alsa_input, alsa_output) + + # Search exists/new output + if self._default.output is None and self.output_devices: + self._default.output = next(iter(self.output_devices)) + _LOGGER.info("Detect output device %s", self._default.output) + + # Search exists/new input + if self._default.input is None and self.input_devices: + self._default.input = next(iter(self.input_devices)) + _LOGGER.info("Detect input device %s", self._default.input) + + return self._default + + def asound(self, alsa_input=None, alsa_output=None): + """Generate a asound data.""" + alsa_input = alsa_input or self.default.input + alsa_output = alsa_output or self.default.output + + # Read Template + asound_file = Path(__file__).parent.joinpath('asound.tmpl') + try: + # pylint: disable=no-member + with asound_file.open('r') as asound: + asound_data = asound.read() + except OSError as err: + _LOGGER.error("Can't read asound.tmpl: %s", err) + return "" + + # Process Template + asound_template = Template(asound_data) + return asound_template.safe_substitute( + input=alsa_input, output=alsa_output + ) diff --git a/hassio/host/asound.tmpl b/hassio/host/asound.tmpl new file mode 100644 index 000000000..248482a73 --- /dev/null +++ b/hassio/host/asound.tmpl @@ -0,0 +1,17 @@ +pcm.!default { + type asym + capture.pcm "mic" + playback.pcm "speaker" +} +pcm.mic { + type plug + slave { + pcm "hw:{$input}" + } +} +pcm.speaker { + type plug + slave { + pcm "hw:{$output}" + } +} diff --git a/hassio/host/audiodb.json b/hassio/host/audiodb.json new file mode 100644 index 000000000..f6cccd456 --- /dev/null +++ b/hassio/host/audiodb.json @@ -0,0 +1,18 @@ +{ + "raspberrypi3": { + "bcm2835 - bcm2835 ALSA": { + "0,0": "Raspberry Jack", + "0,1": "Raspberry HDMI" + }, + "output": "0,0", + "input": null + }, + "raspberrypi2": { + "output": "0,0", + "input": null + }, + "raspberrypi": { + "output": "0,0", + "input": null + } +} diff --git a/hassio/validate.py b/hassio/validate.py index 05d23cac6..480c8592f 100644 --- a/hassio/validate.py +++ b/hassio/validate.py @@ -7,18 +7,17 @@ import pytz from .const import ( ATTR_IMAGE, ATTR_LAST_VERSION, ATTR_CHANNEL, ATTR_TIMEZONE, - ATTR_ADDONS_CUSTOM_LIST, ATTR_AUDIO_OUTPUT, ATTR_AUDIO_INPUT, - ATTR_PASSWORD, ATTR_HOMEASSISTANT, ATTR_HASSIO, ATTR_BOOT, ATTR_LAST_BOOT, - ATTR_SSL, ATTR_PORT, ATTR_WATCHDOG, ATTR_WAIT_BOOT, ATTR_UUID, - CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV) + ATTR_ADDONS_CUSTOM_LIST, ATTR_PASSWORD, ATTR_HOMEASSISTANT, ATTR_HASSIO, + ATTR_BOOT, ATTR_LAST_BOOT, ATTR_SSL, ATTR_PORT, ATTR_WATCHDOG, + ATTR_WAIT_BOOT, ATTR_UUID, CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV) RE_REPOSITORY = re.compile(r"^(?P[^#]+)(?:#(?P[\w\-]+))?$") NETWORK_PORT = vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)) -ALSA_CHANNEL = vol.Match(r"\d+,\d+") WAIT_BOOT = vol.All(vol.Coerce(int), vol.Range(min=1, max=60)) DOCKER_IMAGE = vol.Match(r"^[\w{}]+/[\-\w{}]+$") +ALSA_DEVICE = vol.Any(None, vol.Match(r"\d+,\d+")) CHANNELS = vol.In([CHANNEL_STABLE, CHANNEL_BETA, CHANNEL_DEV]) @@ -110,7 +109,5 @@ SCHEMA_HASSIO_CONFIG = vol.Schema({ vol.Optional(ATTR_ADDONS_CUSTOM_LIST, default=[ "https://github.com/hassio-addons/repository", ]): REPOSITORIES, - vol.Optional(ATTR_AUDIO_OUTPUT): ALSA_CHANNEL, - vol.Optional(ATTR_AUDIO_INPUT): ALSA_CHANNEL, vol.Optional(ATTR_WAIT_BOOT, default=5): WAIT_BOOT, }, extra=vol.REMOVE_EXTRA)