From eefd191e51f0f941eec3acbac1ce7e9421651880 Mon Sep 17 00:00:00 2001 From: Brian Berg Date: Sat, 11 Jul 2020 18:40:42 +0000 Subject: [PATCH] feat(sensor): add recent payout sensor - fetches payouts every hour - displays most recently created in BTC --- custom_components/nicehash/__init__.py | 30 ++++- custom_components/nicehash/const.py | 4 +- .../nicehash/data_coordinators.py | 24 ++++ custom_components/nicehash/nicehash.py | 26 +++- custom_components/nicehash/payout_sensors.py | 118 ++++++++++++++++++ custom_components/nicehash/sensor.py | 18 ++- 6 files changed, 209 insertions(+), 11 deletions(-) create mode 100644 custom_components/nicehash/payout_sensors.py diff --git a/custom_components/nicehash/__init__.py b/custom_components/nicehash/__init__.py index c49cfc1..702349c 100644 --- a/custom_components/nicehash/__init__.py +++ b/custom_components/nicehash/__init__.py @@ -20,6 +20,7 @@ from .const import ( CONF_ORGANIZATION_ID, CONF_RIGS_ENABLED, CONF_DEVICES_ENABLED, + CONF_PAYOUTS_ENABLED, CURRENCY_USD, DOMAIN, STARTUP_MESSAGE, @@ -27,6 +28,7 @@ from .const import ( from .nicehash import NiceHashPrivateClient from .data_coordinators import ( AccountsDataUpdateCoordinator, + MiningPayoutsDataUpdateCoordinator, MiningRigsDataUpdateCoordinator, ) @@ -42,6 +44,7 @@ CONFIG_SCHEMA = vol.Schema( vol.Required(CONF_CURRENCY, default=CURRENCY_USD): cv.string, vol.Required(CONF_RIGS_ENABLED, default=False): cv.boolean, vol.Required(CONF_DEVICES_ENABLED, default=False): cv.boolean, + vol.Required(CONF_PAYOUTS_ENABLED, default=False): cv.boolean, } ) }, @@ -64,22 +67,39 @@ async def async_setup(hass: HomeAssistant, config: Config): currency = nicehash_config.get(CONF_CURRENCY).upper() rigs_enabled = nicehash_config.get(CONF_RIGS_ENABLED) devices_enabled = nicehash_config.get(CONF_DEVICES_ENABLED) + payouts_enabled = nicehash_config.get(CONF_PAYOUTS_ENABLED) client = NiceHashPrivateClient(organization_id, api_key, api_secret) - accounts_coordinator = AccountsDataUpdateCoordinator(hass, client) + hass.data[DOMAIN]["organization_id"] = organization_id + hass.data[DOMAIN]["client"] = client + hass.data[DOMAIN]["currency"] = currency + hass.data[DOMAIN]["rigs_enabled"] = rigs_enabled + hass.data[DOMAIN]["devices_enabled"] = devices_enabled + hass.data[DOMAIN]["payouts_enabled"] = payouts_enabled + # Accounts + accounts_coordinator = AccountsDataUpdateCoordinator(hass, client) await accounts_coordinator.async_refresh() if not accounts_coordinator.last_update_success: _LOGGER.error("Unable to get NiceHash accounts") raise PlatformNotReady - hass.data[DOMAIN]["organization_id"] = organization_id - hass.data[DOMAIN]["client"] = client - hass.data[DOMAIN]["currency"] = currency hass.data[DOMAIN]["accounts_coordinator"] = accounts_coordinator + # Payouts + if payouts_enabled: + payouts_coordinator = MiningPayoutsDataUpdateCoordinator(hass, client) + await payouts_coordinator.async_refresh() + + if not payouts_coordinator.last_update_success: + _LOGGER.error("Unable to get NiceHash mining payouts") + raise PlatformNotReady + + hass.data[DOMAIN]["payouts_coordinator"] = payouts_coordinator + + # Rigs if rigs_enabled or devices_enabled: rigs_coordinator = MiningRigsDataUpdateCoordinator(hass, client) await rigs_coordinator.async_refresh() @@ -88,8 +108,6 @@ async def async_setup(hass: HomeAssistant, config: Config): _LOGGER.error("Unable to get NiceHash mining rigs") raise PlatformNotReady - hass.data[DOMAIN]["rigs_enabled"] = rigs_enabled - hass.data[DOMAIN]["devices_enabled"] = devices_enabled hass.data[DOMAIN]["rigs_coordinator"] = rigs_coordinator await discovery.async_load_platform(hass, "sensor", DOMAIN, {}, config) diff --git a/custom_components/nicehash/const.py b/custom_components/nicehash/const.py index 51965cf..6503cba 100644 --- a/custom_components/nicehash/const.py +++ b/custom_components/nicehash/const.py @@ -31,7 +31,7 @@ CONF_ORGANIZATION_ID = "organization_id" CONF_CURRENCY = "currency" CONF_RIGS_ENABLED = "rigs" CONF_DEVICES_ENABLED = "devices" - +CONF_PAYOUTS_ENABLED = "payouts" # Defaults DEFAULT_NAME = NAME @@ -73,3 +73,5 @@ DEVICE_SPEED_RATE = "device-speed-rate" DEVICE_SPEED_ALGORITHM = "device-speed-algorithm" DEVICE_LOAD = "device-load" DEVICE_RPM = "device-rpm" +# Payout types +PAYOUT_USER = "USER" diff --git a/custom_components/nicehash/data_coordinators.py b/custom_components/nicehash/data_coordinators.py index bcf269c..9877356 100644 --- a/custom_components/nicehash/data_coordinators.py +++ b/custom_components/nicehash/data_coordinators.py @@ -18,6 +18,7 @@ from .nicehash import NiceHashPrivateClient, NiceHashPublicClient SCAN_INTERVAL_RIGS = timedelta(minutes=1) SCAN_INTERVAL_ACCOUNTS = timedelta(minutes=60) +SCAN_INTERVAL_PAYOUTS = timedelta(minutes=60) _LOGGER = logging.getLogger(__name__) @@ -80,3 +81,26 @@ class MiningRigsDataUpdateCoordinator(DataUpdateCoordinator): return data except Exception as e: raise UpdateFailed(e) + + +class MiningPayoutsDataUpdateCoordinator(DataUpdateCoordinator): + """Manages fetching mining rig payout data from NiceHash API""" + + def __init__(self, hass: HomeAssistant, client: NiceHashPrivateClient): + """Initialize""" + self.name = f"{DOMAIN}_mining_payouts_coordinator" + self._client = client + + super().__init__( + hass, _LOGGER, name=self.name, update_interval=SCAN_INTERVAL_PAYOUTS + ) + + async def _async_update_data(self): + """Update mining payouts data""" + try: + data = await self._client.get_rig_payouts(42) # 6 (per day) * 7 days + payouts = data.get("list") + payouts.sort(key=lambda payout: payout.get("created")) + return payouts + except Exception as e: + raise UpdateFailed(e) diff --git a/custom_components/nicehash/nicehash.py b/custom_components/nicehash/nicehash.py index 2610962..387d78f 100644 --- a/custom_components/nicehash/nicehash.py +++ b/custom_components/nicehash/nicehash.py @@ -18,7 +18,7 @@ from .const import NICEHASH_API_URL class MiningRigDevice: - def __init__(self, data): + def __init__(self, data: dict): self.device_id = data.get("id") self.name = data.get("name") self.status = data.get("status").get("description") @@ -29,7 +29,7 @@ class MiningRigDevice: class MiningRig: - def __init__(self, data): + def __init__(self, data: dict): self.rig_id = data.get("rig_id") self.name = data.get("name") self.status = data.get("minerStatus") @@ -46,6 +46,24 @@ class MiningRig: self.temperatures.append(device.temperature) +class Payout: + def __init__(self, data: dict): + self.id = data.get("id") + self.currency = "Unknown" + self.created = data.get("created") + self.amount = data.get("amount") + self.fee = data.get("feeAmount") + self.account_type = "Unknown" + # Currency + currency = data.get("currency") + if currency: + self.currency = currency.get("enumName") + # Account Type + account_type = data.get("accountType") + if account_type: + self.account_type = account_type.get("enumName") + + class NiceHashPublicClient: async def get_exchange_rates(self): exchange_data = await self.request("GET", "/main/api/v2/exchangeRate/list") @@ -89,6 +107,10 @@ class NiceHashPrivateClient: async def get_mining_rig(self, rig_id): return await self.request("GET", f"/main/api/v2/mining/rig2/{rig_id}") + async def get_rig_payouts(self, size=84): + query = f"size={size}" + return await self.request("GET", "/main/api/v2/mining/rigs/payouts", query) + async def request(self, method, path, query="", body=None): xtime = self.get_epoch_ms_from_now() xnonce = str(uuid.uuid4()) diff --git a/custom_components/nicehash/payout_sensors.py b/custom_components/nicehash/payout_sensors.py new file mode 100644 index 0000000..cb08c50 --- /dev/null +++ b/custom_components/nicehash/payout_sensors.py @@ -0,0 +1,118 @@ +""" +NiceHash Rig Payout Sensors +""" +from datetime import datetime +import logging + +from homeassistant.const import ATTR_ATTRIBUTION +from homeassistant.helpers.entity import Entity + +from .const import ( + CURRENCY_BTC, + FORMAT_DATETIME, + ICON_CURRENCY_BTC, + ICON_PULSE, + ICON_THERMOMETER, + NICEHASH_ATTRIBUTION, + PAYOUT_USER, +) +from .data_coordinators import ( + MiningPayoutsDataUpdateCoordinator, + MiningRigsDataUpdateCoordinator, +) +from .nicehash import MiningRig, Payout + +_LOGGER = logging.getLogger(__name__) + + +class RecentMiningPayoutSensor(Entity): + """ + Displays most recent payout of a mining rig + """ + + def __init__( + self, coordinator: MiningPayoutsDataUpdateCoordinator, organization_id: str + ): + """Initialize the sensor""" + self.coordinator = coordinator + self.organization_id = organization_id + self._id = None + self._created = None + self._currency = None + self._amount = 0.00 + self._fee = 0.00 + + @property + def name(self): + """Sensor name""" + return f"Recent Mining Payout" + + @property + def unique_id(self): + """Unique entity id""" + return f"{self.organization_id}:payouts:recent" + + @property + def should_poll(self): + """No need to poll, 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""" + try: + for raw_payout in self.coordinator.data: + payout = Payout(raw_payout) + if payout.account_type == PAYOUT_USER: + self._id = payout.id + self._amount = payout.amount + self._currency = payout.currency + self._created = datetime.fromtimestamp(payout.created / 1000.0) + self._fee = payout.fee + except Exception as e: + _LOGGER.error(f"Unable to get most recent \n{e}") + self._id = None + self._created = None + self._currency = None + self._amount = 0.00 + self._fee = 0.00 + + return self._amount + + @property + def icon(self): + """Sensor icon""" + return ICON_CURRENCY_BTC + + @property + def unit_of_measurement(self): + """Sensor unit of measurement""" + return CURRENCY_BTC + + @property + def device_state_attributes(self): + """Sensor device state attributes""" + created = None + if self._created: + created = self._created.strftime(FORMAT_DATETIME) + return { + ATTR_ATTRIBUTION: NICEHASH_ATTRIBUTION, + "amount": self._amount, + "created": created, + "fee": self._fee, + } + + 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() diff --git a/custom_components/nicehash/sensor.py b/custom_components/nicehash/sensor.py index 136758f..99ede3c 100644 --- a/custom_components/nicehash/sensor.py +++ b/custom_components/nicehash/sensor.py @@ -23,6 +23,7 @@ from .const import ( ) from .nicehash import NiceHashPrivateClient, NiceHashPublicClient from .account_sensors import BalanceSensor +from .payout_sensors import RecentMiningPayoutSensor from .rig_sensors import ( RigStatusSensor, RigTemperatureSensor, @@ -52,6 +53,7 @@ async def async_setup_platform( client = data.get("client") # Options currency = data.get("currency") + payouts_enabled = data.get("payouts_enabled") rigs_enabled = data.get("rigs_enabled") devices_enabled = data.get("devices_enabled") @@ -62,17 +64,22 @@ async def async_setup_platform( ) async_add_entities(balance_sensors, True) + # Payout sensors + if payouts_enabled: + payouts_coordinator = data.get("payouts_coordinator") + payout_sensors = create_payout_sensors(organization_id, payouts_coordinator) + async_add_entities(payout_sensors) + + # Mining rig and device sensors if rigs_enabled or devices_enabled: rigs_coordinator = data.get("rigs_coordinator") rig_data = await client.get_mining_rigs() mining_rigs = rig_data.get("miningRigs") - # Add mining rig sensors if enabled if rigs_enabled: rig_sensors = create_rig_sensors(mining_rigs, rigs_coordinator) async_add_entities(rig_sensors, True) - # Add device sensors if enabled if devices_enabled: device_sensors = create_device_sensors(mining_rigs, rigs_coordinator) async_add_entities(device_sensors, True) @@ -130,6 +137,13 @@ def create_balance_sensors(organization_id, currency, coordinator): return balance_sensors +def create_payout_sensors(organization_id, coordinator): + payout_sensors = [] + payout_sensors.append(RecentMiningPayoutSensor(coordinator, organization_id)) + + return payout_sensors + + def create_rig_sensors(mining_rigs, coordinator): rig_sensors = [] for rig in mining_rigs: