diff --git a/API.md b/API.md index fe19213c0..ae3595074 100644 --- a/API.md +++ b/API.md @@ -226,6 +226,24 @@ Optional: } ``` +- 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` 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 3c27f7be9..736c6e8ec 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -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/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/home-assistant-polymer b/home-assistant-polymer index 1aa387a72..0c428134d 160000 --- a/home-assistant-polymer +++ b/home-assistant-polymer @@ -1 +1 @@ -Subproject commit 1aa387a72f2a8b0c26a005afbba9a8a9fc095e06 +Subproject commit 0c428134dfb6374b4a120cb729396242b4c2f2d7 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' ] )