From 5013a826550d1acc451212fad09f01b5cabb2040 Mon Sep 17 00:00:00 2001 From: dasos Date: Wed, 23 Nov 2016 14:52:14 +0000 Subject: [PATCH] Hook Smart Home support (#4392) * Support for Hook (hooksmarthome.com) * Linting * Add asyncio * Move to aiohttp * Yield more --- .coveragerc | 1 + homeassistant/components/switch/hook.py | 137 ++++++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 homeassistant/components/switch/hook.py diff --git a/.coveragerc b/.coveragerc index 0840e5bcb4d..7b7b063edb0 100644 --- a/.coveragerc +++ b/.coveragerc @@ -317,6 +317,7 @@ omit = homeassistant/components/switch/dlink.py homeassistant/components/switch/edimax.py homeassistant/components/switch/hikvisioncam.py + homeassistant/components/switch/hook.py homeassistant/components/switch/mystrom.py homeassistant/components/switch/netio.py homeassistant/components/switch/orvibo.py diff --git a/homeassistant/components/switch/hook.py b/homeassistant/components/switch/hook.py new file mode 100644 index 00000000000..9bbb168a099 --- /dev/null +++ b/homeassistant/components/switch/hook.py @@ -0,0 +1,137 @@ +""" +Support Hook, available at hooksmarthome.com. + +Controls RF switches like these: + https://www.amazon.com/Etekcity-Wireless-Electrical-Household-Appliances/dp/B00DQELHBS + +There is no way to query for state or success of commands. + +""" +import logging +import asyncio +import voluptuous as vol +import async_timeout +import aiohttp + +from homeassistant.components.switch import SwitchDevice +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +HOOK_ENDPOINT = "https://api.gethook.io/v1/" +TIMEOUT = 10 + +SWITCH_SCHEMA = vol.Schema({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string +}) + + +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Setup Hook by getting the access token and list of actions.""" + username = config.get(CONF_USERNAME) + password = config.get(CONF_PASSWORD) + + try: + with async_timeout.timeout(TIMEOUT, loop=hass.loop): + response = yield from hass.websession.post( + HOOK_ENDPOINT + 'user/login', + data={ + 'username': username, + 'password': password}) + data = yield from response.json() + except (asyncio.TimeoutError, + aiohttp.errors.ClientError, + aiohttp.errors.ClientDisconnectedError) as error: + _LOGGER.error("Failed authentication API call: %s", error) + return False + + try: + token = data['data']['token'] + except KeyError: + _LOGGER.error("No token. Check username and password") + return False + + try: + with async_timeout.timeout(TIMEOUT, loop=hass.loop): + response = yield from hass.websession.get( + HOOK_ENDPOINT + 'device', + params={"token": data['data']['token']}) + data = yield from response.json() + except (asyncio.TimeoutError, + aiohttp.errors.ClientError, + aiohttp.errors.ClientDisconnectedError) as error: + _LOGGER.error("Failed getting devices: %s", error) + return False + + yield from async_add_devices( + HookSmartHome( + hass, + token, + d['device_id'], + d['device_name']) + for lst in data['data'] + for d in lst) + + +class HookSmartHome(SwitchDevice): + """Representation of a Hook device, allowing on and off commands.""" + + # pylint: disable=too-many-arguments + def __init__(self, hass, token, device_id, device_name): + """Initialize the switch.""" + self._hass = hass + self._token = token + self._state = False + self._id = device_id + self._name = device_name + _LOGGER.debug( + "Creating Hook object: ID: " + self._id + + " Name: " + self._name) + + @property + def name(self): + """Return the name of the switch.""" + return self._name + + @property + def is_on(self): + """Return true if device is on.""" + return self._state + + @asyncio.coroutine + def _send(self, url): + """Send the url to the Hook API.""" + try: + _LOGGER.debug("Sending: %s", url) + with async_timeout.timeout(TIMEOUT, loop=self._hass.loop): + response = yield from self._hass.websession.get( + url, + params={"token": self._token}) + data = yield from response.json() + except (asyncio.TimeoutError, + aiohttp.errors.ClientError, + aiohttp.errors.ClientDisconnectedError) as error: + _LOGGER.error("Failed setting state: %s", error) + return False + _LOGGER.debug("Got: %s", data) + return data['return_value'] == '1' + + @asyncio.coroutine + def async_turn_on(self): + """Turn the device on asynchronously.""" + _LOGGER.debug("Turning on: %s", self._name) + success = yield from self._send( + HOOK_ENDPOINT + 'device/trigger/' + self._id + '/On') + self._state = success + + @asyncio.coroutine + def async_turn_off(self): + """Turn the device off asynchronously.""" + _LOGGER.debug("Turning off: %s", self._name) + success = yield from self._send( + HOOK_ENDPOINT + 'device/trigger/' + self._id + '/Off') + # If it wasn't successful, keep state as true + self._state = not success