HassOS support (#522)

* Add support for hassos

* Name command

* Update host.py

* Create hassos.py

* Update const.py

* Update host.py

* Update API.md

* Update const.py

* Update __init__.py

* Update hassos.py

* Update hassos.py

* Update hassos.py

* Update hassos.py

* Update const.py

* Update API.md

* Update hassos.py

* Update hassos.py

* Update API.md

* Update const.py

* Update hassos.py

* Update __init__.py

* fix lint

* Fix lint v2

* remove old function

* fix attribute error

* inittialize hassos

* Fix link

* fix error handling

* Fix handling
This commit is contained in:
Pascal Vizeli 2018-06-22 22:54:03 +02:00 committed by GitHub
parent 408d6eafcc
commit a0c9e5ad26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 168 additions and 37 deletions

41
API.md
View File

@ -227,14 +227,12 @@ return:
```json ```json
{ {
"hostname": "hostname|null", "hostname": "hostname|null",
"features": ["shutdown", "reboot", "update", "hostname", "services"], "features": ["shutdown", "reboot", "hostname", "services", "hassos"],
"operating_system": "Hass.io-OS XY|Ubuntu 16.4|null", "operating_system": "HassOS XY|Ubuntu 16.4|null",
"kernel": "4.15.7|null", "kernel": "4.15.7|null",
"chassis": "specific|null", "chassis": "specific|null",
"type": "Hass.io-OS Type|null",
"deployment": "stable|beta|dev|null", "deployment": "stable|beta|dev|null",
"version": "xy|null", "cpe": "xy|null",
"last_version": "xy|null",
} }
``` ```
@ -246,17 +244,6 @@ return:
} }
``` ```
- POST `/host/update`
Optional:
```json
{
"version": "VERSION"
}
```
- POST `/host/reload` - POST `/host/reload`
#### Services #### Services
@ -280,6 +267,28 @@ Optional:
- POST `/host/service/{unit}/reload` - POST `/host/service/{unit}/reload`
### HassOS
- GET `/hassos/info`
```json
{
"version": "2.3",
"version_latest": "2.4",
"board": "ova|rpi"
}
```
- POST `/hassos/update`
```json
{
"version": "optional"
}
```
- POST `/hassos/config/sync`
Load host configs from a USB stick.
### Hardware ### Hardware
- GET `/hardware/info` - GET `/hardware/info`

View File

@ -9,6 +9,7 @@ from .discovery import APIDiscovery
from .homeassistant import APIHomeAssistant from .homeassistant import APIHomeAssistant
from .hardware import APIHardware from .hardware import APIHardware
from .host import APIHost from .host import APIHost
from .hassos import APIHassOS
from .proxy import APIProxy from .proxy import APIProxy
from .supervisor import APISupervisor from .supervisor import APISupervisor
from .snapshots import APISnapshots from .snapshots import APISnapshots
@ -37,6 +38,7 @@ class RestAPI(CoreSysAttributes):
"""Register REST API Calls.""" """Register REST API Calls."""
self._register_supervisor() self._register_supervisor()
self._register_host() self._register_host()
self._register_hassos()
self._register_hardware() self._register_hardware()
self._register_homeassistant() self._register_homeassistant()
self._register_proxy() self._register_proxy()
@ -55,7 +57,6 @@ class RestAPI(CoreSysAttributes):
web.get('/host/info', api_host.info), web.get('/host/info', api_host.info),
web.post('/host/reboot', api_host.reboot), web.post('/host/reboot', api_host.reboot),
web.post('/host/shutdown', api_host.shutdown), web.post('/host/shutdown', api_host.shutdown),
web.post('/host/update', api_host.update),
web.post('/host/reload', api_host.reload), web.post('/host/reload', api_host.reload),
web.get('/host/services', api_host.services), web.get('/host/services', api_host.services),
web.post('/host/services/{service}/stop', api_host.service_stop), web.post('/host/services/{service}/stop', api_host.service_stop),
@ -66,6 +67,16 @@ class RestAPI(CoreSysAttributes):
'/host/services/{service}/reload', api_host.service_reload), '/host/services/{service}/reload', api_host.service_reload),
]) ])
def _register_hassos(self):
"""Register hassos function."""
api_hassos = APIHassOS()
api_hassos.coresys = self.coresys
self.webapp.add_routes([
web.get('/hassos/info', api_hassos.info),
web.post('/hassos/config/sync', api_hassos.config_sync),
])
def _register_hardware(self): def _register_hardware(self):
"""Register hardware function.""" """Register hardware function."""
api_hardware = APIHardware() api_hardware = APIHardware()

33
hassio/api/hassos.py Normal file
View File

@ -0,0 +1,33 @@
"""Init file for Hass.io hassos rest api."""
import asyncio
import logging
import voluptuous as vol
from .utils import api_process
from ..const import ATTR_VERSION, ATTR_BOARD, ATTR_VERSION_LATEST
from ..coresys import CoreSysAttributes
_LOGGER = logging.getLogger(__name__)
SCHEMA_VERSION = vol.Schema({
vol.Optional(ATTR_VERSION): vol.Coerce(str),
})
class APIHassOS(CoreSysAttributes):
"""Handle rest api for hassos functions."""
@api_process
async def info(self, request):
"""Return hassos information."""
return {
ATTR_VERSION: self.sys_hassos.version,
ATTR_VERSION_LATEST: self.sys_hassos.version,
ATTR_BOARD: self.sys_hassos.board,
}
@api_process
def config_sync(self, request):
"""Trigger config reload on HassOS."""
return asyncio.shield(self.sys_hassos.config_sync())

View File

@ -6,19 +6,15 @@ import voluptuous as vol
from .utils import api_process, api_validate from .utils import api_process, api_validate
from ..const import ( from ..const import (
ATTR_VERSION, ATTR_LAST_VERSION, ATTR_HOSTNAME, ATTR_FEATURES, ATTR_KERNEL, ATTR_HOSTNAME, ATTR_FEATURES, ATTR_KERNEL, ATTR_OPERATING_SYSTEM,
ATTR_TYPE, ATTR_OPERATING_SYSTEM, ATTR_CHASSIS, ATTR_DEPLOYMENT, ATTR_CHASSIS, ATTR_DEPLOYMENT, ATTR_STATE, ATTR_NAME, ATTR_DESCRIPTON,
ATTR_STATE, ATTR_NAME, ATTR_DESCRIPTON, ATTR_SERVICES) ATTR_SERVICES, ATTR_CPE)
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SERVICE = 'service' SERVICE = 'service'
SCHEMA_VERSION = vol.Schema({
vol.Optional(ATTR_VERSION): vol.Coerce(str),
})
SCHEMA_OPTIONS = vol.Schema({ SCHEMA_OPTIONS = vol.Schema({
vol.Optional(ATTR_HOSTNAME): vol.Coerce(str), vol.Optional(ATTR_HOSTNAME): vol.Coerce(str),
}) })
@ -32,9 +28,7 @@ class APIHost(CoreSysAttributes):
"""Return host information.""" """Return host information."""
return { return {
ATTR_CHASSIS: self.sys_host.info.chassis, ATTR_CHASSIS: self.sys_host.info.chassis,
ATTR_VERSION: None, ATTR_CPE: self.sys_host.info.cpe,
ATTR_LAST_VERSION: None,
ATTR_TYPE: None,
ATTR_FEATURES: self.sys_host.supperted_features, ATTR_FEATURES: self.sys_host.supperted_features,
ATTR_HOSTNAME: self.sys_host.info.hostname, ATTR_HOSTNAME: self.sys_host.info.hostname,
ATTR_OPERATING_SYSTEM: self.sys_host.info.operating_system, ATTR_OPERATING_SYSTEM: self.sys_host.info.operating_system,
@ -67,13 +61,6 @@ class APIHost(CoreSysAttributes):
"""Reload host data.""" """Reload host data."""
return asyncio.shield(self.sys_host.reload()) return asyncio.shield(self.sys_host.reload())
@api_process
async def update(self, request):
"""Update host OS."""
pass
# body = await api_validate(SCHEMA_VERSION, request)
# version = body.get(ATTR_VERSION, self.sys_host.last_version)
@api_process @api_process
async def services(self, request): async def services(self, request):
"""Return list of available services.""" """Return list of available services."""

View File

@ -21,6 +21,7 @@ from .services import ServiceManager
from .services import Discovery from .services import Discovery
from .host import HostManager from .host import HostManager
from .dbus import DBusManager from .dbus import DBusManager
from .hassos import HassOS
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -47,6 +48,7 @@ def initialize_coresys(loop):
coresys.services = ServiceManager(coresys) coresys.services = ServiceManager(coresys)
coresys.discovery = Discovery(coresys) coresys.discovery = Discovery(coresys)
coresys.dbus = DBusManager(coresys) coresys.dbus = DBusManager(coresys)
coresys.hassos = HassOS(coresys)
# bootstrap config # bootstrap config
initialize_system_data(coresys) initialize_system_data(coresys)

View File

@ -71,6 +71,7 @@ ATTR_SOURCE = 'source'
ATTR_FEATURES = 'features' ATTR_FEATURES = 'features'
ATTR_ADDONS = 'addons' ATTR_ADDONS = 'addons'
ATTR_VERSION = 'version' ATTR_VERSION = 'version'
ATTR_VERSION_LATEST = 'version_latest'
ATTR_AUTO_UART = 'auto_uart' ATTR_AUTO_UART = 'auto_uart'
ATTR_LAST_BOOT = 'last_boot' ATTR_LAST_BOOT = 'last_boot'
ATTR_LAST_VERSION = 'last_version' ATTR_LAST_VERSION = 'last_version'
@ -166,6 +167,8 @@ ATTR_BRANCH = 'branch'
ATTR_KERNEL = 'kernel' ATTR_KERNEL = 'kernel'
ATTR_APPARMOR = 'apparmor' ATTR_APPARMOR = 'apparmor'
ATTR_DEVICETREE = 'devicetree' ATTR_DEVICETREE = 'devicetree'
ATTR_CPE = 'cpe'
ATTR_BOARD = 'board'
SERVICE_MQTT = 'mqtt' SERVICE_MQTT = 'mqtt'
@ -216,6 +219,6 @@ SECURITY_DISABLE = 'disable'
FEATURES_SHUTDOWN = 'shutdown' FEATURES_SHUTDOWN = 'shutdown'
FEATURES_REBOOT = 'reboot' FEATURES_REBOOT = 'reboot'
FEATURES_UPDATE = 'update' FEATURES_HASSOS = 'hassos'
FEATURES_HOSTNAME = 'hostname' FEATURES_HOSTNAME = 'hostname'
FEATURES_SERVICES = 'services' FEATURES_SERVICES = 'services'

View File

@ -32,6 +32,9 @@ class HassIO(CoreSysAttributes):
# Load Host # Load Host
await self.sys_host.load() await self.sys_host.load()
# Load HassOS
await self.sys_hassos.load()
# Load Supervisor # Load Supervisor
await self.sys_supervisor.load() await self.sys_supervisor.load()

View File

@ -43,6 +43,7 @@ class CoreSys:
self._tasks = None self._tasks = None
self._host = None self._host = None
self._dbus = None self._dbus = None
self._hassos = None
self._services = None self._services = None
self._discovery = None self._discovery = None
@ -249,6 +250,18 @@ class CoreSys:
raise RuntimeError("HostManager already set!") raise RuntimeError("HostManager already set!")
self._host = value self._host = value
@property
def hassos(self):
"""Return HassOS object."""
return self._hassos
@hassos.setter
def hassos(self, value):
"""Set a HassOS object."""
if self._hassos:
raise RuntimeError("HassOS already set!")
self._hassos = value
def run_in_executor(self, funct, *args): def run_in_executor(self, funct, *args):
"""Wrapper for executor pool.""" """Wrapper for executor pool."""
return self._loop.run_in_executor(None, funct, *args) return self._loop.run_in_executor(None, funct, *args)

65
hassio/hassos.py Normal file
View File

@ -0,0 +1,65 @@
"""HassOS support on supervisor."""
import logging
from cpe import CPE
from .coresys import CoreSysAttributes
from .exceptions import HassioNotSupportedError
_LOGGER = logging.getLogger(__name__)
class HassOS(CoreSysAttributes):
"""HassOS interface inside HassIO."""
def __init__(self, coresys):
"""Initialize HassOS handler."""
self.coresys = coresys
self._available = False
self._version = None
self._board = None
@property
def available(self):
"""Return True, if HassOS on host."""
return self._available
@property
def version(self):
"""Return version of HassOS."""
return self._version
@property
def board(self):
"""Return board name."""
return self._board
def _check_host(self):
"""Check if HassOS is availabe."""
if not self.available:
_LOGGER.error("No HassOS availabe")
raise HassioNotSupportedError()
async def load(self):
"""Load HassOS data."""
try:
assert self.sys_host.info.cpe is not None
cpe = CPE(self.sys_host.info.cpe)
assert cpe.get_product()[0] == 'hassos'
except (NotImplementedError, IndexError, AssertionError):
_LOGGER.info("Can't detect HassOS")
return
# Store meta data
self._available = True
self._version = cpe.get_version()[0]
self._board = cpe.get_target_hardware()[0]
_LOGGER.info("Detect HassOS %s on host system", self.version)
def config_sync(self):
"""Trigger a host config reload from usb."""
self._check_host()
_LOGGER.info("Sync config from USB on HassOS.")
return self.sys_host.services.restart('hassos-config.service')

View File

@ -8,7 +8,8 @@ from .control import SystemControl
from .info import InfoCenter from .info import InfoCenter
from .services import ServiceManager from .services import ServiceManager
from ..const import ( from ..const import (
FEATURES_REBOOT, FEATURES_SHUTDOWN, FEATURES_HOSTNAME, FEATURES_SERVICES) FEATURES_REBOOT, FEATURES_SHUTDOWN, FEATURES_HOSTNAME, FEATURES_SERVICES,
FEATURES_HASSOS)
from ..coresys import CoreSysAttributes from ..coresys import CoreSysAttributes
from ..exceptions import HassioError from ..exceptions import HassioError
@ -67,6 +68,9 @@ class HostManager(CoreSysAttributes):
if self.sys_dbus.hostname.is_connected: if self.sys_dbus.hostname.is_connected:
features.append(FEATURES_HOSTNAME) features.append(FEATURES_HOSTNAME)
if self.sys_hassos.available:
features.append(FEATURES_HASSOS)
return features return features
async def reload(self): async def reload(self):

View File

@ -49,6 +49,7 @@ setup(
'gitpython==2.1.10', 'gitpython==2.1.10',
'pytz==2018.4', 'pytz==2018.4',
'pyudev==0.21.0', 'pyudev==0.21.0',
'pycryptodome==3.4.11' 'pycryptodome==3.4.11',
"cpe==1.2.1"
] ]
) )