diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index e0356017e..3444c5d4a 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -20,7 +20,8 @@ import homeassistant.helpers.config_validation as cv from homeassistant.loader import bind_hass from homeassistant.util.dt import utcnow -from .handler import HassIO +from .handler import HassIO, HassioAPIError +from .discovery import async_setup_discovery from .http import HassIOView _LOGGER = logging.getLogger(__name__) @@ -138,10 +139,12 @@ def is_hassio(hass): def async_check_config(hass): """Check configuration over Hass.io API.""" hassio = hass.data[DOMAIN] - result = yield from hassio.check_homeassistant_config() - if not result: - return "Hass.io config check API error" + try: + result = yield from hassio.check_homeassistant_config() + except HassioAPIError as err: + _LOGGER.error("Error on Hass.io API: %s", err) + if result['result'] == "error": return result['message'] return None @@ -150,16 +153,11 @@ def async_check_config(hass): @asyncio.coroutine def async_setup(hass, config): """Set up the Hass.io component.""" - try: - host = os.environ['HASSIO'] - except KeyError: - _LOGGER.error("Missing HASSIO environment variable.") - return False - - try: - os.environ['HASSIO_TOKEN'] - except KeyError: - _LOGGER.error("Missing HASSIO_TOKEN environment variable.") + # Check local setup + for env in ('HASSIO', 'HASSIO_TOKEN'): + if os.environ.get(env): + continue + _LOGGER.error("Missing %s environment variable.", env) return False websession = hass.helpers.aiohttp_client.async_get_clientsession() @@ -233,13 +231,13 @@ def async_setup(hass, config): payload = data # Call API - ret = yield from hassio.send_command( - api_command.format(addon=addon, snapshot=snapshot), - payload=payload, timeout=MAP_SERVICE_API[service.service][2] - ) - - if not ret or ret['result'] != "ok": - _LOGGER.error("Error on Hass.io API: %s", ret['message']) + try: + ret = yield from hassio.send_command( + api_command.format(addon=addon, snapshot=snapshot), + payload=payload, timeout=MAP_SERVICE_API[service.service][2] + ) + except HassioAPIError as err: + _LOGGER.error("Error on Hass.io API: %s", err) for service, settings in MAP_SERVICE_API.items(): hass.services.async_register( @@ -248,9 +246,11 @@ def async_setup(hass, config): @asyncio.coroutine def update_homeassistant_version(now): """Update last available Home Assistant version.""" - data = yield from hassio.get_homeassistant_info() - if data: + try: + data = yield from hassio.get_homeassistant_info() hass.data[DATA_HOMEASSISTANT_VERSION] = data['last_version'] + except HassioAPIError as err: + _LOGGER.warning("Can't read last version: %s", err) hass.helpers.event.async_track_point_in_utc_time( update_homeassistant_version, utcnow() + HASSIO_UPDATE_INTERVAL) @@ -282,4 +282,7 @@ def async_setup(hass, config): hass.services.async_register( HASS_DOMAIN, service, async_handle_core_service) + # Init discovery Hass.io feature + async_setup_discovery(hass, hassio) + return True diff --git a/homeassistant/components/hassio/discovery.py b/homeassistant/components/hassio/discovery.py new file mode 100644 index 000000000..6cabbee9d --- /dev/null +++ b/homeassistant/components/hassio/discovery.py @@ -0,0 +1,59 @@ +"""Implement the serivces discovery feature from Hass.io for Add-ons.""" +import asyncio +import logging +import os + +import voluptuous as vol + +from homeassistant.core import callback +from homeassistant.const import EVENT_HOMEASSISTANT_START + +from .handler import HassioAPIError + +_LOGGER = logging.getLogger(__name__) + +EVENT_DISCOVERY_ADD = 'hassio_discovery_add' +EVENT_DISCOVERY_DEL = 'hassio_discovery_del' + +ATTR_UUID = 'uuid' +ATTR_DISCOVERY = 'discovery' + + +@callback +def async_setup_discovery(hass, hassio): + """Discovery setup.""" + async def async_discovery_event_handler(event): + """Handle events from Hass.io discovery.""" + uuid = event.data[ATTR_UUID] + + try: + data = await hassio.get_services_discovery(uuid) + except HassioAPIError as err: + _LOGGER.error( + "Can't read discover %s info: %s", uuid, err) + return + + hass.async_add_job(async_process_discovery, hass, data) + + hass.bus.async_listen( + EVENT_DISCOVERY_ADD, async_discovery_event_handler) + + async def async_discovery_start_handler(event): + """Process all exists discovery on startup.""" + try: + data = await hassio.retrieve_services_discovery() + except HassioAPIError as err: + _LOGGER.error( + "Can't read discover %s info: %s", uuid, err) + return + + for discovery in data[ATTR_DISCOVERY]: + hass.async_add_job(async_process_discovery, hass, discovery) + + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_START, async_discovery_start_handler) + + +@callback +def async_process_discovery(hass, data): + """Process a discovery request.""" diff --git a/homeassistant/components/hassio/handler.py b/homeassistant/components/hassio/handler.py index d75529a99..eb4c4bec0 100644 --- a/homeassistant/components/hassio/handler.py +++ b/homeassistant/components/hassio/handler.py @@ -21,12 +21,20 @@ _LOGGER = logging.getLogger(__name__) X_HASSIO = 'X-HASSIO-KEY' +class HassioAPIError(RuntimeError): + """Return if a API trow a error.""" + pass + + def _api_bool(funct): """Return a boolean.""" async def _wrapper(*argv, **kwargs): """Wrap function.""" - data = await funct(*argv, **kwargs) - return data and data['result'] == "ok" + try: + data = await funct(*argv, **kwargs) + return data['result'] == "ok" + except HassioAPIError: + return False return _wrapper @@ -36,9 +44,9 @@ def _api_data(funct): async def _wrapper(*argv, **kwargs): """Wrap function.""" data = await funct(*argv, **kwargs) - if data and data['result'] == "ok": + if data['result'] == "ok": return data['data'] - return None + raise HassioAPIError(data['message']) return _wrapper @@ -91,6 +99,23 @@ class HassIO: """ return self.send_command("/homeassistant/check", timeout=300) + @_api_data + def retrieve_services_discovery(self): + """Return all discovery data from Hass.io API. + + This method return a coroutine. + """ + return self.send_command("/services/discovery", method="get") + + @_api_data + def get_services_discovery_entry(self, uuid): + """Return a single discovery data entry. + + This method return a coroutine. + """ + return self.send_command( + "/services/discovery/{}".format(uuid), method="get") + @_api_bool async def update_hass_api(self, http_config, refresh_token): """Update Home Assistant API data on Hass.io.""" @@ -137,7 +162,7 @@ class HassIO: if request.status not in (200, 400): _LOGGER.error( "%s return code %d.", command, request.status) - return None + raise HassioAPIError() answer = yield from request.json() return answer @@ -148,4 +173,4 @@ class HassIO: except aiohttp.ClientError as err: _LOGGER.error("Client error on %s request %s", command, err) - return None + raise HassioAPIError()