From 6e7442f90d4ef662b9431667f71c0b53b231d21c Mon Sep 17 00:00:00 2001 From: UpstreamData <75442874+UpstreamData@users.noreply.github.com> Date: Thu, 4 Jan 2024 13:03:45 -0700 Subject: [PATCH] Update data locations to be typed with dataclasses and enums. (#82) * feature: swap AntminerModern to new data location style. * bug: fix a bunch of missed instances of `nominal_` naming. * feature: add support for S19 Pro Hydro. * version: bump version number. * dependencies: bump httpx version * version: bump version number. * feature: implement data locations for all remaining miners. * refactor: remove some unused docstrings. * feature: swap AntminerModern to new data location style. * feature: implement data locations for all remaining miners. * refactor: remove some unused docstrings. * bug: fix misnamed data locations, and update base miner get_data to use new data locations. * bug: fix include/exclude implementation on get_data. * bug: swap ePIC to BaseMiner subclass. * feature: add DataOptions to __all__ * tests: update data tests with new data locations method. * bug: remove bad command from bosminer commands. * dependencies: update dependencies. * bug: fix some typing issues with python 3.8, and remove useless semaphore and scan threads. * bug: fix KeyError when pools rpc command returns broken data. --- pyasic/API/btminer.py | 6 +- pyasic/__init__.py | 3 +- pyasic/config/mining.py | 4 +- pyasic/config/pools.py | 13 +- pyasic/miners/backends/antminer.py | 173 ++++---- pyasic/miners/backends/bfgminer.py | 92 ++--- pyasic/miners/backends/bfgminer_goldshell.py | 80 ++-- pyasic/miners/backends/bmminer.py | 94 ++--- pyasic/miners/backends/bosminer.py | 380 +++++++----------- pyasic/miners/backends/bosminer_old.py | 3 - pyasic/miners/backends/btminer.py | 172 ++++---- pyasic/miners/backends/cgminer.py | 97 ++--- pyasic/miners/backends/cgminer_avalon.py | 103 +++-- pyasic/miners/backends/epic.py | 162 ++++---- pyasic/miners/backends/luxminer.py | 121 ++---- pyasic/miners/backends/vnish.py | 76 ++-- pyasic/miners/base.py | 139 +++++-- .../miners/innosilicon/cgminer/A10X/A10X.py | 26 -- pyasic/miners/innosilicon/cgminer/T3X/T3H.py | 26 -- pyasic/miners/unknown.py | 28 +- pyasic/network/__init__.py | 34 +- pyasic/settings/__init__.py | 1 - pyproject.toml | 6 +- tests/miners_tests/__init__.py | 6 +- 24 files changed, 861 insertions(+), 984 deletions(-) diff --git a/pyasic/API/btminer.py b/pyasic/API/btminer.py index 1ce805cc..fb1414a1 100644 --- a/pyasic/API/btminer.py +++ b/pyasic/API/btminer.py @@ -48,10 +48,10 @@ PrePowerOnMessage = Union[ def _crypt(word: str, salt: str) -> str: - """Encrypts a word with a salt, using a standard salt format. + r"""Encrypts a word with a salt, using a standard salt format. Encrypts a word using a salt with the format - '\s*\$(\d+)\$([\w\./]*)\$'. If this format is not used, a + \s*\$(\d+)\$([\w\./]*)\$. If this format is not used, a ValueError is raised. Parameters: @@ -62,7 +62,7 @@ def _crypt(word: str, salt: str) -> str: An MD5 hash of the word with the salt. """ # compile a standard format for the salt - standard_salt = re.compile("\s*\$(\d+)\$([\w\./]*)\$") + standard_salt = re.compile(r"\s*\$(\d+)\$([\w\./]*)\$") # check if the salt matches match = standard_salt.match(salt) # if the matching fails, the salt is incorrect diff --git a/pyasic/__init__.py b/pyasic/__init__.py index 14b24259..df2b4cbb 100644 --- a/pyasic/__init__.py +++ b/pyasic/__init__.py @@ -29,7 +29,7 @@ from pyasic.data import ( ) from pyasic.errors import APIError, APIWarning from pyasic.miners import get_miner -from pyasic.miners.base import AnyMiner +from pyasic.miners.base import AnyMiner, DataOptions from pyasic.miners.miner_factory import MinerFactory, miner_factory from pyasic.miners.miner_listener import MinerListener from pyasic.network import MinerNetwork @@ -50,6 +50,7 @@ __all__ = [ "APIWarning", "get_miner", "AnyMiner", + "DataOptions", "MinerFactory", "miner_factory", "MinerListener", diff --git a/pyasic/config/mining.py b/pyasic/config/mining.py index ccb43fe9..38ca847c 100644 --- a/pyasic/config/mining.py +++ b/pyasic/config/mining.py @@ -14,7 +14,7 @@ # limitations under the License. - # ------------------------------------------------------------------------------ from dataclasses import dataclass, field -from typing import Union +from typing import Dict, Union from pyasic.config.base import MinerConfigOption, MinerConfigValue @@ -132,7 +132,7 @@ class MiningModeManual(MinerConfigValue): global_freq: float global_volt: float - boards: dict[int, ManualBoardSettings] = field(default_factory=dict) + boards: Dict[int, ManualBoardSettings] = field(default_factory=dict) @classmethod def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeManual": diff --git a/pyasic/config/pools.py b/pyasic/config/pools.py index ab739d86..c6b330b2 100644 --- a/pyasic/config/pools.py +++ b/pyasic/config/pools.py @@ -16,7 +16,7 @@ import random import string from dataclasses import dataclass, field -from typing import Union +from typing import Dict, List, Union from pyasic.config.base import MinerConfigValue @@ -144,7 +144,7 @@ class Pool(MinerConfigValue): @dataclass class PoolGroup(MinerConfigValue): - pools: list[Pool] = field(default_factory=list) + pools: List[Pool] = field(default_factory=list) quota: int = 1 name: str = None @@ -278,7 +278,7 @@ class PoolGroup(MinerConfigValue): @dataclass class PoolConfig(MinerConfigValue): - groups: list[PoolGroup] = field(default_factory=list) + groups: List[PoolGroup] = field(default_factory=list) @classmethod def default(cls) -> "PoolConfig": @@ -292,7 +292,7 @@ class PoolConfig(MinerConfigValue): return cls(groups=[PoolGroup.from_dict(g) for g in dict_conf["groups"]]) @classmethod - def simple(cls, pools: list[Union[Pool, dict[str, str]]]) -> "PoolConfig": + def simple(cls, pools: List[Union[Pool, Dict[str, str]]]) -> "PoolConfig": group_pools = [] for pool in pools: if isinstance(pool, dict): @@ -342,7 +342,10 @@ class PoolConfig(MinerConfigValue): @classmethod def from_api(cls, api_pools: dict) -> "PoolConfig": - pool_data = api_pools["POOLS"] + try: + pool_data = api_pools["POOLS"] + except KeyError: + return PoolConfig.default() pool_data = sorted(pool_data, key=lambda x: int(x["POOL"])) return cls([PoolGroup.from_api(pool_data)]) diff --git a/pyasic/miners/backends/antminer.py b/pyasic/miners/backends/antminer.py index b0213494..e4db9916 100644 --- a/pyasic/miners/backends/antminer.py +++ b/pyasic/miners/backends/antminer.py @@ -14,7 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import asyncio from typing import List, Optional, Union from pyasic.API import APIError @@ -23,50 +22,62 @@ from pyasic.data import Fan, HashBoard from pyasic.data.error_codes import MinerErrorData, X19Error from pyasic.miners.backends.bmminer import BMMiner from pyasic.miners.backends.cgminer import CGMiner +from pyasic.miners.base import ( + DataFunction, + DataLocations, + DataOptions, + RPCAPICommand, + WebAPICommand, +) from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI -ANTMINER_MODERN_DATA_LOC = { - "mac": { - "cmd": "get_mac", - "kwargs": {"web_get_system_info": {"web": "get_system_info"}}, - }, - "model": {"cmd": "get_model", "kwargs": {}}, - "api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, - "fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, - "hostname": { - "cmd": "get_hostname", - "kwargs": {"web_get_system_info": {"web": "get_system_info"}}, - }, - "hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, - "expected_hashrate": { - "cmd": "get_expected_hashrate", - "kwargs": {"api_stats": {"api": "stats"}}, - }, - "hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, - "env_temp": {"cmd": "get_env_temp", "kwargs": {}}, - "wattage": {"cmd": "get_wattage", "kwargs": {}}, - "wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, - "fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, - "fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, - "errors": {"cmd": "get_errors", "kwargs": {"web_summary": {"web": "summary"}}}, - "fault_light": { - "cmd": "get_fault_light", - "kwargs": {"web_get_blink_status": {"web": "get_blink_status"}}, - }, - "pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, - "is_mining": { - "cmd": "is_mining", - "kwargs": {"web_get_conf": {"web": "get_miner_conf"}}, - }, - "uptime": { - "cmd": "get_uptime", - "kwargs": {"api_stats": {"api": "stats"}}, - }, - "config": { - "cmd": "get_config", - "kwargs": {}, - }, -} +ANTMINER_MODERN_DATA_LOC = DataLocations( + **{ + str(DataOptions.MAC): DataFunction( + "get_mac", [WebAPICommand("web_get_system_info", "get_system_info")] + ), + str(DataOptions.MODEL): DataFunction("get_model"), + str(DataOptions.API_VERSION): DataFunction( + "get_api_ver", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.FW_VERSION): DataFunction( + "get_fw_ver", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.HOSTNAME): DataFunction( + "get_hostname", [WebAPICommand("web_get_system_info", "get_system_info")] + ), + str(DataOptions.HASHRATE): DataFunction( + "get_hashrate", [RPCAPICommand("api_summary", "summary")] + ), + str(DataOptions.EXPECTED_HASHRATE): DataFunction( + "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.HASHBOARDS): DataFunction( + "get_hashboards", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), + str(DataOptions.WATTAGE): DataFunction("get_wattage"), + str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), + str(DataOptions.FANS): DataFunction( + "get_fans", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.ERRORS): DataFunction( + "get_errors", [WebAPICommand("web_summary", "summary")] + ), + str(DataOptions.FAULT_LIGHT): DataFunction( + "get_fault_light", + [WebAPICommand("web_get_blink_status", "get_blink_status")], + ), + str(DataOptions.IS_MINING): DataFunction( + "is_mining", [WebAPICommand("web_get_conf", "get_miner_conf")] + ), + str(DataOptions.UPTIME): DataFunction( + "get_uptime", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.CONFIG): DataFunction("get_config"), + } +) class AntminerModern(BMMiner): @@ -298,39 +309,49 @@ class AntminerModern(BMMiner): pass -ANTMINER_OLD_DATA_LOC = { - "mac": {"cmd": "get_mac", "kwargs": {}}, - "model": {"cmd": "get_model", "kwargs": {}}, - "api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, - "fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, - "hostname": { - "cmd": "get_hostname", - "kwargs": {"web_get_system_info": {"web": "get_system_info"}}, - }, - "hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, - "expected_hashrate": { - "cmd": "get_expected_hashrate", - "kwargs": {"api_stats": {"api": "stats"}}, - }, - "hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, - "env_temp": {"cmd": "get_env_temp", "kwargs": {}}, - "wattage": {"cmd": "get_wattage", "kwargs": {}}, - "wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, - "fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, - "fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, - "errors": {"cmd": "get_errors", "kwargs": {}}, - "fault_light": { - "cmd": "get_fault_light", - "kwargs": {"web_get_blink_status": {"web": "get_blink_status"}}, - }, - "pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, - "is_mining": { - "cmd": "is_mining", - "kwargs": {"web_get_conf": {"web": "get_miner_conf"}}, - }, - "uptime": {"cmd": "get_uptime", "kwargs": {"api_stats": {"api": "stats"}}}, - "config": {"cmd": "get_config", "kwargs": {}}, -} +ANTMINER_OLD_DATA_LOC = DataLocations( + **{ + str(DataOptions.MAC): DataFunction("get_mac"), + str(DataOptions.MODEL): DataFunction("get_model"), + str(DataOptions.API_VERSION): DataFunction( + "get_api_ver", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.FW_VERSION): DataFunction( + "get_fw_ver", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.HOSTNAME): DataFunction( + "get_hostname", [WebAPICommand("web_get_system_info", "get_system_info")] + ), + str(DataOptions.HASHRATE): DataFunction( + "get_hashrate", [RPCAPICommand("api_summary", "summary")] + ), + str(DataOptions.EXPECTED_HASHRATE): DataFunction( + "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.HASHBOARDS): DataFunction( + "get_hashboards", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), + str(DataOptions.WATTAGE): DataFunction("get_wattage"), + str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), + str(DataOptions.FANS): DataFunction( + "get_fans", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.ERRORS): DataFunction("get_errors"), + str(DataOptions.FAULT_LIGHT): DataFunction( + "get_fault_light", + [WebAPICommand("web_get_blink_status", "get_blink_status")], + ), + str(DataOptions.IS_MINING): DataFunction( + "is_mining", [WebAPICommand("web_get_conf", "get_miner_conf")] + ), + str(DataOptions.UPTIME): DataFunction( + "get_uptime", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.CONFIG): DataFunction("get_config"), + } +) class AntminerOld(CGMiner): diff --git a/pyasic/miners/backends/bfgminer.py b/pyasic/miners/backends/bfgminer.py index 29925ce7..06f83423 100644 --- a/pyasic/miners/backends/bfgminer.py +++ b/pyasic/miners/backends/bfgminer.py @@ -22,32 +22,48 @@ from pyasic.config import MinerConfig from pyasic.data import Fan, HashBoard from pyasic.data.error_codes import MinerErrorData from pyasic.errors import APIError -from pyasic.miners.base import BaseMiner +from pyasic.miners.base import ( + BaseMiner, + DataFunction, + DataLocations, + DataOptions, + RPCAPICommand, +) -BFGMINER_DATA_LOC = { - "mac": {"cmd": "get_mac", "kwargs": {}}, - "model": {"cmd": "get_model", "kwargs": {}}, - "api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, - "fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, - "hostname": {"cmd": "get_hostname", "kwargs": {}}, - "hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, - "expected_hashrate": { - "cmd": "get_expected_hashrate", - "kwargs": {"api_stats": {"api": "stats"}}, - }, - "hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, - "env_temp": {"cmd": "get_env_temp", "kwargs": {}}, - "wattage": {"cmd": "get_wattage", "kwargs": {}}, - "wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, - "fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, - "fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, - "errors": {"cmd": "get_errors", "kwargs": {}}, - "fault_light": {"cmd": "get_fault_light", "kwargs": {}}, - "pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, - "is_mining": {"cmd": "is_mining", "kwargs": {}}, - "uptime": {"cmd": "get_uptime", "kwargs": {}}, - "config": {"cmd": "get_config", "kwargs": {}}, -} +BFGMINER_DATA_LOC = DataLocations( + **{ + str(DataOptions.MAC): DataFunction("get_mac"), + str(DataOptions.MODEL): DataFunction("get_model"), + str(DataOptions.API_VERSION): DataFunction( + "get_api_ver", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.FW_VERSION): DataFunction( + "get_fw_ver", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.HOSTNAME): DataFunction("get_hostname"), + str(DataOptions.HASHRATE): DataFunction( + "get_hashrate", [RPCAPICommand("api_summary", "summary")] + ), + str(DataOptions.EXPECTED_HASHRATE): DataFunction( + "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.HASHBOARDS): DataFunction( + "get_hashboards", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), + str(DataOptions.WATTAGE): DataFunction("get_wattage"), + str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), + str(DataOptions.FANS): DataFunction( + "get_fans", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.ERRORS): DataFunction("get_errors"), + str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"), + str(DataOptions.IS_MINING): DataFunction("is_mining"), + str(DataOptions.UPTIME): DataFunction("get_uptime"), + str(DataOptions.CONFIG): DataFunction("get_config"), + } +) class BFGMiner(BaseMiner): @@ -268,32 +284,6 @@ class BFGMiner(BaseMiner): return fans - async def get_pools(self, api_pools: dict = None) -> List[dict]: - groups = [] - - if not api_pools: - try: - api_pools = await self.api.pools() - except APIError: - pass - - if api_pools: - try: - pools = {} - for i, pool in enumerate(api_pools["POOLS"]): - pools[f"pool_{i + 1}_url"] = ( - pool["URL"] - .replace("stratum+tcp://", "") - .replace("stratum2+tcp://", "") - ) - pools[f"pool_{i + 1}_user"] = pool["User"] - pools["quota"] = pool["Quota"] if pool.get("Quota") else "0" - - groups.append(pools) - except KeyError: - pass - return groups - async def get_errors(self) -> List[MinerErrorData]: return [] diff --git a/pyasic/miners/backends/bfgminer_goldshell.py b/pyasic/miners/backends/bfgminer_goldshell.py index 4826d981..07c2d0a9 100644 --- a/pyasic/miners/backends/bfgminer_goldshell.py +++ b/pyasic/miners/backends/bfgminer_goldshell.py @@ -20,41 +20,55 @@ from pyasic.data import HashBoard from pyasic.errors import APIError from pyasic.logger import logger from pyasic.miners.backends import BFGMiner +from pyasic.miners.base import ( + DataFunction, + DataLocations, + DataOptions, + RPCAPICommand, + WebAPICommand, +) from pyasic.web.goldshell import GoldshellWebAPI -GOLDSHELL_DATA_LOC = { - "mac": {"cmd": "get_mac", "kwargs": {"web_setting": {"web": "setting"}}}, - "model": {"cmd": "get_model", "kwargs": {}}, - "api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, - "fw_ver": {"cmd": "get_fw_ver", "kwargs": {"web_status": {"web": "status"}}}, - "hostname": {"cmd": "get_hostname", "kwargs": {}}, - "hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, - "expected_hashrate": { - "cmd": "get_expected_hashrate", - "kwargs": {"api_stats": {"api": "stats"}}, - }, - "hashboards": { - "cmd": "get_hashboards", - "kwargs": { - "api_devs": {"api": "devs"}, - "api_devdetails": {"api": "devdetails"}, - }, - }, - "env_temp": {"cmd": "get_env_temp", "kwargs": {}}, - "wattage": {"cmd": "get_wattage", "kwargs": {}}, - "wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, - "fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, - "fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, - "errors": {"cmd": "get_errors", "kwargs": {}}, - "fault_light": {"cmd": "get_fault_light", "kwargs": {}}, - "pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, - "is_mining": {"cmd": "is_mining", "kwargs": {}}, - "uptime": {"cmd": "get_uptime", "kwargs": {}}, - "config": { - "cmd": "get_config", - "kwargs": {}, - }, -} +GOLDSHELL_DATA_LOC = DataLocations( + **{ + str(DataOptions.MAC): DataFunction( + "get_mac", [WebAPICommand("web_setting", "setting")] + ), + str(DataOptions.MODEL): DataFunction("get_model"), + str(DataOptions.API_VERSION): DataFunction( + "get_api_ver", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.FW_VERSION): DataFunction( + "get_fw_ver", [WebAPICommand("web_status", "status")] + ), + str(DataOptions.HOSTNAME): DataFunction("get_hostname"), + str(DataOptions.HASHRATE): DataFunction( + "get_hashrate", [RPCAPICommand("api_summary", "summary")] + ), + str(DataOptions.EXPECTED_HASHRATE): DataFunction( + "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.HASHBOARDS): DataFunction( + "get_hashboards", + [ + RPCAPICommand("api_devs", "devs"), + RPCAPICommand("api_devdetails", "devdetails"), + ], + ), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), + str(DataOptions.WATTAGE): DataFunction("get_wattage"), + str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), + str(DataOptions.FANS): DataFunction( + "get_fans", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.ERRORS): DataFunction("get_errors"), + str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"), + str(DataOptions.IS_MINING): DataFunction("is_mining"), + str(DataOptions.UPTIME): DataFunction("get_uptime"), + str(DataOptions.CONFIG): DataFunction("get_config"), + } +) class BFGMinerGoldshell(BFGMiner): diff --git a/pyasic/miners/backends/bmminer.py b/pyasic/miners/backends/bmminer.py index 41d8582f..5999c692 100644 --- a/pyasic/miners/backends/bmminer.py +++ b/pyasic/miners/backends/bmminer.py @@ -23,32 +23,50 @@ from pyasic.config import MinerConfig from pyasic.data import Fan, HashBoard from pyasic.data.error_codes import MinerErrorData from pyasic.errors import APIError -from pyasic.miners.base import BaseMiner +from pyasic.miners.base import ( + BaseMiner, + DataFunction, + DataLocations, + DataOptions, + RPCAPICommand, +) -BMMINER_DATA_LOC = { - "mac": {"cmd": "get_mac", "kwargs": {}}, - "model": {"cmd": "get_model", "kwargs": {}}, - "api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, - "fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, - "hostname": {"cmd": "get_hostname", "kwargs": {}}, - "hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, - "expected_hashrate": { - "cmd": "get_expected_hashrate", - "kwargs": {"api_stats": {"api": "stats"}}, - }, - "hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, - "env_temp": {"cmd": "get_env_temp", "kwargs": {}}, - "wattage": {"cmd": "get_wattage", "kwargs": {}}, - "wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, - "fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, - "fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, - "errors": {"cmd": "get_errors", "kwargs": {}}, - "fault_light": {"cmd": "get_fault_light", "kwargs": {}}, - "pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, - "is_mining": {"cmd": "is_mining", "kwargs": {}}, - "uptime": {"cmd": "get_uptime", "kwargs": {"api_stats": {"api": "stats"}}}, - "config": {"cmd": "get_config", "kwargs": {}}, -} +BMMINER_DATA_LOC = DataLocations( + **{ + str(DataOptions.MAC): DataFunction("get_mac"), + str(DataOptions.MODEL): DataFunction("get_model"), + str(DataOptions.API_VERSION): DataFunction( + "get_api_ver", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.FW_VERSION): DataFunction( + "get_fw_ver", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.HOSTNAME): DataFunction("get_hostname"), + str(DataOptions.HASHRATE): DataFunction( + "get_hashrate", [RPCAPICommand("api_summary", "summary")] + ), + str(DataOptions.EXPECTED_HASHRATE): DataFunction( + "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.HASHBOARDS): DataFunction( + "get_hashboards", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), + str(DataOptions.WATTAGE): DataFunction("get_wattage"), + str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), + str(DataOptions.FANS): DataFunction( + "get_fans", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.ERRORS): DataFunction("get_errors"), + str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"), + str(DataOptions.IS_MINING): DataFunction("is_mining"), + str(DataOptions.UPTIME): DataFunction( + "get_uptime", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.CONFIG): DataFunction("get_config"), + } +) class BMMiner(BaseMiner): @@ -315,32 +333,6 @@ class BMMiner(BaseMiner): return fans - async def get_pools(self, api_pools: dict = None) -> List[dict]: - groups = [] - - if not api_pools: - try: - api_pools = await self.api.pools() - except APIError: - pass - - if api_pools: - try: - pools = {} - for i, pool in enumerate(api_pools["POOLS"]): - pools[f"pool_{i + 1}_url"] = ( - pool["URL"] - .replace("stratum+tcp://", "") - .replace("stratum2+tcp://", "") - ) - pools[f"pool_{i + 1}_user"] = pool["User"] - pools["quota"] = pool["Quota"] if pool.get("Quota") else "0" - - groups.append(pools) - except KeyError: - pass - return groups - async def get_errors(self) -> List[MinerErrorData]: return [] diff --git a/pyasic/miners/backends/bosminer.py b/pyasic/miners/backends/bosminer.py index e949133b..51720562 100644 --- a/pyasic/miners/backends/bosminer.py +++ b/pyasic/miners/backends/bosminer.py @@ -27,157 +27,157 @@ from pyasic.config.mining import MiningModePowerTune from pyasic.data import Fan, HashBoard from pyasic.data.error_codes import BraiinsOSError, MinerErrorData from pyasic.errors import APIError -from pyasic.miners.base import BaseMiner +from pyasic.miners.base import ( + BaseMiner, + DataFunction, + DataLocations, + DataOptions, + GraphQLCommand, + RPCAPICommand, + WebAPICommand, +) from pyasic.web.bosminer import BOSMinerWebAPI -BOSMINER_DATA_LOC = { - "mac": { - "cmd": "get_mac", - "kwargs": { - "web_net_conf": {"web": "/cgi-bin/luci/admin/network/iface_status/lan"} - }, - }, - "model": {"cmd": "get_model", "kwargs": {}}, - "api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, - "fw_ver": { - "cmd": "get_fw_ver", - "kwargs": { - "graphql_version": {"web": {"bos": {"info": {"version": {"full": None}}}}} - }, - }, - "hostname": { - "cmd": "get_hostname", - "kwargs": {"graphql_hostname": {"web": {"bos": {"hostname": None}}}}, - }, - "hashrate": { - "cmd": "get_hashrate", - "kwargs": { - "api_summary": {"api": "summary"}, - "graphql_hashrate": { - "web": { - "bosminer": { - "info": {"workSolver": {"realHashrate": {"mhs1M": None}}} - } - }, - }, - }, - }, - "expected_hashrate": { - "cmd": "get_expected_hashrate", - "kwargs": {"api_devs": {"api": "devs"}}, - }, - "hashboards": { - "cmd": "get_hashboards", - "kwargs": { - "api_temps": {"api": "temps"}, - "api_devdetails": {"api": "devdetails"}, - "api_devs": {"api": "devs"}, - "graphql_boards": { - "web": { - "bosminer": { - "info": { - "workSolver": { - "childSolvers": { - "name": None, - "realHashrate": {"mhs1M": None}, - "hwDetails": {"chips": None}, - "temperatures": {"degreesC": None}, +BOSMINER_DATA_LOC = DataLocations( + **{ + str(DataOptions.MAC): DataFunction( + "get_mac", + [ + WebAPICommand( + "web_net_conf", "/cgi-bin/luci/admin/network/iface_status/lan" + ) + ], + ), + str(DataOptions.MODEL): DataFunction("get_model"), + str(DataOptions.API_VERSION): DataFunction( + "get_api_ver", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.FW_VERSION): DataFunction( + "get_fw_ver", + [ + GraphQLCommand( + "graphql_version", {"bos": {"info": {"version": {"full": None}}}} + ) + ], + ), + str(DataOptions.HOSTNAME): DataFunction( + "get_hostname", + [GraphQLCommand("graphql_hostname", {"bos": {"hostname": None}})], + ), + str(DataOptions.HASHRATE): DataFunction( + "get_hashrate", + [ + RPCAPICommand("api_summary", "summary"), + GraphQLCommand( + "graphql_hashrate", + { + "bosminer": { + "info": {"workSolver": {"realHashrate": {"mhs1M": None}}} + } + }, + ), + ], + ), + str(DataOptions.EXPECTED_HASHRATE): DataFunction( + "get_expected_hashrate", [RPCAPICommand("api_devs", "devs")] + ), + str(DataOptions.HASHBOARDS): DataFunction( + "get_hashboards", + [ + RPCAPICommand("api_temps", "temps"), + RPCAPICommand("api_devdetails", "devdetails"), + RPCAPICommand("api_devs", "devs"), + GraphQLCommand( + "graphql_boards", + { + "bosminer": { + "info": { + "workSolver": { + "childSolvers": { + "name": None, + "realHashrate": {"mhs1M": None}, + "hwDetails": {"chips": None}, + "temperatures": {"degreesC": None}, + } } } } - } - }, - }, - }, - }, - "wattage": { - "cmd": "get_wattage", - "kwargs": { - "api_tunerstatus": {"api": "tunerstatus"}, - "graphql_wattage": { - "web": { - "bosminer": { - "info": {"workSolver": {"power": {"approxConsumptionW": None}}} - } - } - }, - }, - }, - "wattage_limit": { - "cmd": "get_wattage_limit", - "kwargs": { - "api_tunerstatus": {"api": "tunerstatus"}, - "graphql_wattage_limit": { - "web": { - "bosminer": {"info": {"workSolver": {"power": {"limitW": None}}}} - } - }, - }, - }, - "fans": { - "cmd": "get_fans", - "kwargs": { - "api_fans": {"api": "fans"}, - "graphql_fans": { - "web": {"bosminer": {"info": {"fans": {"name": None, "rpm": None}}}} - }, - }, - }, - "fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, - "env_temp": {"cmd": "get_env_temp", "kwargs": {}}, - "errors": { - "cmd": "get_errors", - "kwargs": { - "api_tunerstatus": {"api": "tunerstatus"}, - "graphql_errors": { - "web": { - "bosminer": { - "info": { - "workSolver": { - "childSolvers": { - "name": None, - "tuner": {"statusMessages": None}, + }, + ), + ], + ), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), + str(DataOptions.WATTAGE): DataFunction( + "get_wattage", + [ + RPCAPICommand("api_tunerstatus", "tunerstatus"), + GraphQLCommand( + "graphql_wattage", + { + "bosminer": { + "info": { + "workSolver": {"power": {"approxConsumptionW": None}} + } + } + }, + ), + ], + ), + str(DataOptions.WATTAGE_LIMIT): DataFunction( + "get_wattage_limit", + [ + RPCAPICommand("api_tunerstatus", "tunerstatus"), + GraphQLCommand( + "graphql_wattage_limit", + {"bosminer": {"info": {"workSolver": {"power": {"limitW": None}}}}}, + ), + ], + ), + str(DataOptions.FANS): DataFunction( + "get_fans", + [ + RPCAPICommand("api_fans", "fans"), + GraphQLCommand( + "graphql_fans", + {"bosminer": {"info": {"fans": {"name": None, "rpm": None}}}}, + ), + ], + ), + str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.ERRORS): DataFunction( + "get_errors", + [ + RPCAPICommand("api_tunerstatus", "tunerstatus"), + GraphQLCommand( + "graphql_errors", + { + "bosminer": { + "info": { + "workSolver": { + "childSolvers": { + "name": None, + "tuner": {"statusMessages": None}, + } } } } - } - } - }, - }, - }, - "fault_light": { - "cmd": "get_fault_light", - "kwargs": {"graphql_fault_light": {"web": {"bos": {"faultLight": None}}}}, - }, - "pools": { - "cmd": "get_pools", - "kwargs": { - "api_pools": {"api": "pools"}, - "graphql_pools": { - "web": { - "bosminer": { - "config": { - "... on BosminerConfig": { - "groups": { - "pools": {"url": None, "user": None}, - "strategy": { - "... on QuotaStrategy": {"quota": None} - }, - } - } - } - } - } - }, - }, - }, - "is_mining": { - "cmd": "is_mining", - "kwargs": {"api_devdetails": {"api": "devdetails"}}, - }, - "uptime": {"cmd": "get_uptime", "kwargs": {"api_summary": {"api": "summary"}}}, - "config": {"cmd": "get_config", "kwargs": {}}, -} + }, + ), + ], + ), + str(DataOptions.FAULT_LIGHT): DataFunction( + "get_fault_light", + [GraphQLCommand("graphql_fault_light", {"bos": {"faultLight": None}})], + ), + str(DataOptions.IS_MINING): DataFunction( + "is_mining", [RPCAPICommand("api_devdetails", "devdetails")] + ), + str(DataOptions.UPTIME): DataFunction( + "get_uptime", [RPCAPICommand("api_summary", "summary")] + ), + str(DataOptions.CONFIG): DataFunction("get_config"), + } +) class BOSMiner(BaseMiner): @@ -231,7 +231,6 @@ class BOSMiner(BaseMiner): return result async def fault_light_on(self) -> bool: - """Sends command to turn on fault light on the miner.""" logging.debug(f"{self}: Sending fault_light on command.") ret = await self.send_ssh_command("miner fault_light on") logging.debug(f"{self}: fault_light on command completed.") @@ -241,7 +240,6 @@ class BOSMiner(BaseMiner): return False async def fault_light_off(self) -> bool: - """Sends command to turn off fault light on the miner.""" logging.debug(f"{self}: Sending fault_light off command.") self.light = False ret = await self.send_ssh_command("miner fault_light off") @@ -252,11 +250,9 @@ class BOSMiner(BaseMiner): return False async def restart_backend(self) -> bool: - """Restart bosminer hashing process. Wraps [`restart_bosminer`][pyasic.miners.backends.bosminer.BOSMiner.restart_bosminer] to standardize.""" return await self.restart_bosminer() async def restart_bosminer(self) -> bool: - """Restart bosminer hashing process.""" logging.debug(f"{self}: Sending bosminer restart command.") ret = await self.send_ssh_command("/etc/init.d/bosminer restart") logging.debug(f"{self}: bosminer restart command completed.") @@ -285,7 +281,6 @@ class BOSMiner(BaseMiner): return False async def reboot(self) -> bool: - """Reboots power to the physical miner.""" logging.debug(f"{self}: Sending reboot command.") ret = await self.send_ssh_command("/sbin/reboot") logging.debug(f"{self}: Reboot command completed.") @@ -833,95 +828,6 @@ class BOSMiner(BaseMiner): async def get_fan_psu(self) -> Optional[int]: return None - async def get_pools( - self, api_pools: dict = None, graphql_pools: dict = None - ) -> List[dict]: - if not graphql_pools and not api_pools: - try: - graphql_pools = await self.web.send_command( - { - "bosminer": { - "config": { - "... on BosminerConfig": { - "groups": { - "pools": {"urluser"}, - "strategy": {"... on QuotaStrategy": {"quota"}}, - } - } - } - } - } - ) - except APIError: - pass - - if graphql_pools: - groups = [] - try: - g = graphql_pools["data"]["bosminer"]["config"]["groups"] - for group in g: - pools = {"quota": group["strategy"]["quota"]} - for i, pool in enumerate(group["pools"]): - pools[f"pool_{i + 1}_url"] = ( - pool["url"] - .replace("stratum+tcp://", "") - .replace("stratum2+tcp://", "") - ) - pools[f"pool_{i + 1}_user"] = pool["user"] - groups.append(pools) - return groups - except (KeyError, TypeError): - pass - - if not api_pools: - try: - api_pools = await self.api.pools() - except APIError: - pass - - if api_pools: - seen = [] - groups = [{"quota": "0"}] - if api_pools.get("POOLS"): - for i, pool in enumerate(api_pools["POOLS"]): - if len(seen) == 0: - seen.append(pool["User"]) - if not pool["User"] in seen: - # need to use get_config, as this will never read perfectly as there are some bad edge cases - groups = [] - cfg = await self.get_config() - if cfg: - for group in cfg.pool_groups: - pools = {"quota": group.quota} - for _i, _pool in enumerate(group.pools): - pools[f"pool_{_i + 1}_url"] = _pool.url.replace( - "stratum+tcp://", "" - ).replace("stratum2+tcp://", "") - pools[f"pool_{_i + 1}_user"] = _pool.username - groups.append(pools) - return groups - else: - groups[0][f"pool_{i + 1}_url"] = ( - pool["URL"] - .replace("stratum+tcp://", "") - .replace("stratum2+tcp://", "") - ) - groups[0][f"pool_{i + 1}_user"] = pool["User"] - else: - groups = [] - cfg = await self.get_config() - if cfg: - for group in cfg.pool_groups: - pools = {"quota": group.quota} - for _i, _pool in enumerate(group.pools): - pools[f"pool_{_i + 1}_url"] = _pool.url.replace( - "stratum+tcp://", "" - ).replace("stratum2+tcp://", "") - pools[f"pool_{_i + 1}_user"] = _pool.username - groups.append(pools) - return groups - return groups - async def get_errors( self, api_tunerstatus: dict = None, graphql_errors: dict = None ) -> List[MinerErrorData]: diff --git a/pyasic/miners/backends/bosminer_old.py b/pyasic/miners/backends/bosminer_old.py index 5934fa13..5e6def99 100644 --- a/pyasic/miners/backends/bosminer_old.py +++ b/pyasic/miners/backends/bosminer_old.py @@ -136,9 +136,6 @@ class BOSMinerOld(BOSMiner): async def get_fw_ver(self, *args, **kwargs) -> Optional[str]: return None - async def get_pools(self, *args, **kwargs) -> List[dict]: - return [] - async def get_errors(self, *args, **kwargs) -> List[MinerErrorData]: return [] diff --git a/pyasic/miners/backends/btminer.py b/pyasic/miners/backends/btminer.py index ac1ca8d9..aa2a4bc1 100644 --- a/pyasic/miners/backends/btminer.py +++ b/pyasic/miners/backends/btminer.py @@ -23,74 +23,86 @@ from pyasic.config import MinerConfig, MiningModeConfig from pyasic.data import Fan, HashBoard from pyasic.data.error_codes import MinerErrorData, WhatsminerError from pyasic.errors import APIError -from pyasic.miners.base import BaseMiner +from pyasic.miners.base import ( + BaseMiner, + DataFunction, + DataLocations, + DataOptions, + RPCAPICommand, + WebAPICommand, +) -BTMINER_DATA_LOC = { - "mac": { - "cmd": "get_mac", - "kwargs": { - "api_summary": {"api": "summary"}, - "api_get_miner_info": {"api": "get_miner_info"}, - }, - }, - "model": {"cmd": "get_model", "kwargs": {}}, - "api_ver": { - "cmd": "get_api_ver", - "kwargs": {"api_get_version": {"api": "get_version"}}, - }, - "fw_ver": { - "cmd": "get_fw_ver", - "kwargs": { - "api_get_version": {"api": "get_version"}, - "api_summary": {"api": "summary"}, - }, - }, - "hostname": { - "cmd": "get_hostname", - "kwargs": {"api_get_miner_info": {"api": "get_miner_info"}}, - }, - "hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, - "expected_hashrate": { - "cmd": "get_expected_hashrate", - "kwargs": {"api_summary": {"api": "summary"}}, - }, - "hashboards": {"cmd": "get_hashboards", "kwargs": {"api_devs": {"api": "devs"}}}, - "env_temp": {"cmd": "get_env_temp", "kwargs": {"api_summary": {"api": "summary"}}}, - "wattage": {"cmd": "get_wattage", "kwargs": {"api_summary": {"api": "summary"}}}, - "wattage_limit": { - "cmd": "get_wattage_limit", - "kwargs": {"api_summary": {"api": "summary"}}, - }, - "fans": { - "cmd": "get_fans", - "kwargs": { - "api_summary": {"api": "summary"}, - "api_get_psu": {"api": "get_psu"}, - }, - }, - "fan_psu": { - "cmd": "get_fan_psu", - "kwargs": { - "api_summary": {"api": "summary"}, - "api_get_psu": {"api": "get_psu"}, - }, - }, - "errors": { - "cmd": "get_errors", - "kwargs": { - "api_summary": {"api": "summary"}, - "api_get_error_code": {"api": "get_error_code"}, - }, - }, - "fault_light": { - "cmd": "get_fault_light", - "kwargs": {"api_get_miner_info": {"api": "get_miner_info"}}, - }, - "pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, - "is_mining": {"cmd": "is_mining", "kwargs": {"api_status": {"api": "status"}}}, - "uptime": {"cmd": "get_uptime", "kwargs": {"api_summary": {"api": "summary"}}}, - "config": {"cmd": "get_config", "kwargs": {}}, -} +BTMINER_DATA_LOC = DataLocations( + **{ + str(DataOptions.MAC): DataFunction( + "get_mac", + [ + RPCAPICommand("api_summary", "summary"), + RPCAPICommand("api_get_miner_info", "get_miner_info"), + ], + ), + str(DataOptions.MODEL): DataFunction("get_model"), + str(DataOptions.API_VERSION): DataFunction( + "get_api_ver", [RPCAPICommand("api_get_version", "get_version")] + ), + str(DataOptions.FW_VERSION): DataFunction( + "get_fw_ver", + [ + RPCAPICommand("api_get_version", "get_version"), + RPCAPICommand("api_summary", "summary"), + ], + ), + str(DataOptions.HOSTNAME): DataFunction( + "get_hostname", [RPCAPICommand("api_get_miner_info", "get_miner_info")] + ), + str(DataOptions.HASHRATE): DataFunction( + "get_hashrate", [RPCAPICommand("api_summary", "summary")] + ), + str(DataOptions.EXPECTED_HASHRATE): DataFunction( + "get_expected_hashrate", [RPCAPICommand("api_summary", "summary")] + ), + str(DataOptions.HASHBOARDS): DataFunction( + "get_hashboards", [RPCAPICommand("api_devs", "devs")] + ), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction( + "get_env_temp", [RPCAPICommand("api_summary", "summary")] + ), + str(DataOptions.WATTAGE): DataFunction( + "get_wattage", [RPCAPICommand("api_summary", "summary")] + ), + str(DataOptions.WATTAGE_LIMIT): DataFunction( + "get_wattage_limit", [RPCAPICommand("api_summary", "summary")] + ), + str(DataOptions.FANS): DataFunction( + "get_fans", + [ + RPCAPICommand("api_summary", "summary"), + RPCAPICommand("api_get_psu", "get_psu"), + ], + ), + str(DataOptions.FAN_PSU): DataFunction( + "get_fan_psu", + [ + RPCAPICommand("api_summary", "summary"), + RPCAPICommand("api_get_psu", "get_psu"), + ], + ), + str(DataOptions.ERRORS): DataFunction( + "get_errors", [RPCAPICommand("api_get_error_code", "get_error_code")] + ), + str(DataOptions.FAULT_LIGHT): DataFunction( + "get_fault_light", + [RPCAPICommand("api_get_miner_info", "get_miner_info")], + ), + str(DataOptions.IS_MINING): DataFunction( + "is_mining", [RPCAPICommand("api_status", "status")] + ), + str(DataOptions.UPTIME): DataFunction( + "get_uptime", [RPCAPICommand("api_summary", "summary")] + ), + str(DataOptions.CONFIG): DataFunction("get_config"), + } +) class BTMiner(BaseMiner): @@ -529,32 +541,6 @@ class BTMiner(BaseMiner): except (KeyError, TypeError): pass - async def get_pools(self, api_pools: dict = None) -> List[dict]: - groups = [] - - if not api_pools: - try: - api_pools = await self.api.pools() - except APIError: - pass - - if api_pools: - try: - pools = {} - for i, pool in enumerate(api_pools["POOLS"]): - pools[f"pool_{i + 1}_url"] = ( - pool["URL"] - .replace("stratum+tcp://", "") - .replace("stratum2+tcp://", "") - ) - pools[f"pool_{i + 1}_user"] = pool["User"] - pools["quota"] = pool["Quota"] if pool.get("Quota") else "0" - - groups.append(pools) - except KeyError: - pass - return groups - async def get_errors( self, api_summary: dict = None, api_get_error_code: dict = None ) -> List[MinerErrorData]: diff --git a/pyasic/miners/backends/cgminer.py b/pyasic/miners/backends/cgminer.py index 1b5c73e4..424a979c 100644 --- a/pyasic/miners/backends/cgminer.py +++ b/pyasic/miners/backends/cgminer.py @@ -23,32 +23,50 @@ from pyasic.config import MinerConfig from pyasic.data import Fan, HashBoard from pyasic.data.error_codes import MinerErrorData from pyasic.errors import APIError -from pyasic.miners.base import BaseMiner +from pyasic.miners.base import ( + BaseMiner, + DataFunction, + DataLocations, + DataOptions, + RPCAPICommand, +) -CGMINER_DATA_LOC = { - "mac": {"cmd": "get_mac", "kwargs": {}}, - "model": {"cmd": "get_model", "kwargs": {}}, - "api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, - "fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, - "hostname": {"cmd": "get_hostname", "kwargs": {}}, - "hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, - "expected_hashrate": { - "cmd": "get_expected_hashrate", - "kwargs": {"api_stats": {"api": "stats"}}, - }, - "hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, - "env_temp": {"cmd": "get_env_temp", "kwargs": {}}, - "wattage": {"cmd": "get_wattage", "kwargs": {}}, - "wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, - "fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, - "fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, - "errors": {"cmd": "get_errors", "kwargs": {}}, - "fault_light": {"cmd": "get_fault_light", "kwargs": {}}, - "pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, - "is_mining": {"cmd": "is_mining", "kwargs": {}}, - "uptime": {"cmd": "get_uptime", "kwargs": {"api_stats": {"api": "stats"}}}, - "config": {"cmd": "get_config", "kwargs": {}}, -} +CGMINER_DATA_LOC = DataLocations( + **{ + str(DataOptions.MAC): DataFunction("get_mac"), + str(DataOptions.MODEL): DataFunction("get_model"), + str(DataOptions.API_VERSION): DataFunction( + "get_api_ver", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.FW_VERSION): DataFunction( + "get_fw_ver", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.HOSTNAME): DataFunction("get_hostname"), + str(DataOptions.HASHRATE): DataFunction( + "get_hashrate", [RPCAPICommand("api_summary", "summary")] + ), + str(DataOptions.EXPECTED_HASHRATE): DataFunction( + "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.HASHBOARDS): DataFunction( + "get_hashboards", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), + str(DataOptions.WATTAGE): DataFunction("get_wattage"), + str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), + str(DataOptions.FANS): DataFunction( + "get_fans", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.ERRORS): DataFunction("get_errors"), + str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"), + str(DataOptions.IS_MINING): DataFunction("is_mining"), + str(DataOptions.UPTIME): DataFunction( + "get_uptime", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.CONFIG): DataFunction("get_config"), + } +) class CGMiner(BaseMiner): @@ -94,11 +112,9 @@ class CGMiner(BaseMiner): return result async def restart_backend(self) -> bool: - """Restart cgminer hashing process. Wraps [`restart_cgminer`][pyasic.miners.backends.cgminer.CGMiner.restart_cgminer] to standardize.""" return await self.restart_cgminer() async def restart_cgminer(self) -> bool: - """Restart cgminer hashing process.""" commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"] commands = ";".join(commands) ret = await self.send_ssh_command(commands) @@ -107,7 +123,6 @@ class CGMiner(BaseMiner): return True async def reboot(self) -> bool: - """Reboots power to the physical miner.""" logging.debug(f"{self}: Sending reboot command.") ret = await self.send_ssh_command("reboot") if ret is None: @@ -328,32 +343,6 @@ class CGMiner(BaseMiner): async def get_fan_psu(self) -> Optional[int]: return None - async def get_pools(self, api_pools: dict = None) -> List[dict]: - groups = [] - - if not api_pools: - try: - api_pools = await self.api.pools() - except APIError: - pass - - if api_pools: - try: - pools = {} - for i, pool in enumerate(api_pools["POOLS"]): - pools[f"pool_{i + 1}_url"] = ( - pool["URL"] - .replace("stratum+tcp://", "") - .replace("stratum2+tcp://", "") - ) - pools[f"pool_{i + 1}_user"] = pool["User"] - pools["quota"] = pool["Quota"] if pool.get("Quota") else "0" - - groups.append(pools) - except KeyError: - pass - return groups - async def get_errors(self) -> List[MinerErrorData]: return [] diff --git a/pyasic/miners/backends/cgminer_avalon.py b/pyasic/miners/backends/cgminer_avalon.py index 2c5028a4..1db2dd9e 100644 --- a/pyasic/miners/backends/cgminer_avalon.py +++ b/pyasic/miners/backends/cgminer_avalon.py @@ -23,37 +23,52 @@ from pyasic.data import Fan, HashBoard from pyasic.data.error_codes import MinerErrorData from pyasic.errors import APIError from pyasic.miners.backends import CGMiner +from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPICommand -AVALON_DATA_LOC = { - "mac": {"cmd": "get_mac", "kwargs": {"api_version": {"api": "version"}}}, - "model": {"cmd": "get_model", "kwargs": {}}, - "api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, - "fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, - "hostname": {"cmd": "get_hostname", "kwargs": {"mac": {"api": "version"}}}, - "hashrate": {"cmd": "get_hashrate", "kwargs": {"api_devs": {"api": "devs"}}}, - "expected_hashrate": { - "cmd": "get_expected_hashrate", - "kwargs": {"api_stats": {"api": "stats"}}, - }, - "hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, - "env_temp": {"cmd": "get_env_temp", "kwargs": {"api_stats": {"api": "stats"}}}, - "wattage": {"cmd": "get_wattage", "kwargs": {}}, - "wattage_limit": { - "cmd": "get_wattage_limit", - "kwargs": {"api_stats": {"api": "stats"}}, - }, - "fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, - "fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, - "errors": {"cmd": "get_errors", "kwargs": {}}, - "fault_light": { - "cmd": "get_fault_light", - "kwargs": {"api_stats": {"api": "stats"}}, - }, - "pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, - "is_mining": {"cmd": "is_mining", "kwargs": {}}, - "uptime": {"cmd": "get_uptime", "kwargs": {}}, - "config": {"cmd": "get_config", "kwargs": {}}, -} +AVALON_DATA_LOC = DataLocations( + **{ + str(DataOptions.MAC): DataFunction( + "get_mac", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.MODEL): DataFunction("get_model"), + str(DataOptions.API_VERSION): DataFunction( + "get_api_ver", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.FW_VERSION): DataFunction( + "get_fw_ver", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.HOSTNAME): DataFunction( + "get_hostname", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.HASHRATE): DataFunction( + "get_hashrate", [RPCAPICommand("api_devs", "devs")] + ), + str(DataOptions.EXPECTED_HASHRATE): DataFunction( + "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.HASHBOARDS): DataFunction( + "get_hashboards", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction( + "get_env_temp", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.WATTAGE): DataFunction("get_wattage"), + str(DataOptions.WATTAGE_LIMIT): DataFunction( + "get_wattage_limit", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.FANS): DataFunction( + "get_fans", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.ERRORS): DataFunction("get_errors"), + str(DataOptions.FAULT_LIGHT): DataFunction( + "get_fault_light", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.IS_MINING): DataFunction("is_mining"), + str(DataOptions.UPTIME): DataFunction("get_uptime"), + str(DataOptions.CONFIG): DataFunction("get_config"), + } +) class CGMinerAvalon(CGMiner): @@ -116,7 +131,7 @@ class CGMinerAvalon(CGMiner): @staticmethod def parse_stats(stats): - _stats_items = re.findall(".+?\[*?]", stats) + _stats_items = re.findall(".+?\\[*?]", stats) stats_items = [] stats_dict = {} for item in _stats_items: @@ -318,32 +333,6 @@ class CGMinerAvalon(CGMiner): pass return fans_data - async def get_pools(self, api_pools: dict = None) -> List[dict]: - groups = [] - - if not api_pools: - try: - api_pools = await self.api.pools() - except APIError: - pass - - if api_pools: - try: - pools = {} - for i, pool in enumerate(api_pools["POOLS"]): - pools[f"pool_{i + 1}_url"] = ( - pool["URL"] - .replace("stratum+tcp://", "") - .replace("stratum2+tcp://", "") - ) - pools[f"pool_{i + 1}_user"] = pool["User"] - pools["quota"] = pool["Quota"] if pool.get("Quota") else "0" - - groups.append(pools) - except KeyError: - pass - return groups - async def get_errors(self) -> List[MinerErrorData]: return [] diff --git a/pyasic/miners/backends/epic.py b/pyasic/miners/backends/epic.py index 9a40a9d1..88358403 100644 --- a/pyasic/miners/backends/epic.py +++ b/pyasic/miners/backends/epic.py @@ -14,52 +14,74 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -from typing import List, Optional, Tuple, Union +from typing import List, Optional, Tuple +from pyasic import MinerConfig from pyasic.data import Fan, HashBoard from pyasic.data.error_codes import MinerErrorData, X19Error from pyasic.config import MinerConfig, MiningModeConfig from pyasic.errors import APIError from pyasic.logger import logger -from pyasic.miners.backends.bmminer import BMMiner +from pyasic.miners.base import ( + BaseMiner, + DataFunction, + DataLocations, + DataOptions, + WebAPICommand, +) from pyasic.web.epic import ePICWebAPI -EPIC_DATA_LOC = { - "mac": {"cmd": "get_mac", "kwargs": {"web_summary": {"web": "network"}}}, - "model": {"cmd": "get_model", "kwargs": {}}, - "api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, - "fw_ver": {"cmd": "get_fw_ver", "kwargs": {"web_summary": {"web": "summary"}}}, - "hostname": {"cmd": "get_hostname", "kwargs": {"web_summary": {"web": "summary"}}}, - "hashrate": {"cmd": "get_hashrate", "kwargs": {"web_summary": {"web": "summary"}}}, - "expected_hashrate": { - "cmd": "get_expected_hashrate", - "kwargs": {"web_summary": {"web": "summary"}}, - }, - "hashboards": { - "cmd": "get_hashboards", - "kwargs": { - "web_summary": {"web": "summary"}, - "web_hashrate": {"web": "hashrate"}, - }, - }, - "env_temp": {"cmd": "get_env_temp", "kwargs": {}}, - "wattage": {"cmd": "get_wattage", "kwargs": {"web_summary": {"web": "summary"}}}, - "wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, - "fans": {"cmd": "get_fans", "kwargs": {"web_summary": {"web": "summary"}}}, - "fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, - "fault_light": { - "cmd": "get_fault_light", - "kwargs": {"web_summary": {"web": "summary"}}, - }, - "pools": {"cmd": "get_pools", "kwargs": {"web_summary": {"web": "summary"}}}, - "is_mining": {"cmd": "is_mining", "kwargs": {}}, - "uptime": {"cmd": "get_uptime", "kwargs": {"web_summary": {"web": "summary"}}}, - "errors": {"cmd": "get_errors", "kwargs": {"web_summary": {"web": "summary"}}}, - "config": {"cmd": "get_config", "kwargs": {}}, -} +EPIC_DATA_LOC = DataLocations( + **{ + str(DataOptions.MAC): DataFunction( + "get_mac", [WebAPICommand("web_network", "network")] + ), + str(DataOptions.MODEL): DataFunction("get_model"), + str(DataOptions.API_VERSION): DataFunction("get_api_ver"), + str(DataOptions.FW_VERSION): DataFunction( + "get_fw_ver", [WebAPICommand("web_summary", "summary")] + ), + str(DataOptions.HOSTNAME): DataFunction( + "get_hostname", [WebAPICommand("web_summary", "summary")] + ), + str(DataOptions.HASHRATE): DataFunction( + "get_hashrate", [WebAPICommand("web_summary", "summary")] + ), + str(DataOptions.EXPECTED_HASHRATE): DataFunction( + "get_expected_hashrate", [WebAPICommand("web_summary", "summary")] + ), + str(DataOptions.HASHBOARDS): DataFunction( + "get_hashboards", + [ + WebAPICommand("web_summary", "summary"), + WebAPICommand("web_hashrate", "hashrate"), + ], + ), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), + str(DataOptions.WATTAGE): DataFunction( + "get_wattage", [WebAPICommand("web_summary", "summary")] + ), + str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), + str(DataOptions.FANS): DataFunction( + "get_fans", [WebAPICommand("web_summary", "summary")] + ), + str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.ERRORS): DataFunction( + "get_errors", [WebAPICommand("web_summary", "summary")] + ), + str(DataOptions.FAULT_LIGHT): DataFunction( + "get_fault_light", [WebAPICommand("web_summary", "summary")] + ), + str(DataOptions.IS_MINING): DataFunction("is_mining"), + str(DataOptions.UPTIME): DataFunction( + "get_uptime", [WebAPICommand("web_summary", "summary")] + ), + str(DataOptions.CONFIG): DataFunction("get_config"), + } +) -class ePIC(BMMiner): +class ePIC(BaseMiner): def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: super().__init__(ip, api_ver) # interfaces @@ -128,13 +150,13 @@ class ePIC(BMMiner): pass return False - async def get_mac(self, web_summary: dict = None) -> str: - if not web_summary: - web_summary = await self.web.network() - if web_summary: + async def get_mac(self, web_network: dict = None) -> str: + if not web_network: + web_network = await self.web.network() + if web_network: try: - for network in web_summary: - mac = web_summary[network]["mac_address"] + for network in web_network: + mac = web_network[network]["mac_address"] return mac except KeyError: pass @@ -267,32 +289,6 @@ class ePIC(BMMiner): async def is_mining(self, *args, **kwargs) -> Optional[bool]: return None - async def get_pools(self, web_summary: dict = None) -> List[dict]: - groups = [] - - if not web_summary: - try: - web_summary = await self.api.summary() - except APIError: - pass - - if web_summary: - try: - pools = {} - for i, pool in enumerate(web_summary["StratumConfigs"]): - pools[f"pool_{i + 1}_url"] = ( - pool["pool"] - .replace("stratum+tcp://", "") - .replace("stratum2+tcp://", "") - ) - pools[f"pool_{i + 1}_user"] = pool["login"] - pools["quota"] = pool["Quota"] if pool.get("Quota") else "0" - - groups.append(pools) - except KeyError: - pass - return groups - async def get_uptime(self, web_summary: dict = None) -> Optional[int]: if not web_summary: web_summary = await self.web.summary() @@ -328,3 +324,33 @@ class ePIC(BMMiner): except KeyError: pass return errors + + def fault_light_off(self) -> bool: + return False + + def fault_light_on(self) -> bool: + return False + + def get_api_ver(self, *args, **kwargs) -> Optional[str]: + pass + + def get_config(self) -> MinerConfig: + return self.config + + def get_env_temp(self, *args, **kwargs) -> Optional[float]: + pass + + def get_fan_psu(self, *args, **kwargs) -> Optional[int]: + pass + + def get_version(self, *args, **kwargs) -> Tuple[Optional[str], Optional[str]]: + pass + + def get_wattage_limit(self, *args, **kwargs) -> Optional[int]: + pass + + def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: + pass + + def set_power_limit(self, wattage: int) -> bool: + return False diff --git a/pyasic/miners/backends/luxminer.py b/pyasic/miners/backends/luxminer.py index 4dda54cf..3467581c 100644 --- a/pyasic/miners/backends/luxminer.py +++ b/pyasic/miners/backends/luxminer.py @@ -26,30 +26,52 @@ from pyasic.config import MinerConfig from pyasic.data import Fan, HashBoard from pyasic.data.error_codes import BraiinsOSError, MinerErrorData from pyasic.errors import APIError -from pyasic.miners.base import BaseMiner +from pyasic.miners.base import ( + BaseMiner, + DataFunction, + DataLocations, + DataOptions, + RPCAPICommand, + WebAPICommand, +) from pyasic.web.bosminer import BOSMinerWebAPI -LUXMINER_DATA_LOC = { - "mac": {"cmd": "get_mac", "kwargs": {"api_config": {"api": "config"}}}, - "model": {"cmd": "get_model", "kwargs": {}}, - "api_ver": {"cmd": "get_api_ver", "kwargs": {}}, - "fw_ver": {"cmd": "get_fw_ver", "kwargs": {}}, - "hostname": {"cmd": "get_hostname", "kwargs": {}}, - "hashrate": {"cmd": "get_hashrate", "kwargs": {}}, - "expected_hashrate": {"cmd": "get_expected_hashrate", "kwargs": {}}, - "hashboards": {"cmd": "get_hashboards", "kwargs": {}}, - "wattage": {"cmd": "get_wattage", "kwargs": {}}, - "wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, - "fans": {"cmd": "get_fans", "kwargs": {}}, - "fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, - "env_temp": {"cmd": "get_env_temp", "kwargs": {}}, - "errors": {"cmd": "get_errors", "kwargs": {}}, - "fault_light": {"cmd": "get_fault_light", "kwargs": {}}, - "pools": {"cmd": "get_pools", "kwargs": {}}, - "is_mining": {"cmd": "is_mining", "kwargs": {}}, - "uptime": {"cmd": "get_uptime", "kwargs": {"api_stats": {"api": "stats"}}}, - "config": {"cmd": "get_config", "kwargs": {}}, -} +LUXMINER_DATA_LOC = DataLocations( + **{ + str(DataOptions.MAC): DataFunction( + "get_mac", [RPCAPICommand("api_config", "config")] + ), + str(DataOptions.MODEL): DataFunction("get_model"), + str(DataOptions.API_VERSION): DataFunction("get_api_ver"), + str(DataOptions.FW_VERSION): DataFunction("get_fw_ver"), + str(DataOptions.HOSTNAME): DataFunction("get_hostname"), + str(DataOptions.HASHRATE): DataFunction( + "get_hashrate", [RPCAPICommand("api_summary", "summary")] + ), + str(DataOptions.EXPECTED_HASHRATE): DataFunction( + "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.HASHBOARDS): DataFunction( + "get_hashboards", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), + str(DataOptions.WATTAGE): DataFunction( + "get_wattage", [RPCAPICommand("api_power", "power")] + ), + str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), + str(DataOptions.FANS): DataFunction( + "get_fans", [RPCAPICommand("api_fans", "fans")] + ), + str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.ERRORS): DataFunction("get_errors"), + str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"), + str(DataOptions.IS_MINING): DataFunction("is_mining"), + str(DataOptions.UPTIME): DataFunction( + "get_uptime", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.CONFIG): DataFunction("get_config"), + } +) class LUXMiner(BaseMiner): @@ -85,7 +107,6 @@ class LUXMiner(BaseMiner): return async def fault_light_on(self) -> bool: - """Sends command to turn on fault light on the miner.""" try: session_id = await self._get_session() if session_id: @@ -96,7 +117,6 @@ class LUXMiner(BaseMiner): return False async def fault_light_off(self) -> bool: - """Sends command to turn off fault light on the miner.""" try: session_id = await self._get_session() if session_id: @@ -107,11 +127,9 @@ class LUXMiner(BaseMiner): return False async def restart_backend(self) -> bool: - """Restart luxminer hashing process. Wraps [`restart_luxminer`][pyasic.miners.backends.luxminer.LUXMiner.restart_luxminer] to standardize.""" return await self.restart_luxminer() async def restart_luxminer(self) -> bool: - """Restart luxminer hashing process.""" try: session_id = await self._get_session() if session_id: @@ -141,7 +159,6 @@ class LUXMiner(BaseMiner): pass async def reboot(self) -> bool: - """Reboots power to the physical miner.""" try: session_id = await self._get_session() if session_id: @@ -303,56 +320,6 @@ class LUXMiner(BaseMiner): async def get_fan_psu(self) -> Optional[int]: return None - async def get_pools(self, api_pools: dict = None) -> List[dict]: - if not api_pools: - try: - api_pools = await self.api.pools() - except APIError: - pass - - if api_pools: - seen = [] - groups = [{"quota": "0"}] - if api_pools.get("POOLS"): - for i, pool in enumerate(api_pools["POOLS"]): - if len(seen) == 0: - seen.append(pool["User"]) - if not pool["User"] in seen: - # need to use get_config, as this will never read perfectly as there are some bad edge cases - groups = [] - cfg = await self.get_config() - if cfg: - for group in cfg.pool_groups: - pools = {"quota": group.quota} - for _i, _pool in enumerate(group.pools): - pools[f"pool_{_i + 1}_url"] = _pool.url.replace( - "stratum+tcp://", "" - ).replace("stratum2+tcp://", "") - pools[f"pool_{_i + 1}_user"] = _pool.username - groups.append(pools) - return groups - else: - groups[0][f"pool_{i + 1}_url"] = ( - pool["URL"] - .replace("stratum+tcp://", "") - .replace("stratum2+tcp://", "") - ) - groups[0][f"pool_{i + 1}_user"] = pool["User"] - else: - groups = [] - cfg = await self.get_config() - if cfg: - for group in cfg.pool_groups: - pools = {"quota": group.quota} - for _i, _pool in enumerate(group.pools): - pools[f"pool_{_i + 1}_url"] = _pool.url.replace( - "stratum+tcp://", "" - ).replace("stratum2+tcp://", "") - pools[f"pool_{_i + 1}_user"] = _pool.username - groups.append(pools) - return groups - return groups - async def get_errors(self) -> List[MinerErrorData]: pass diff --git a/pyasic/miners/backends/vnish.py b/pyasic/miners/backends/vnish.py index 1a671a6d..30e24810 100644 --- a/pyasic/miners/backends/vnish.py +++ b/pyasic/miners/backends/vnish.py @@ -19,35 +19,57 @@ from typing import Optional from pyasic.errors import APIError from pyasic.logger import logger from pyasic.miners.backends.bmminer import BMMiner +from pyasic.miners.base import ( + DataFunction, + DataLocations, + DataOptions, + RPCAPICommand, + WebAPICommand, +) from pyasic.web.vnish import VNishWebAPI -VNISH_DATA_LOC = { - "mac": {"cmd": "get_mac", "kwargs": {"web_summary": {"web": "summary"}}}, - "model": {"cmd": "get_model", "kwargs": {}}, - "api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, - "fw_ver": {"cmd": "get_fw_ver", "kwargs": {"web_summary": {"web": "summary"}}}, - "hostname": {"cmd": "get_hostname", "kwargs": {"web_summary": {"web": "summary"}}}, - "hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, - "expected_hashrate": { - "cmd": "get_expected_hashrate", - "kwargs": {"api_stats": {"api": "stats"}}, - }, - "hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, - "env_temp": {"cmd": "get_env_temp", "kwargs": {}}, - "wattage": {"cmd": "get_wattage", "kwargs": {"web_summary": {"web": "summary"}}}, - "wattage_limit": { - "cmd": "get_wattage_limit", - "kwargs": {"web_settings": {"web": "settings"}}, - }, - "fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, - "fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, - "errors": {"cmd": "get_errors", "kwargs": {}}, - "fault_light": {"cmd": "get_fault_light", "kwargs": {}}, - "pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, - "is_mining": {"cmd": "is_mining", "kwargs": {}}, - "uptime": {"cmd": "get_uptime", "kwargs": {}}, - "config": {"cmd": "get_config", "kwargs": {}}, -} +VNISH_DATA_LOC = DataLocations( + **{ + str(DataOptions.MAC): DataFunction( + "get_mac", [WebAPICommand("web_summary", "summary")] + ), + str(DataOptions.MODEL): DataFunction("get_model"), + str(DataOptions.API_VERSION): DataFunction( + "get_api_ver", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.FW_VERSION): DataFunction( + "get_fw_ver", [WebAPICommand("web_summary", "summary")] + ), + str(DataOptions.HOSTNAME): DataFunction( + "get_hostname", [WebAPICommand("web_summary", "summary")] + ), + str(DataOptions.HASHRATE): DataFunction( + "get_hashrate", [WebAPICommand("web_summary", "summary")] + ), + str(DataOptions.EXPECTED_HASHRATE): DataFunction( + "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.HASHBOARDS): DataFunction( + "get_hashboards", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), + str(DataOptions.WATTAGE): DataFunction( + "get_wattage", [WebAPICommand("web_summary", "summary")] + ), + str(DataOptions.WATTAGE_LIMIT): DataFunction( + "get_wattage_limit", [WebAPICommand("web_settings", "settings")] + ), + str(DataOptions.FANS): DataFunction( + "get_fans", [WebAPICommand("web_summary", "summary")] + ), + str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.ERRORS): DataFunction("get_errors"), + str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"), + str(DataOptions.IS_MINING): DataFunction("is_mining"), + str(DataOptions.UPTIME): DataFunction("get_uptime"), + str(DataOptions.CONFIG): DataFunction("get_config"), + } +) class VNish(BMMiner): diff --git a/pyasic/miners/base.py b/pyasic/miners/base.py index ca580c50..52aa88d1 100644 --- a/pyasic/miners/base.py +++ b/pyasic/miners/base.py @@ -17,7 +17,9 @@ import asyncio import ipaddress import logging from abc import ABC, abstractmethod -from typing import List, Optional, Tuple, TypeVar +from dataclasses import dataclass, field, make_dataclass +from enum import Enum +from typing import List, Optional, Tuple, TypeVar, Union import asyncssh @@ -27,6 +29,70 @@ from pyasic.data.error_codes import MinerErrorData from pyasic.logger import logger +class DataOptions(Enum): + MAC = "mac" + MODEL = "model" + API_VERSION = "api_ver" + FW_VERSION = "fw_ver" + HOSTNAME = "hostname" + HASHRATE = "hashrate" + EXPECTED_HASHRATE = "expected_hashrate" + HASHBOARDS = "hashboards" + ENVIRONMENT_TEMP = "env_temp" + WATTAGE = "wattage" + WATTAGE_LIMIT = "wattage_limit" + FANS = "fans" + FAN_PSU = "fan_psu" + ERRORS = "errors" + FAULT_LIGHT = "fault_light" + IS_MINING = "is_mining" + UPTIME = "uptime" + CONFIG = "config" + + def __str__(self): + return self.value + + +@dataclass +class RPCAPICommand: + name: str + cmd: str + + +@dataclass +class WebAPICommand: + name: str + cmd: str + + +@dataclass +class GRPCCommand(WebAPICommand): + name: str + cmd: str + + +@dataclass +class GraphQLCommand(WebAPICommand): + name: str + cmd: dict + + +@dataclass +class DataFunction: + cmd: str + kwargs: list[ + Union[RPCAPICommand, WebAPICommand, GRPCCommand, GraphQLCommand] + ] = field(default_factory=list) + + +DataLocations = make_dataclass( + "DataLocations", + [(enum_value.value, str) for enum_value in DataOptions], +) +# add default value with +# [(enum_value.value, str, , DataFunction(enum_value.value)) for enum_value in DataOptions], + + class BaseMiner(ABC): def __init__(self, ip: str, *args, **kwargs) -> None: # interfaces @@ -46,7 +112,7 @@ class BaseMiner(ABC): self.expected_chips = 0 self.fan_count = 2 # data gathering locations - self.data_locations = None + self.data_locations: DataLocations = None # autotuning/shutdown support self.supports_autotuning = False self.supports_shutdown = False @@ -359,15 +425,6 @@ class BaseMiner(ABC): """ pass - @abstractmethod - async def get_pools(self, *args, **kwargs) -> List[dict]: - """Get pool information from the miner. - - Returns: - Pool groups and quotas in a list of dicts. - """ - pass - @abstractmethod async def get_errors(self, *args, **kwargs) -> List[MinerErrorData]: """Get a list of the errors the miner is experiencing. @@ -414,28 +471,33 @@ class BaseMiner(ABC): pass async def _get_data( - self, allow_warning: bool, include: list = None, exclude: list = None + self, + allow_warning: bool, + include: List[Union[str, DataOptions]] = None, + exclude: List[Union[str, DataOptions]] = None, ) -> dict: - if include is None: + if include is not None: + include = [str(i) for i in include] + else: # everything - include = list(self.data_locations.keys()) + include = [str(enum_value.value) for enum_value in DataOptions] if exclude is not None: for item in exclude: - if item in include: - include.remove(item) + if str(item) in include: + include.remove(str(item)) api_multicommand = set() web_multicommand = [] for data_name in include: try: - fn_args = self.data_locations[data_name]["kwargs"] - for arg_name in fn_args: - if fn_args[arg_name].get("api"): - api_multicommand.add(fn_args[arg_name]["api"]) - if fn_args[arg_name].get("web"): - if not fn_args[arg_name]["web"] in web_multicommand: - web_multicommand.append(fn_args[arg_name]["web"]) + fn_args = getattr(self.data_locations, data_name).kwargs + for arg in fn_args: + if isinstance(arg, RPCAPICommand): + api_multicommand.add(arg.cmd) + if isinstance(arg, WebAPICommand): + if arg.cmd not in web_multicommand: + web_multicommand.append(arg.cmd) except KeyError as e: logger.error(e, data_name) continue @@ -465,37 +527,36 @@ class BaseMiner(ABC): for data_name in include: try: - fn_args = self.data_locations[data_name]["kwargs"] - args_to_send = {k: None for k in fn_args} - for arg_name in fn_args: + fn_args = getattr(self.data_locations, data_name).kwargs + args_to_send = {k.name: None for k in fn_args} + for arg in fn_args: try: - if fn_args[arg_name].get("api"): + if isinstance(arg, RPCAPICommand): if api_command_data.get("multicommand"): - args_to_send[arg_name] = api_command_data[ - fn_args[arg_name]["api"] - ][0] + args_to_send[arg.name] = api_command_data[arg.cmd][0] else: - args_to_send[arg_name] = api_command_data - if fn_args[arg_name].get("web"): + args_to_send[arg.name] = api_command_data + if isinstance(arg, WebAPICommand): if web_command_data is not None: if web_command_data.get("multicommand"): - args_to_send[arg_name] = web_command_data[ - fn_args[arg_name]["web"] - ] + args_to_send[arg.name] = web_command_data[arg.cmd] else: if not web_command_data == {"multicommand": False}: - args_to_send[arg_name] = web_command_data + args_to_send[arg.name] = web_command_data except LookupError: - args_to_send[arg_name] = None + args_to_send[arg.name] = None except LookupError: continue - function = getattr(self, self.data_locations[data_name]["cmd"]) + function = getattr(self, getattr(self.data_locations, data_name).cmd) miner_data[data_name] = await function(**args_to_send) return miner_data async def get_data( - self, allow_warning: bool = False, include: list = None, exclude: list = None + self, + allow_warning: bool = False, + include: List[Union[str, DataOptions]] = None, + exclude: List[Union[str, DataOptions]] = None, ) -> MinerData: """Get data from the miner in the form of [`MinerData`][pyasic.data.MinerData]. diff --git a/pyasic/miners/innosilicon/cgminer/A10X/A10X.py b/pyasic/miners/innosilicon/cgminer/A10X/A10X.py index 739e3b82..d1e8c77d 100644 --- a/pyasic/miners/innosilicon/cgminer/A10X/A10X.py +++ b/pyasic/miners/innosilicon/cgminer/A10X/A10X.py @@ -267,32 +267,6 @@ class CGMinerA10X(CGMiner, A10X): return fans - async def get_pools(self, api_pools: dict = None) -> List[dict]: - groups = [] - - if not api_pools: - try: - api_pools = await self.api.pools() - except APIError: - pass - - if api_pools: - try: - pools = {} - for i, pool in enumerate(api_pools["POOLS"]): - pools[f"pool_{i + 1}_url"] = ( - pool["URL"] - .replace("stratum+tcp://", "") - .replace("stratum2+tcp://", "") - ) - pools[f"pool_{i + 1}_user"] = pool["User"] - pools["quota"] = pool["Quota"] if pool.get("Quota") else "0" - - groups.append(pools) - except KeyError: - pass - return groups - async def get_errors( self, web_get_error_detail: dict = None ) -> List[MinerErrorData]: # noqa: named this way for automatic functionality diff --git a/pyasic/miners/innosilicon/cgminer/T3X/T3H.py b/pyasic/miners/innosilicon/cgminer/T3X/T3H.py index 795e8ce7..321c151a 100644 --- a/pyasic/miners/innosilicon/cgminer/T3X/T3H.py +++ b/pyasic/miners/innosilicon/cgminer/T3X/T3H.py @@ -246,32 +246,6 @@ class CGMinerT3HPlus(CGMiner, T3HPlus): return fans - async def get_pools(self, api_pools: dict = None) -> List[dict]: - groups = [] - - if not api_pools: - try: - api_pools = await self.api.pools() - except APIError: - pass - - if api_pools: - try: - pools = {} - for i, pool in enumerate(api_pools["POOLS"]): - pools[f"pool_{i + 1}_url"] = ( - pool["URL"] - .replace("stratum+tcp://", "") - .replace("stratum2+tcp://", "") - ) - pools[f"pool_{i + 1}_user"] = pool["User"] - pools["quota"] = pool["Quota"] if pool.get("Quota") else "0" - - groups.append(pools) - except KeyError: - pass - return groups - async def get_errors( self, web_get_error_detail: dict = None ) -> List[MinerErrorData]: # noqa: named this way for automatic functionality diff --git a/pyasic/miners/unknown.py b/pyasic/miners/unknown.py index f5efca38..c1b1cf97 100644 --- a/pyasic/miners/unknown.py +++ b/pyasic/miners/unknown.py @@ -113,32 +113,6 @@ class UnknownMiner(BaseMiner): async def get_fw_ver(self) -> Optional[str]: return None - async def get_pools(self, api_pools: dict = None) -> List[dict]: - groups = [] - - if not api_pools: - try: - api_pools = await self.api.pools() - except APIError: - pass - - if api_pools: - try: - pools = {} - for i, pool in enumerate(api_pools["POOLS"]): - pools[f"pool_{i + 1}_url"] = ( - pool["URL"] - .replace("stratum+tcp://", "") - .replace("stratum2+tcp://", "") - ) - pools[f"pool_{i + 1}_user"] = pool["User"] - pools["quota"] = pool["Quota"] if pool.get("Quota") else "0" - - groups.append(pools) - except KeyError: - pass - return groups - async def get_errors(self) -> List[MinerErrorData]: return [] @@ -155,6 +129,6 @@ class UnknownMiner(BaseMiner): return None async def get_data( - self, allow_warning: bool = False, data_to_get: list = None + self, allow_warning: bool = False, data_to_get: list = None, **kwargs ) -> MinerData: return MinerData(ip=str(self.ip)) diff --git a/pyasic/network/__init__.py b/pyasic/network/__init__.py index 3e626765..cfe2adcd 100644 --- a/pyasic/network/__init__.py +++ b/pyasic/network/__init__.py @@ -123,9 +123,8 @@ class MinerNetwork: # clear cached miners miner_factory.clear_cached_miners() - limit = asyncio.Semaphore(settings.get("network_scan_threads", 300)) miners = await asyncio.gather( - *[self.ping_and_get_miner(host, limit) for host in self.hosts] + *[self.ping_and_get_miner(host) for host in self.hosts] ) # remove all None from the miner list @@ -148,12 +147,8 @@ class MinerNetwork: loop = asyncio.get_event_loop() # create a list of scan tasks - limit = asyncio.Semaphore(settings.get("network_scan_threads", 300)) miners = asyncio.as_completed( - [ - loop.create_task(self.ping_and_get_miner(host, limit)) - for host in self.hosts - ] + [loop.create_task(self.ping_and_get_miner(host)) for host in self.hosts] ) for miner in miners: try: @@ -162,21 +157,16 @@ class MinerNetwork: yield None @staticmethod - async def ping_and_get_miner( - ip: ipaddress.ip_address, semaphore: asyncio.Semaphore - ) -> Union[None, AnyMiner]: - async with semaphore: - try: - return await ping_and_get_miner(ip) - except ConnectionRefusedError: - tasks = [ - ping_and_get_miner(ip, port=port) for port in [4028, 4029, 8889] - ] - for miner in asyncio.as_completed(tasks): - try: - return await miner - except ConnectionRefusedError: - pass + async def ping_and_get_miner(ip: ipaddress.ip_address) -> Union[None, AnyMiner]: + try: + return await ping_and_get_miner(ip) + except ConnectionRefusedError: + tasks = [ping_and_get_miner(ip, port=port) for port in [4028, 4029, 8889]] + for miner in asyncio.as_completed(tasks): + try: + return await miner + except ConnectionRefusedError: + pass async def ping_and_get_miner( diff --git a/pyasic/settings/__init__.py b/pyasic/settings/__init__.py index 53c6fa67..d1c0f608 100644 --- a/pyasic/settings/__init__.py +++ b/pyasic/settings/__init__.py @@ -24,7 +24,6 @@ from httpx import AsyncHTTPTransport _settings = { # defaults "network_ping_retries": 1, "network_ping_timeout": 3, - "network_scan_threads": 300, "factory_get_retries": 1, "factory_get_timeout": 3, "get_data_retries": 1, diff --git a/pyproject.toml b/pyproject.toml index 79e5d428..670571a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,10 +10,10 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.8" httpx = "^0.26.0" -asyncssh = "^2.14.1" -grpc-requests = "^0.1.12" +asyncssh = "^2.14.2" +grpc-requests = "^0.1.13" passlib = "^1.7.4" -pyaml = "^23.9.7" +pyaml = "^23.12.0" toml = "^0.10.2" betterproto = "2.0.0b6" diff --git a/tests/miners_tests/__init__.py b/tests/miners_tests/__init__.py index a7d7f698..1247fc7d 100644 --- a/tests/miners_tests/__init__.py +++ b/tests/miners_tests/__init__.py @@ -18,6 +18,7 @@ import inspect import sys import unittest import warnings +from dataclasses import asdict from pyasic.miners.backends import CGMiner # noqa from pyasic.miners.base import BaseMiner @@ -57,7 +58,6 @@ class MinersTest(unittest.TestCase): "mac", "model", "expected_hashrate", - "pools", "uptime", "wattage", "wattage_limit", @@ -72,7 +72,9 @@ class MinersTest(unittest.TestCase): miner_api=miner_api, ): miner = MINER_CLASSES[miner_model][miner_api]("127.0.0.1") - miner_keys = sorted(list(miner.data_locations.keys())) + miner_keys = sorted( + [str(k) for k in asdict(miner.data_locations).keys()] + ) self.assertEqual(miner_keys, keys)