diff --git a/API.md b/API.md index 46098df0f..47ad52f69 100644 --- a/API.md +++ b/API.md @@ -227,14 +227,12 @@ return: ```json { "hostname": "hostname|null", - "features": ["shutdown", "reboot", "update", "hostname", "services"], - "operating_system": "Hass.io-OS XY|Ubuntu 16.4|null", + "features": ["shutdown", "reboot", "hostname", "services", "hassos"], + "operating_system": "HassOS XY|Ubuntu 16.4|null", "kernel": "4.15.7|null", "chassis": "specific|null", - "type": "Hass.io-OS Type|null", "deployment": "stable|beta|dev|null", - "version": "xy|null", - "last_version": "xy|null", + "cpe": "xy|null", } ``` @@ -246,17 +244,6 @@ return: } ``` - -- POST `/host/update` - -Optional: - -```json -{ - "version": "VERSION" -} -``` - - POST `/host/reload` #### Services @@ -280,6 +267,28 @@ Optional: - 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 - GET `/hardware/info` diff --git a/hassio/api/__init__.py b/hassio/api/__init__.py index 7dbea66e9..d458a3014 100644 --- a/hassio/api/__init__.py +++ b/hassio/api/__init__.py @@ -9,6 +9,7 @@ from .discovery import APIDiscovery from .homeassistant import APIHomeAssistant from .hardware import APIHardware from .host import APIHost +from .hassos import APIHassOS from .proxy import APIProxy from .supervisor import APISupervisor from .snapshots import APISnapshots @@ -37,6 +38,7 @@ class RestAPI(CoreSysAttributes): """Register REST API Calls.""" self._register_supervisor() self._register_host() + self._register_hassos() self._register_hardware() self._register_homeassistant() self._register_proxy() @@ -55,7 +57,6 @@ class RestAPI(CoreSysAttributes): web.get('/host/info', api_host.info), web.post('/host/reboot', api_host.reboot), web.post('/host/shutdown', api_host.shutdown), - web.post('/host/update', api_host.update), web.post('/host/reload', api_host.reload), web.get('/host/services', api_host.services), 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), ]) + 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): """Register hardware function.""" api_hardware = APIHardware() diff --git a/hassio/api/hassos.py b/hassio/api/hassos.py new file mode 100644 index 000000000..426b174e8 --- /dev/null +++ b/hassio/api/hassos.py @@ -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()) diff --git a/hassio/api/host.py b/hassio/api/host.py index e5ce6de03..49352dfc9 100644 --- a/hassio/api/host.py +++ b/hassio/api/host.py @@ -6,19 +6,15 @@ import voluptuous as vol from .utils import api_process, api_validate from ..const import ( - ATTR_VERSION, ATTR_LAST_VERSION, ATTR_HOSTNAME, ATTR_FEATURES, ATTR_KERNEL, - ATTR_TYPE, ATTR_OPERATING_SYSTEM, ATTR_CHASSIS, ATTR_DEPLOYMENT, - ATTR_STATE, ATTR_NAME, ATTR_DESCRIPTON, ATTR_SERVICES) + ATTR_HOSTNAME, ATTR_FEATURES, ATTR_KERNEL, ATTR_OPERATING_SYSTEM, + ATTR_CHASSIS, ATTR_DEPLOYMENT, ATTR_STATE, ATTR_NAME, ATTR_DESCRIPTON, + ATTR_SERVICES, ATTR_CPE) from ..coresys import CoreSysAttributes _LOGGER = logging.getLogger(__name__) SERVICE = 'service' -SCHEMA_VERSION = vol.Schema({ - vol.Optional(ATTR_VERSION): vol.Coerce(str), -}) - SCHEMA_OPTIONS = vol.Schema({ vol.Optional(ATTR_HOSTNAME): vol.Coerce(str), }) @@ -32,9 +28,7 @@ class APIHost(CoreSysAttributes): """Return host information.""" return { ATTR_CHASSIS: self.sys_host.info.chassis, - ATTR_VERSION: None, - ATTR_LAST_VERSION: None, - ATTR_TYPE: None, + ATTR_CPE: self.sys_host.info.cpe, ATTR_FEATURES: self.sys_host.supperted_features, ATTR_HOSTNAME: self.sys_host.info.hostname, ATTR_OPERATING_SYSTEM: self.sys_host.info.operating_system, @@ -67,13 +61,6 @@ class APIHost(CoreSysAttributes): """Reload host data.""" 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 async def services(self, request): """Return list of available services.""" diff --git a/hassio/bootstrap.py b/hassio/bootstrap.py index d35ccb207..4bc718aea 100644 --- a/hassio/bootstrap.py +++ b/hassio/bootstrap.py @@ -21,6 +21,7 @@ from .services import ServiceManager from .services import Discovery from .host import HostManager from .dbus import DBusManager +from .hassos import HassOS _LOGGER = logging.getLogger(__name__) @@ -47,6 +48,7 @@ def initialize_coresys(loop): coresys.services = ServiceManager(coresys) coresys.discovery = Discovery(coresys) coresys.dbus = DBusManager(coresys) + coresys.hassos = HassOS(coresys) # bootstrap config initialize_system_data(coresys) diff --git a/hassio/const.py b/hassio/const.py index fc1751ffe..6f482e748 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -71,6 +71,7 @@ ATTR_SOURCE = 'source' ATTR_FEATURES = 'features' ATTR_ADDONS = 'addons' ATTR_VERSION = 'version' +ATTR_VERSION_LATEST = 'version_latest' ATTR_AUTO_UART = 'auto_uart' ATTR_LAST_BOOT = 'last_boot' ATTR_LAST_VERSION = 'last_version' @@ -166,6 +167,8 @@ ATTR_BRANCH = 'branch' ATTR_KERNEL = 'kernel' ATTR_APPARMOR = 'apparmor' ATTR_DEVICETREE = 'devicetree' +ATTR_CPE = 'cpe' +ATTR_BOARD = 'board' SERVICE_MQTT = 'mqtt' @@ -216,6 +219,6 @@ SECURITY_DISABLE = 'disable' FEATURES_SHUTDOWN = 'shutdown' FEATURES_REBOOT = 'reboot' -FEATURES_UPDATE = 'update' +FEATURES_HASSOS = 'hassos' FEATURES_HOSTNAME = 'hostname' FEATURES_SERVICES = 'services' diff --git a/hassio/core.py b/hassio/core.py index e6a498933..4181d8f27 100644 --- a/hassio/core.py +++ b/hassio/core.py @@ -32,6 +32,9 @@ class HassIO(CoreSysAttributes): # Load Host await self.sys_host.load() + # Load HassOS + await self.sys_hassos.load() + # Load Supervisor await self.sys_supervisor.load() diff --git a/hassio/coresys.py b/hassio/coresys.py index 7242b9dd7..af00041d9 100644 --- a/hassio/coresys.py +++ b/hassio/coresys.py @@ -43,6 +43,7 @@ class CoreSys: self._tasks = None self._host = None self._dbus = None + self._hassos = None self._services = None self._discovery = None @@ -249,6 +250,18 @@ class CoreSys: raise RuntimeError("HostManager already set!") 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): """Wrapper for executor pool.""" return self._loop.run_in_executor(None, funct, *args) diff --git a/hassio/hassos.py b/hassio/hassos.py new file mode 100644 index 000000000..6141966a7 --- /dev/null +++ b/hassio/hassos.py @@ -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') diff --git a/hassio/host/__init__.py b/hassio/host/__init__.py index de1bbd479..b64e89664 100644 --- a/hassio/host/__init__.py +++ b/hassio/host/__init__.py @@ -8,7 +8,8 @@ from .control import SystemControl from .info import InfoCenter from .services import ServiceManager 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 ..exceptions import HassioError @@ -67,6 +68,9 @@ class HostManager(CoreSysAttributes): if self.sys_dbus.hostname.is_connected: features.append(FEATURES_HOSTNAME) + if self.sys_hassos.available: + features.append(FEATURES_HASSOS) + return features async def reload(self): diff --git a/setup.py b/setup.py index c61f51300..1444f408b 100644 --- a/setup.py +++ b/setup.py @@ -49,6 +49,7 @@ setup( 'gitpython==2.1.10', 'pytz==2018.4', 'pyudev==0.21.0', - 'pycryptodome==3.4.11' + 'pycryptodome==3.4.11', + "cpe==1.2.1" ] )