mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-24 09:36:31 +00:00
commit
e52af3bfb4
8
API.md
8
API.md
@ -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"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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]:
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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,
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user