Hardware interface for UI (#116)

* Init hardware object

* Update API

* Update hardware list

* fix api description

* fix lint

* add hardware to API

* fix lint

* fix wrong

* fix view
This commit is contained in:
Pascal Vizeli 2017-07-28 23:05:40 +02:00 committed by GitHub
parent fa687e982e
commit 1155ee07e5
8 changed files with 131 additions and 7 deletions

18
API.md
View File

@ -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`

View File

@ -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)

View File

@ -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,
}

View File

@ -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'

View File

@ -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,

87
hassio/hardware.py Normal file
View File

@ -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

@ -1 +1 @@
Subproject commit 1aa387a72f2a8b0c26a005afbba9a8a9fc095e06
Subproject commit 0c428134dfb6374b4a120cb729396242b4c2f2d7

View File

@ -46,6 +46,7 @@ setup(
'gitpython',
'pyotp',
'pyqrcode',
'pytz'
'pytz',
'pyudev'
]
)