From 6de403e0ac8c055dec19589cea6fe9adc6d52a22 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 5 Oct 2017 18:12:02 +0200 Subject: [PATCH] Support for The Things Network (#9627) * Support for The Things network's Data Storage * Rename platform and other changes (async and dict) * Rename sensor platform and remove check for 200 --- .coveragerc | 3 + .../components/sensor/thethingsnetwork.py | 163 ++++++++++++++++++ homeassistant/components/thethingsnetwork.py | 47 +++++ 3 files changed, 213 insertions(+) create mode 100644 homeassistant/components/sensor/thethingsnetwork.py create mode 100644 homeassistant/components/thethingsnetwork.py diff --git a/.coveragerc b/.coveragerc index c1714f60fe3..8b31cca97b5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -182,6 +182,9 @@ omit = homeassistant/components/tesla.py homeassistant/components/*/tesla.py + homeassistant/components/thethingsnetwork.py + homeassistant/components/*/thethingsnetwork.py + homeassistant/components/*/thinkingcleaner.py homeassistant/components/tradfri.py diff --git a/homeassistant/components/sensor/thethingsnetwork.py b/homeassistant/components/sensor/thethingsnetwork.py new file mode 100644 index 00000000000..90b21cc19e5 --- /dev/null +++ b/homeassistant/components/sensor/thethingsnetwork.py @@ -0,0 +1,163 @@ +""" +Support for The Things Network's Data storage integration. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/sensor.thethingsnetwork_data/ +""" +import asyncio +import logging + +import aiohttp +import async_timeout +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.thethingsnetwork import ( + DATA_TTN, TTN_APP_ID, TTN_ACCESS_KEY, TTN_DATA_STORAGE_URL) +from homeassistant.const import CONTENT_TYPE_JSON +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.entity import Entity + +_LOGGER = logging.getLogger(__name__) + +ATTR_DEVICE_ID = 'device_id' +ATTR_RAW = 'raw' +ATTR_TIME = 'time' + +DEFAULT_TIMEOUT = 10 +DEPENDENCIES = ['thethingsnetwork'] + +CONF_DEVICE_ID = 'device_id' +CONF_VALUES = 'values' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_DEVICE_ID): cv.string, + vol.Required(CONF_VALUES): {cv.string: cv.string}, +}) + + +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up The Things Network Data storage sensors.""" + ttn = hass.data.get(DATA_TTN) + device_id = config.get(CONF_DEVICE_ID) + values = config.get(CONF_VALUES) + app_id = ttn.get(TTN_APP_ID) + access_key = ttn.get(TTN_ACCESS_KEY) + + ttn_data_storage = TtnDataStorage( + hass, app_id, device_id, access_key, values) + success = yield from ttn_data_storage.async_update() + + if not success: + return False + + devices = [] + for value, unit_of_measurement in values.items(): + devices.append(TtnDataSensor( + ttn_data_storage, device_id, value, unit_of_measurement)) + async_add_devices(devices, True) + + +class TtnDataSensor(Entity): + """Representation of a The Things Network Data Storage sensor.""" + + def __init__(self, ttn_data_storage, device_id, value, + unit_of_measurement): + """Initialize a The Things Network Data Storage sensor.""" + self._ttn_data_storage = ttn_data_storage + self._state = None + self._device_id = device_id + self._unit_of_measurement = unit_of_measurement + self._value = value + self._name = '{} {}'.format(self._device_id, self._value) + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the entity.""" + if self._ttn_data_storage.data is not None: + try: + return round(self._state[self._value], 1) + except KeyError: + pass + + @property + def unit_of_measurement(self): + """Return the unit this state is expressed in.""" + return self._unit_of_measurement + + @property + def device_state_attributes(self): + """Return the state attributes of the sensor.""" + if self._ttn_data_storage.data is not None: + return { + ATTR_DEVICE_ID: self._device_id, + ATTR_RAW: self._state['raw'], + ATTR_TIME: self._state['time'], + } + + @asyncio.coroutine + def async_update(self): + """Get the current state.""" + yield from self._ttn_data_storage.async_update() + self._state = self._ttn_data_storage.data + + +class TtnDataStorage(object): + """Get the latest data from The Things Network Data Storage.""" + + def __init__(self, hass, app_id, device_id, access_key, values): + """Initialize the data object.""" + self.data = None + self._hass = hass + self._app_id = app_id + self._device_id = device_id + self._values = values + self._url = TTN_DATA_STORAGE_URL.format( + app_id=app_id, endpoint='api/v2/query', device_id=device_id) + self._headers = { + 'Accept': CONTENT_TYPE_JSON, + 'Authorization': 'key {}'.format(access_key), + } + + @asyncio.coroutine + def async_update(self): + """Get the current state from The Things Network Data Storage.""" + try: + session = async_get_clientsession(self._hass) + with async_timeout.timeout(DEFAULT_TIMEOUT, loop=self._hass.loop): + req = yield from session.get(self._url, headers=self._headers) + + except (asyncio.TimeoutError, aiohttp.ClientError): + _LOGGER.error("Error while accessing: %s", self._url) + return False + + status = req.status + + if status == 204: + _LOGGER.error("The device is not available: %s", self._device_id) + return False + + if status == 401: + _LOGGER.error( + "Not authorized for Application ID: %s", self._app_id) + return False + + if status == 404: + _LOGGER.error("Application ID is not available: %s", self._app_id) + return False + + data = yield from req.json() + self.data = data[0] + + for value in self._values.items(): + if value[0] not in self.data.keys(): + _LOGGER.warning("Value not available: %s", value[0]) + + return req diff --git a/homeassistant/components/thethingsnetwork.py b/homeassistant/components/thethingsnetwork.py new file mode 100644 index 00000000000..08715c74d1f --- /dev/null +++ b/homeassistant/components/thethingsnetwork.py @@ -0,0 +1,47 @@ +""" +Support for The Things network. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/thethingsnetwork/ +""" +import asyncio +import logging + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +CONF_ACCESS_KEY = 'access_key' +CONF_APP_ID = 'app_id' + +DATA_TTN = 'data_thethingsnetwork' +DOMAIN = 'thethingsnetwork' + +TTN_ACCESS_KEY = 'ttn_access_key' +TTN_APP_ID = 'ttn_app_id' +TTN_DATA_STORAGE_URL = \ + 'https://{app_id}.data.thethingsnetwork.org/{endpoint}/{device_id}' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_APP_ID): cv.string, + vol.Required(CONF_ACCESS_KEY): cv.string, + }), +}, extra=vol.ALLOW_EXTRA) + + +@asyncio.coroutine +def async_setup(hass, config): + """Initialize of The Things Network component.""" + conf = config[DOMAIN] + app_id = conf.get(CONF_APP_ID) + access_key = conf.get(CONF_ACCESS_KEY) + + hass.data[DATA_TTN] = { + TTN_ACCESS_KEY: access_key, + TTN_APP_ID: app_id, + } + + return True