diff --git a/API.md b/API.md index ae5400f95..bbec173cf 100644 --- a/API.md +++ b/API.md @@ -336,7 +336,9 @@ Get all available addons. "privileged": ["NET_ADMIN", "SYS_ADMIN"], "devices": ["/dev/xy"], "url": "null|url", - "logo": "bool" + "logo": "bool", + "audio": "bool", + "hassio_api": "bool" } ], "repositories": [ @@ -373,7 +375,11 @@ Get all available addons. "privileged": ["NET_ADMIN", "SYS_ADMIN"], "devices": ["/dev/xy"], "logo": "bool", - "webui": "null|http(s)://[HOST]:port/xy/zx" + "hassio_api": "bool", + "webui": "null|http(s)://[HOST]:port/xy/zx", + "audio": "bool", + "audio_input": "null|0,0", + "audio_output": "null|0,0" } ``` @@ -389,10 +395,12 @@ Get all available addons. "CONTAINER": "port|[ip, port]" }, "options": {}, + "audio_output": "null|0,0", + "audio_input": "null|0,0" } ``` -For reset custom network settings, set it `null`. +For reset custom network/audio settings, set it `null`. - POST `/addons/{addon}/start` diff --git a/hassio/addons/addon.py b/hassio/addons/addon.py index f3bcb9f20..72293adcf 100644 --- a/hassio/addons/addon.py +++ b/hassio/addons/addon.py @@ -19,7 +19,8 @@ from ..const import ( ATTR_URL, ATTR_ARCH, ATTR_LOCATON, ATTR_DEVICES, ATTR_ENVIRONMENT, ATTR_HOST_NETWORK, ATTR_TMPFS, ATTR_PRIVILEGED, ATTR_STARTUP, STATE_STARTED, STATE_STOPPED, STATE_NONE, ATTR_USER, ATTR_SYSTEM, - ATTR_STATE, ATTR_TIMEOUT, ATTR_AUTO_UPDATE, ATTR_NETWORK, ATTR_WEBUI) + ATTR_STATE, ATTR_TIMEOUT, ATTR_AUTO_UPDATE, ATTR_NETWORK, ATTR_WEBUI, + ATTR_HASSIO_API, ATTR_AUDIO, ATTR_AUDIO_OUTPUT, ATTR_AUDIO_INPUT) from .util import check_installed from ..dock.addon import DockerAddon from ..tools import write_json_file, read_json_file @@ -244,6 +245,56 @@ class Addon(object): """Return list of privilege.""" return self._mesh.get(ATTR_PRIVILEGED) + @property + def use_hassio_api(self): + """Return True if the add-on access to hassio api.""" + return self._mesh[ATTR_HASSIO_API] + + @property + def with_audio(self): + """Return True if the add-on access to audio.""" + return self._mesh[ATTR_AUDIO] + + @property + def audio_output(self): + """Return ALSA config for output or None.""" + if not self.with_audio: + return + + 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 + + @audio_output.setter + def audio_output(self, value): + """Set/remove custom audio output settings.""" + if value is None: + self.data.user[self._id].pop(ATTR_AUDIO_OUTPUT, None) + else: + self.data.user[self._id][ATTR_AUDIO_OUTPUT] = value + self.data.save() + + @property + def audio_input(self): + """Return ALSA config for input or None.""" + if not self.with_audio: + return + + 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 + + @audio_input.setter + def audio_input(self, value): + """Set/remove custom audio input settings.""" + if value is None: + self.data.user[self._id].pop(ATTR_AUDIO_INPUT, None) + else: + self.data.user[self._id][ATTR_AUDIO_INPUT] = value + self.data.save() + @property def url(self): """Return url of addon.""" diff --git a/hassio/addons/validate.py b/hassio/addons/validate.py index 34564cbb2..d7d7def84 100644 --- a/hassio/addons/validate.py +++ b/hassio/addons/validate.py @@ -10,8 +10,9 @@ from ..const import ( ARCH_AARCH64, ARCH_AMD64, ARCH_I386, ATTR_TMPFS, ATTR_PRIVILEGED, ATTR_USER, ATTR_STATE, ATTR_SYSTEM, STATE_STARTED, STATE_STOPPED, ATTR_LOCATON, ATTR_REPOSITORY, ATTR_TIMEOUT, ATTR_NETWORK, - ATTR_AUTO_UPDATE, ATTR_WEBUI) -from ..validate import NETWORK_PORT, DOCKER_PORTS + ATTR_AUTO_UPDATE, ATTR_WEBUI, ATTR_AUDIO, ATTR_AUDIO_INPUT, + ATTR_AUDIO_OUTPUT, ATTR_HASSIO_API) +from ..validate import NETWORK_PORT, DOCKER_PORTS, ALSA_CHANNEL MAP_VOLUME = r"^(config|ssl|addons|backup|share)(?::(rw|:ro))?$" @@ -73,6 +74,8 @@ SCHEMA_ADDON_CONFIG = vol.Schema({ vol.Optional(ATTR_MAP, default=[]): [vol.Match(MAP_VOLUME)], vol.Optional(ATTR_ENVIRONMENT): {vol.Match(r"\w*"): vol.Coerce(str)}, vol.Optional(ATTR_PRIVILEGED): [vol.In(PRIVILEGED_ALL)], + vol.Optional(ATTR_AUDIO, default=False): vol.Boolean(), + vol.Optional(ATTR_HASSIO_API, default=False): vol.Boolean(), vol.Required(ATTR_OPTIONS): dict, vol.Required(ATTR_SCHEMA): vol.Any(vol.Schema({ vol.Coerce(str): vol.Any(ADDON_ELEMENT, [ @@ -101,6 +104,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, }) diff --git a/hassio/api/addons.py b/hassio/api/addons.py index 33bc547a2..3451f81f8 100644 --- a/hassio/api/addons.py +++ b/hassio/api/addons.py @@ -12,6 +12,7 @@ from ..const import ( ATTR_BUILD, ATTR_AUTO_UPDATE, ATTR_NETWORK, ATTR_HOST_NETWORK, ATTR_SLUG, ATTR_SOURCE, ATTR_REPOSITORIES, ATTR_ADDONS, ATTR_ARCH, ATTR_MAINTAINER, ATTR_INSTALLED, ATTR_LOGO, ATTR_WEBUI, ATTR_DEVICES, ATTR_PRIVILEGED, + ATTR_AUDIO, ATTR_AUDIO_INPUT, ATTR_AUDIO_OUTPUT, ATTR_HASSIO_API, BOOT_AUTO, BOOT_MANUAL, CONTENT_TYPE_PNG, CONTENT_TYPE_BINARY) from ..validate import DOCKER_PORTS @@ -76,6 +77,8 @@ class APIAddons(object): ATTR_DEVICES: self._pretty_devices(addon), ATTR_URL: addon.url, ATTR_LOGO: addon.with_logo, + ATTR_HASSIO_API: addon.use_hassio_api, + ATTR_AUDIO: addon.with_audio, }) data_repositories = [] @@ -123,6 +126,10 @@ class APIAddons(object): ATTR_DEVICES: self._pretty_devices(addon), ATTR_LOGO: addon.with_logo, ATTR_WEBUI: addon.webui, + ATTR_HASSIO_API: addon.use_hassio_api, + ATTR_AUDIO: addon.with_audio, + ATTR_AUDIO_INPUT: addon.audio_input, + ATTR_AUDIO_OUTPUT: addon.audio_output, } @api_process @@ -144,6 +151,10 @@ class APIAddons(object): addon.auto_update = body[ATTR_AUTO_UPDATE] if ATTR_NETWORK in body: addon.ports = body[ATTR_NETWORK] + if ATTR_AUDIO_INPUT in body: + addon.audio_input = body[ATTR_AUDIO_INPUT] + if ATTR_AUDIO_OUTPUT in body: + addon.audio_output = body[ATTR_AUDIO_OUTPUT] return True diff --git a/hassio/api/host.py b/hassio/api/host.py index 074452897..39fbbeb78 100644 --- a/hassio/api/host.py +++ b/hassio/api/host.py @@ -52,7 +52,6 @@ class APIHost(object): 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] diff --git a/hassio/const.py b/hassio/const.py index 65f7ea853..a5c5ab073 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -96,6 +96,7 @@ ATTR_SYSTEM = 'system' ATTR_SNAPSHOTS = 'snapshots' ATTR_HOMEASSISTANT = 'homeassistant' ATTR_HASSIO = 'hassio' +ATTR_HASSIO_API = 'hassio_api' ATTR_FOLDERS = 'folders' ATTR_SIZE = 'size' ATTR_TYPE = 'type' diff --git a/hassio/dock/addon.py b/hassio/dock/addon.py index 8d573b58e..e5e3df2b0 100644 --- a/hassio/dock/addon.py +++ b/hassio/dock/addon.py @@ -32,6 +32,11 @@ class DockerAddon(DockerBase): def environment(self): """Return environment for docker add-on.""" addon_env = self.addon.environment or {} + if self.addon.with_audio: + addon_env.update({ + 'ALSA_OUTPUT': self.addon.audio_output, + 'ALSA_INPUT': self.addon.audio_input, + }) return { **addon_env, @@ -46,6 +51,16 @@ class DockerAddon(DockerBase): return {"/tmpfs": "{}".format(options)} return None + @property + def mapping(self): + """Return hosts mapping.""" + if not self.addon.use_hassio_api: + return None + + return { + 'hassio': self.config.api_endpoint, + } + @property def volumes(self): """Generate volumes for mappings.""" @@ -111,6 +126,7 @@ class DockerAddon(DockerBase): detach=True, network_mode=self.addon.network_mode, ports=self.addon.ports, + extra_hosts=self.mapping, devices=self.addon.devices, cap_add=self.addon.privileged, environment=self.environment, diff --git a/hassio/updater.py b/hassio/updater.py index eebb8e0bb..d880e8327 100644 --- a/hassio/updater.py +++ b/hassio/updater.py @@ -62,6 +62,7 @@ class Updater(JsonConfig): """ url = URL_HASSIO_VERSION.format(self.upstream) try: + _LOGGER.info("Fetch update data from %s", url) with async_timeout.timeout(10, loop=self.loop): async with self.websession.get(url) as request: data = await request.json(content_type=None)