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

View File

@ -1,24 +1,27 @@
"""Init file for Supervisor Home Assistant RESTful API."""
import asyncio
import logging
from typing import Coroutine, Dict, Any
from typing import Any, Coroutine, Dict
import voluptuous as vol
from aiohttp import web
import voluptuous as vol
from ..const import (
ATTR_ARCH,
ATTR_AUDIO_INPUT,
ATTR_AUDIO_OUTPUT,
ATTR_BLK_READ,
ATTR_BLK_WRITE,
ATTR_BOOT,
ATTR_CPU_PERCENT,
ATTR_CUSTOM,
ATTR_IMAGE,
ATTR_IP_ADDRESS,
ATTR_LAST_VERSION,
ATTR_MACHINE,
ATTR_MEMORY_LIMIT,
ATTR_MEMORY_USAGE,
ATTR_MEMORY_PERCENT,
ATTR_MEMORY_USAGE,
ATTR_NETWORK_RX,
ATTR_NETWORK_TX,
ATTR_PORT,
@ -27,7 +30,6 @@ from ..const import (
ATTR_VERSION,
ATTR_WAIT_BOOT,
ATTR_WATCHDOG,
ATTR_IP_ADDRESS,
CONTENT_TYPE_BINARY,
)
from ..coresys import CoreSysAttributes
@ -48,6 +50,8 @@ SCHEMA_OPTIONS = vol.Schema(
vol.Optional(ATTR_WATCHDOG): vol.Boolean(),
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_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_WATCHDOG: self.sys_homeassistant.watchdog,
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
@ -102,6 +108,12 @@ class APIHomeAssistant(CoreSysAttributes):
if ATTR_REFRESH_TOKEN in body:
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()
@api_process

View File

@ -4,11 +4,11 @@ from contextlib import suppress
import logging
from pathlib import Path
import shutil
from typing import Awaitable, List, Optional
from typing import Awaitable, Optional
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 .docker.audio import DockerAudio
from .docker.stats import DockerStats
@ -35,7 +35,7 @@ class Audio(JsonConfig, CoreSysAttributes):
@property
def path_extern_pulse(self) -> Path:
"""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
def path_extern_asound(self) -> Path:
@ -157,8 +157,6 @@ class Audio(JsonConfig, CoreSysAttributes):
_LOGGER.error("Can't start Audio plugin")
raise AudioError() from None
await self._restart_audio_addons()
async def start(self) -> None:
"""Run CoreDNS."""
# Start Instance
@ -169,8 +167,6 @@ class Audio(JsonConfig, CoreSysAttributes):
_LOGGER.error("Can't start Audio plugin")
raise AudioError() from None
await self._restart_audio_addons()
def logs(self) -> Awaitable[bytes]:
"""Get CoreDNS docker logs.
@ -221,22 +217,3 @@ class Audio(JsonConfig, CoreSysAttributes):
default_source=input_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 pathlib import Path
SUPERVISOR_VERSION = "204"
SUPERVISOR_VERSION = "205"
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_source %}default-source = {{ default_source }}{% endif %}
default-server = unix://run/pulse.sock
default-server = unix://run/audio/pulse.sock
; default-dbus-server =
autospawn = no

View File

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

View File

@ -2,7 +2,7 @@
from contextlib import suppress
from ipaddress import IPv4Address
import logging
from typing import Awaitable, Optional
from typing import Awaitable, Dict, Optional
import docker
@ -45,6 +45,46 @@ class DockerHomeAssistant(DockerInterface):
"""Return IP address of this container."""
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:
"""Run Docker image.
@ -67,6 +107,7 @@ class DockerHomeAssistant(DockerInterface):
privileged=True,
init=False,
network_mode="host",
volumes=self.volumes,
environment={
"HASSIO": 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_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
@ -105,18 +135,8 @@ class DockerHomeAssistant(DockerInterface):
detach=True,
stdout=True,
stderr=True,
volumes=self.volumes,
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]:

View File

@ -19,6 +19,8 @@ from packaging import version as pkg_version
from .const import (
ATTR_ACCESS_TOKEN,
ATTR_AUDIO_INPUT,
ATTR_AUDIO_OUTPUT,
ATTR_BOOT,
ATTR_IMAGE,
ATTR_LAST_VERSION,
@ -232,6 +234,36 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
"""Set Home Assistant refresh_token."""
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
async def install_landingpage(self) -> None:
"""Install a landing page."""
@ -334,6 +366,9 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
self._data[ATTR_ACCESS_TOKEN] = secrets.token_hex(56)
self.save_data()
# Write audio settings
self.write_pulse()
try:
await self.instance.run()
except DockerAPIError:
@ -602,3 +637,18 @@ class HomeAssistant(JsonConfig, CoreSysAttributes):
await self.instance.install(self.version)
except DockerAPIError:
_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 (
ATTR_ADDONS,
ATTR_AUDIO_INPUT,
ATTR_AUDIO_OUTPUT,
ATTR_BOOT,
ATTR_CRYPTO,
ATTR_DATE,
@ -443,6 +445,10 @@ class Snapshot(CoreSysAttributes):
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):
"""Write all data to the Home Assistant object."""
self.sys_homeassistant.watchdog = self.homeassistant[ATTR_WATCHDOG]
@ -463,6 +469,10 @@ class Snapshot(CoreSysAttributes):
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
self.sys_homeassistant.save_data()

View File

@ -3,6 +3,8 @@ import voluptuous as vol
from ..const import (
ATTR_ADDONS,
ATTR_AUDIO_INPUT,
ATTR_AUDIO_OUTPUT,
ATTR_BOOT,
ATTR_CRYPTO,
ATTR_DATE,
@ -68,6 +70,12 @@ SCHEMA_SNAPSHOT = vol.Schema(
vol.Optional(ATTR_WAIT_BOOT, default=600): vol.All(
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,
),

View File

@ -9,6 +9,8 @@ from .const import (
ATTR_ACCESS_TOKEN,
ATTR_ADDONS_CUSTOM_LIST,
ATTR_AUDIO,
ATTR_AUDIO_INPUT,
ATTR_AUDIO_OUTPUT,
ATTR_BOOT,
ATTR_CHANNEL,
ATTR_CLI,
@ -111,6 +113,8 @@ SCHEMA_HASS_CONFIG = vol.Schema(
vol.Optional(ATTR_WAIT_BOOT, default=600): vol.All(
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,
)