Merge pull request #1547 from home-assistant/dev

Release 205
This commit is contained in:
Pascal Vizeli 2020-02-29 12:18:51 +01:00 committed by GitHub
commit e52af3bfb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 151 additions and 72 deletions

8
API.md
View File

@ -379,7 +379,9 @@ Trigger an udev reload
"port": 8123, "port": 8123,
"ssl": "bool", "ssl": "bool",
"watchdog": "bool", "watchdog": "bool",
"wait_boot": 600 "wait_boot": 600,
"audio_input": "null|profile",
"audio_output": "null|profile"
} }
``` ```
@ -413,7 +415,9 @@ Output is the raw Docker log.
"ssl": "bool", "ssl": "bool",
"refresh_token": "", "refresh_token": "",
"watchdog": "bool", "watchdog": "bool",
"wait_boot": 600 "wait_boot": 600,
"audio_input": "null|profile",
"audio_output": "null|profile"
} }
``` ```

View File

@ -294,11 +294,8 @@ class Addon(AddonModel):
@audio_output.setter @audio_output.setter
def audio_output(self, value: Optional[str]): def audio_output(self, value: Optional[str]):
"""Set/reset audio output profile settings.""" """Set audio output profile settings."""
if value is None: self.persist[ATTR_AUDIO_OUTPUT] = value
self.persist.pop(ATTR_AUDIO_OUTPUT, None)
else:
self.persist[ATTR_AUDIO_OUTPUT] = value
@property @property
def audio_input(self) -> Optional[str]: def audio_input(self) -> Optional[str]:
@ -315,11 +312,8 @@ class Addon(AddonModel):
@audio_input.setter @audio_input.setter
def audio_input(self, value: Optional[str]): def audio_input(self, value: Optional[str]):
"""Set/reset audio input settings.""" """Set audio input settings."""
if value is None: self.persist[ATTR_AUDIO_INPUT] = value
self.persist.pop(ATTR_AUDIO_INPUT, None)
else:
self.persist[ATTR_AUDIO_INPUT] = value
@property @property
def image(self): def image(self):

View File

@ -1,24 +1,27 @@
"""Init file for Supervisor Home Assistant RESTful API.""" """Init file for Supervisor Home Assistant RESTful API."""
import asyncio import asyncio
import logging import logging
from typing import Coroutine, Dict, Any from typing import Any, Coroutine, Dict
import voluptuous as vol
from aiohttp import web from aiohttp import web
import voluptuous as vol
from ..const import ( from ..const import (
ATTR_ARCH, ATTR_ARCH,
ATTR_AUDIO_INPUT,
ATTR_AUDIO_OUTPUT,
ATTR_BLK_READ, ATTR_BLK_READ,
ATTR_BLK_WRITE, ATTR_BLK_WRITE,
ATTR_BOOT, ATTR_BOOT,
ATTR_CPU_PERCENT, ATTR_CPU_PERCENT,
ATTR_CUSTOM, ATTR_CUSTOM,
ATTR_IMAGE, ATTR_IMAGE,
ATTR_IP_ADDRESS,
ATTR_LAST_VERSION, ATTR_LAST_VERSION,
ATTR_MACHINE, ATTR_MACHINE,
ATTR_MEMORY_LIMIT, ATTR_MEMORY_LIMIT,
ATTR_MEMORY_USAGE,
ATTR_MEMORY_PERCENT, ATTR_MEMORY_PERCENT,
ATTR_MEMORY_USAGE,
ATTR_NETWORK_RX, ATTR_NETWORK_RX,
ATTR_NETWORK_TX, ATTR_NETWORK_TX,
ATTR_PORT, ATTR_PORT,
@ -27,7 +30,6 @@ from ..const import (
ATTR_VERSION, ATTR_VERSION,
ATTR_WAIT_BOOT, ATTR_WAIT_BOOT,
ATTR_WATCHDOG, ATTR_WATCHDOG,
ATTR_IP_ADDRESS,
CONTENT_TYPE_BINARY, CONTENT_TYPE_BINARY,
) )
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
@ -48,6 +50,8 @@ SCHEMA_OPTIONS = vol.Schema(
vol.Optional(ATTR_WATCHDOG): vol.Boolean(), vol.Optional(ATTR_WATCHDOG): vol.Boolean(),
vol.Optional(ATTR_WAIT_BOOT): vol.All(vol.Coerce(int), vol.Range(min=60)), vol.Optional(ATTR_WAIT_BOOT): vol.All(vol.Coerce(int), vol.Range(min=60)),
vol.Optional(ATTR_REFRESH_TOKEN): vol.Maybe(vol.Coerce(str)), vol.Optional(ATTR_REFRESH_TOKEN): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_AUDIO_OUTPUT): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_AUDIO_INPUT): vol.Maybe(vol.Coerce(str)),
} }
) )
@ -73,6 +77,8 @@ class APIHomeAssistant(CoreSysAttributes):
ATTR_SSL: self.sys_homeassistant.api_ssl, ATTR_SSL: self.sys_homeassistant.api_ssl,
ATTR_WATCHDOG: self.sys_homeassistant.watchdog, ATTR_WATCHDOG: self.sys_homeassistant.watchdog,
ATTR_WAIT_BOOT: self.sys_homeassistant.wait_boot, ATTR_WAIT_BOOT: self.sys_homeassistant.wait_boot,
ATTR_AUDIO_INPUT: self.sys_homeassistant.audio_input,
ATTR_AUDIO_OUTPUT: self.sys_homeassistant.audio_output,
} }
@api_process @api_process
@ -102,6 +108,12 @@ class APIHomeAssistant(CoreSysAttributes):
if ATTR_REFRESH_TOKEN in body: if ATTR_REFRESH_TOKEN in body:
self.sys_homeassistant.refresh_token = body[ATTR_REFRESH_TOKEN] self.sys_homeassistant.refresh_token = body[ATTR_REFRESH_TOKEN]
if ATTR_AUDIO_INPUT in body:
self.sys_homeassistant.audio_input = body[ATTR_AUDIO_INPUT]
if ATTR_AUDIO_OUTPUT in body:
self.sys_homeassistant.audio_output = body[ATTR_AUDIO_OUTPUT]
self.sys_homeassistant.save_data() self.sys_homeassistant.save_data()
@api_process @api_process

View File

@ -4,11 +4,11 @@ from contextlib import suppress
import logging import logging
from pathlib import Path from pathlib import Path
import shutil import shutil
from typing import Awaitable, List, Optional from typing import Awaitable, Optional
import jinja2 import jinja2
from .const import ATTR_VERSION, FILE_HASSIO_AUDIO, STATE_STARTED from .const import ATTR_VERSION, FILE_HASSIO_AUDIO
from .coresys import CoreSys, CoreSysAttributes from .coresys import CoreSys, CoreSysAttributes
from .docker.audio import DockerAudio from .docker.audio import DockerAudio
from .docker.stats import DockerStats from .docker.stats import DockerStats
@ -35,7 +35,7 @@ class Audio(JsonConfig, CoreSysAttributes):
@property @property
def path_extern_pulse(self) -> Path: def path_extern_pulse(self) -> Path:
"""Return path of pulse socket file.""" """Return path of pulse socket file."""
return self.sys_config.path_extern_audio.joinpath("external/pulse.sock") return self.sys_config.path_extern_audio.joinpath("external")
@property @property
def path_extern_asound(self) -> Path: def path_extern_asound(self) -> Path:
@ -157,8 +157,6 @@ class Audio(JsonConfig, CoreSysAttributes):
_LOGGER.error("Can't start Audio plugin") _LOGGER.error("Can't start Audio plugin")
raise AudioError() from None raise AudioError() from None
await self._restart_audio_addons()
async def start(self) -> None: async def start(self) -> None:
"""Run CoreDNS.""" """Run CoreDNS."""
# Start Instance # Start Instance
@ -169,8 +167,6 @@ class Audio(JsonConfig, CoreSysAttributes):
_LOGGER.error("Can't start Audio plugin") _LOGGER.error("Can't start Audio plugin")
raise AudioError() from None raise AudioError() from None
await self._restart_audio_addons()
def logs(self) -> Awaitable[bytes]: def logs(self) -> Awaitable[bytes]:
"""Get CoreDNS docker logs. """Get CoreDNS docker logs.
@ -221,22 +217,3 @@ class Audio(JsonConfig, CoreSysAttributes):
default_source=input_profile, default_source=input_profile,
default_sink=output_profile, default_sink=output_profile,
) )
async def _restart_audio_addons(self):
"""Restart all Add-ons they can be connect to unix socket."""
tasks: List[Awaitable[None]] = []
# Find all Add-ons running with audio
for addon in self.sys_addons.installed:
if not addon.with_audio:
continue
if await addon.state() != STATE_STARTED:
continue
tasks.append(addon.restart())
if not tasks:
return
# restart
_LOGGER.info("Restart all Add-ons attach to pulse server: %d", len(tasks))
await asyncio.wait(tasks)

View File

@ -3,7 +3,7 @@ from enum import Enum
from ipaddress import ip_network from ipaddress import ip_network
from pathlib import Path from pathlib import Path
SUPERVISOR_VERSION = "204" SUPERVISOR_VERSION = "205"
URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons" URL_HASSIO_ADDONS = "https://github.com/home-assistant/hassio-addons"

View File

@ -20,7 +20,7 @@
{% if default_sink %}default-sink = {{ default_sink }}{% endif %} {% if default_sink %}default-sink = {{ default_sink }}{% endif %}
{% if default_source %}default-source = {{ default_source }}{% endif %} {% if default_source %}default-source = {{ default_source }}{% endif %}
default-server = unix://run/pulse.sock default-server = unix://run/audio/pulse.sock
; default-dbus-server = ; default-dbus-server =
autospawn = no autospawn = no

View File

@ -213,10 +213,10 @@ class DockerAddon(DockerInterface):
@property @property
def volumes(self) -> Dict[str, Dict[str, str]]: def volumes(self) -> Dict[str, Dict[str, str]]:
"""Generate volumes for mappings.""" """Generate volumes for mappings."""
volumes = {str(self.addon.path_extern_data): {"bind": "/data", "mode": "rw"}}
addon_mapping = self.addon.map_volumes addon_mapping = self.addon.map_volumes
volumes = {str(self.addon.path_extern_data): {"bind": "/data", "mode": "rw"}}
# setup config mappings # setup config mappings
if MAP_CONFIG in addon_mapping: if MAP_CONFIG in addon_mapping:
volumes.update( volumes.update(
@ -298,7 +298,7 @@ class DockerAddon(DockerInterface):
# Host D-Bus system # Host D-Bus system
if self.addon.host_dbus: if self.addon.host_dbus:
volumes.update({"/run/dbus": {"bind": "/run/dbus", "mode": "rw"}}) volumes.update({"/run/dbus": {"bind": "/run/dbus", "mode": "ro"}})
# Configuration Audio # Configuration Audio
if self.addon.with_audio: if self.addon.with_audio:
@ -309,8 +309,8 @@ class DockerAddon(DockerInterface):
"mode": "ro", "mode": "ro",
}, },
str(self.sys_audio.path_extern_pulse): { str(self.sys_audio.path_extern_pulse): {
"bind": "/run/pulse.sock", "bind": "/run/audio",
"mode": "rw", "mode": "ro",
}, },
str(self.sys_audio.path_extern_asound): { str(self.sys_audio.path_extern_asound): {
"bind": "/etc/asound.conf", "bind": "/etc/asound.conf",

View File

@ -2,7 +2,7 @@
from contextlib import suppress from contextlib import suppress
from ipaddress import IPv4Address from ipaddress import IPv4Address
import logging import logging
from typing import Awaitable, Optional from typing import Awaitable, Dict, Optional
import docker import docker
@ -45,6 +45,46 @@ class DockerHomeAssistant(DockerInterface):
"""Return IP address of this container.""" """Return IP address of this container."""
return self.sys_docker.network.gateway return self.sys_docker.network.gateway
@property
def volumes(self) -> Dict[str, Dict[str, str]]:
"""Return Volumes for the mount."""
volumes = {}
# Add folders
volumes.update(
{
str(self.sys_config.path_extern_homeassistant): {
"bind": "/config",
"mode": "rw",
},
str(self.sys_config.path_extern_ssl): {"bind": "/ssl", "mode": "ro"},
str(self.sys_config.path_extern_share): {
"bind": "/share",
"mode": "rw",
},
}
)
# Configuration Audio
volumes.update(
{
str(self.sys_homeassistant.path_extern_pulse): {
"bind": "/etc/pulse/client.conf",
"mode": "ro",
},
str(self.sys_audio.path_extern_pulse): {
"bind": "/run/audio",
"mode": "ro",
},
str(self.sys_audio.path_extern_asound): {
"bind": "/etc/asound.conf",
"mode": "ro",
},
}
)
return volumes
def _run(self) -> None: def _run(self) -> None:
"""Run Docker image. """Run Docker image.
@ -67,6 +107,7 @@ class DockerHomeAssistant(DockerInterface):
privileged=True, privileged=True,
init=False, init=False,
network_mode="host", network_mode="host",
volumes=self.volumes,
environment={ environment={
"HASSIO": self.sys_docker.network.supervisor, "HASSIO": self.sys_docker.network.supervisor,
"SUPERVISOR": self.sys_docker.network.supervisor, "SUPERVISOR": self.sys_docker.network.supervisor,
@ -74,17 +115,6 @@ class DockerHomeAssistant(DockerInterface):
ENV_TOKEN: self.sys_homeassistant.hassio_token, ENV_TOKEN: self.sys_homeassistant.hassio_token,
ENV_TOKEN_OLD: self.sys_homeassistant.hassio_token, ENV_TOKEN_OLD: self.sys_homeassistant.hassio_token,
}, },
volumes={
str(self.sys_config.path_extern_homeassistant): {
"bind": "/config",
"mode": "rw",
},
str(self.sys_config.path_extern_ssl): {"bind": "/ssl", "mode": "ro"},
str(self.sys_config.path_extern_share): {
"bind": "/share",
"mode": "rw",
},
},
) )
self._meta = docker_container.attrs self._meta = docker_container.attrs
@ -105,18 +135,8 @@ class DockerHomeAssistant(DockerInterface):
detach=True, detach=True,
stdout=True, stdout=True,
stderr=True, stderr=True,
volumes=self.volumes,
environment={ENV_TIME: self.sys_timezone}, environment={ENV_TIME: self.sys_timezone},
volumes={
str(self.sys_config.path_extern_homeassistant): {
"bind": "/config",
"mode": "rw",
},
str(self.sys_config.path_extern_ssl): {"bind": "/ssl", "mode": "ro"},
str(self.sys_config.path_extern_share): {
"bind": "/share",
"mode": "ro",
},
},
) )
def is_initialize(self) -> Awaitable[bool]: def is_initialize(self) -> Awaitable[bool]:

View File

@ -19,6 +19,8 @@ from packaging import version as pkg_version
from .const import ( from .const import (
ATTR_ACCESS_TOKEN, ATTR_ACCESS_TOKEN,
ATTR_AUDIO_INPUT,
ATTR_AUDIO_OUTPUT,
ATTR_BOOT, ATTR_BOOT,
ATTR_IMAGE, ATTR_IMAGE,
ATTR_LAST_VERSION, ATTR_LAST_VERSION,
@ -232,6 +234,36 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
"""Set Home Assistant refresh_token.""" """Set Home Assistant refresh_token."""
self._data[ATTR_REFRESH_TOKEN] = value self._data[ATTR_REFRESH_TOKEN] = value
@property
def path_pulse(self):
"""Return path to asound config."""
return Path(self.sys_config.path_tmp, f"homeassistant_pulse")
@property
def path_extern_pulse(self):
"""Return path to asound config for Docker."""
return Path(self.sys_config.path_extern_tmp, f"homeassistant_pulse")
@property
def audio_output(self) -> Optional[str]:
"""Return a pulse profile for output or None."""
return self._data[ATTR_AUDIO_OUTPUT]
@audio_output.setter
def audio_output(self, value: Optional[str]):
"""Set audio output profile settings."""
self._data[ATTR_AUDIO_OUTPUT] = value
@property
def audio_input(self) -> Optional[str]:
"""Return pulse profile for input or None."""
return self._data[ATTR_AUDIO_INPUT]
@audio_input.setter
def audio_input(self, value: Optional[str]):
"""Set audio input settings."""
self._data[ATTR_AUDIO_INPUT] = value
@process_lock @process_lock
async def install_landingpage(self) -> None: async def install_landingpage(self) -> None:
"""Install a landing page.""" """Install a landing page."""
@ -334,6 +366,9 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
self._data[ATTR_ACCESS_TOKEN] = secrets.token_hex(56) self._data[ATTR_ACCESS_TOKEN] = secrets.token_hex(56)
self.save_data() self.save_data()
# Write audio settings
self.write_pulse()
try: try:
await self.instance.run() await self.instance.run()
except DockerAPIError: except DockerAPIError:
@ -602,3 +637,18 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
await self.instance.install(self.version) await self.instance.install(self.version)
except DockerAPIError: except DockerAPIError:
_LOGGER.error("Repairing of Home Assistant fails") _LOGGER.error("Repairing of Home Assistant fails")
def write_pulse(self):
"""Write asound config to file and return True on success."""
pulse_config = self.sys_audio.pulse_client(
input_profile=self.audio_input, output_profile=self.audio_output
)
try:
with self.path_pulse.open("w") as config_file:
config_file.write(pulse_config)
except OSError as err:
_LOGGER.error("Home Assistant can't write pulse/client.config: %s", err)
raise HomeAssistantError()
_LOGGER.debug("Home Assistant write pulse/client.config: %s", self.path_pulse)

View File

@ -16,6 +16,8 @@ from voluptuous.humanize import humanize_error
from ..const import ( from ..const import (
ATTR_ADDONS, ATTR_ADDONS,
ATTR_AUDIO_INPUT,
ATTR_AUDIO_OUTPUT,
ATTR_BOOT, ATTR_BOOT,
ATTR_CRYPTO, ATTR_CRYPTO,
ATTR_DATE, ATTR_DATE,
@ -443,6 +445,10 @@ class Snapshot(CoreSysAttributes):
self.sys_homeassistant.refresh_token self.sys_homeassistant.refresh_token
) )
# Audio
self.homeassistant[ATTR_AUDIO_INPUT] = self.sys_homeassistant.audio_input
self.homeassistant[ATTR_AUDIO_OUTPUT] = self.sys_homeassistant.audio_output
def restore_homeassistant(self): def restore_homeassistant(self):
"""Write all data to the Home Assistant object.""" """Write all data to the Home Assistant object."""
self.sys_homeassistant.watchdog = self.homeassistant[ATTR_WATCHDOG] self.sys_homeassistant.watchdog = self.homeassistant[ATTR_WATCHDOG]
@ -463,6 +469,10 @@ class Snapshot(CoreSysAttributes):
self.homeassistant[ATTR_REFRESH_TOKEN] self.homeassistant[ATTR_REFRESH_TOKEN]
) )
# Audio
self.sys_homeassistant.audio_input = self.homeassistant[ATTR_AUDIO_INPUT]
self.sys_homeassistant.audio_output = self.homeassistant[ATTR_AUDIO_OUTPUT]
# save # save
self.sys_homeassistant.save_data() self.sys_homeassistant.save_data()

View File

@ -3,6 +3,8 @@ import voluptuous as vol
from ..const import ( from ..const import (
ATTR_ADDONS, ATTR_ADDONS,
ATTR_AUDIO_INPUT,
ATTR_AUDIO_OUTPUT,
ATTR_BOOT, ATTR_BOOT,
ATTR_CRYPTO, ATTR_CRYPTO,
ATTR_DATE, ATTR_DATE,
@ -68,6 +70,12 @@ SCHEMA_SNAPSHOT = vol.Schema(
vol.Optional(ATTR_WAIT_BOOT, default=600): vol.All( vol.Optional(ATTR_WAIT_BOOT, default=600): vol.All(
vol.Coerce(int), vol.Range(min=60) vol.Coerce(int), vol.Range(min=60)
), ),
vol.Optional(ATTR_AUDIO_OUTPUT, default=None): vol.Maybe(
vol.Coerce(str)
),
vol.Optional(ATTR_AUDIO_INPUT, default=None): vol.Maybe(
vol.Coerce(str)
),
}, },
extra=vol.REMOVE_EXTRA, extra=vol.REMOVE_EXTRA,
), ),

View File

@ -9,6 +9,8 @@ from .const import (
ATTR_ACCESS_TOKEN, ATTR_ACCESS_TOKEN,
ATTR_ADDONS_CUSTOM_LIST, ATTR_ADDONS_CUSTOM_LIST,
ATTR_AUDIO, ATTR_AUDIO,
ATTR_AUDIO_INPUT,
ATTR_AUDIO_OUTPUT,
ATTR_BOOT, ATTR_BOOT,
ATTR_CHANNEL, ATTR_CHANNEL,
ATTR_CLI, ATTR_CLI,
@ -111,6 +113,8 @@ SCHEMA_HASS_CONFIG = vol.Schema(
vol.Optional(ATTR_WAIT_BOOT, default=600): vol.All( vol.Optional(ATTR_WAIT_BOOT, default=600): vol.All(
vol.Coerce(int), vol.Range(min=60) vol.Coerce(int), vol.Range(min=60)
), ),
vol.Optional(ATTR_AUDIO_OUTPUT, default=None): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_AUDIO_INPUT, default=None): vol.Maybe(vol.Coerce(str)),
}, },
extra=vol.REMOVE_EXTRA, extra=vol.REMOVE_EXTRA,
) )