Compare commits

..

10 Commits

Author SHA1 Message Date
Cathal O Cuimin
a7a91c9dfe
Add unit of measurement for rig speed 2023-07-03 17:52:53 +02:00
Cathal O Cuimin
6b85fe5e70
Add support for all currencies 2023-07-03 17:52:43 +02:00
Evan Lawrence
0e94842c9d
Added switches for each device which allow for enabling/disabling mining.
very basic, needs testing.

* To do: Set switch to reflect the current state on init. (Currently defaults to off after HA reboot.)
* Occasionally the custom component fails to load on HA start.  Usually fixed by restarting HA.
* When switches are toggled occasionally a false error is reported.  This could be due to async call.  The desired behavior still occurs though.
2023-07-03 17:51:08 +02:00
Newlance
bbe51fef8d
Update README.md 2023-07-03 17:47:22 +02:00
Evan Lawrence
991755ee4e
- Device switch status now properly reflects status of mining device (there is a delay for the status to update into Home Assistant)
- Added property to switches which contains the last response from the API.  This will reflect "Success!" or otherwise the error message that was provided by the API.
- Added additional properties 'status' and 'device' to switches.
2023-07-03 17:47:11 +02:00
Pascal Berski
2aca201d3b
added icon power 2023-07-03 17:45:11 +02:00
Pascal Berski
53f35f04ea
added class DevicePowerSensor 2023-07-03 17:45:03 +02:00
Pascal Berski
829d1cd3f7
added DevicePowerSensor (sensor.py / nicehash.py) 2023-07-03 17:44:54 +02:00
Pascal Berski
dde8ba7417
Update README.md 2023-07-03 17:44:45 +02:00
Pascal Berski
454ee1008d
Update README.md 2023-07-03 17:43:03 +02:00
11 changed files with 273 additions and 6 deletions

View File

@ -28,8 +28,10 @@ A [Home Assistant][homeassistant] integration that creates a collection of [Nice
- Algorithm
- Speed
- Temperature
- HotSpot Temperature
- Load
- RPM
- Power
- Most Recent Mining Payout
None of the sensors are added by default. See installation instructions for available configuration options.
@ -46,6 +48,8 @@ Supported API permissions and associated sensors
- Account balance sensors
- Mining Permissions > View mining data...
- Rig, device, and payout sensors
- Mining Permissions > Manage Rigs
- Device start/stop switch
See this [repository](https://github.com/nicehash/rest-clients-demo) for further assistance generating an API key.

View File

@ -118,6 +118,8 @@ async def async_setup(hass: HomeAssistant, config: Config):
hass.data[DOMAIN]["rigs_coordinator"] = rigs_coordinator
await discovery.async_load_platform(hass, "switch", DOMAIN, {}, config)
await discovery.async_load_platform(hass, "sensor", DOMAIN, {}, config)
return True

View File

@ -102,8 +102,11 @@ class BalanceSensor(Entity):
return ICON_CURRENCY_EUR
elif self.currency == CURRENCY_USD:
return ICON_CURRENCY_USD
elif self.currency == CURRENCY_BTC:
return ICON_CURRENCY_BTC
return ICON_CURRENCY_USD
@property
def unit_of_measurement(self):
"""Sensor unit of measurement"""

View File

@ -19,6 +19,7 @@ ICON_PICKAXE = "mdi:pickaxe"
ICON_PULSE = "mdi:pulse"
ICON_THERMOMETER = "mdi:thermometer"
ICON_SPEEDOMETER = "mdi:speedometer"
ICON_POWER = "mdi:power-plug"
# Platforms
SENSOR = "sensor"
@ -57,6 +58,23 @@ NICEHASH_ATTRIBUTION = "Data provided by NiceHash"
CURRENCY_BTC = "BTC"
CURRENCY_USD = "USD"
CURRENCY_EUR = "EUR"
SUPPORTED_CURRENCIES = [
"ERN","HKD","GGP","RSD","SHP","USD","MYR","PYG","RON","DOP","TWD","AWG",
"CVE","BND","RUB","NGN","XCD","JEP","ZWL","HNL","NZD","AFN","MUR","DKK",
"CNY","JOD","CHF","COP","XAF","XAG","ZMK","GNF","ZMW","GIP","BTC","MKD",
"WST","IDR","IQD","BHD","YER","MAD","KGS","PHP","PEN","BMD","DJF","MVR",
"QAR","JPY","SCR","IMP","KRW","HRK","SOS","VUV","NIO","KYD","LAK","ISK",
"BOB","IRR","NPR","EGP","BBD","CAD","XAU","CUP","SDG","PKR","UZS","CLF",
"CUC","STD","MGA","FJD","DZD","TJS","EURKM","SZL","THB","SRD","BDT",
"BTN","CZK","AMD","UGX","TRY","AUD","UAH","HUF","SLL","VND","RWF","LBP",
"ANG","SAR","LVL","KHR","BYR","TTD","OMR","LTL","GTQ","ALL","MRO","MWK",
"LSL","SBD","BGN","LRD","JMD","CRC","ETB","NAD","GYD","LKR","INR","SEK",
"KES","KMF","VEF","ARS","HTG","BAM","BWP","GEL","KZT","AED","KWD","XDR",
"EUR","TND","MDL","LYD","BSD","GHS","MOP","PAB","ZAR","AZN","TOP","SVC",
"KPW","TMT","BZD","GMD","XOF","UYU","MNT","NOK","XPF","BIF","BYN","FKP",
"GBP","MXN","SYP","PGK","MZN","PLN","MMK","SGD","AOA","ILS","CLP","TZS",
"CDF","BRL"
]
# Balance type
BALANCE_TYPE_AVAILABLE = "available"
BALANCE_TYPE_PENDING = "pending"

View File

@ -0,0 +1,89 @@
"""
NiceHash Rig controls
"""
from datetime import datetime
import logging
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.helpers.entity import Entity
from homeassistant.components.switch import SwitchEntity
from .coordinators import MiningRigsDataUpdateCoordinator
from .nicehash import MiningRig, MiningRigDevice, NiceHashPrivateClient
from .const import DOMAIN, NICEHASH_ATTRIBUTION
import asyncio
class DeviceSwitch(SwitchEntity):
def __init__(
self,
coordinator: MiningRigsDataUpdateCoordinator,
rig: MiningRig,
device: MiningRigDevice,
client: NiceHashPrivateClient,
):
"""Initialize the switch"""
self.coordinator = coordinator
self._rig_id = rig.id
self._rig_name = rig.name
self._device_id = device.id
self._device_name = device.name
self._client = client
self._status = device.status
def _get_device(self):
try:
mining_rigs = self.coordinator.data.get("miningRigs")
rig = MiningRig(mining_rigs.get(self._rig_id))
return rig.devices.get(self._device_id)
except Exception as e:
_LOGGER.error(f"Unable to get mining device ({self._device_id})\n{e}")
class GPUSwitch(DeviceSwitch):
_is_on = False
_last_response = "N/A"
@property
def name(self):
"""switch name"""
return f"{self._device_name} Switch"
@property
def unique_id(self):
"""Unique entity id"""
return f"{self._device_id}:switch"
@property
def is_on(self):
device = self._get_device()
if device.status == "Mining":
self._is_on = True
elif device:
self._is_on = False
else:
self._is_on = "unavailable"
return self._is_on
@property
def device_state_attributes(self):
"""Sensor device state attributes"""
return {
ATTR_ATTRIBUTION: NICEHASH_ATTRIBUTION,
"status": self._status,
"device": self._device_name,
"last_response": self._last_response,
}
def turn_on(self, **kwargs):
"""Turn the switch on."""
self._is_on = True
response = asyncio.run(self._client.toggle_device(self._device_id, "START", self._rig_id))
self._last_response = "Success!" if response["success"] else response["message"]
def turn_off(self, **kwargs):
"""Turn the switch off."""
self._is_on = False
response = asyncio.run(self._client.toggle_device(self._device_id, "STOP", self._rig_id))
self._last_response = "Success!" if response["success"] else response["message"]

View File

@ -12,6 +12,7 @@ from homeassistant.helpers.update_coordinator import (
from .const import (
CURRENCY_BTC,
CURRENCY_USD,
DOMAIN,
)
from .nicehash import NiceHashPrivateClient, NiceHashPublicClient
@ -41,13 +42,25 @@ class AccountsDataUpdateCoordinator(DataUpdateCoordinator):
accounts = await self._client.get_accounts()
exchange_rates = await NiceHashPublicClient().get_exchange_rates()
rates_dict = dict()
# NiceHash supports BTC->USD and BTC->EUR only.
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
# For non-USD/EUR currencies, get exchange rate using USD as an intermediary. e.g. BTC->USD->CAD
btc_to_usd_rate = rates_dict[f"BTC-USD"]
for rate in exchange_rates:
fromCurrency = rate.get("fromCurrency")
if fromCurrency == CURRENCY_USD:
toCurrency = rate.get("toCurrency")
exchange_rate = float(rate.get("exchangeRate")) * btc_to_usd_rate
if f"{CURRENCY_BTC}-{toCurrency}" not in rates_dict:
rates_dict[f"{CURRENCY_BTC}-{toCurrency}"] = exchange_rate
return {
"accounts": accounts,
"exchange_rates": rates_dict,

View File

@ -17,6 +17,7 @@ from .const import (
ICON_PULSE,
ICON_THERMOMETER,
ICON_SPEEDOMETER,
ICON_POWER,
NICEHASH_ATTRIBUTION,
)
from .coordinators import MiningRigsDataUpdateCoordinator
@ -381,3 +382,48 @@ class DeviceRPMSensor(DeviceSensor):
"rig": self._rig_name,
}
class DevicePowerSensor(DeviceSensor):
"""
Displays power of a mining rig device
"""
@property
def name(self):
"""Sensor name"""
return f"{self._device_name} Power"
@property
def unique_id(self):
"""Unique entity id"""
return f"{self._device_id}:power"
@property
def state(self):
"""Sensor state"""
device = self._get_device()
if device:
self._power = device.power
else:
self._power = 0
return self._power
@property
def icon(self):
"""Sensor icon"""
return ICON_POWER
@property
def unit_of_measurement(self):
"""Sensor unit of measurement"""
return "W"
@property
def device_state_attributes(self):
"""Sensor device state attributes"""
return {
ATTR_ATTRIBUTION: NICEHASH_ATTRIBUTION,
"power": self._power,
"rig": self._rig_name,
}

View File

@ -49,6 +49,7 @@ class MiningRigDevice:
self.load = float(data.get("load"))
self.rpm = float(data.get("revolutionsPerMinute"))
self.speeds = data.get("speeds")
self.power = float(data.get("powerUsage"))
class MiningRig:
@ -156,6 +157,13 @@ class NiceHashPrivateClient:
query = f"size={size}"
return await self.request("GET", "/main/api/v2/mining/rigs/payouts", query)
async def toggle_device(self, device_id, action, rig_id):
query = ""
body = {"deviceId":device_id,
"action":action,
"rigId":rig_id}
return await self.request("POST", "/main/api/v2/mining/rigs/status2", query, body)
async def request(self, method, path, query="", body=None):
xtime = self.get_epoch_ms_from_now()
xnonce = str(uuid.uuid4())

View File

@ -378,7 +378,7 @@ class RigSpeedSensor(RigSensor):
@property
def unit_of_measurement(self):
"""Sensor unit of measurement"""
return None
return f"{self._unit}"
@property
def device_state_attributes(self):

View File

@ -20,6 +20,7 @@ from .const import (
DEVICE_RPM,
DEVICE_SPEED_RATE,
DEVICE_SPEED_ALGORITHM,
SUPPORTED_CURRENCIES,
)
from .nicehash import (
MiningRig,
@ -39,6 +40,7 @@ from .rig_sensors import (
)
from .device_sensors import (
DeviceAlgorithmSensor,
DevicePowerSensor,
DeviceSpeedSensor,
DeviceStatusSensor,
DeviceLoadSensor,
@ -121,7 +123,7 @@ def create_balance_sensors(organization_id, currency, coordinator):
balance_type=BALANCE_TYPE_TOTAL,
),
]
if currency == CURRENCY_USD or currency == CURRENCY_EUR:
if currency in SUPPORTED_CURRENCIES:
_LOGGER.debug(f"Creating {currency} account balance sensors")
balance_sensors.append(
BalanceSensor(
@ -148,8 +150,7 @@ def create_balance_sensors(organization_id, currency, coordinator):
)
)
else:
_LOGGER.warn("Invalid currency: must be EUR or USD")
_LOGGER.warn("Currency is invalid.")
return balance_sensors
@ -192,5 +193,6 @@ def create_device_sensors(mining_rigs, coordinator):
device_sensors.append(DeviceTemperatureSensor(coordinator, rig, device))
device_sensors.append(DeviceLoadSensor(coordinator, rig, device))
device_sensors.append(DeviceRPMSensor(coordinator, rig, device))
device_sensors.append(DevicePowerSensor(coordinator, rig, device))
return device_sensors

View File

@ -0,0 +1,82 @@
"""
Sensor platform for NiceHash
"""
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.core import Config, HomeAssistant
from .const import (
BALANCE_TYPE_AVAILABLE,
BALANCE_TYPE_PENDING,
BALANCE_TYPE_TOTAL,
CURRENCY_BTC,
CURRENCY_EUR,
CURRENCY_USD,
DOMAIN,
DEVICE_LOAD,
DEVICE_RPM,
DEVICE_SPEED_RATE,
DEVICE_SPEED_ALGORITHM,
)
from .nicehash import (
MiningRig,
MiningRigDevice,
NiceHashPrivateClient,
NiceHashPublicClient,
)
from .control_switches import (
GPUSwitch
)
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(
hass: HomeAssistant, config: Config, async_add_entities, discovery_info=None
):
"""Setup NiceHash sensor platform"""
_LOGGER.debug("Creating new NiceHash switch components")
data = hass.data[DOMAIN]
# Configuration
organization_id = data.get("organization_id")
client = data.get("client")
# Options
currency = data.get("currency")
balances_enabled = data.get("balances_enabled")
payouts_enabled = data.get("payouts_enabled")
rigs_enabled = data.get("rigs_enabled")
devices_enabled = data.get("devices_enabled")
# 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")
_LOGGER.debug(f"Found {len(mining_rigs)} rigs")
if devices_enabled:
_LOGGER.debug("Device sensors enabled")
device_switches = create_device_switches(mining_rigs, rigs_coordinator,client)
async_add_entities(device_switches, True)
def create_device_switches(mining_rigs, coordinator, client):
device_switches = []
for rig_data in mining_rigs:
rig = MiningRig(rig_data)
devices = rig.devices.values()
_LOGGER.debug(
f"Found {len(devices)} device switches(s) for {rig.name} ({rig.id})"
)
for device in devices:
_LOGGER.debug(f"Creating {device.name} ({device.id}) switches")
device_switches.append(GPUSwitch(coordinator, rig, device, client))
return device_switches