mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-25 18:16:32 +00:00
commit
15bf1ee50e
@ -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 \
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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
93
API.md
@ -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
|
||||||
|
29
Dockerfile
29
Dockerfile
@ -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 /
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
include LICENSE.md
|
include LICENSE.md
|
||||||
graft hassio
|
graft supervisor
|
||||||
recursive-exclude * *.py[co]
|
recursive-exclude * *.py[co]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[](https://dev.azure.com/home-assistant/Hass.io/_build/latest?definitionId=2&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.
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Installation instructions can be found at <https://home-assistant.io/hassio>.
|
Installation instructions can be found at <https://home-assistant.io/hassio>.
|
||||||
|
@ -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:
|
||||||
|
@ -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
13
build.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
13
entry.sh
13
entry.sh
@ -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
|
|
@ -1 +0,0 @@
|
|||||||
"""Init file for Hass.io."""
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
@ -1 +0,0 @@
|
|||||||
"""Special object and tools for Hass.io."""
|
|
BIN
misc/hassio.png
BIN
misc/hassio.png
Binary file not shown.
Before Width: | Height: | Size: 37 KiB |
@ -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>
|
|
@ -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
|
|
||||||
|
9
rootfs/etc/cont-init.d/udev.sh
Normal file
9
rootfs/etc/cont-init.d/udev.sh
Normal 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
|
35
rootfs/etc/pulse/client.conf
Normal file
35
rootfs/etc/pulse/client.conf
Normal 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
|
5
rootfs/etc/services.d/supervisor/finish
Normal file
5
rootfs/etc/services.d/supervisor/finish
Normal 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
|
5
rootfs/etc/services.d/supervisor/run
Normal file
5
rootfs/etc/services.d/supervisor/run
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/with-contenv bashio
|
||||||
|
# ==============================================================================
|
||||||
|
# Start Service service
|
||||||
|
# ==============================================================================
|
||||||
|
exec python3 -m supervisor
|
@ -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
|
||||||
|
@ -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/
|
23
setup.py
23
setup.py
@ -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
1
supervisor/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Init file for Supervisor."""
|
@ -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)
|
@ -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):
|
@ -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:
|
@ -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
|
||||||
|
|
@ -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."""
|
@ -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
|
@ -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:
|
@ -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(),
|
||||||
},
|
},
|
@ -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")
|
@ -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
127
supervisor/api/audio.py
Normal 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]))
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
@ -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
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
Loading…
x
Reference in New Issue
Block a user