Merge pull request #1528 from home-assistant/dev

Release 202
This commit is contained in:
Pascal Vizeli 2020-02-26 14:46:50 +01:00 committed by GitHub
commit 15bf1ee50e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
222 changed files with 1340 additions and 606 deletions

View File

@ -33,6 +33,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
containerd.io \ containerd.io \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Install tools
RUN apt-get update && apt-get install -y --no-install-recommends \
jq \
dbus \
network-manager \
libpulse0 \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies from requirements.txt if it exists # Install Python dependencies from requirements.txt if it exists
COPY requirements.txt requirements_tests.txt ./ COPY requirements.txt requirements_tests.txt ./
RUN pip3 install -r requirements.txt -r requirements_tests.txt \ RUN pip3 install -r requirements.txt -r requirements_tests.txt \

View File

@ -1,31 +1,24 @@
// See https://aka.ms/vscode-remote/devcontainer.json for format details. // See https://aka.ms/vscode-remote/devcontainer.json for format details.
{ {
"name": "Hass.io dev", "name": "Supervisor dev",
"context": "..", "context": "..",
"dockerFile": "Dockerfile", "dockerFile": "Dockerfile",
"appPort": "9123:8123", "appPort": "9123:8123",
"runArgs": [ "runArgs": ["-e", "GIT_EDITOR=code --wait", "--privileged"],
"-e", "extensions": [
"GIT_EDITOR=code --wait", "ms-python.python",
"--privileged" "visualstudioexptteam.vscodeintellicode",
], "esbenp.prettier-vscode"
"extensions": [ ],
"ms-python.python", "settings": {
"visualstudioexptteam.vscodeintellicode", "python.pythonPath": "/usr/local/bin/python",
"esbenp.prettier-vscode" "python.linting.pylintEnabled": true,
], "python.linting.enabled": true,
"settings": { "python.formatting.provider": "black",
"python.pythonPath": "/usr/local/bin/python", "python.formatting.blackArgs": ["--target-version", "py37"],
"python.linting.pylintEnabled": true, "editor.formatOnPaste": false,
"python.linting.enabled": true, "editor.formatOnSave": true,
"python.formatting.provider": "black", "editor.formatOnType": true,
"python.formatting.blackArgs": [ "files.trimTrailingWhitespace": true
"--target-version", }
"py37" }
],
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true
}
}

View File

@ -14,10 +14,10 @@
# virtualenv # virtualenv
venv/ venv/
# HA # Data
home-assistant-polymer/* home-assistant-polymer/
misc/* script/
script/* tests/
# Test ENV # Test ENV
data/ data/

93
API.md
View File

@ -853,6 +853,99 @@ return:
} }
``` ```
### Audio
- GET `/audio/info`
```json
{
"host": "ip-address",
"version": "1",
"latest_version": "2",
"audio": {
"input": [
{
"name": "...",
"description": "...",
"volume": 0.3,
"default": false
}
],
"output": [
{
"name": "...",
"description": "...",
"volume": 0.3,
"default": false
}
]
}
}
```
- POST `/audio/update`
```json
{
"version": "VERSION"
}
```
- POST `/audio/restart`
- POST `/audio/reload`
- GET `/audio/logs`
- POST `/audio/volume/input`
```json
{
"name": "...",
"volume": 0.5
}
```
- POST `/audio/volume/output`
```json
{
"name": "...",
"volume": 0.5
}
```
- POST `/audio/default/input`
```json
{
"name": "..."
}
```
- POST `/audio/default/output`
```json
{
"name": "..."
}
```
- GET `/audio/stats`
```json
{
"cpu_percent": 0.0,
"memory_usage": 283123,
"memory_limit": 329392,
"memory_percent": 1.4,
"network_tx": 0,
"network_rx": 0,
"blk_read": 0,
"blk_write": 0
}
```
### Auth / SSO API ### Auth / SSO API
You can use the user system on homeassistant. We handle this auth system on You can use the user system on homeassistant. We handle this auth system on

View File

@ -3,14 +3,15 @@ FROM $BUILD_FROM
# Install base # Install base
RUN apk add --no-cache \ RUN apk add --no-cache \
openssl \
libffi \
musl \
git \
socat \
glib \
eudev \ eudev \
eudev-libs eudev-libs \
git \
glib \
libffi \
libpulse \
musl \
openssl \
socat
ARG BUILD_ARCH ARG BUILD_ARCH
WORKDIR /usr/src WORKDIR /usr/src
@ -23,15 +24,11 @@ RUN export MAKEFLAGS="-j$(nproc)" \
-r ./requirements.txt \ -r ./requirements.txt \
&& rm -f requirements.txt && rm -f requirements.txt
# Install HassIO # Install Home Assistant Supervisor
COPY . hassio COPY . supervisor
RUN pip3 install --no-cache-dir -e ./hassio \ RUN pip3 install --no-cache-dir -e ./supervisor \
&& python3 -m compileall ./hassio/hassio && python3 -m compileall ./supervisor/supervisor
# Initialize udev daemon, handle CMD
COPY entry.sh /bin/
ENTRYPOINT ["/bin/entry.sh"]
WORKDIR / WORKDIR /
CMD [ "python3", "-m", "hassio" ] COPY rootfs /

View File

@ -1,3 +1,3 @@
include LICENSE.md include LICENSE.md
graft hassio graft supervisor
recursive-exclude * *.py[co] recursive-exclude * *.py[co]

View File

@ -1,6 +1,6 @@
[![Build Status](https://dev.azure.com/home-assistant/Hass.io/_apis/build/status/hassio?branchName=dev)](https://dev.azure.com/home-assistant/Hass.io/_build/latest?definitionId=2&branchName=dev) [![Build Status](https://dev.azure.com/home-assistant/Hass.io/_apis/build/status/hassio?branchName=dev)](https://dev.azure.com/home-assistant/Hass.io/_build/latest?definitionId=2&branchName=dev)
# Hass.io # Home Assistant Supervisor
## First private cloud solution for home automation ## First private cloud solution for home automation
@ -10,8 +10,6 @@ communicates with the Supervisor. The Supervisor provides an API to manage the
installation. This includes changing network settings or installing installation. This includes changing network settings or installing
and updating software. and updating software.
![](misc/hassio.png?raw=true)
## Installation ## Installation
Installation instructions can be found at <https://home-assistant.io/hassio>. Installation instructions can be found at <https://home-assistant.io/hassio>.

View File

@ -17,6 +17,10 @@ jobs:
pool: pool:
vmImage: "ubuntu-latest" vmImage: "ubuntu-latest"
steps: steps:
- script: |
sudo apt-get update
sudo apt-get install -y libpulse0 libudev1
displayName: "Install Host library"
- task: UsePythonVersion@0 - task: UsePythonVersion@0
displayName: "Use Python 3.7" displayName: "Use Python 3.7"
inputs: inputs:

View File

@ -10,10 +10,8 @@ trigger:
- "*" - "*"
pr: none pr: none
variables: variables:
- name: basePythonTag
value: "3.7-alpine3.11"
- name: versionBuilder - name: versionBuilder
value: "6.9" value: "7.0"
- group: docker - group: docker
jobs: jobs:
@ -51,6 +49,5 @@ jobs:
-v ~/.docker:/root/.docker \ -v ~/.docker:/root/.docker \
-v /run/docker.sock:/run/docker.sock:rw -v $(pwd):/data:ro \ -v /run/docker.sock:/run/docker.sock:rw -v $(pwd):/data:ro \
homeassistant/amd64-builder:$(versionBuilder) \ homeassistant/amd64-builder:$(versionBuilder) \
--supervisor $(basePythonTag) --version $(Build.SourceBranchName) \ --generic $(Build.SourceBranchName) --all -t /data
--all -t /data --docker-hub homeassistant
displayName: "Build Release" displayName: "Build Release"

13
build.json Normal file
View File

@ -0,0 +1,13 @@
{
"image": "homeassistant/{arch}-hassio-supervisor",
"build_from": {
"aarch64": "homeassistant/aarch64-base-python:3.7-alpine3.11",
"armhf": "homeassistant/armhf-base-python:3.7-alpine3.11",
"armv7": "homeassistant/armv7-base-python:3.7-alpine3.11",
"amd64": "homeassistant/amd64-base-python:3.7-alpine3.11",
"i386": "homeassistant/i386-base-python:3.7-alpine3.11"
},
"labels": {
"io.hass.type": "supervisor"
}
}

View File

@ -1,13 +0,0 @@
#!/bin/bash
set -e
udevd --daemon
udevadm trigger
if CMD="$(command -v "$1")"; then
shift
exec "$CMD" "$@"
else
echo "Command not found: $1"
exit 1
fi

View File

@ -1 +0,0 @@
"""Init file for Hass.io."""

View File

@ -1,17 +0,0 @@
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"
}
}

View File

@ -1,18 +0,0 @@
{
"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
}
}

View File

@ -1,138 +0,0 @@
"""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, CHAN_ID, CHAN_TYPE
from ..coresys import CoreSysAttributes
_LOGGER: logging.Logger = logging.getLogger(__name__)
@attr.s()
class DefaultConfig:
"""Default config input/output ALSA channel."""
input: str = attr.ib()
output: str = attr.ib()
AUDIODB_JSON: Path = Path(__file__).parents[1].joinpath("data/audiodb.json")
ASOUND_TMPL: Path = Path(__file__).parents[1].joinpath("data/asound.tmpl")
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.sys_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.sys_hardware.audio_devices.items():
for chan_info in dev_data[ATTR_DEVICES]:
chan_id = chan_info[CHAN_ID]
chan_type = chan_info[CHAN_TYPE]
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.sys_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."""
try:
return json.loads(AUDIODB_JSON.read_text())
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.sys_machine, {}).get(ATTR_INPUT)
alsa_output = database.get(self.sys_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 an asound data."""
alsa_input = alsa_input or self.default.input
alsa_output = alsa_output or self.default.output
# Read Template
try:
asound_data = ASOUND_TMPL.read_text()
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)

View File

@ -1 +0,0 @@
"""Special object and tools for Hass.io."""

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

View File

@ -1 +0,0 @@
<mxfile userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" version="7.9.5" editor="www.draw.io" type="device"><diagram name="Page-1" id="535f6c39-9b73-04c2-941c-82630de90f1a">5VrLcqM4FP0aLzsFiOcycefRVTPVqfFippdYKLYmMvII4cd8/QiQDEKQ4Bicnmp7Yevqybk691zJnoH55vDI4u36d5ogMnOs5DADX2eOY1vAER+F5VhZ3DCoDCuGE9moNizwv0j1lNYcJyjTGnJKCcdb3QhpmiLINVvMGN3rzV4o0WfdxitkGBYwJqb1T5zwtbTaflRXPCG8WsupQ8evKpYxfF0xmqdyvpkDXspXVb2J1VjyQbN1nNB9wwTuZ2DOKOXVt81hjkiBrYKt6vfQU3taN0MpH9JB+mkXkxypFftEdL17oWIEsUB+lKD4/+RUVXzJSpfdigZitoP4EBUl0KJuL4EpalPKNjGpO4tvq+Lz+0LNI9ZWTVVVSFhOszr7NeZosY1hUd6L7SYarfmGiJJdrAYTMqeEsrK1ABv5EAp7xhl9RY0aq3zJ9S/k+B14SdMOMY4ODZPE7xHRDeLsKJqo2ghUXeRe9yLp2329c1wF9LqxaXzZLpabdXUaunaY+CJ91u0/YPjvW4oLvy2OGUebC9GECVqGyy40gQ8ikJz8NS6AwUAAnREAdA0Av1L4itilyEHkCdJfGznXRM7pQg6MgJxvIPc0N1ArQyEqehTUO5PLIUTdXF6GnutZ42Do+p6OoW9i6FkmhN4IEEYGXigROiSLlPE1XdE0Jve19U5HtIEeOmD+V2G+CTxZ/KGqUrGwqs5TxR9yhL8R50epwHHOqTDVE/9G6VaO0Qt1RnMG5fKlyvOYrRDXtknxYG+6gyESc7zTBfgScFUuMTa6zhvoRiLxaeFbFp4Rw+IBELsS6O5ngR705hPLWuHPSzBsv0gw2gnEIt8itsOZCAlqAqbqnuIs+/a9N8E4mZe9SUe9Dez3w5YRnuZz369SDT2gJR4KE3ecsAU8PWyBjqzDDjvilj2GatrOFNyyG8RSUezELY1XZRgbSqJMMIPfFqcCYYBEbA4MlfkBE7WKQVyz1WmkQbbgs8gGpolwmhd0J7Tkoy62A9xAzIe6EKWJOZgwNobqTPjn80sc64Sfpl0qHjSSKzHKl1vx6ALDIppdJ2LFKHyBYyWresRyOtL8U3DS0nx3jIjlX5kr9o2l5wI3dhhemg8MpFWDLilNkcaVN9NmjRHAZITal9dnhDuJ4kifNZK5kRAe7tC+awqYs92Jzx922Kdpk2veTHzAgRoIvd4832d9InK52zrx/rjrrqE1pqduk4SmmeGvbB1vi69bRiHKsvd1RhelwarzIF6lcleHAMFSy/EDEDnA90InDC0XTJRFd2mSY3umJkUjSJK6vJsypNWltuRcmtTJsNck2Sgn2/FClez6THF50JQuV2ei9rlJjVDRUnZyGjfnZ45TUdkYp9wUp6cZtk9Ck6CQU/OKUvEz35CqAbgrqIChQD5eIvJMM8wxTUWTJeWcbkQDUlTcnX610K7Sy98t6jFuCV4VfTk9j+b1zXv7rl5OMAKRW5d4oOMSD3SklqNcwZs0HkBSK9BY6r7HUtvk6BA6XkXzztTxQYqofkH8KZIZtZgGA/f7vRm9CcHbrHSDZCIkNE8u1smrECjS45lrdZzOgqnuk8DbN+Fyc3/gOHYmRybK5RtaW58Bq0U6vWo7jCauSRO1WydXUre1ZdrRdDwJBP0/01lP+bJXCWHMLqefX7466OcV73HoF4FWOtFFv67r3FEULJiIfc19H4yZZU5P2WHs867BvsFu9AySPGK+npoefeqE7MRDwTT0cNWh9Sr0CH8VcYp8naPBZdrk/xraZP4R4g+0LY5alGHUf4vy/yWfusifgHyiWP/5rXJG/Q9DcP8f</diagram></mxfile>

View File

@ -6,11 +6,13 @@ colorlog==4.1.0
cpe==1.2.1 cpe==1.2.1
cryptography==2.8 cryptography==2.8
docker==4.2.0 docker==4.2.0
gitpython==3.0.7 gitpython==3.1.0
jinja2==2.11.1
packaging==20.1 packaging==20.1
ptvsd==4.3.2
pulsectl==20.2.2
pytz==2019.3 pytz==2019.3
pyudev==0.22.0 pyudev==0.22.0
ruamel.yaml==0.15.100 ruamel.yaml==0.15.100
uvloop==0.14.0 uvloop==0.14.0
voluptuous==0.11.7 voluptuous==0.11.7
ptvsd==4.3.2

View File

@ -0,0 +1,9 @@
#!/usr/bin/with-contenv bashio
# ==============================================================================
# Start udev service
# ==============================================================================
udevd --daemon
bashio::log.info "Update udev informations"
udevadm trigger
udevadm settle

View File

@ -0,0 +1,35 @@
# This file is part of PulseAudio.
#
# PulseAudio is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# PulseAudio is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
## Configuration file for PulseAudio clients. See pulse-client.conf(5) for
## more information. Default values are commented out. Use either ; or # for
## commenting.
; default-sink =
; default-source =
default-server = unix://data/audio/external/pulse.sock
; default-dbus-server =
autospawn = no
; daemon-binary = /usr/bin/pulseaudio
; extra-arguments = --log-target=syslog
; cookie-file =
; enable-shm = yes
; shm-size-bytes = 0 # setting this 0 will use the system-default, usually 64 MiB
; auto-connect-localhost = no
; auto-connect-display = no

View File

@ -0,0 +1,5 @@
#!/usr/bin/execlineb -S0
# ==============================================================================
# Take down the S6 supervision tree when Supervisor fails
# ==============================================================================
s6-svscanctl -t /var/run/s6/services

View File

@ -0,0 +1,5 @@
#!/usr/bin/with-contenv bashio
# ==============================================================================
# Start Service service
# ==============================================================================
exec python3 -m supervisor

View File

@ -61,9 +61,7 @@ function build_supervisor() {
docker run --rm --privileged \ docker run --rm --privileged \
-v /run/docker.sock:/run/docker.sock -v "$(pwd):/data" \ -v /run/docker.sock:/run/docker.sock -v "$(pwd):/data" \
homeassistant/amd64-builder:dev \ homeassistant/amd64-builder:dev \
--supervisor 3.7-alpine3.11 --version dev \ --generic dev -t /data --test --amd64 --no-cache
-t /data --test --amd64 \
--no-cache --docker-hub homeassistant
} }
@ -79,7 +77,7 @@ function cleanup_lastboot() {
function cleanup_docker() { function cleanup_docker() {
echo "Cleaning up stopped containers..." echo "Cleaning up stopped containers..."
docker rm $(docker ps -a -q) docker rm $(docker ps -a -q) || true
} }
@ -108,6 +106,22 @@ function setup_test_env() {
} }
function init_dbus() {
if pgrep dbus-daemon; then
echo "Dbus is running"
return 0
fi
echo "Startup dbus"
mkdir -p /var/lib/dbus
cp -f /etc/machine-id /var/lib/dbus/machine-id
# run
mkdir -p /run/dbus
dbus-daemon --system --print-address
}
echo "Start Test-Env" echo "Start Test-Env"
start_docker start_docker
@ -117,5 +131,6 @@ build_supervisor
install_cli install_cli
cleanup_lastboot cleanup_lastboot
cleanup_docker cleanup_docker
init_dbus
setup_test_env setup_test_env
stop_docker stop_docker

View File

@ -14,5 +14,5 @@ cd hassio
./script/build_hassio ./script/build_hassio
# Copy frontend # Copy frontend
rm -f ../../hassio/api/panel/chunk.* rm -f ../../supervisor/hassio/api/panel/chunk.*
cp -rf build/* ../../hassio/api/panel/ cp -rf build/* ../../supervisor/api/panel/

View File

@ -1,10 +1,11 @@
"""Home Assistant Supervisor setup."""
from setuptools import setup from setuptools import setup
from hassio.const import HASSIO_VERSION from supervisor.const import SUPERVISOR_VERSION
setup( setup(
name="HassIO", name="Supervisor",
version=HASSIO_VERSION, version=SUPERVISOR_VERSION,
license="BSD License", license="BSD License",
author="The Home Assistant Authors", author="The Home Assistant Authors",
author_email="hello@home-assistant.io", author_email="hello@home-assistant.io",
@ -24,19 +25,19 @@ setup(
"Topic :: Scientific/Engineering :: Atmospheric Science", "Topic :: Scientific/Engineering :: Atmospheric Science",
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers", "Intended Audience :: Developers",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7",
], ],
keywords=["docker", "home-assistant", "api"], keywords=["docker", "home-assistant", "api"],
zip_safe=False, zip_safe=False,
platforms="any", platforms="any",
packages=[ packages=[
"hassio", "supervisor",
"hassio.docker", "supervisor.docker",
"hassio.addons", "supervisor.addons",
"hassio.api", "supervisor.api",
"hassio.misc", "supervisor.misc",
"hassio.utils", "supervisor.utils",
"hassio.snapshots", "supervisor.snapshots",
], ],
include_package_data=True, include_package_data=True,
) )

1
supervisor/__init__.py Normal file
View File

@ -0,0 +1 @@
"""Init file for Supervisor."""

View File

@ -1,10 +1,10 @@
"""Main file for Hass.io.""" """Main file for Supervisor."""
import asyncio import asyncio
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
import logging import logging
import sys import sys
from hassio import bootstrap from supervisor import bootstrap
_LOGGER: logging.Logger = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
@ -29,7 +29,7 @@ if __name__ == "__main__":
# Init async event loop # Init async event loop
loop = initialize_event_loop() loop = initialize_event_loop()
# Check if all information are available to setup Hass.io # Check if all information are available to setup Supervisor
if not bootstrap.check_environment(): if not bootstrap.check_environment():
sys.exit(1) sys.exit(1)
@ -37,27 +37,27 @@ if __name__ == "__main__":
executor = ThreadPoolExecutor(thread_name_prefix="SyncWorker") executor = ThreadPoolExecutor(thread_name_prefix="SyncWorker")
loop.set_default_executor(executor) loop.set_default_executor(executor)
_LOGGER.info("Initialize Hass.io setup") _LOGGER.info("Initialize Supervisor setup")
coresys = loop.run_until_complete(bootstrap.initialize_coresys()) coresys = loop.run_until_complete(bootstrap.initialize_coresys())
loop.run_until_complete(coresys.core.connect()) loop.run_until_complete(coresys.core.connect())
bootstrap.supervisor_debugger(coresys) bootstrap.supervisor_debugger(coresys)
bootstrap.migrate_system_env(coresys) bootstrap.migrate_system_env(coresys)
_LOGGER.info("Setup HassIO") _LOGGER.info("Setup Supervisor")
loop.run_until_complete(coresys.core.setup()) loop.run_until_complete(coresys.core.setup())
loop.call_soon_threadsafe(loop.create_task, coresys.core.start()) loop.call_soon_threadsafe(loop.create_task, coresys.core.start())
loop.call_soon_threadsafe(bootstrap.reg_signal, loop) loop.call_soon_threadsafe(bootstrap.reg_signal, loop)
try: try:
_LOGGER.info("Run Hass.io") _LOGGER.info("Run Supervisor")
loop.run_forever() loop.run_forever()
finally: finally:
_LOGGER.info("Stopping Hass.io") _LOGGER.info("Stopping Supervisor")
loop.run_until_complete(coresys.core.stop()) loop.run_until_complete(coresys.core.stop())
executor.shutdown(wait=False) executor.shutdown(wait=False)
loop.close() loop.close()
_LOGGER.info("Close Hass.io") _LOGGER.info("Close Supervisor")
sys.exit(0) sys.exit(0)

View File

@ -1,4 +1,4 @@
"""Init file for Hass.io add-ons.""" """Init file for Supervisor add-ons."""
import asyncio import asyncio
from contextlib import suppress from contextlib import suppress
import logging import logging
@ -25,7 +25,7 @@ AnyAddon = Union[Addon, AddonStore]
class AddonManager(CoreSysAttributes): class AddonManager(CoreSysAttributes):
"""Manage add-ons inside Hass.io.""" """Manage add-ons inside Supervisor."""
def __init__(self, coresys: CoreSys): def __init__(self, coresys: CoreSys):
"""Initialize Docker base wrapper.""" """Initialize Docker base wrapper."""
@ -57,7 +57,7 @@ class AddonManager(CoreSysAttributes):
return self.store.get(addon_slug) return self.store.get(addon_slug)
def from_token(self, token: str) -> Optional[Addon]: def from_token(self, token: str) -> Optional[Addon]:
"""Return an add-on from Hass.io token.""" """Return an add-on from Supervisor token."""
for addon in self.installed: for addon in self.installed:
if token == addon.hassio_token: if token == addon.hassio_token:
return addon return addon
@ -152,9 +152,9 @@ class AddonManager(CoreSysAttributes):
await addon.remove_data() await addon.remove_data()
# Cleanup audio settings # Cleanup audio settings
if addon.path_asound.exists(): if addon.path_pulse.exists():
with suppress(OSError): with suppress(OSError):
addon.path_asound.unlink() addon.path_pulse.unlink()
# Cleanup AppArmor profile # Cleanup AppArmor profile
with suppress(HostAppArmorError): with suppress(HostAppArmorError):

View File

@ -1,4 +1,4 @@
"""Init file for Hass.io add-ons.""" """Init file for Supervisor add-ons."""
from contextlib import suppress from contextlib import suppress
from copy import deepcopy from copy import deepcopy
from ipaddress import IPv4Address from ipaddress import IPv4Address
@ -65,7 +65,7 @@ RE_WEBUI = re.compile(
class Addon(AddonModel): class Addon(AddonModel):
"""Hold data for add-on inside Hass.io.""" """Hold data for add-on inside Supervisor."""
def __init__(self, coresys: CoreSys, slug: str): def __init__(self, coresys: CoreSys, slug: str):
"""Initialize data holder.""" """Initialize data holder."""
@ -163,12 +163,12 @@ class Addon(AddonModel):
@property @property
def hassio_token(self) -> Optional[str]: def hassio_token(self) -> Optional[str]:
"""Return access token for Hass.io API.""" """Return access token for Supervisor API."""
return self.persist.get(ATTR_ACCESS_TOKEN) return self.persist.get(ATTR_ACCESS_TOKEN)
@property @property
def ingress_token(self) -> Optional[str]: def ingress_token(self) -> Optional[str]:
"""Return access token for Hass.io API.""" """Return access token for Supervisor API."""
return self.persist.get(ATTR_INGRESS_TOKEN) return self.persist.get(ATTR_INGRESS_TOKEN)
@property @property
@ -250,7 +250,7 @@ class Addon(AddonModel):
# lookup the correct protocol from config # lookup the correct protocol from config
if t_proto: if t_proto:
proto = "https" if self.options[t_proto] else "http" proto = "https" if self.options.get(t_proto) else "http"
else: else:
proto = s_prefix proto = s_prefix
@ -279,14 +279,14 @@ class Addon(AddonModel):
@property @property
def audio_output(self) -> Optional[str]: def audio_output(self) -> Optional[str]:
"""Return ALSA config for output or None.""" """Return a pulse profile for output or None."""
if not self.with_audio: if not self.with_audio:
return None return None
return self.persist.get(ATTR_AUDIO_OUTPUT, self.sys_host.alsa.default.output) return self.persist.get(ATTR_AUDIO_OUTPUT)
@audio_output.setter @audio_output.setter
def audio_output(self, value: Optional[str]): def audio_output(self, value: Optional[str]):
"""Set/reset audio output settings.""" """Set/reset audio output profile settings."""
if value is None: if value is None:
self.persist.pop(ATTR_AUDIO_OUTPUT, None) self.persist.pop(ATTR_AUDIO_OUTPUT, None)
else: else:
@ -294,10 +294,10 @@ class Addon(AddonModel):
@property @property
def audio_input(self) -> Optional[str]: def audio_input(self) -> Optional[str]:
"""Return ALSA config for input or None.""" """Return pulse profile for input or None."""
if not self.with_audio: if not self.with_audio:
return None return None
return self.persist.get(ATTR_AUDIO_INPUT, self.sys_host.alsa.default.input) return self.persist.get(ATTR_AUDIO_INPUT)
@audio_input.setter @audio_input.setter
def audio_input(self, value: Optional[str]): def audio_input(self, value: Optional[str]):
@ -333,14 +333,14 @@ class Addon(AddonModel):
return Path(self.path_data, "options.json") return Path(self.path_data, "options.json")
@property @property
def path_asound(self): def path_pulse(self):
"""Return path to asound config.""" """Return path to asound config."""
return Path(self.sys_config.path_tmp, f"{self.slug}_asound") return Path(self.sys_config.path_tmp, f"{self.slug}_pulse")
@property @property
def path_extern_asound(self): def path_extern_pulse(self):
"""Return path to asound config for Docker.""" """Return path to asound config for Docker."""
return Path(self.sys_config.path_extern_tmp, f"{self.slug}_asound") return Path(self.sys_config.path_extern_tmp, f"{self.slug}_pulse")
def save_persist(self): def save_persist(self):
"""Save data of add-on.""" """Save data of add-on."""
@ -379,20 +379,24 @@ class Addon(AddonModel):
_LOGGER.info("Remove add-on data folder %s", self.path_data) _LOGGER.info("Remove add-on data folder %s", self.path_data)
await remove_data(self.path_data) await remove_data(self.path_data)
def write_asound(self): def write_pulse(self):
"""Write asound config to file and return True on success.""" """Write asound config to file and return True on success."""
asound_config = self.sys_host.alsa.asound( pulse_config = self.sys_audio.pulse_client(
alsa_input=self.audio_input, alsa_output=self.audio_output input_profile=self.audio_input, output_profile=self.audio_output
) )
try: try:
with self.path_asound.open("w") as config_file: with self.path_pulse.open("w") as config_file:
config_file.write(asound_config) config_file.write(pulse_config)
except OSError as err: except OSError as err:
_LOGGER.error("Add-on %s can't write asound: %s", self.slug, err) _LOGGER.error(
"Add-on %s can't write pulse/client.config: %s", self.slug, err
)
raise AddonsError() raise AddonsError()
_LOGGER.debug("Add-on %s write asound: %s", self.slug, self.path_asound) _LOGGER.debug(
"Add-on %s write pulse/client.config: %s", self.slug, self.path_pulse
)
async def install_apparmor(self) -> None: async def install_apparmor(self) -> None:
"""Install or Update AppArmor profile for Add-on.""" """Install or Update AppArmor profile for Add-on."""
@ -468,7 +472,7 @@ class Addon(AddonModel):
# Sound # Sound
if self.with_audio: if self.with_audio:
self.write_asound() self.write_pulse()
# Start Add-on # Start Add-on
try: try:

View File

@ -1,4 +1,4 @@
"""Hass.io add-on build environment.""" """Supervisor add-on build environment."""
from __future__ import annotations from __future__ import annotations
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Dict from typing import TYPE_CHECKING, Dict
@ -16,7 +16,7 @@ class AddonBuild(JsonConfig, CoreSysAttributes):
"""Handle build options for add-ons.""" """Handle build options for add-ons."""
def __init__(self, coresys: CoreSys, addon: AnyAddon) -> None: def __init__(self, coresys: CoreSys, addon: AnyAddon) -> None:
"""Initialize Hass.io add-on builder.""" """Initialize Supervisor add-on builder."""
self.coresys: CoreSys = coresys self.coresys: CoreSys = coresys
self.addon = addon self.addon = addon

View File

@ -1,4 +1,4 @@
"""Init file for Hass.io add-on data.""" """Init file for Supervisor add-on data."""
from copy import deepcopy from copy import deepcopy
import logging import logging
from typing import Any, Dict from typing import Any, Dict
@ -23,7 +23,7 @@ Config = Dict[str, Any]
class AddonsData(JsonConfig, CoreSysAttributes): class AddonsData(JsonConfig, CoreSysAttributes):
"""Hold data for installed Add-ons inside Hass.io.""" """Hold data for installed Add-ons inside Supervisor."""
def __init__(self, coresys: CoreSys): def __init__(self, coresys: CoreSys):
"""Initialize data holder.""" """Initialize data holder."""

View File

@ -1,4 +1,4 @@
"""Init file for Hass.io add-ons.""" """Init file for Supervisor add-ons."""
from pathlib import Path from pathlib import Path
from typing import Any, Awaitable, Dict, List, Optional from typing import Any, Awaitable, Dict, List, Optional
@ -137,12 +137,12 @@ class AddonModel(CoreSysAttributes):
@property @property
def hassio_token(self) -> Optional[str]: def hassio_token(self) -> Optional[str]:
"""Return access token for Hass.io API.""" """Return access token for Supervisor API."""
return None return None
@property @property
def ingress_token(self) -> Optional[str]: def ingress_token(self) -> Optional[str]:
"""Return access token for Hass.io API.""" """Return access token for Supervisor API."""
return None return None
@property @property
@ -326,7 +326,7 @@ class AddonModel(CoreSysAttributes):
@property @property
def access_hassio_api(self) -> bool: def access_hassio_api(self) -> bool:
"""Return True if the add-on access to Hass.io REASTful API.""" """Return True if the add-on access to Supervisor REASTful API."""
return self.data[ATTR_HASSIO_API] return self.data[ATTR_HASSIO_API]
@property @property
@ -336,7 +336,7 @@ class AddonModel(CoreSysAttributes):
@property @property
def hassio_role(self) -> str: def hassio_role(self) -> str:
"""Return Hass.io role for API.""" """Return Supervisor role for API."""
return self.data[ATTR_HASSIO_ROLE] return self.data[ATTR_HASSIO_ROLE]
@property @property

View File

@ -59,7 +59,7 @@ def rating_security(addon: AddonModel) -> int:
): ):
rating += -1 rating += -1
# API Hass.io role # API Supervisor role
if addon.hassio_role == ROLE_MANAGER: if addon.hassio_role == ROLE_MANAGER:
rating += -1 rating += -1
elif addon.hassio_role == ROLE_ADMIN: elif addon.hassio_role == ROLE_ADMIN:

View File

@ -96,7 +96,6 @@ from ..discovery.validate import valid_discovery_service
from ..validate import ( from ..validate import (
DOCKER_PORTS, DOCKER_PORTS,
DOCKER_PORTS_DESCRIPTION, DOCKER_PORTS_DESCRIPTION,
alsa_device,
network_port, network_port,
token, token,
uuid_match, uuid_match,
@ -296,8 +295,8 @@ SCHEMA_ADDON_USER = vol.Schema(
vol.Optional(ATTR_AUTO_UPDATE, default=False): vol.Boolean(), vol.Optional(ATTR_AUTO_UPDATE, default=False): vol.Boolean(),
vol.Optional(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]), vol.Optional(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]),
vol.Optional(ATTR_NETWORK): DOCKER_PORTS, vol.Optional(ATTR_NETWORK): DOCKER_PORTS,
vol.Optional(ATTR_AUDIO_OUTPUT): alsa_device, vol.Optional(ATTR_AUDIO_OUTPUT): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_AUDIO_INPUT): alsa_device, vol.Optional(ATTR_AUDIO_INPUT): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_PROTECTED, default=True): vol.Boolean(), vol.Optional(ATTR_PROTECTED, default=True): vol.Boolean(),
vol.Optional(ATTR_INGRESS_PANEL, default=False): vol.Boolean(), vol.Optional(ATTR_INGRESS_PANEL, default=False): vol.Boolean(),
}, },

View File

@ -1,4 +1,4 @@
"""Init file for Hass.io RESTful API.""" """Init file for Supervisor RESTful API."""
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
@ -21,6 +21,7 @@ from .security import SecurityMiddleware
from .services import APIServices from .services import APIServices
from .snapshots import APISnapshots from .snapshots import APISnapshots
from .supervisor import APISupervisor from .supervisor import APISupervisor
from .audio import APIAudio
_LOGGER: logging.Logger = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
@ -29,7 +30,7 @@ MAX_CLIENT_SIZE: int = 1024 ** 2 * 16
class RestAPI(CoreSysAttributes): class RestAPI(CoreSysAttributes):
"""Handle RESTful API for Hass.io.""" """Handle RESTful API for Supervisor."""
def __init__(self, coresys: CoreSys): def __init__(self, coresys: CoreSys):
"""Initialize Docker base wrapper.""" """Initialize Docker base wrapper."""
@ -61,6 +62,7 @@ class RestAPI(CoreSysAttributes):
self._register_info() self._register_info()
self._register_auth() self._register_auth()
self._register_dns() self._register_dns()
self._register_audio()
def _register_host(self) -> None: def _register_host(self) -> None:
"""Register hostcontrol functions.""" """Register hostcontrol functions."""
@ -93,7 +95,7 @@ class RestAPI(CoreSysAttributes):
web.post("/os/update", api_hassos.update), web.post("/os/update", api_hassos.update),
web.post("/os/update/cli", api_hassos.update_cli), web.post("/os/update/cli", api_hassos.update_cli),
web.post("/os/config/sync", api_hassos.config_sync), web.post("/os/config/sync", api_hassos.config_sync),
# Remove with old Hass.io fallback # Remove with old Supervisor fallback
web.get("/hassos/info", api_hassos.info), web.get("/hassos/info", api_hassos.info),
web.post("/hassos/update", api_hassos.update), web.post("/hassos/update", api_hassos.update),
web.post("/hassos/update/cli", api_hassos.update_cli), web.post("/hassos/update/cli", api_hassos.update_cli),
@ -165,7 +167,7 @@ class RestAPI(CoreSysAttributes):
web.post("/core/start", api_hass.start), web.post("/core/start", api_hass.start),
web.post("/core/check", api_hass.check), web.post("/core/check", api_hass.check),
web.post("/core/rebuild", api_hass.rebuild), web.post("/core/rebuild", api_hass.rebuild),
# Remove with old Hass.io fallback # Remove with old Supervisor fallback
web.get("/homeassistant/info", api_hass.info), web.get("/homeassistant/info", api_hass.info),
web.get("/homeassistant/logs", api_hass.logs), web.get("/homeassistant/logs", api_hass.logs),
web.get("/homeassistant/stats", api_hass.stats), web.get("/homeassistant/stats", api_hass.stats),
@ -192,7 +194,7 @@ class RestAPI(CoreSysAttributes):
web.post("/core/api/{path:.+}", api_proxy.api), web.post("/core/api/{path:.+}", api_proxy.api),
web.get("/core/api/{path:.+}", api_proxy.api), web.get("/core/api/{path:.+}", api_proxy.api),
web.get("/core/api/", api_proxy.api), web.get("/core/api/", api_proxy.api),
# Remove with old Hass.io fallback # Remove with old Supervisor fallback
web.get("/homeassistant/api/websocket", api_proxy.websocket), web.get("/homeassistant/api/websocket", api_proxy.websocket),
web.get("/homeassistant/websocket", api_proxy.websocket), web.get("/homeassistant/websocket", api_proxy.websocket),
web.get("/homeassistant/api/stream", api_proxy.stream), web.get("/homeassistant/api/stream", api_proxy.stream),
@ -314,6 +316,24 @@ class RestAPI(CoreSysAttributes):
] ]
) )
def _register_audio(self) -> None:
"""Register Audio functions."""
api_audio = APIAudio()
api_audio.coresys = self.coresys
self.webapp.add_routes(
[
web.get("/audio/info", api_audio.info),
web.get("/audio/stats", api_audio.stats),
web.get("/audio/logs", api_audio.logs),
web.post("/audio/update", api_audio.update),
web.post("/audio/restart", api_audio.restart),
web.post("/audio/reload", api_audio.reload),
web.post("/audio/volume/{source}", api_audio.set_volume),
web.post("/audio/default/{source}", api_audio.set_default),
]
)
def _register_panel(self) -> None: def _register_panel(self) -> None:
"""Register panel for Home Assistant.""" """Register panel for Home Assistant."""
panel_dir = Path(__file__).parent.joinpath("panel") panel_dir = Path(__file__).parent.joinpath("panel")

View File

@ -1,4 +1,4 @@
"""Init file for Hass.io Home Assistant RESTful API.""" """Init file for Supervisor Home Assistant RESTful API."""
import asyncio import asyncio
import logging import logging
from typing import Any, Awaitable, Dict, List from typing import Any, Awaitable, Dict, List
@ -96,7 +96,7 @@ from ..const import (
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..docker.stats import DockerStats from ..docker.stats import DockerStats
from ..exceptions import APIError from ..exceptions import APIError
from ..validate import DOCKER_PORTS, alsa_device from ..validate import DOCKER_PORTS
from .utils import api_process, api_process_raw, api_validate from .utils import api_process, api_process_raw, api_validate
_LOGGER: logging.Logger = logging.getLogger(__name__) _LOGGER: logging.Logger = logging.getLogger(__name__)
@ -107,10 +107,10 @@ SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})
SCHEMA_OPTIONS = vol.Schema( SCHEMA_OPTIONS = vol.Schema(
{ {
vol.Optional(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]), vol.Optional(ATTR_BOOT): vol.In([BOOT_AUTO, BOOT_MANUAL]),
vol.Optional(ATTR_NETWORK): vol.Any(None, DOCKER_PORTS), vol.Optional(ATTR_NETWORK): vol.Maybe(DOCKER_PORTS),
vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(), vol.Optional(ATTR_AUTO_UPDATE): vol.Boolean(),
vol.Optional(ATTR_AUDIO_OUTPUT): alsa_device, vol.Optional(ATTR_AUDIO_OUTPUT): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_AUDIO_INPUT): alsa_device, vol.Optional(ATTR_AUDIO_INPUT): vol.Maybe(vol.Coerce(str)),
vol.Optional(ATTR_INGRESS_PANEL): vol.Boolean(), vol.Optional(ATTR_INGRESS_PANEL): vol.Boolean(),
} }
) )

127
supervisor/api/audio.py Normal file
View File

@ -0,0 +1,127 @@
"""Init file for Supervisor Audio RESTful API."""
import asyncio
import logging
from typing import Any, Awaitable, Dict
from aiohttp import web
import attr
import voluptuous as vol
from ..const import (
ATTR_AUDIO,
ATTR_BLK_READ,
ATTR_BLK_WRITE,
ATTR_CPU_PERCENT,
ATTR_HOST,
ATTR_INPUT,
ATTR_LATEST_VERSION,
ATTR_MEMORY_LIMIT,
ATTR_MEMORY_PERCENT,
ATTR_MEMORY_USAGE,
ATTR_NAME,
ATTR_NETWORK_RX,
ATTR_NETWORK_TX,
ATTR_OUTPUT,
ATTR_VERSION,
ATTR_VOLUME,
CONTENT_TYPE_BINARY,
)
from ..coresys import CoreSysAttributes
from ..exceptions import APIError
from ..host.sound import SourceType
from .utils import api_process, api_process_raw, api_validate
_LOGGER: logging.Logger = logging.getLogger(__name__)
SCHEMA_VERSION = vol.Schema({vol.Optional(ATTR_VERSION): vol.Coerce(str)})
SCHEMA_VOLUME = vol.Schema(
{
vol.Required(ATTR_NAME): vol.Coerce(str),
vol.Required(ATTR_VOLUME): vol.Coerce(float),
}
)
SCHEMA_DEFAULT = vol.Schema({vol.Required(ATTR_NAME): vol.Coerce(str)})
class APIAudio(CoreSysAttributes):
"""Handle RESTful API for Audio functions."""
@api_process
async def info(self, request: web.Request) -> Dict[str, Any]:
"""Return Audio information."""
return {
ATTR_VERSION: self.sys_audio.version,
ATTR_LATEST_VERSION: self.sys_audio.latest_version,
ATTR_HOST: str(self.sys_docker.network.audio),
ATTR_AUDIO: {
ATTR_INPUT: [
attr.asdict(profile)
for profile in self.sys_host.sound.input_profiles
],
ATTR_OUTPUT: [
attr.asdict(profile)
for profile in self.sys_host.sound.output_profiles
],
},
}
@api_process
async def stats(self, request: web.Request) -> Dict[str, Any]:
"""Return resource information."""
stats = await self.sys_audio.stats()
return {
ATTR_CPU_PERCENT: stats.cpu_percent,
ATTR_MEMORY_USAGE: stats.memory_usage,
ATTR_MEMORY_LIMIT: stats.memory_limit,
ATTR_MEMORY_PERCENT: stats.memory_percent,
ATTR_NETWORK_RX: stats.network_rx,
ATTR_NETWORK_TX: stats.network_tx,
ATTR_BLK_READ: stats.blk_read,
ATTR_BLK_WRITE: stats.blk_write,
}
@api_process
async def update(self, request: web.Request) -> None:
"""Update Audio plugin."""
body = await api_validate(SCHEMA_VERSION, request)
version = body.get(ATTR_VERSION, self.sys_audio.latest_version)
if version == self.sys_audio.version:
raise APIError("Version {} is already in use".format(version))
await asyncio.shield(self.sys_audio.update(version))
@api_process_raw(CONTENT_TYPE_BINARY)
def logs(self, request: web.Request) -> Awaitable[bytes]:
"""Return Audio Docker logs."""
return self.sys_audio.logs()
@api_process
def restart(self, request: web.Request) -> Awaitable[None]:
"""Restart Audio plugin."""
return asyncio.shield(self.sys_audio.restart())
@api_process
def reload(self, request: web.Request) -> Awaitable[None]:
"""Reload Audio information."""
return asyncio.shield(self.sys_host.sound.update())
@api_process
async def set_volume(self, request: web.Request) -> None:
"""Set Audio information."""
source: SourceType = SourceType(request.match_info.get("source"))
body = await api_validate(SCHEMA_VOLUME, request)
await asyncio.shield(
self.sys_host.sound.set_volume(source, body[ATTR_NAME], body[ATTR_VOLUME])
)
@api_process
async def set_default(self, request: web.Request) -> None:
"""Set Audio default sources."""
source: SourceType = SourceType(request.match_info.get("source"))
body = await api_validate(SCHEMA_DEFAULT, request)
await asyncio.shield(self.sys_host.sound.set_default(source, body[ATTR_NAME]))

View File

@ -1,4 +1,4 @@
"""Init file for Hass.io auth/SSO RESTful API.""" """Init file for Supervisor auth/SSO RESTful API."""
import asyncio import asyncio
import logging import logging
from typing import Dict from typing import Dict
@ -76,7 +76,7 @@ class APIAuth(CoreSysAttributes):
return await self._process_dict(request, addon, data) return await self._process_dict(request, addon, data)
raise HTTPUnauthorized( raise HTTPUnauthorized(
headers={WWW_AUTHENTICATE: 'Basic realm="Hass.io Authentication"'} headers={WWW_AUTHENTICATE: 'Basic realm="Home Assistant Authentication"'}
) )
@api_process @api_process

View File

@ -1,4 +1,4 @@
"""Init file for Hass.io network RESTful API.""" """Init file for Supervisor network RESTful API."""
import voluptuous as vol import voluptuous as vol
from .utils import api_process, api_validate from .utils import api_process, api_validate

View File

@ -1,4 +1,4 @@
"""Init file for Hass.io DNS RESTful API.""" """Init file for Supervisor DNS RESTful API."""
import asyncio import asyncio
import logging import logging
from typing import Any, Awaitable, Dict from typing import Any, Awaitable, Dict

View File

@ -1,4 +1,4 @@
"""Init file for Hass.io hardware RESTful API.""" """Init file for Supervisor hardware RESTful API."""
import asyncio import asyncio
import logging import logging
from typing import Any, Dict from typing import Any, Dict
@ -37,11 +37,17 @@ class APIHardware(CoreSysAttributes):
@api_process @api_process
async def audio(self, request: web.Request) -> Dict[str, Any]: async def audio(self, request: web.Request) -> Dict[str, Any]:
"""Show ALSA audio devices.""" """Show pulse audio profiles."""
return { return {
ATTR_AUDIO: { ATTR_AUDIO: {
ATTR_INPUT: self.sys_host.alsa.input_devices, ATTR_INPUT: {
ATTR_OUTPUT: self.sys_host.alsa.output_devices, profile.name: profile.description
for profile in self.sys_host.sound.input_profiles
},
ATTR_OUTPUT: {
profile.name: profile.description
for profile in self.sys_host.sound.output_profiles
},
} }
} }

View File

@ -1,4 +1,4 @@
"""Init file for Hass.io HassOS RESTful API.""" """Init file for Supervisor HassOS RESTful API."""
import asyncio import asyncio
import logging import logging
from typing import Any, Awaitable, Dict from typing import Any, Awaitable, Dict

View File

@ -1,4 +1,4 @@
"""Init file for Hass.io 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 Coroutine, Dict, Any

View File

@ -1,4 +1,4 @@
"""Init file for Hass.io host RESTful API.""" """Init file for Supervisor host RESTful API."""
import asyncio import asyncio
import logging import logging

View File

@ -1,4 +1,4 @@
"""Init file for Hass.io info RESTful API.""" """Init file for Supervisor info RESTful API."""
import logging import logging
from typing import Any, Dict from typing import Any, Dict

View File

@ -1,4 +1,4 @@
"""Hass.io Add-on ingress service.""" """Supervisor Add-on ingress service."""
import asyncio import asyncio
from ipaddress import ip_address from ipaddress import ip_address
import logging import logging
@ -81,7 +81,7 @@ class APIIngress(CoreSysAttributes):
async def handler( async def handler(
self, request: web.Request self, request: web.Request
) -> Union[web.Response, web.StreamResponse, web.WebSocketResponse]: ) -> Union[web.Response, web.StreamResponse, web.WebSocketResponse]:
"""Route data to Hass.io ingress service.""" """Route data to Supervisor ingress service."""
self._check_ha_access(request) self._check_ha_access(request)
# Check Ingress Session # Check Ingress Session

Some files were not shown because too many files have changed in this diff Show More