"""The kraken integration.""" from __future__ import annotations import logging from homeassistant.components.sensor import SensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import device_registry from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import KrakenData from .const import ( CONF_TRACKED_ASSET_PAIRS, DISPATCH_CONFIG_UPDATED, DOMAIN, SENSOR_TYPES, SensorType, ) _LOGGER = logging.getLogger(__name__) async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Add kraken entities from a config_entry.""" @callback def async_update_sensors(hass: HomeAssistant, config_entry: ConfigEntry) -> None: dev_reg = device_registry.async_get(hass) existing_devices = { device.name: device.id for device in device_registry.async_entries_for_config_entry( dev_reg, config_entry.entry_id ) } sensors = [] for tracked_asset_pair in config_entry.options[CONF_TRACKED_ASSET_PAIRS]: # Only create new devices if ( device_name := create_device_name(tracked_asset_pair) ) in existing_devices: existing_devices.pop(device_name) else: for sensor_type in SENSOR_TYPES: sensors.append( KrakenSensor( hass.data[DOMAIN], tracked_asset_pair, sensor_type, ) ) async_add_entities(sensors, True) # Remove devices for asset pairs which are no longer tracked for device_id in existing_devices.values(): dev_reg.async_remove_device(device_id) async_update_sensors(hass, config_entry) config_entry.async_on_unload( async_dispatcher_connect( hass, DISPATCH_CONFIG_UPDATED, async_update_sensors, ) ) class KrakenSensor(CoordinatorEntity, SensorEntity): """Define a Kraken sensor.""" def __init__( self, kraken_data: KrakenData, tracked_asset_pair: str, sensor_type: SensorType, ) -> None: """Initialize.""" assert kraken_data.coordinator is not None super().__init__(kraken_data.coordinator) self.tracked_asset_pair_wsname = kraken_data.tradable_asset_pairs[ tracked_asset_pair ] self._source_asset = tracked_asset_pair.split("/")[0] self._target_asset = tracked_asset_pair.split("/")[1] self._sensor_type = sensor_type["name"] self._enabled_by_default = sensor_type["enabled_by_default"] self._unit_of_measurement = self._target_asset self._device_name = f"{self._source_asset} {self._target_asset}" self._name = "_".join( [ tracked_asset_pair.split("/")[0], tracked_asset_pair.split("/")[1], sensor_type["name"], ] ) self._received_data_at_least_once = False self._available = True self._state = None @property def entity_registry_enabled_default(self) -> bool: """Return if the entity should be enabled when first added to the entity registry.""" return self._enabled_by_default @property def name(self) -> str: """Return the name.""" return self._name @property def unique_id(self) -> str: """Set unique_id for sensor.""" return self._name.lower() @property def native_value(self) -> StateType: """Return the state.""" return self._state async def async_added_to_hass(self) -> None: """Handle entity which will be added.""" await super().async_added_to_hass() self._update_internal_state() def _handle_coordinator_update(self) -> None: self._update_internal_state() super()._handle_coordinator_update() def _update_internal_state(self) -> None: try: if self._sensor_type == "last_trade_closed": self._state = self.coordinator.data[self.tracked_asset_pair_wsname][ "last_trade_closed" ][0] if self._sensor_type == "ask": self._state = self.coordinator.data[self.tracked_asset_pair_wsname][ "ask" ][0] if self._sensor_type == "ask_volume": self._state = self.coordinator.data[self.tracked_asset_pair_wsname][ "ask" ][1] if self._sensor_type == "bid": self._state = self.coordinator.data[self.tracked_asset_pair_wsname][ "bid" ][0] if self._sensor_type == "bid_volume": self._state = self.coordinator.data[self.tracked_asset_pair_wsname][ "bid" ][1] if self._sensor_type == "volume_today": self._state = self.coordinator.data[self.tracked_asset_pair_wsname][ "volume" ][0] if self._sensor_type == "volume_last_24h": self._state = self.coordinator.data[self.tracked_asset_pair_wsname][ "volume" ][1] if self._sensor_type == "volume_weighted_average_today": self._state = self.coordinator.data[self.tracked_asset_pair_wsname][ "volume_weighted_average" ][0] if self._sensor_type == "volume_weighted_average_last_24h": self._state = self.coordinator.data[self.tracked_asset_pair_wsname][ "volume_weighted_average" ][1] if self._sensor_type == "number_of_trades_today": self._state = self.coordinator.data[self.tracked_asset_pair_wsname][ "number_of_trades" ][0] if self._sensor_type == "number_of_trades_last_24h": self._state = self.coordinator.data[self.tracked_asset_pair_wsname][ "number_of_trades" ][1] if self._sensor_type == "low_today": self._state = self.coordinator.data[self.tracked_asset_pair_wsname][ "low" ][0] if self._sensor_type == "low_last_24h": self._state = self.coordinator.data[self.tracked_asset_pair_wsname][ "low" ][1] if self._sensor_type == "high_today": self._state = self.coordinator.data[self.tracked_asset_pair_wsname][ "high" ][0] if self._sensor_type == "high_last_24h": self._state = self.coordinator.data[self.tracked_asset_pair_wsname][ "high" ][1] if self._sensor_type == "opening_price_today": self._state = self.coordinator.data[self.tracked_asset_pair_wsname][ "opening_price" ] self._received_data_at_least_once = True # Received data at least one time. except TypeError: if self._received_data_at_least_once: if self._available: _LOGGER.warning( "Asset Pair %s is no longer available", self._device_name, ) self._available = False @property def icon(self) -> str: """Return the icon.""" if self._target_asset == "EUR": return "mdi:currency-eur" if self._target_asset == "GBP": return "mdi:currency-gbp" if self._target_asset == "USD": return "mdi:currency-usd" if self._target_asset == "JPY": return "mdi:currency-jpy" if self._target_asset == "XBT": return "mdi:currency-btc" return "mdi:cash" @property def native_unit_of_measurement(self) -> str | None: """Return the unit the value is expressed in.""" if "number_of" not in self._sensor_type: return self._unit_of_measurement return None @property def available(self) -> bool: """Could the api be accessed during the last update call.""" return self._available and self.coordinator.last_update_success @property def device_info(self) -> DeviceInfo: """Return a device description for device registry.""" return { "identifiers": {(DOMAIN, f"{self._source_asset}_{self._target_asset}")}, "name": self._device_name, "manufacturer": "Kraken.com", "entry_type": "service", } def create_device_name(tracked_asset_pair: str) -> str: """Create the device name for a given tracked asset pair.""" return f"{tracked_asset_pair.split('/')[0]} {tracked_asset_pair.split('/')[1]}"