Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """The kraken integration."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 import logging
8 
10  SensorEntity,
11  SensorEntityDescription,
12  SensorStateClass,
13 )
14 from homeassistant.config_entries import ConfigEntry
15 from homeassistant.core import HomeAssistant, callback
16 from homeassistant.helpers import device_registry as dr
17 from homeassistant.helpers.device_registry import DeviceInfo
18 from homeassistant.helpers.dispatcher import async_dispatcher_connect
19 from homeassistant.helpers.entity_platform import AddEntitiesCallback
21  CoordinatorEntity,
22  DataUpdateCoordinator,
23 )
24 
25 from . import KrakenData
26 from .const import (
27  CONF_TRACKED_ASSET_PAIRS,
28  DISPATCH_CONFIG_UPDATED,
29  DOMAIN,
30  KrakenResponse,
31 )
32 
33 _LOGGER = logging.getLogger(__name__)
34 
35 
36 @dataclass(frozen=True, kw_only=True)
38  """Describes Kraken sensor entity."""
39 
40  value_fn: Callable[[DataUpdateCoordinator[KrakenResponse], str], float | int]
41 
42 
43 SENSOR_TYPES: tuple[KrakenSensorEntityDescription, ...] = (
45  key="ask",
46  translation_key="ask",
47  value_fn=lambda x, y: x.data[y]["ask"][0],
48  ),
50  key="ask_volume",
51  translation_key="ask_volume",
52  value_fn=lambda x, y: x.data[y]["ask"][1],
53  entity_registry_enabled_default=False,
54  ),
56  key="bid",
57  translation_key="bid",
58  value_fn=lambda x, y: x.data[y]["bid"][0],
59  ),
61  key="bid_volume",
62  translation_key="bid_volume",
63  value_fn=lambda x, y: x.data[y]["bid"][1],
64  entity_registry_enabled_default=False,
65  ),
67  key="volume_today",
68  translation_key="volume_today",
69  value_fn=lambda x, y: x.data[y]["volume"][0],
70  entity_registry_enabled_default=False,
71  ),
73  key="volume_last_24h",
74  translation_key="volume_last_24h",
75  value_fn=lambda x, y: x.data[y]["volume"][1],
76  entity_registry_enabled_default=False,
77  ),
79  key="volume_weighted_average_today",
80  translation_key="volume_weighted_average_today",
81  value_fn=lambda x, y: x.data[y]["volume_weighted_average"][0],
82  entity_registry_enabled_default=False,
83  ),
85  key="volume_weighted_average_last_24h",
86  translation_key="volume_weighted_average_last_24h",
87  value_fn=lambda x, y: x.data[y]["volume_weighted_average"][1],
88  entity_registry_enabled_default=False,
89  ),
91  key="number_of_trades_today",
92  translation_key="number_of_trades_today",
93  value_fn=lambda x, y: x.data[y]["number_of_trades"][0],
94  entity_registry_enabled_default=False,
95  ),
97  key="number_of_trades_last_24h",
98  translation_key="number_of_trades_last_24h",
99  value_fn=lambda x, y: x.data[y]["number_of_trades"][1],
100  entity_registry_enabled_default=False,
101  ),
103  key="last_trade_closed",
104  translation_key="last_trade_closed",
105  value_fn=lambda x, y: x.data[y]["last_trade_closed"][0],
106  entity_registry_enabled_default=False,
107  ),
109  key="low_today",
110  translation_key="low_today",
111  value_fn=lambda x, y: x.data[y]["low"][0],
112  ),
114  key="low_last_24h",
115  translation_key="low_last_24h",
116  value_fn=lambda x, y: x.data[y]["low"][1],
117  entity_registry_enabled_default=False,
118  ),
120  key="high_today",
121  translation_key="high_today",
122  value_fn=lambda x, y: x.data[y]["high"][0],
123  ),
125  key="high_last_24h",
126  translation_key="high_last_24h",
127  value_fn=lambda x, y: x.data[y]["high"][1],
128  entity_registry_enabled_default=False,
129  ),
131  key="opening_price_today",
132  translation_key="opening_price_today",
133  value_fn=lambda x, y: x.data[y]["opening_price"],
134  entity_registry_enabled_default=False,
135  ),
136 )
137 
138 
140  hass: HomeAssistant,
141  config_entry: ConfigEntry,
142  async_add_entities: AddEntitiesCallback,
143 ) -> None:
144  """Add kraken entities from a config_entry."""
145 
146  def _async_add_kraken_sensors(tracked_asset_pairs: list[str]) -> None:
147  entities = []
148  for tracked_asset_pair in tracked_asset_pairs:
149  entities.extend(
150  [
151  KrakenSensor(
152  hass.data[DOMAIN],
153  tracked_asset_pair,
154  description,
155  )
156  for description in SENSOR_TYPES
157  ]
158  )
159  async_add_entities(entities, True)
160 
161  _async_add_kraken_sensors(config_entry.options[CONF_TRACKED_ASSET_PAIRS])
162 
163  @callback
164  def async_update_sensors(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
165  """Add or remove sensors for configured tracked asset pairs."""
166  dev_reg = dr.async_get(hass)
167 
168  existing_devices = {
169  device.name: device.id
170  for device in dr.async_entries_for_config_entry(
171  dev_reg, config_entry.entry_id
172  )
173  }
174 
175  asset_pairs_to_add = []
176  for tracked_asset_pair in config_entry.options[CONF_TRACKED_ASSET_PAIRS]:
177  # Only create new devices
178  if (
179  device_name := create_device_name(tracked_asset_pair)
180  ) in existing_devices:
181  existing_devices.pop(device_name)
182  else:
183  asset_pairs_to_add.append(tracked_asset_pair)
184  _async_add_kraken_sensors(asset_pairs_to_add)
185 
186  # Remove devices for asset pairs which are no longer tracked
187  for device_id in existing_devices.values():
188  dev_reg.async_remove_device(device_id)
189 
190  config_entry.async_on_unload(
192  hass,
193  DISPATCH_CONFIG_UPDATED,
194  async_update_sensors,
195  )
196  )
197 
198 
200  CoordinatorEntity[DataUpdateCoordinator[KrakenResponse | None]], SensorEntity
201 ):
202  """Define a Kraken sensor."""
203 
204  entity_description: KrakenSensorEntityDescription
205 
206  _attr_state_class = SensorStateClass.MEASUREMENT
207  _attr_has_entity_name = True
208 
209  def __init__(
210  self,
211  kraken_data: KrakenData,
212  tracked_asset_pair: str,
213  description: KrakenSensorEntityDescription,
214  ) -> None:
215  """Initialize."""
216  assert kraken_data.coordinator is not None
217  super().__init__(kraken_data.coordinator)
218  self.entity_descriptionentity_description = description
219  self.tracked_asset_pair_wsnametracked_asset_pair_wsname = kraken_data.tradable_asset_pairs[
220  tracked_asset_pair
221  ]
222  self._target_asset_target_asset = tracked_asset_pair.split("/")[1]
223  if "number_of" not in description.key:
224  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = self._target_asset_target_asset
225  self._device_name_device_name = create_device_name(tracked_asset_pair)
226  self._attr_unique_id_attr_unique_id = "_".join(
227  [
228  tracked_asset_pair.split("/")[0],
229  tracked_asset_pair.split("/")[1],
230  description.key,
231  ]
232  ).lower()
233  self._received_data_at_least_once_received_data_at_least_once = False
234  self._available_available = True
235 
236  self._attr_device_info_attr_device_info = DeviceInfo(
237  configuration_url="https://www.kraken.com/",
238  entry_type=dr.DeviceEntryType.SERVICE,
239  identifiers={(DOMAIN, "_".join(self._device_name_device_name.split(" ")))},
240  manufacturer="Kraken.com",
241  name=self._device_name_device_name,
242  )
243 
244  async def async_added_to_hass(self) -> None:
245  """Handle entity which will be added."""
246  await super().async_added_to_hass()
247  self._update_internal_state_update_internal_state()
248 
249  def _handle_coordinator_update(self) -> None:
250  self._update_internal_state_update_internal_state()
252 
253  def _update_internal_state(self) -> None:
254  if not self.coordinator.data:
255  return
256  try:
257  self._attr_native_value_attr_native_value = self.entity_descriptionentity_description.value_fn(
258  self.coordinator, # type: ignore[arg-type]
259  self.tracked_asset_pair_wsnametracked_asset_pair_wsname,
260  )
261  self._received_data_at_least_once_received_data_at_least_once = True
262  except KeyError:
263  if self._received_data_at_least_once_received_data_at_least_once:
264  if self._available_available:
265  _LOGGER.warning(
266  "Asset Pair %s is no longer available",
267  self._device_name_device_name,
268  )
269  self._available_available = False
270 
271  @property
272  def icon(self) -> str:
273  """Return the icon."""
274  if self._target_asset_target_asset == "EUR":
275  return "mdi:currency-eur"
276  if self._target_asset_target_asset == "GBP":
277  return "mdi:currency-gbp"
278  if self._target_asset_target_asset == "USD":
279  return "mdi:currency-usd"
280  if self._target_asset_target_asset == "JPY":
281  return "mdi:currency-jpy"
282  if self._target_asset_target_asset == "XBT":
283  return "mdi:currency-btc"
284  return "mdi:cash"
285 
286  @property
287  def available(self) -> bool:
288  """Could the api be accessed during the last update call."""
289  return self._available_available and self.coordinator.last_update_success
290 
291 
292 def create_device_name(tracked_asset_pair: str) -> str:
293  """Create the device name for a given tracked asset pair."""
294  return f"{tracked_asset_pair.split('/')[0]} {tracked_asset_pair.split('/')[1]}"
None __init__(self, KrakenData kraken_data, str tracked_asset_pair, KrakenSensorEntityDescription description)
Definition: sensor.py:214
str create_device_name(str tracked_asset_pair)
Definition: sensor.py:292
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:143
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103