refactor: split out data coordinators and sensors

- remove unused imports
This commit is contained in:
Brian Berg 2020-06-17 01:46:53 +00:00
parent da7e12c75d
commit 96498e580e
4 changed files with 314 additions and 275 deletions

View File

@ -4,16 +4,13 @@ Integrates NiceHash with Home Assistant
For more details about this integration, please refer to
https://github.com/brianberg/ha-nicehash
"""
from datetime import timedelta
import logging
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_DEVICES, CONF_TIMEOUT
from homeassistant.core import Config, HomeAssistant
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.exceptions import PlatformNotReady
from .const import (
@ -21,15 +18,15 @@ from .const import (
CONF_API_SECRET,
CONF_CURRENCY,
CONF_ORGANIZATION_ID,
CURRENCY_BTC,
CURRENCY_USD,
DOMAIN,
STARTUP_MESSAGE,
)
from .nicehash import NiceHashPrivateClient, NiceHashPublicClient
SCAN_INTERVAL_RIGS = timedelta(minutes=1)
SCAN_INTERVAL_ACCOUNTS = timedelta(minutes=60)
from .nicehash import NiceHashPrivateClient
from .data_coordinators import (
NiceHashAccountsDataUpdateCoordinator,
NiceHashMiningRigsDataUpdateCoordinator,
)
_LOGGER = logging.getLogger(__name__)
@ -86,63 +83,3 @@ async def async_setup(hass: HomeAssistant, config: Config):
await discovery.async_load_platform(hass, "sensor", DOMAIN, {}, config)
return True
class NiceHashAccountsDataUpdateCoordinator(DataUpdateCoordinator):
"""Manages fetching accounts data from NiceHash API"""
def __init__(self, hass: HomeAssistant, client: NiceHashPrivateClient):
"""Initialize"""
self.name = f"{DOMAIN}_accounts_coordinator"
self._client = client
super().__init__(
hass, _LOGGER, name=self.name, update_interval=SCAN_INTERVAL_ACCOUNTS
)
async def _async_update_data(self):
"""Update accounts data and exchange rates"""
try:
accounts = await self._client.get_accounts()
exchange_rates = await NiceHashPublicClient().get_exchange_rates()
rates_dict = dict()
for rate in exchange_rates:
fromCurrency = rate.get("fromCurrency")
# Only care about the Bitcoin exchange rates
if fromCurrency == CURRENCY_BTC:
toCurrency = rate.get("toCurrency")
exchange_rate = float(rate.get("exchangeRate"))
rates_dict[f"{fromCurrency}-{toCurrency}"] = exchange_rate
return {
"accounts": accounts,
"exchange_rates": rates_dict,
}
except Exception as e:
raise UpdateFailed(e)
class NiceHashMiningRigsDataUpdateCoordinator(DataUpdateCoordinator):
"""Manages fetching mining rigs data from NiceHash API"""
def __init__(self, hass: HomeAssistant, client: NiceHashPrivateClient):
"""Initialize"""
self.name = f"{DOMAIN}_mining_rigs_coordinator"
self._client = client
super().__init__(
hass, _LOGGER, name=self.name, update_interval=SCAN_INTERVAL_RIGS
)
async def _async_update_data(self):
"""Update mining rigs data"""
try:
data = await self._client.get_mining_rigs()
mining_rigs = data.get("miningRigs")
rigs_dict = dict()
for rig in mining_rigs:
rig_id = rig.get("rigId")
rigs_dict[f"{rig_id}"] = rig
data["miningRigs"] = rigs_dict
return data
except Exception as e:
raise UpdateFailed(e)

View File

@ -0,0 +1,82 @@
"""
NiceHash Data Update Coordinators
"""
from datetime import timedelta
import logging
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import (
DataUpdateCoordinator,
UpdateFailed,
)
from .const import (
CURRENCY_BTC,
DOMAIN,
)
from .nicehash import NiceHashPrivateClient, NiceHashPublicClient
SCAN_INTERVAL_RIGS = timedelta(minutes=1)
SCAN_INTERVAL_ACCOUNTS = timedelta(minutes=60)
_LOGGER = logging.getLogger(__name__)
class NiceHashAccountsDataUpdateCoordinator(DataUpdateCoordinator):
"""Manages fetching accounts data from NiceHash API"""
def __init__(self, hass: HomeAssistant, client: NiceHashPrivateClient):
"""Initialize"""
self.name = f"{DOMAIN}_accounts_coordinator"
self._client = client
super().__init__(
hass, _LOGGER, name=self.name, update_interval=SCAN_INTERVAL_ACCOUNTS
)
async def _async_update_data(self):
"""Update accounts data and exchange rates"""
try:
accounts = await self._client.get_accounts()
exchange_rates = await NiceHashPublicClient().get_exchange_rates()
rates_dict = dict()
for rate in exchange_rates:
fromCurrency = rate.get("fromCurrency")
# Only care about the Bitcoin exchange rates
if fromCurrency == CURRENCY_BTC:
toCurrency = rate.get("toCurrency")
exchange_rate = float(rate.get("exchangeRate"))
rates_dict[f"{fromCurrency}-{toCurrency}"] = exchange_rate
return {
"accounts": accounts,
"exchange_rates": rates_dict,
}
except Exception as e:
raise UpdateFailed(e)
class NiceHashMiningRigsDataUpdateCoordinator(DataUpdateCoordinator):
"""Manages fetching mining rigs data from NiceHash API"""
def __init__(self, hass: HomeAssistant, client: NiceHashPrivateClient):
"""Initialize"""
self.name = f"{DOMAIN}_mining_rigs_coordinator"
self._client = client
super().__init__(
hass, _LOGGER, name=self.name, update_interval=SCAN_INTERVAL_RIGS
)
async def _async_update_data(self):
"""Update mining rigs data"""
try:
data = await self._client.get_mining_rigs()
mining_rigs = data.get("miningRigs")
rigs_dict = dict()
for rig in mining_rigs:
rig_id = rig.get("rigId")
rigs_dict[f"{rig_id}"] = rig
data["miningRigs"] = rigs_dict
return data
except Exception as e:
raise UpdateFailed(e)

View File

@ -1,16 +1,13 @@
"""
Sensor platform for NiceHash
"""
from datetime import datetime, timedelta
import logging
import os
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.core import Config, HomeAssistant
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
from .const import (
BALANCE_TYPE_AVAILABLE,
@ -28,11 +25,7 @@ from .const import (
ICON_TEMPERATURE,
)
from .nicehash import NiceHashPrivateClient, NiceHashPublicClient
ATTRIBUTION = "Data provided by NiceHash"
FORMAT_DATETIME = "%d-%m-%Y %H:%M"
SCAN_INTERVAL_RIGS = timedelta(minutes=1)
SCAN_INTERVAL_ACCOUNTS = timedelta(minutes=60)
from .sensors import NiceHashBalanceSensor, NiceHashRigTemperatureSensor
_LOGGER = logging.getLogger(__name__)
@ -109,202 +102,3 @@ async def async_setup_platform(
[NiceHashRigTemperatureSensor(rigs_coordinator, rig) for rig in mining_rigs],
True,
)
class NiceHashBalanceSensor(Entity):
"""NiceHash Account Balance Sensor"""
def __init__(
self,
coordinator,
organization_id,
currency,
balance_type=BALANCE_TYPE_AVAILABLE,
):
"""Initialize the sensor"""
_LOGGER.debug(f"Account Balance Sensor: {balance_type} {currency}")
self.coordinator = coordinator
self.currency = currency
self.organization_id = organization_id
self.balance_type = balance_type
self._available = 0.00
self._pending = 0.00
self._total_balance = 0.00
self._exchange_rate = 0.00
@property
def name(self):
"""Sensor name"""
balance_type = self.balance_type[0].upper() + self.balance_type[1:]
return f"{DEFAULT_NAME} {balance_type} Account Balance {self.currency}"
@property
def unique_id(self):
"""Unique entity id"""
return f"{self.organization_id}:{self.currency}:{self.balance_type}"
@property
def should_poll(self):
"""No need to pool, Coordinator notifies entity of updates"""
return False
@property
def available(self):
"""Whether sensor is available"""
return self.coordinator.last_update_success
@property
def state(self):
"""Sensor state"""
accounts = self.coordinator.data.get("accounts")
total = accounts.get("total")
self._pending = float(total.get("pending"))
self._available = float(total.get("available"))
self._total_balance = float(total.get("totalBalance"))
if self.balance_type == BALANCE_TYPE_TOTAL:
balance = self._total_balance
elif self.balance_type == BALANCE_TYPE_PENDING:
balance = self._pending
else:
balance = self._available
if self.currency == CURRENCY_BTC:
return balance
else:
exchange_rates = self.coordinator.data.get("exchange_rates")
self._exchange_rate = exchange_rates.get(f"{CURRENCY_BTC}-{self.currency}")
return round(balance * self._exchange_rate, 2)
@property
def icon(self):
"""Sensor icon"""
if self.currency == CURRENCY_EUR:
return ICON_CURRENCY_EUR
elif self.currency == CURRENCY_USD:
return ICON_CURRENCY_USD
return ICON_CURRENCY_BTC
@property
def unit_of_measurement(self):
"""Sensor unit of measurement"""
return self.currency
@property
def device_state_attributes(self):
"""Sensor device state attributes"""
return {
ATTR_ATTRIBUTION: ATTRIBUTION,
"total": self._total_balance,
"available": self._available,
"pending": self._pending,
"exchange_rate": self._exchange_rate,
}
async def async_added_to_hass(self):
"""Connect to dispatcher listening for entity data notifications"""
self.async_on_remove(
self.coordinator.async_add_listener(self.async_write_ha_state)
)
async def async_update(self):
"""Update entity"""
await self.coordinator.async_request_refresh()
class NiceHashRigTemperatureSensor(Entity):
"""NichHash Mining Rig Temperature Sensor"""
def __init__(self, coordinator, rig):
"""Initialize the sensor"""
self.coordinator = coordinator
self._rig_id = rig["rigId"]
self._name = rig["name"]
self._temps = []
self._num_devices = 0
self._num_active_devices = 0
_LOGGER.debug(f"Mining Rig Temperature Sensor: {self._name} ({self._rig_id})")
@property
def name(self):
"""Sensor name"""
return self._name
@property
def unique_id(self):
"""Unique entity id"""
return self._rig_id
@property
def should_poll(self):
"""No need to pool, Coordinator notifies entity of updates"""
return False
@property
def available(self):
"""Whether sensor is available"""
return self.coordinator.last_update_success
@property
def state(self):
"""Sensor state"""
mining_rigs = self.coordinator.data.get("miningRigs")
try:
rig_data = mining_rigs.get(self._rig_id)
devices = rig_data.get("devices")
highest_temp = 0
self._temps = []
self._num_devices = len(devices)
if self._num_devices > 0:
_LOGGER.debug(f"{self._name}: Found {self._num_devices} devices")
for device in devices:
status = device.get("status").get("enumName")
# Ignore inactive devices
if status == DEVICE_STATUS_INACTIVE:
continue
temp = int(device.get("temperature"))
self._temps.append(temp)
if temp > highest_temp:
highest_temp = temp
self._num_active_devices = len(self._temps)
return highest_temp
else:
_LOGGER.debug(f"{self._name}: No devices found")
self._num_devices = 0
return 0
except Exception as e:
_LOGGER.error(f"Unable to get mining rig {self._rig_id}\n{e}")
return 0
@property
def icon(self):
"""Sensor icon"""
return ICON_TEMPERATURE
@property
def unit_of_measurement(self):
"""Sensor unit of measurement"""
# Not Celsius because then HA might convert to Fahrenheit
return "C"
@property
def device_state_attributes(self):
"""Sensor device state attributes"""
return {
ATTR_ATTRIBUTION: ATTRIBUTION,
"temperatures": self._temps,
"active_devices": self._num_active_devices,
"total_devices": self._num_devices,
}
async def async_added_to_hass(self):
"""Connect to dispatcher listening for entity data notifications"""
self.async_on_remove(
self.coordinator.async_add_listener(self.async_write_ha_state)
)
async def async_update(self):
"""Update entity"""
await self.coordinator.async_request_refresh()

View File

@ -0,0 +1,226 @@
"""
NiceHash Mining Rig Temperature Sensor
"""
import logging
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.helpers.entity import Entity
from .const import (
BALANCE_TYPE_AVAILABLE,
BALANCE_TYPE_PENDING,
BALANCE_TYPE_TOTAL,
CURRENCY_BTC,
CURRENCY_EUR,
CURRENCY_USD,
DEFAULT_NAME,
DEVICE_STATUS_INACTIVE,
ICON_CURRENCY_BTC,
ICON_CURRENCY_EUR,
ICON_CURRENCY_USD,
ICON_TEMPERATURE,
)
ATTRIBUTION = "Data provided by NiceHash"
FORMAT_DATETIME = "%d-%m-%Y %H:%M"
_LOGGER = logging.getLogger(__name__)
class NiceHashBalanceSensor(Entity):
"""NiceHash Account Balance Sensor"""
def __init__(
self,
coordinator,
organization_id,
currency,
balance_type=BALANCE_TYPE_AVAILABLE,
):
"""Initialize the sensor"""
_LOGGER.debug(f"Account Balance Sensor: {balance_type} {currency}")
self.coordinator = coordinator
self.currency = currency
self.organization_id = organization_id
self.balance_type = balance_type
self._available = 0.00
self._pending = 0.00
self._total_balance = 0.00
self._exchange_rate = 0.00
@property
def name(self):
"""Sensor name"""
balance_type = self.balance_type[0].upper() + self.balance_type[1:]
return f"{DEFAULT_NAME} {balance_type} Account Balance {self.currency}"
@property
def unique_id(self):
"""Unique entity id"""
return f"{self.organization_id}:{self.currency}:{self.balance_type}"
@property
def should_poll(self):
"""No need to pool, Coordinator notifies entity of updates"""
return False
@property
def available(self):
"""Whether sensor is available"""
return self.coordinator.last_update_success
@property
def state(self):
"""Sensor state"""
accounts = self.coordinator.data.get("accounts")
total = accounts.get("total")
self._pending = float(total.get("pending"))
self._available = float(total.get("available"))
self._total_balance = float(total.get("totalBalance"))
if self.balance_type == BALANCE_TYPE_TOTAL:
balance = self._total_balance
elif self.balance_type == BALANCE_TYPE_PENDING:
balance = self._pending
else:
balance = self._available
if self.currency == CURRENCY_BTC:
return balance
else:
exchange_rates = self.coordinator.data.get("exchange_rates")
self._exchange_rate = exchange_rates.get(f"{CURRENCY_BTC}-{self.currency}")
return round(balance * self._exchange_rate, 2)
@property
def icon(self):
"""Sensor icon"""
if self.currency == CURRENCY_EUR:
return ICON_CURRENCY_EUR
elif self.currency == CURRENCY_USD:
return ICON_CURRENCY_USD
return ICON_CURRENCY_BTC
@property
def unit_of_measurement(self):
"""Sensor unit of measurement"""
return self.currency
@property
def device_state_attributes(self):
"""Sensor device state attributes"""
return {
ATTR_ATTRIBUTION: ATTRIBUTION,
"total": self._total_balance,
"available": self._available,
"pending": self._pending,
"exchange_rate": self._exchange_rate,
}
async def async_added_to_hass(self):
"""Connect to dispatcher listening for entity data notifications"""
self.async_on_remove(
self.coordinator.async_add_listener(self.async_write_ha_state)
)
async def async_update(self):
"""Update entity"""
await self.coordinator.async_request_refresh()
class NiceHashRigTemperatureSensor(Entity):
"""Displays highest temperature of active mining rig devices"""
def __init__(self, coordinator, rig):
"""Initialize the sensor"""
self.coordinator = coordinator
self._rig_id = rig["rigId"]
self._name = rig["name"]
self._temps = []
self._num_devices = 0
self._num_active_devices = 0
_LOGGER.debug(f"Mining Rig Temperature Sensor: {self._name} ({self._rig_id})")
@property
def name(self):
"""Sensor name"""
return self._name
@property
def unique_id(self):
"""Unique entity id"""
return self._rig_id
@property
def should_poll(self):
"""No need to pool, Coordinator notifies entity of updates"""
return False
@property
def available(self):
"""Whether sensor is available"""
return self.coordinator.last_update_success
@property
def state(self):
"""Sensor state"""
mining_rigs = self.coordinator.data.get("miningRigs")
try:
rig_data = mining_rigs.get(self._rig_id)
devices = rig_data.get("devices")
highest_temp = 0
self._temps = []
self._num_devices = len(devices)
if self._num_devices > 0:
_LOGGER.debug(f"{self._name}: Found {self._num_devices} devices")
for device in devices:
status = device.get("status").get("enumName")
# Ignore inactive devices
if status == DEVICE_STATUS_INACTIVE:
continue
temp = int(device.get("temperature"))
self._temps.append(temp)
if temp > highest_temp:
highest_temp = temp
self._num_active_devices = len(self._temps)
return highest_temp
else:
_LOGGER.debug(f"{self._name}: No devices found")
self._num_devices = 0
return 0
except Exception as e:
_LOGGER.error(f"Unable to get mining rig {self._rig_id}\n{e}")
return 0
@property
def icon(self):
"""Sensor icon"""
return ICON_TEMPERATURE
@property
def unit_of_measurement(self):
"""Sensor unit of measurement"""
# Not Celsius because then HA might convert to Fahrenheit
return "C"
@property
def device_state_attributes(self):
"""Sensor device state attributes"""
return {
ATTR_ATTRIBUTION: ATTRIBUTION,
"temperatures": self._temps,
"active_devices": self._num_active_devices,
"total_devices": self._num_devices,
}
async def async_added_to_hass(self):
"""Connect to dispatcher listening for entity data notifications"""
self.async_on_remove(
self.coordinator.async_add_listener(self.async_write_ha_state)
)
async def async_update(self):
"""Update entity"""
await self.coordinator.async_request_refresh()