diff --git a/.coveragerc b/.coveragerc index d97befce096..4029b48d47c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -36,6 +36,7 @@ omit = homeassistant/components/notify/pushbullet.py homeassistant/components/notify/pushover.py homeassistant/components/notify/xmpp.py + homeassistant/components/sensor/bitcoin.py homeassistant/components/sensor/mysensors.py homeassistant/components/sensor/openweathermap.py homeassistant/components/sensor/sabnzbd.py diff --git a/homeassistant/components/sensor/bitcoin.py b/homeassistant/components/sensor/bitcoin.py new file mode 100644 index 00000000000..8d174981900 --- /dev/null +++ b/homeassistant/components/sensor/bitcoin.py @@ -0,0 +1,250 @@ +""" +homeassistant.components.sensor.bitcoin +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Bitcoin information service that uses blockchain.info and its online wallet. + +Configuration: + +You need to enable the API access for your online wallet to get the balance. +To do that log in and move to 'Account Setting', choose 'IP Restrictions', and +check 'Enable Api Access'. You will get an email message from blockchain.info +where you must authorize the API access. + +To use the Bitcoin sensor you will need to add something like the following +to your config/configuration.yaml + +sensor: + platform: bitcoin + wallet: 'YOUR WALLET_ID' + password: YOUR_ACCOUNT_PASSWORD + currency: YOUR CURRENCY + display_options: + - exchangerate + - trade_volume_btc + - miners_revenue_usd + - btc_mined + - trade_volume_usd + - difficulty + - minutes_between_blocks + - number_of_transactions + - hash_rate + - timestamp + - mined_blocks + - blocks_size + - total_fees_btc + - total_btc_sent + - estimated_btc_sent + - total_btc + - total_blocks + - next_retarget + - estimated_transaction_volume_usd + - miners_revenue_btc + - market_price_usd + +Variables: + +wallet +*Optional +This is your wallet identifier from https://blockchain.info to access the +online wallet. + +password +*Optional +Password your your online wallet. + +currency +*Optional +The currency to exchange to, eg. CHF, USD, EUR,etc. Default is USD. + +display_options +*Optional +An array specifying the variables to display. + +These are the variables for the display_options array. See the configuration +example above for a list of all available variables. +""" +import logging +from datetime import timedelta + +from homeassistant.util import Throttle +from homeassistant.helpers.entity import Entity + + +_LOGGER = logging.getLogger(__name__) +OPTION_TYPES = { + 'wallet': ['Wallet balance', 'BTC'], + 'exchangerate': ['Exchange rate (1 BTC)', ''], + 'trade_volume_btc': ['Trade volume', 'BTC'], + 'miners_revenue_usd': ['Miners revenue', 'USD'], + 'btc_mined': ['Mined', 'BTC'], + 'trade_volume_usd': ['Trade volume', 'USD'], + 'difficulty': ['Difficulty', ''], + 'minutes_between_blocks': ['Time between Blocks', 'min'], + 'number_of_transactions': ['No. of Transactions', ''], + 'hash_rate': ['Hash rate', 'PH/s'], + 'timestamp': ['Timestamp', ''], + 'mined_blocks': ['Minded Blocks', ''], + 'blocks_size': ['Block size', ''], + 'total_fees_btc': ['Total fees', 'BTC'], + 'total_btc_sent': ['Total sent', 'BTC'], + 'estimated_btc_sent': ['Estimated sent', 'BTC'], + 'total_btc': ['Total', 'BTC'], + 'total_blocks': ['Total Blocks', ''], + 'next_retarget': ['Next retarget', ''], + 'estimated_transaction_volume_usd': ['Est. Transaction volume', 'USD'], + 'miners_revenue_btc': ['Miners revenue', 'BTC'], + 'market_price_usd': ['Market price', 'USD'] +} + +# Return cached results if last scan was less then this time ago +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Get the Bitcoin sensor. """ + + try: + from blockchain.wallet import Wallet + from blockchain import exchangerates, exceptions + + except ImportError: + _LOGGER.exception( + "Unable to import blockchain. " + "Did you maybe not install the 'blockchain' package?") + + return None + + wallet_id = config.get('wallet', None) + password = config.get('password', None) + currency = config.get('currency', 'USD') + + if currency not in exchangerates.get_ticker(): + _LOGGER.error('Currency "%s" is not available. Using "USD".', currency) + currency = 'USD' + + wallet = Wallet(wallet_id, password) + + try: + wallet.get_balance() + except exceptions.APIException as error: + _LOGGER.error(error) + wallet = None + + data = BitcoinData() + dev = [] + if wallet is not None and password is not None: + dev.append(BitcoinSensor(data, 'wallet', currency, wallet)) + + for variable in config['display_options']: + if variable not in OPTION_TYPES: + _LOGGER.error('Option type: "%s" does not exist', variable) + else: + dev.append(BitcoinSensor(data, variable, currency)) + + add_devices(dev) + + +# pylint: disable=too-few-public-methods +class BitcoinSensor(Entity): + """ Implements a Bitcoin sensor. """ + + def __init__(self, data, option_type, currency, wallet=''): + self.data = data + self._name = OPTION_TYPES[option_type][0] + self._unit_of_measurement = OPTION_TYPES[option_type][1] + self._currency = currency + self._wallet = wallet + self.type = option_type + self._state = None + self.update() + + @property + def name(self): + """ Returns the name of the device. """ + return self._name + + @property + def state(self): + """ Returns the state of the device. """ + return self._state + + @property + def unit_of_measurement(self): + return self._unit_of_measurement + + # pylint: disable=too-many-branches + def update(self): + """ Gets the latest data and updates the states. """ + + self.data.update() + stats = self.data.stats + ticker = self.data.ticker + + # pylint: disable=no-member + if self.type == 'wallet' and self._wallet is not None: + self._state = '{0:.8f}'.format(self._wallet.get_balance() * + 0.00000001) + elif self.type == 'exchangerate': + self._state = ticker[self._currency].p15min + self._unit_of_measurement = self._currency + elif self.type == 'trade_volume_btc': + self._state = '{0:.1f}'.format(stats.trade_volume_btc) + elif self.type == 'miners_revenue_usd': + self._state = '{0:.0f}'.format(stats.miners_revenue_usd) + elif self.type == 'btc_mined': + self._state = '{}'.format(stats.btc_mined * 0.00000001) + elif self.type == 'trade_volume_usd': + self._state = '{0:.1f}'.format(stats.trade_volume_usd) + elif self.type == 'difficulty': + self._state = '{0:.0f}'.format(stats.difficulty) + elif self.type == 'minutes_between_blocks': + self._state = '{0:.2f}'.format(stats.minutes_between_blocks) + elif self.type == 'number_of_transactions': + self._state = '{}'.format(stats.number_of_transactions) + elif self.type == 'hash_rate': + self._state = '{0:.1f}'.format(stats.hash_rate * 0.000001) + elif self.type == 'timestamp': + self._state = stats.timestamp + elif self.type == 'mined_blocks': + self._state = '{}'.format(stats.mined_blocks) + elif self.type == 'blocks_size': + self._state = '{0:.1f}'.format(stats.blocks_size) + elif self.type == 'total_fees_btc': + self._state = '{0:.2f}'.format(stats.total_fees_btc * 0.00000001) + elif self.type == 'total_btc_sent': + self._state = '{0:.2f}'.format(stats.total_btc_sent * 0.00000001) + elif self.type == 'estimated_btc_sent': + self._state = '{0:.2f}'.format(stats.estimated_btc_sent * + 0.00000001) + elif self.type == 'total_btc': + self._state = '{0:.2f}'.format(stats.total_btc * 0.00000001) + elif self.type == 'total_blocks': + self._state = '{0:.2f}'.format(stats.total_blocks) + elif self.type == 'next_retarget': + self._state = '{0:.2f}'.format(stats.next_retarget) + elif self.type == 'estimated_transaction_volume_usd': + self._state = '{0:.2f}'.format( + stats.estimated_transaction_volume_usd) + elif self.type == 'miners_revenue_btc': + self._state = '{0:.1f}'.format(stats.miners_revenue_btc * + 0.00000001) + elif self.type == 'market_price_usd': + self._state = '{0:.2f}'.format(stats.market_price_usd) + + +class BitcoinData(object): + """ Gets the latest data and updates the states. """ + + def __init__(self): + self.stats = None + self.ticker = None + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """ Gets the latest data from blockchain.info. """ + + from blockchain import statistics, exchangerates + + self.stats = statistics.get() + self.ticker = exchangerates.get_ticker() diff --git a/requirements.txt b/requirements.txt index 63e3f3d5022..86605af82c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -56,3 +56,5 @@ pyowm>=2.2.0 # XMPP Bindings (notify.xmpp) sleekxmpp>=1.3.1 +# Blockchain (sensor.bitcoin) +blockchain>=1.1.2