diff --git a/API.md b/API.md index 5946adf57..ae3595074 100644 --- a/API.md +++ b/API.md @@ -1,10 +1,11 @@ -# HassIO Server +# Hass.io Server -## HassIO REST API +## Hass.io RESTful API -Interface for HomeAssistant to control things from supervisor. +Interface for Home Assistant to control things from supervisor. On error: + ```json { "result": "error", @@ -12,7 +13,8 @@ On error: } ``` -On success +On success: + ```json { "result": "ok", @@ -20,10 +22,9 @@ On success } ``` -### HassIO +### Hass.io - GET `/supervisor/ping` - - GET `/supervisor/info` The addons from `addons` are only installed one. @@ -54,7 +55,9 @@ The addons from `addons` are only installed one. ``` - POST `/supervisor/update` + Optional: + ```json { "version": "VERSION" @@ -62,6 +65,7 @@ Optional: ``` - POST `/supervisor/options` + ```json { "beta_channel": "true|false", @@ -78,11 +82,12 @@ Reload addons/version. - GET `/supervisor/logs` -Output the raw docker log +Output is the raw docker log. ### Security - GET `/security/info` + ```json { "initialize": "bool", @@ -91,6 +96,7 @@ Output the raw docker log ``` - POST `/security/options` + ```json { "password": "xy" @@ -98,6 +104,7 @@ Output the raw docker log ``` - POST `/security/totp` + ```json { "password": "xy" @@ -117,6 +124,7 @@ Return QR-Code ### Backup/Snapshot - GET `/snapshots` + ```json { "snapshots": [ @@ -132,6 +140,7 @@ Return QR-Code - POST `/snapshots/reload` - POST `/snapshots/new/full` + ```json { "name": "Optional" @@ -139,6 +148,7 @@ Return QR-Code ``` - POST `/snapshots/new/partial` + ```json { "name": "Optional", @@ -150,6 +160,7 @@ Return QR-Code - POST `/snapshots/reload` - GET `/snapshots/{slug}/info` + ```json { "slug": "SNAPSHOT ID", @@ -174,10 +185,9 @@ Return QR-Code ``` - POST `/snapshots/{slug}/remove` - - POST `/snapshots/{slug}/restore/full` - - POST `/snapshots/{slug}/restore/partial` + ```json { "homeassistant": "bool", @@ -187,14 +197,14 @@ Return QR-Code ``` ### Host + - POST `/host/reload` - - POST `/host/shutdown` - - POST `/host/reboot` - - GET `/host/info` + See HostControl info command. + ```json { "type": "", @@ -207,16 +217,37 @@ See HostControl info command. ``` - POST `/host/update` + Optional: + ```json { "version": "VERSION" } ``` +- GET `/host/hardware` +```json +{ + "serial": ["/dev/xy"], + "input": ["Input device name"], + "disk": ["/dev/sdax"], + "audio": { + "CARD_ID": { + "name": "xy", + "type": "microphone", + "devices": { + "DEV_ID": "type of device" + } + } + } +} +``` + ### Network - GET `/network/info` + ```json { "hostname": "" @@ -224,6 +255,7 @@ Optional: ``` - POST `/network/options` + ```json { "hostname": "", @@ -235,7 +267,7 @@ Optional: } ``` -### HomeAssistant +### Home Assistant - GET `/homeassistant/info` @@ -250,7 +282,9 @@ Optional: ``` - POST `/homeassistant/update` + Optional: + ```json { "version": "VERSION" @@ -259,11 +293,11 @@ Optional: - GET `/homeassistant/logs` -Output the raw docker log +Output is the raw Docker log. - POST `/homeassistant/restart` - - POST `/homeassistant/options` + ```json { "devices": [], @@ -274,11 +308,11 @@ Output the raw docker log Image with `null` and last_version with `null` reset this options. -### REST API addons +### RESTful for API addons - GET `/addons` -Get all available addons +Get all available addons. ```json { @@ -312,8 +346,8 @@ Get all available addons ``` - POST `/addons/reload` - - GET `/addons/{addon}/info` + ```json { "name": "xy bla", @@ -340,6 +374,7 @@ Get all available addons - GET `/addons/{addon}/logo` - POST `/addons/{addon}/options` + ```json { "boot": "auto|manual", @@ -358,7 +393,9 @@ For reset custom network settings, set it `null`. - POST `/addons/{addon}/stop` - POST `/addons/{addon}/install` + Optional: + ```json { "version": "VERSION" @@ -368,7 +405,9 @@ Optional: - POST `/addons/{addon}/uninstall` - POST `/addons/{addon}/update` + Optional: + ```json { "version": "VERSION" @@ -377,15 +416,16 @@ Optional: - GET `/addons/{addon}/logs` -Output the raw docker log +Output is the raw Docker log. - POST `/addons/{addon}/restart` ## Host Control -Communicate over unix socket with a host daemon. +Communicate over UNIX socket with a host daemon. - commands + ``` # info -> {'type', 'version', 'last_version', 'features', 'hostname'} @@ -404,7 +444,8 @@ Communicate over unix socket with a host daemon. # network int route xy ``` -features: +Features: + - shutdown - reboot - update diff --git a/hassio/api/__init__.py b/hassio/api/__init__.py index 2d5c0110a..832649ddf 100644 --- a/hassio/api/__init__.py +++ b/hassio/api/__init__.py @@ -28,11 +28,12 @@ class RestAPI(object): self._handler = None self.server = None - def register_host(self, host_control): + def register_host(self, host_control, hardware): """Register hostcontrol function.""" - api_host = APIHost(self.config, self.loop, host_control) + api_host = APIHost(self.config, self.loop, host_control, hardware) self.webapp.router.add_get('/host/info', api_host.info) + self.webapp.router.add_get('/host/hardware', api_host.hardware) self.webapp.router.add_post('/host/reboot', api_host.reboot) self.webapp.router.add_post('/host/shutdown', api_host.shutdown) self.webapp.router.add_post('/host/update', api_host.update) diff --git a/hassio/api/host.py b/hassio/api/host.py index ab2021198..1ea2ad9ef 100644 --- a/hassio/api/host.py +++ b/hassio/api/host.py @@ -7,7 +7,7 @@ import voluptuous as vol from .util import api_process_hostcontrol, api_process, api_validate from ..const import ( ATTR_VERSION, ATTR_LAST_VERSION, ATTR_TYPE, ATTR_HOSTNAME, ATTR_FEATURES, - ATTR_OS) + ATTR_OS, ATTR_SERIAL, ATTR_INPUT, ATTR_DISK, ATTR_AUDIO) _LOGGER = logging.getLogger(__name__) @@ -19,11 +19,12 @@ SCHEMA_VERSION = vol.Schema({ class APIHost(object): """Handle rest api for host functions.""" - def __init__(self, config, loop, host_control): + def __init__(self, config, loop, host_control, hardware): """Initialize host rest api part.""" self.config = config self.loop = loop self.host_control = host_control + self.local_hw = hardware @api_process async def info(self, request): @@ -58,3 +59,13 @@ class APIHost(object): return await asyncio.shield( self.host_control.update(version=version), loop=self.loop) + + @api_process + async def hardware(self, request): + """Return local hardware infos.""" + return { + ATTR_SERIAL: self.local_hw.serial_devices, + ATTR_INPUT: self.local_hw.input_devices, + ATTR_DISK: self.local_hw.disk_devices, + ATTR_AUDIO: self.local_hw.audio_devices, + } diff --git a/hassio/const.py b/hassio/const.py index 2fc4baf4e..736c6e8ec 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -1,7 +1,7 @@ """Const file for HassIO.""" from pathlib import Path -HASSIO_VERSION = '0.48' +HASSIO_VERSION = '0.49' URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/' 'hassio/master/version.json') @@ -101,6 +101,10 @@ ATTR_TYPE = 'type' ATTR_TIMEOUT = 'timeout' ATTR_AUTO_UPDATE = 'auto_update' ATTR_CUSTOM = 'custom' +ATTR_AUDIO = 'audio' +ATTR_INPUT = 'input' +ATTR_DISK = 'disk' +ATTR_SERIAL = 'serial' STARTUP_INITIALIZE = 'initialize' STARTUP_SYSTEM = 'system' diff --git a/hassio/core.py b/hassio/core.py index 71f48afb6..1a4fedc80 100644 --- a/hassio/core.py +++ b/hassio/core.py @@ -14,6 +14,7 @@ from .const import ( RUN_CLEANUP_API_SESSIONS, STARTUP_SYSTEM, STARTUP_SERVICES, STARTUP_APPLICATION, STARTUP_INITIALIZE, RUN_RELOAD_SNAPSHOTS_TASKS, RUN_UPDATE_ADDONS_TASKS) +from .hardware import Hardware from .homeassistant import HomeAssistant from .scheduler import Scheduler from .dock.supervisor import DockerSupervisor @@ -36,6 +37,7 @@ class HassIO(object): self.websession = aiohttp.ClientSession(loop=loop) self.scheduler = Scheduler(loop) self.api = RestAPI(config, loop) + self.hardware = Hardware() self.dock = docker.DockerClient( base_url="unix:/{}".format(str(SOCKET_DOCKER)), version='auto') @@ -81,7 +83,7 @@ class HassIO(object): self.host_control.load, RUN_UPDATE_INFO_TASKS) # rest api views - self.api.register_host(self.host_control) + self.api.register_host(self.host_control, self.hardware) self.api.register_network(self.host_control) self.api.register_supervisor( self.supervisor, self.snapshots, self.addons, self.host_control, diff --git a/hassio/dock/addon.py b/hassio/dock/addon.py index 8e4737b0e..679ea961c 100644 --- a/hassio/dock/addon.py +++ b/hassio/dock/addon.py @@ -107,6 +107,7 @@ class DockerAddon(DockerBase): self.dock.containers.run( self.image, name=self.name, + hostname=self.name, detach=True, network_mode=self.addon.network_mode, ports=self.addon.ports, diff --git a/hassio/dock/homeassistant.py b/hassio/dock/homeassistant.py index d86202b66..5e485f2a3 100644 --- a/hassio/dock/homeassistant.py +++ b/hassio/dock/homeassistant.py @@ -50,6 +50,7 @@ class DockerHomeAssistant(DockerBase): self.dock.containers.run( self.image, name=self.name, + hostname=self.name, detach=True, privileged=True, devices=self.devices, diff --git a/hassio/hardware.py b/hassio/hardware.py new file mode 100644 index 000000000..fd0ac6c00 --- /dev/null +++ b/hassio/hardware.py @@ -0,0 +1,87 @@ +"""Read hardware info from system.""" +import logging +from pathlib import Path +import re + +import pyudev + +from .const import ATTR_NAME, ATTR_TYPE, ATTR_DEVICES + +_LOGGER = logging.getLogger(__name__) + +ASOUND_CARDS = Path("/proc/asound/cards") +RE_CARDS = re.compile(r"(\d+) \[(\w*) *\]: (.*\w)") + +ASOUND_DEVICES = Path("/proc/asound/devices") +RE_DEVICES = re.compile(r"\[.*(\d+)- (\d+).*\]: ([\w ]*)") + + +class Hardware(object): + """Represent a interface to procfs, sysfs and udev.""" + + def __init__(self): + """Init hardware object.""" + self.context = pyudev.Context() + + @property + def serial_devices(self): + """Return all serial and connected devices.""" + dev_list = set() + for device in self.context.list_devices(subsystem='tty'): + if 'ID_VENDOR' in device: + dev_list.add(device.device_node) + + return list(dev_list) + + @property + def input_devices(self): + """Return all input devices.""" + dev_list = set() + for device in self.context.list_devices(subsystem='input'): + if 'NAME' in device: + dev_list.add(device['NAME'].replace('"', '')) + + return list(dev_list) + + @property + def disk_devices(self): + """Return all disk devices.""" + dev_list = set() + for device in self.context.list_devices(subsystem='block'): + if 'ID_VENDOR' in device: + dev_list.add(device.device_node) + + return list(dev_list) + + @property + def audio_devices(self): + """Return all available audio interfaces.""" + try: + with ASOUND_CARDS.open('r') as cards_file: + cards = cards_file.read() + with ASOUND_DEVICES.open('r') as devices_file: + devices = devices_file.read() + except OSError as err: + _LOGGER.error("Can't read asound data -> %s", err) + return + + audio_list = {} + + # parse cards + for match in RE_CARDS.finditer(cards): + audio_list[match.group(1)] = { + ATTR_NAME: match.group(3), + ATTR_TYPE: match.group(2), + ATTR_DEVICES: {}, + } + + # parse devices + for match in RE_DEVICES.finditer(devices): + try: + audio_list[match.group(1)][ATTR_DEVICES][match.group(2)] = \ + match.group(3) + except KeyError: + _LOGGER.warning("Wrong audio device found %s", match.group(0)) + continue + + return audio_list diff --git a/hassio/panel/hassio-main.html b/hassio/panel/hassio-main.html index cc81fc76d..a2347372f 100644 --- a/hassio/panel/hassio-main.html +++ b/hassio/panel/hassio-main.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/hassio/panel/hassio-main.html.gz b/hassio/panel/hassio-main.html.gz index 6070d77e6..0f8028e74 100644 Binary files a/hassio/panel/hassio-main.html.gz and b/hassio/panel/hassio-main.html.gz differ diff --git a/setup.py b/setup.py index 75aa87bbe..179b6831d 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,7 @@ setup( 'gitpython', 'pyotp', 'pyqrcode', - 'pytz' + 'pytz', + 'pyudev' ] ) diff --git a/version.json b/version.json index 29ef9875c..95f2f3e99 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "hassio": "0.48", + "hassio": "0.49", "homeassistant": "0.49.1", "resinos": "1.0", "resinhup": "0.3",