From 3cba09c6f62a63d6245b82f2bc498640e618f6d9 Mon Sep 17 00:00:00 2001 From: Nolan Gilley Date: Wed, 10 Jan 2018 01:47:22 -0500 Subject: [PATCH] Coinbase.com sensor platform (#11036) * coinbase sensors use hass.data, load_platform * add exchange rate sensors dont pass complex object over event bus * check for auth error --- .coveragerc | 3 + homeassistant/components/coinbase.py | 90 +++++++++++++ homeassistant/components/sensor/coinbase.py | 141 ++++++++++++++++++++ requirements_all.txt | 3 + 4 files changed, 237 insertions(+) create mode 100755 homeassistant/components/coinbase.py create mode 100755 homeassistant/components/sensor/coinbase.py diff --git a/.coveragerc b/.coveragerc index 4ab2bb25637..d0026245d03 100644 --- a/.coveragerc +++ b/.coveragerc @@ -50,6 +50,9 @@ omit = homeassistant/components/bloomsky.py homeassistant/components/*/bloomsky.py + homeassistant/components/coinbase.py + homeassistant/components/sensor/coinbase.py + homeassistant/components/comfoconnect.py homeassistant/components/*/comfoconnect.py diff --git a/homeassistant/components/coinbase.py b/homeassistant/components/coinbase.py new file mode 100755 index 00000000000..bdb091325cf --- /dev/null +++ b/homeassistant/components/coinbase.py @@ -0,0 +1,90 @@ +""" +Support for Coinbase. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/coinbase/ +""" +from datetime import timedelta + +import logging +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.const import CONF_API_KEY +from homeassistant.util import Throttle +from homeassistant.helpers.discovery import load_platform + +REQUIREMENTS = ['coinbase==2.0.6'] +_LOGGER = logging.getLogger(__name__) + +DOMAIN = 'coinbase' + +CONF_API_SECRET = 'api_secret' +CONF_EXCHANGE_CURRENCIES = 'exchange_rate_currencies' + +MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1) + +DATA_COINBASE = 'coinbase_cache' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_API_SECRET): cv.string, + vol.Optional(CONF_EXCHANGE_CURRENCIES, default=[]): + vol.All(cv.ensure_list, [cv.string]) + }) +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, config): + """Set up the Coinbase component. + + Will automatically setup sensors to support + wallets discovered on the network. + """ + api_key = config[DOMAIN].get(CONF_API_KEY) + api_secret = config[DOMAIN].get(CONF_API_SECRET) + exchange_currencies = config[DOMAIN].get(CONF_EXCHANGE_CURRENCIES) + + hass.data[DATA_COINBASE] = coinbase_data = CoinbaseData(api_key, + api_secret) + + if not hasattr(coinbase_data, 'accounts'): + return False + for account in coinbase_data.accounts.data: + load_platform(hass, 'sensor', DOMAIN, + {'account': account}, config) + for currency in exchange_currencies: + if currency not in coinbase_data.exchange_rates.rates: + _LOGGER.warning("Currency %s not found", currency) + continue + native = coinbase_data.exchange_rates.currency + load_platform(hass, + 'sensor', + DOMAIN, + {'native_currency': native, + 'exchange_currency': currency}, + config) + + return True + + +class CoinbaseData(object): + """Get the latest data and update the states.""" + + def __init__(self, api_key, api_secret): + """Init the coinbase data object.""" + from coinbase.wallet.client import Client + self.client = Client(api_key, api_secret) + self.update() + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Get the latest data from coinbase.""" + from coinbase.wallet.error import AuthenticationError + try: + self.accounts = self.client.get_accounts() + self.exchange_rates = self.client.get_exchange_rates() + except AuthenticationError as coinbase_error: + _LOGGER.error("Authentication error connecting" + " to coinbase: %s", coinbase_error) diff --git a/homeassistant/components/sensor/coinbase.py b/homeassistant/components/sensor/coinbase.py new file mode 100755 index 00000000000..d66c7d4e4b6 --- /dev/null +++ b/homeassistant/components/sensor/coinbase.py @@ -0,0 +1,141 @@ +""" +Support for Coinbase sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.coinbase/ +""" +from homeassistant.helpers.entity import Entity +from homeassistant.const import ATTR_ATTRIBUTION + + +DEPENDENCIES = ['coinbase'] + +DATA_COINBASE = 'coinbase_cache' + +CONF_ATTRIBUTION = "Data provided by coinbase.com" +ATTR_NATIVE_BALANCE = "Balance in native currency" + +BTC_ICON = 'mdi:currency-btc' +ETH_ICON = 'mdi:currency-eth' +COIN_ICON = 'mdi:coin' + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the Coinbase sensors.""" + if discovery_info is None: + return + if 'account' in discovery_info: + account = discovery_info['account'] + sensor = AccountSensor(hass.data[DATA_COINBASE], + account['name'], + account['balance']['currency']) + if 'exchange_currency' in discovery_info: + sensor = ExchangeRateSensor(hass.data[DATA_COINBASE], + discovery_info['exchange_currency'], + discovery_info['native_currency']) + + add_devices([sensor], True) + + +class AccountSensor(Entity): + """Representation of a Coinbase.com sensor.""" + + def __init__(self, coinbase_data, name, currency): + """Initialize the sensor.""" + self._coinbase_data = coinbase_data + self._name = "Coinbase {}".format(name) + self._state = None + self._unit_of_measurement = currency + self._native_balance = None + self._native_currency = None + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement this sensor expresses itself in.""" + return self._unit_of_measurement + + @property + def icon(self): + """Return the icon to use in the frontend, if any.""" + if self._name == "Coinbase BTC Wallet": + return BTC_ICON + if self._name == "Coinbase ETH Wallet": + return ETH_ICON + return COIN_ICON + + @property + def device_state_attributes(self): + """Return the state attributes of the sensor.""" + return { + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, + ATTR_NATIVE_BALANCE: "{} {}".format(self._native_balance, + self._native_currency) + } + + def update(self): + """Get the latest state of the sensor.""" + self._coinbase_data.update() + for account in self._coinbase_data.accounts['data']: + if self._name == "Coinbase {}".format(account['name']): + self._state = account['balance']['amount'] + self._native_balance = account['native_balance']['amount'] + self._native_currency = account['native_balance']['currency'] + + +class ExchangeRateSensor(Entity): + """Representation of a Coinbase.com sensor.""" + + def __init__(self, coinbase_data, exchange_currency, native_currency): + """Initialize the sensor.""" + self._coinbase_data = coinbase_data + self.currency = exchange_currency + self._name = "{} Exchange Rate".format(exchange_currency) + self._state = None + self._unit_of_measurement = native_currency + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the unit of measurement this sensor expresses itself in.""" + return self._unit_of_measurement + + @property + def icon(self): + """Return the icon to use in the frontend, if any.""" + if self._name == "BTC Exchange Rate": + return BTC_ICON + if self._name == "ETH Exchange Rate": + return ETH_ICON + return COIN_ICON + + @property + def device_state_attributes(self): + """Return the state attributes of the sensor.""" + return { + ATTR_ATTRIBUTION: CONF_ATTRIBUTION + } + + def update(self): + """Get the latest state of the sensor.""" + self._coinbase_data.update() + rate = self._coinbase_data.exchange_rates.rates[self.currency] + self._state = round(1 / float(rate), 2) diff --git a/requirements_all.txt b/requirements_all.txt index e868cf68d58..bc28ddd3b69 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -166,6 +166,9 @@ caldav==0.5.0 # homeassistant.components.notify.ciscospark ciscosparkapi==0.4.2 +# homeassistant.components.coinbase +coinbase==2.0.6 + # homeassistant.components.sensor.coinmarketcap coinmarketcap==4.1.1