Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """The kraken integration."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from datetime import timedelta
7 import logging
8 
9 import krakenex
10 import pykrakenapi
11 
12 from homeassistant.config_entries import ConfigEntry
13 from homeassistant.const import CONF_SCAN_INTERVAL, Platform
14 from homeassistant.core import HomeAssistant
15 from homeassistant.helpers.dispatcher import async_dispatcher_send
16 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
17 
18 from .const import (
19  CONF_TRACKED_ASSET_PAIRS,
20  DEFAULT_SCAN_INTERVAL,
21  DEFAULT_TRACKED_ASSET_PAIR,
22  DISPATCH_CONFIG_UPDATED,
23  DOMAIN,
24  KrakenResponse,
25 )
26 from .utils import get_tradable_asset_pairs
27 
28 CALL_RATE_LIMIT_SLEEP = 1
29 
30 PLATFORMS = [Platform.SENSOR]
31 
32 _LOGGER = logging.getLogger(__name__)
33 
34 
35 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
36  """Set up kraken from a config entry."""
37  kraken_data = KrakenData(hass, entry)
38  await kraken_data.async_setup()
39  hass.data[DOMAIN] = kraken_data
40  entry.async_on_unload(entry.add_update_listener(async_options_updated))
41  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
42  return True
43 
44 
45 async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
46  """Unload a config entry."""
47  unload_ok = await hass.config_entries.async_unload_platforms(
48  config_entry, PLATFORMS
49  )
50  if unload_ok:
51  hass.data.pop(DOMAIN)
52 
53  return unload_ok
54 
55 
56 class KrakenData:
57  """Define an object to hold kraken data."""
58 
59  def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
60  """Initialize."""
61  self._hass_hass = hass
62  self._config_entry_config_entry = config_entry
63  self._api_api = pykrakenapi.KrakenAPI(krakenex.API(), retry=0, crl_sleep=0)
64  self.tradable_asset_pairstradable_asset_pairs: dict[str, str] = {}
65  self.coordinatorcoordinator: DataUpdateCoordinator[KrakenResponse | None] | None = None
66 
67  async def async_update(self) -> KrakenResponse | None:
68  """Get the latest data from the Kraken.com REST API.
69 
70  All tradeable asset pairs are retrieved, not the tracked asset pairs
71  selected by the user. This enables us to check for an unknown and
72  thus likely removed asset pair in sensor.py and only log a warning
73  once.
74  """
75  try:
76  async with asyncio.timeout(10):
77  return await self._hass_hass.async_add_executor_job(self._get_kraken_data_get_kraken_data)
78  except pykrakenapi.pykrakenapi.KrakenAPIError as error:
79  if "Unknown asset pair" in str(error):
80  _LOGGER.warning(
81  "Kraken.com reported an unknown asset pair. Refreshing list of"
82  " tradable asset pairs"
83  )
84  await self._async_refresh_tradable_asset_pairs_async_refresh_tradable_asset_pairs()
85  else:
86  raise UpdateFailed(
87  f"Unable to fetch data from Kraken.com: {error}"
88  ) from error
89  except pykrakenapi.pykrakenapi.CallRateLimitError:
90  _LOGGER.warning(
91  "Exceeded the Kraken.com call rate limit. Increase the update interval"
92  " to prevent this error"
93  )
94  return None
95 
96  def _get_kraken_data(self) -> KrakenResponse:
97  websocket_name_pairs = self._get_websocket_name_asset_pairs_get_websocket_name_asset_pairs()
98  ticker_df = self._api_api.get_ticker_information(websocket_name_pairs)
99  # Rename columns to their full name
100  ticker_df = ticker_df.rename(
101  columns={
102  "a": "ask",
103  "b": "bid",
104  "c": "last_trade_closed",
105  "v": "volume",
106  "p": "volume_weighted_average",
107  "t": "number_of_trades",
108  "l": "low",
109  "h": "high",
110  "o": "opening_price",
111  }
112  )
113  response_dict: KrakenResponse = ticker_df.transpose().to_dict()
114  return response_dict
115 
116  async def _async_refresh_tradable_asset_pairs(self) -> None:
117  self.tradable_asset_pairstradable_asset_pairs = await self._hass_hass.async_add_executor_job(
118  get_tradable_asset_pairs, self._api_api
119  )
120 
121  async def async_setup(self) -> None:
122  """Set up the Kraken integration."""
123  if not self._config_entry_config_entry.options:
124  options = {
125  CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL,
126  CONF_TRACKED_ASSET_PAIRS: [DEFAULT_TRACKED_ASSET_PAIR],
127  }
128  self._hass_hass.config_entries.async_update_entry(
129  self._config_entry_config_entry, options=options
130  )
131  await self._async_refresh_tradable_asset_pairs_async_refresh_tradable_asset_pairs()
132  # Wait 1 second to avoid triggering the KrakenAPI CallRateLimiter
133  await asyncio.sleep(CALL_RATE_LIMIT_SLEEP)
135  self._hass_hass,
136  _LOGGER,
137  name=DOMAIN,
138  update_method=self.async_updateasync_update,
139  update_interval=timedelta(
140  seconds=self._config_entry_config_entry.options[CONF_SCAN_INTERVAL]
141  ),
142  )
143  await self.coordinatorcoordinator.async_config_entry_first_refresh()
144  # Wait 1 second to avoid triggering the KrakenAPI CallRateLimiter
145  await asyncio.sleep(CALL_RATE_LIMIT_SLEEP)
146 
148  return ",".join(wsname for wsname in self.tradable_asset_pairstradable_asset_pairs.values())
149 
150  def set_update_interval(self, update_interval: int) -> None:
151  """Set the coordinator update_interval to the supplied update_interval."""
152  if self.coordinatorcoordinator is not None:
153  self.coordinatorcoordinator.update_interval = timedelta(seconds=update_interval)
154 
155 
156 async def async_options_updated(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
157  """Triggered by config entry options updates."""
158  hass.data[DOMAIN].set_update_interval(config_entry.options[CONF_SCAN_INTERVAL])
159  async_dispatcher_send(hass, DISPATCH_CONFIG_UPDATED, hass, config_entry)
None set_update_interval(self, int update_interval)
Definition: __init__.py:150
None __init__(self, HomeAssistant hass, ConfigEntry config_entry)
Definition: __init__.py:59
KrakenResponse|None async_update(self)
Definition: __init__.py:67
KrakenResponse _get_kraken_data(self)
Definition: __init__.py:96
timedelta set_update_interval(int instances_count, int requests_remaining)
Definition: coordinator.py:31
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:35
None async_options_updated(HomeAssistant hass, ConfigEntry config_entry)
Definition: __init__.py:156
bool async_unload_entry(HomeAssistant hass, ConfigEntry config_entry)
Definition: __init__.py:45
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193