Compare commits

..

22 Commits

Author SHA1 Message Date
b-rowan
96bb56ebd1 version: bump version number. 2024-01-14 09:59:06 -07:00
b-rowan
cdd7beccbe Merge pull request #92 from fdeh75/fix-vnish-data-gathering
Fix VNish get_hashrate and get_fans errors
2024-01-14 09:58:16 -07:00
fdeh
1a544851df Fix VNish get_hashrate and get_fans errors
Update vnish.py. Fix data locations according to the method arguments
2024-01-14 19:53:47 +03:00
snyk-bot
84ac991685 fix: docs/requirements.txt to reduce vulnerabilities
The following vulnerabilities are fixed by pinning transitive dependencies:
- https://snyk.io/vuln/SNYK-PYTHON-JINJA2-6150717
2024-01-11 16:00:03 +00:00
b-rowan
9da7b44177 feature: add vnish config parsing. 2024-01-06 11:31:12 -07:00
UpstreamData
e7f05f7a28 version: bump version number. 2024-01-05 16:22:03 -07:00
UpstreamData
2d229be9fd feature: add board serial numbers to whatsminers. 2024-01-05 16:18:03 -07:00
UpstreamData
de5038e57a feature: add AntminerModern serial numbers to Hashboard data. 2024-01-05 15:57:26 -07:00
UpstreamData
8ad1b3f72a refactor: fix formatting issue. 2024-01-05 08:49:44 -07:00
b-rowan
070fb26dbc version: bump version number. 2024-01-04 20:58:44 -07:00
b-rowan
80d9d7df1d bug: fix possible empty command when getting small data points. 2024-01-04 20:58:15 -07:00
UpstreamData
928c24f56f version: bump version number. 2024-01-04 13:07:13 -07:00
UpstreamData
6e7442f90d 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.
2024-01-04 13:03:45 -07:00
b-rowan
936474ed3b Merge pull request #84 from jpcomps/master 2023-12-23 13:07:47 -07:00
John-Paul Compagnone
2e28060e05 fixes, changes, and formatting 2023-12-23 15:01:42 -05:00
John-Paul Compagnone
07f92557c6 cover chiptune case 2023-12-22 23:35:13 -05:00
John-Paul Compagnone
6f6f5743cf add get_config to ePIC backend 2023-12-22 23:35:13 -05:00
Upstream Data
b89ea1fa92 version: bump version number. 2023-12-22 16:29:03 -07:00
Upstream Data
3588197741 dependencies: bump httpx version 2023-12-22 16:28:46 -07:00
Upstream Data
8adc3d2adf version: bump version number. 2023-12-22 15:47:25 -07:00
Upstream Data
040c0b6842 feature: add support for S19 Pro Hydro. 2023-12-22 15:40:23 -07:00
Upstream Data
550b4a97a1 bug: fix a bunch of missed instances of nominal_ naming. 2023-12-22 15:32:01 -07:00
48 changed files with 1347 additions and 1163 deletions

View File

@@ -1,3 +1,3 @@
jinja2<3.1.0 jinja2<3.1.3
mkdocs mkdocs
mkdocstrings[python] mkdocstrings[python]

View File

@@ -263,6 +263,12 @@ If you are sure you want to use this command please use API.send_command("{comma
else: else:
return False, data["STATUS"][0]["Msg"] return False, data["STATUS"][0]["Msg"]
elif isinstance(data["STATUS"], dict):
# new style X19 command
if data["STATUS"]["STATUS"] not in ["S", "I"]:
return False, data["STATUS"]["Msg"]
return True, None
if data["STATUS"] not in ["S", "I"]: if data["STATUS"] not in ["S", "I"]:
return False, data["Msg"] return False, data["Msg"]
else: else:

View File

@@ -48,10 +48,10 @@ PrePowerOnMessage = Union[
def _crypt(word: str, salt: str) -> str: 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 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. ValueError is raised.
Parameters: Parameters:
@@ -62,7 +62,7 @@ def _crypt(word: str, salt: str) -> str:
An MD5 hash of the word with the salt. An MD5 hash of the word with the salt.
""" """
# compile a standard format for 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 # check if the salt matches
match = standard_salt.match(salt) match = standard_salt.match(salt)
# if the matching fails, the salt is incorrect # if the matching fails, the salt is incorrect

View File

@@ -29,7 +29,7 @@ from pyasic.data import (
) )
from pyasic.errors import APIError, APIWarning from pyasic.errors import APIError, APIWarning
from pyasic.miners import get_miner 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_factory import MinerFactory, miner_factory
from pyasic.miners.miner_listener import MinerListener from pyasic.miners.miner_listener import MinerListener
from pyasic.network import MinerNetwork from pyasic.network import MinerNetwork
@@ -50,6 +50,7 @@ __all__ = [
"APIWarning", "APIWarning",
"get_miner", "get_miner",
"AnyMiner", "AnyMiner",
"DataOptions",
"MinerFactory", "MinerFactory",
"miner_factory", "miner_factory",
"MinerListener", "MinerListener",

View File

@@ -161,6 +161,24 @@ class MinerConfig:
power_scaling=PowerScalingConfig.from_bosminer(toml_conf), power_scaling=PowerScalingConfig.from_bosminer(toml_conf),
) )
@classmethod
def from_epic(cls, web_conf: dict) -> "MinerConfig":
return cls(
pools=PoolConfig.from_epic(web_conf),
fan_mode=FanModeConfig.from_epic(web_conf),
temperature=TemperatureConfig.from_epic(web_conf),
mining_mode=MiningModeConfig.from_epic(web_conf),
)
@classmethod
def from_vnish(cls, web_settings: dict) -> "MinerConfig":
return cls(
pools=PoolConfig.from_vnish(web_settings),
fan_mode=FanModeConfig.from_vnish(web_settings),
temperature=TemperatureConfig.from_vnish(web_settings),
mining_mode=MiningModeConfig.from_vnish(web_settings),
)
def merge(a: dict, b: dict) -> dict: def merge(a: dict, b: dict) -> dict:
result = deepcopy(a) result = deepcopy(a)

View File

@@ -50,6 +50,9 @@ class MinerConfigOption(Enum):
def as_epic(self) -> dict: def as_epic(self) -> dict:
return self.value.as_epic() return self.value.as_epic()
def as_vnish(self) -> dict:
return self.value.as_vnish()
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
return self.value(*args, **kwargs) return self.value(*args, **kwargs)
@@ -93,3 +96,6 @@ class MinerConfigValue:
def as_epic(self) -> dict: def as_epic(self) -> dict:
return {} return {}
def as_vnish(self) -> dict:
return {}

View File

@@ -22,10 +22,26 @@ from pyasic.config.base import MinerConfigOption, MinerConfigValue
@dataclass @dataclass
class FanModeNormal(MinerConfigValue): class FanModeNormal(MinerConfigValue):
mode: str = field(init=False, default="normal") mode: str = field(init=False, default="normal")
minimum_fans: int = 1
minimum_speed: int = 0
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeNormal": def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeNormal":
return cls() cls_conf = {}
if dict_conf.get("minimum_fans") is not None:
cls_conf["minimum_fans"] = dict_conf["minimum_fans"]
if dict_conf.get("minimum_speed") is not None:
cls_conf["minimum_speed"] = dict_conf["minimum_speed"]
return cls(**cls_conf)
@classmethod
def from_vnish(cls, web_cooling_settings: dict):
cls_conf = {}
if web_cooling_settings.get("fan_min_count") is not None:
cls_conf["minimum_fans"] = web_cooling_settings["fan_min_count"]
if web_cooling_settings.get("fan_min_duty") is not None:
cls_conf["minimum_speed"] = web_cooling_settings["fan_min_duty"]
return cls(**cls_conf)
def as_am_modern(self) -> dict: def as_am_modern(self) -> dict:
return {"bitmain-fan-ctrl": False, "bitmain-fan-pwn": "100"} return {"bitmain-fan-ctrl": False, "bitmain-fan-pwn": "100"}
@@ -58,6 +74,15 @@ class FanModeManual(MinerConfigValue):
cls_conf["speed"] = toml_fan_conf["speed"] cls_conf["speed"] = toml_fan_conf["speed"]
return cls(**cls_conf) return cls(**cls_conf)
@classmethod
def from_vnish(cls, web_cooling_settings: dict) -> "FanModeManual":
cls_conf = {}
if web_cooling_settings.get("fan_min_count") is not None:
cls_conf["minimum_fans"] = web_cooling_settings["fan_min_count"]
if web_cooling_settings["mode"].get("param") is not None:
cls_conf["speed"] = web_cooling_settings["mode"]["param"]
return cls(**cls_conf)
def as_am_modern(self) -> dict: def as_am_modern(self) -> dict:
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwn": str(self.speed)} return {"bitmain-fan-ctrl": True, "bitmain-fan-pwn": str(self.speed)}
@@ -116,6 +141,17 @@ class FanModeConfig(MinerConfigOption):
else: else:
return cls.default() return cls.default()
@classmethod
def from_epic(cls, web_conf: dict):
try:
fan_mode = web_conf["Fans"]["Fan Mode"]
if fan_mode.get("Manual") is not None:
return cls.manual(speed=fan_mode.get("Manual"))
else:
return cls.normal()
except KeyError:
return cls.default()
@classmethod @classmethod
def from_bosminer(cls, toml_conf: dict): def from_bosminer(cls, toml_conf: dict):
if toml_conf.get("temp_control") is None: if toml_conf.get("temp_control") is None:
@@ -132,3 +168,17 @@ class FanModeConfig(MinerConfigOption):
return cls.manual() return cls.manual()
elif mode == "disabled": elif mode == "disabled":
return cls.immersion() return cls.immersion()
@classmethod
def from_vnish(cls, web_settings: dict):
try:
mode = web_settings["miner"]["cooling"]["mode"]["name"]
except LookupError:
return cls.default()
if mode == "auto":
return cls.normal().from_vnish(web_settings["miner"]["cooling"])
elif mode == "manual":
return cls.manual().from_vnish(web_settings["miner"]["cooling"])
elif mode == "immers":
return cls.immersion()

View File

@@ -14,7 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Union from typing import Dict, Union
from pyasic.config.base import MinerConfigOption, MinerConfigValue from pyasic.config.base import MinerConfigOption, MinerConfigValue
@@ -132,7 +132,7 @@ class MiningModeManual(MinerConfigValue):
global_freq: float global_freq: float
global_volt: float global_volt: float
boards: dict[int, ManualBoardSettings] = field(default_factory=dict) boards: Dict[int, ManualBoardSettings] = field(default_factory=dict)
@classmethod @classmethod
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeManual": def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeManual":
@@ -145,6 +145,20 @@ class MiningModeManual(MinerConfigValue):
def as_am_modern(self) -> dict: def as_am_modern(self) -> dict:
return {"miner-mode": "0"} return {"miner-mode": "0"}
@classmethod
def from_vnish(cls, web_overclock_settings: dict) -> "MiningModeManual":
# will raise KeyError if it cant find the settings, values cannot be empty
voltage = web_overclock_settings["globals"]["volt"]
freq = web_overclock_settings["globals"]["freq"]
boards = {
idx: ManualBoardSettings(
freq=board["freq"],
volt=voltage if not board["freq"] == 0 else 0,
)
for idx, board in enumerate(web_overclock_settings["chains"])
}
return cls(global_freq=freq, global_volt=voltage, boards=boards)
class MiningModeConfig(MinerConfigOption): class MiningModeConfig(MinerConfigOption):
normal = MiningModeNormal normal = MiningModeNormal
@@ -186,6 +200,29 @@ class MiningModeConfig(MinerConfigOption):
return cls.low() return cls.low()
return cls.default() return cls.default()
@classmethod
def from_epic(cls, web_conf: dict):
try:
work_mode = web_conf["PerpetualTune"]["Running"]
if work_mode:
if (
web_conf["PerpetualTune"]["Algorithm"].get("VoltageOptimizer")
is not None
):
return cls.hashrate_tuning(
web_conf["PerpetualTune"]["Algorithm"]["VoltageOptimizer"][
"Target"
]
)
else:
return cls.hashrate_tuning(
web_conf["PerpetualTune"]["Algorithm"]["ChipTune"]["Target"]
)
else:
return cls.normal()
except KeyError:
return cls.default()
@classmethod @classmethod
def from_bosminer(cls, toml_conf: dict): def from_bosminer(cls, toml_conf: dict):
if toml_conf.get("autotuning") is None: if toml_conf.get("autotuning") is None:
@@ -211,3 +248,15 @@ class MiningModeConfig(MinerConfigOption):
if autotuning_conf.get("hashrate_target") is not None: if autotuning_conf.get("hashrate_target") is not None:
return cls.hashrate_tuning(autotuning_conf["hashrate_target"]) return cls.hashrate_tuning(autotuning_conf["hashrate_target"])
return cls.hashrate_tuning() return cls.hashrate_tuning()
@classmethod
def from_vnish(cls, web_settings: dict):
try:
mode_settings = web_settings["miner"]["overclock"]
except KeyError:
return cls.default()
if mode_settings["preset"] == "disabled":
return MiningModeManual.from_vnish(mode_settings)
else:
return cls.power_tuning(int(mode_settings["preset"]))

View File

@@ -16,7 +16,7 @@
import random import random
import string import string
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Union from typing import Dict, List, Union
from pyasic.config.base import MinerConfigValue from pyasic.config.base import MinerConfigValue
@@ -108,6 +108,12 @@ class Pool(MinerConfigValue):
def from_api(cls, api_pool: dict) -> "Pool": def from_api(cls, api_pool: dict) -> "Pool":
return cls(url=api_pool["URL"], user=api_pool["User"], password="x") return cls(url=api_pool["URL"], user=api_pool["User"], password="x")
@classmethod
def from_epic(cls, api_pool: dict) -> "Pool":
return cls(
url=api_pool["pool"], user=api_pool["login"], password=api_pool["password"]
)
@classmethod @classmethod
def from_am_modern(cls, web_pool: dict) -> "Pool": def from_am_modern(cls, web_pool: dict) -> "Pool":
return cls( return cls(
@@ -135,10 +141,18 @@ class Pool(MinerConfigValue):
password=toml_pool_conf["password"], password=toml_pool_conf["password"],
) )
@classmethod
def from_vnish(cls, web_pool: dict) -> "Pool":
return cls(
url=web_pool["url"],
user=web_pool["user"],
password=web_pool["pass"],
)
@dataclass @dataclass
class PoolGroup(MinerConfigValue): class PoolGroup(MinerConfigValue):
pools: list[Pool] = field(default_factory=list) pools: List[Pool] = field(default_factory=list)
quota: int = 1 quota: int = 1
name: str = None name: str = None
@@ -237,6 +251,13 @@ class PoolGroup(MinerConfigValue):
pools.append(Pool.from_api(pool)) pools.append(Pool.from_api(pool))
return cls(pools=pools) return cls(pools=pools)
@classmethod
def from_epic(cls, api_pool_list: list) -> "PoolGroup":
pools = []
for pool in api_pool_list:
pools.append(Pool.from_epic(pool))
return cls(pools=pools)
@classmethod @classmethod
def from_am_modern(cls, web_pool_list: list) -> "PoolGroup": def from_am_modern(cls, web_pool_list: list) -> "PoolGroup":
pools = [] pools = []
@@ -262,10 +283,14 @@ class PoolGroup(MinerConfigValue):
) )
return cls() return cls()
@classmethod
def from_vnish(cls, web_settings_pools: dict) -> "PoolGroup":
return cls([Pool.from_vnish(p) for p in web_settings_pools])
@dataclass @dataclass
class PoolConfig(MinerConfigValue): class PoolConfig(MinerConfigValue):
groups: list[PoolGroup] = field(default_factory=list) groups: List[PoolGroup] = field(default_factory=list)
@classmethod @classmethod
def default(cls) -> "PoolConfig": def default(cls) -> "PoolConfig":
@@ -279,7 +304,7 @@ class PoolConfig(MinerConfigValue):
return cls(groups=[PoolGroup.from_dict(g) for g in dict_conf["groups"]]) return cls(groups=[PoolGroup.from_dict(g) for g in dict_conf["groups"]])
@classmethod @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 = [] group_pools = []
for pool in pools: for pool in pools:
if isinstance(pool, dict): if isinstance(pool, dict):
@@ -329,11 +354,19 @@ class PoolConfig(MinerConfigValue):
@classmethod @classmethod
def from_api(cls, api_pools: dict) -> "PoolConfig": def from_api(cls, api_pools: dict) -> "PoolConfig":
try:
pool_data = api_pools["POOLS"] pool_data = api_pools["POOLS"]
except KeyError:
return PoolConfig.default()
pool_data = sorted(pool_data, key=lambda x: int(x["POOL"])) pool_data = sorted(pool_data, key=lambda x: int(x["POOL"]))
return cls([PoolGroup.from_api(pool_data)]) return cls([PoolGroup.from_api(pool_data)])
@classmethod
def from_epic(cls, web_conf: dict) -> "PoolConfig":
pool_data = web_conf["StratumConfigs"]
return cls([PoolGroup.from_epic(pool_data)])
@classmethod @classmethod
def from_am_modern(cls, web_conf: dict) -> "PoolConfig": def from_am_modern(cls, web_conf: dict) -> "PoolConfig":
pool_data = web_conf["pools"] pool_data = web_conf["pools"]
@@ -354,3 +387,10 @@ class PoolConfig(MinerConfigValue):
return cls() return cls()
return cls([PoolGroup.from_bosminer(g) for g in toml_conf["group"]]) return cls([PoolGroup.from_bosminer(g) for g in toml_conf["group"]])
@classmethod
def from_vnish(cls, web_settings: dict) -> "PoolConfig":
try:
return cls([PoolGroup.from_vnish(web_settings["miner"]["pools"])])
except LookupError:
return cls()

View File

@@ -56,3 +56,27 @@ class TemperatureConfig(MinerConfigValue):
hot=temp_control.get("hot_temp"), hot=temp_control.get("hot_temp"),
danger=temp_control.get("dangerous_temp"), danger=temp_control.get("dangerous_temp"),
) )
@classmethod
def from_epic(cls, web_conf: dict) -> "TemperatureConfig":
dangerous_temp = None
try:
hot_temp = web_conf["Misc"]["Shutdown Temp"]
except KeyError:
hot_temp = None
# Need to do this in two blocks to avoid KeyError if one is missing
try:
target_temp = web_conf["Fans"]["Fan Mode"]["Auto"]["Target Temperature"]
except KeyError:
target_temp = None
return cls(target=target_temp, hot=hot_temp, danger=dangerous_temp)
@classmethod
def from_vnish(cls, web_settings: dict):
try:
if web_settings["miner"]["cooling"]["mode"]["name"] == "auto":
return cls(target=web_settings["miner"]["cooling"]["mode"]["param"])
except KeyError:
pass
return cls()

View File

@@ -48,6 +48,7 @@ class HashBoard:
chip_temp: int = None chip_temp: int = None
chips: int = None chips: int = None
expected_chips: int = None expected_chips: int = None
serial_number: str = None
missing: bool = True missing: bool = True
def get(self, __key: str, default: Any = None): def get(self, __key: str, default: Any = None):

View File

@@ -27,6 +27,7 @@ from pyasic.miners.types import (
S19jPro, S19jPro,
S19Plus, S19Plus,
S19Pro, S19Pro,
S19ProHydro,
S19ProPlus, S19ProPlus,
) )
@@ -77,3 +78,7 @@ class BMMinerS19jPro(AntminerModern, S19jPro):
class BMMinerS19L(AntminerModern, S19L): class BMMinerS19L(AntminerModern, S19L):
pass pass
class BMMinerS19ProHydro(AntminerModern, S19ProHydro):
pass

View File

@@ -25,6 +25,7 @@ from .S19 import (
BMMinerS19L, BMMinerS19L,
BMMinerS19Plus, BMMinerS19Plus,
BMMinerS19Pro, BMMinerS19Pro,
BMMinerS19ProHydro,
BMMinerS19ProPlus, BMMinerS19ProPlus,
BMMinerS19XP, BMMinerS19XP,
) )

View File

@@ -14,7 +14,6 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import asyncio
from typing import List, Optional, Union from typing import List, Optional, Union
from pyasic.API import APIError from pyasic.API import APIError
@@ -23,50 +22,60 @@ from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, X19Error from pyasic.data.error_codes import MinerErrorData, X19Error
from pyasic.miners.backends.bmminer import BMMiner from pyasic.miners.backends.bmminer import BMMiner
from pyasic.miners.backends.cgminer import CGMiner from pyasic.miners.backends.cgminer import CGMiner
from pyasic.miners.base import (
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
WebAPICommand,
)
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
ANTMINER_MODERN_DATA_LOC = { ANTMINER_MODERN_DATA_LOC = DataLocations(
"mac": { **{
"cmd": "get_mac", str(DataOptions.MAC): DataFunction(
"kwargs": {"web_get_system_info": {"web": "get_system_info"}}, "get_mac", [WebAPICommand("web_get_system_info", "get_system_info")]
}, ),
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MODEL): DataFunction("get_model"),
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.API_VERSION): DataFunction(
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, "get_api_ver", [RPCAPICommand("api_version", "version")]
"hostname": { ),
"cmd": "get_hostname", str(DataOptions.FW_VERSION): DataFunction(
"kwargs": {"web_get_system_info": {"web": "get_system_info"}}, "get_fw_ver", [RPCAPICommand("api_version", "version")]
}, ),
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, str(DataOptions.HOSTNAME): DataFunction(
"expected_hashrate": { "get_hostname", [WebAPICommand("web_get_system_info", "get_system_info")]
"cmd": "get_expected_hashrate", ),
"kwargs": {"api_stats": {"api": "stats"}}, str(DataOptions.HASHRATE): DataFunction(
}, "get_hashrate", [RPCAPICommand("api_summary", "summary")]
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, ),
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"wattage": {"cmd": "get_wattage", "kwargs": {}}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, ),
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.HASHBOARDS): DataFunction("get_hashboards", []),
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"errors": {"cmd": "get_errors", "kwargs": {"web_summary": {"web": "summary"}}}, str(DataOptions.WATTAGE): DataFunction("get_wattage"),
"fault_light": { str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
"cmd": "get_fault_light", str(DataOptions.FANS): DataFunction(
"kwargs": {"web_get_blink_status": {"web": "get_blink_status"}}, "get_fans", [RPCAPICommand("api_stats", "stats")]
}, ),
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
"is_mining": { str(DataOptions.ERRORS): DataFunction(
"cmd": "is_mining", "get_errors", [WebAPICommand("web_summary", "summary")]
"kwargs": {"web_get_conf": {"web": "get_miner_conf"}}, ),
}, str(DataOptions.FAULT_LIGHT): DataFunction(
"uptime": { "get_fault_light",
"cmd": "get_uptime", [WebAPICommand("web_get_blink_status", "get_blink_status")],
"kwargs": {"api_stats": {"api": "stats"}}, ),
}, str(DataOptions.IS_MINING): DataFunction(
"config": { "is_mining", [WebAPICommand("web_get_conf", "get_miner_conf")]
"cmd": "get_config", ),
"kwargs": {}, str(DataOptions.UPTIME): DataFunction(
}, "get_uptime", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.CONFIG): DataFunction("get_config"),
} }
)
class AntminerModern(BMMiner): class AntminerModern(BMMiner):
@@ -185,6 +194,42 @@ class AntminerModern(BMMiner):
pass pass
return errors return errors
async def get_hashboards(self) -> List[HashBoard]:
hashboards = [
HashBoard(idx, expected_chips=self.expected_chips)
for idx in range(self.expected_hashboards)
]
try:
api_stats = await self.api.send_command("stats", new_api=True)
except APIError:
return hashboards
if api_stats:
try:
for board in api_stats["STATS"][0]["chain"]:
hashboards[board["index"]].hashrate = round(
board["rate_real"] / 1000, 2
)
hashboards[board["index"]].chips = board["asic_num"]
board_temp_data = list(
filter(lambda x: not x == 0, board["temp_pcb"])
)
hashboards[board["index"]].temp = sum(board_temp_data) / len(
board_temp_data
)
chip_temp_data = list(
filter(lambda x: not x == 0, board["temp_chip"])
)
hashboards[board["index"]].chip_temp = sum(chip_temp_data) / len(
chip_temp_data
)
hashboards[board["index"]].serial_number = board["sn"]
hashboards[board["index"]].missing = False
except LookupError:
pass
return hashboards
async def get_fault_light(self, web_get_blink_status: dict = None) -> bool: async def get_fault_light(self, web_get_blink_status: dict = None) -> bool:
if self.light: if self.light:
return self.light return self.light
@@ -298,39 +343,49 @@ class AntminerModern(BMMiner):
pass pass
ANTMINER_OLD_DATA_LOC = { ANTMINER_OLD_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction("get_mac"),
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.MODEL): DataFunction("get_model"),
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.API_VERSION): DataFunction(
"hostname": { "get_api_ver", [RPCAPICommand("api_version", "version")]
"cmd": "get_hostname", ),
"kwargs": {"web_get_system_info": {"web": "get_system_info"}}, str(DataOptions.FW_VERSION): DataFunction(
}, "get_fw_ver", [RPCAPICommand("api_version", "version")]
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, ),
"expected_hashrate": { str(DataOptions.HOSTNAME): DataFunction(
"cmd": "get_expected_hashrate", "get_hostname", [WebAPICommand("web_get_system_info", "get_system_info")]
"kwargs": {"api_stats": {"api": "stats"}}, ),
}, str(DataOptions.HASHRATE): DataFunction(
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, "get_hashrate", [RPCAPICommand("api_summary", "summary")]
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, ),
"wattage": {"cmd": "get_wattage", "kwargs": {}}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, ),
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, str(DataOptions.HASHBOARDS): DataFunction(
"errors": {"cmd": "get_errors", "kwargs": {}}, "get_hashboards", [RPCAPICommand("api_stats", "stats")]
"fault_light": { ),
"cmd": "get_fault_light", str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"kwargs": {"web_get_blink_status": {"web": "get_blink_status"}}, str(DataOptions.WATTAGE): DataFunction("get_wattage"),
}, str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, str(DataOptions.FANS): DataFunction(
"is_mining": { "get_fans", [RPCAPICommand("api_stats", "stats")]
"cmd": "is_mining", ),
"kwargs": {"web_get_conf": {"web": "get_miner_conf"}}, str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
}, str(DataOptions.ERRORS): DataFunction("get_errors"),
"uptime": {"cmd": "get_uptime", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.FAULT_LIGHT): DataFunction(
"config": {"cmd": "get_config", "kwargs": {}}, "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): class AntminerOld(CGMiner):

View File

@@ -22,32 +22,48 @@ from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner from pyasic.miners.base import (
BaseMiner,
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
)
BFGMINER_DATA_LOC = { BFGMINER_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction("get_mac"),
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.MODEL): DataFunction("get_model"),
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.API_VERSION): DataFunction(
"hostname": {"cmd": "get_hostname", "kwargs": {}}, "get_api_ver", [RPCAPICommand("api_version", "version")]
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, ),
"expected_hashrate": { str(DataOptions.FW_VERSION): DataFunction(
"cmd": "get_expected_hashrate", "get_fw_ver", [RPCAPICommand("api_version", "version")]
"kwargs": {"api_stats": {"api": "stats"}}, ),
}, str(DataOptions.HOSTNAME): DataFunction("get_hostname"),
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.HASHRATE): DataFunction(
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, "get_hashrate", [RPCAPICommand("api_summary", "summary")]
"wattage": {"cmd": "get_wattage", "kwargs": {}}, ),
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, ),
"errors": {"cmd": "get_errors", "kwargs": {}}, str(DataOptions.HASHBOARDS): DataFunction(
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, "get_hashboards", [RPCAPICommand("api_stats", "stats")]
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, ),
"is_mining": {"cmd": "is_mining", "kwargs": {}}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"uptime": {"cmd": "get_uptime", "kwargs": {}}, str(DataOptions.WATTAGE): DataFunction("get_wattage"),
"config": {"cmd": "get_config", "kwargs": {}}, 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): class BFGMiner(BaseMiner):
@@ -268,32 +284,6 @@ class BFGMiner(BaseMiner):
return fans 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]: async def get_errors(self) -> List[MinerErrorData]:
return [] return []

View File

@@ -20,41 +20,55 @@ from pyasic.data import HashBoard
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.logger import logger from pyasic.logger import logger
from pyasic.miners.backends import BFGMiner from pyasic.miners.backends import BFGMiner
from pyasic.miners.base import (
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
WebAPICommand,
)
from pyasic.web.goldshell import GoldshellWebAPI from pyasic.web.goldshell import GoldshellWebAPI
GOLDSHELL_DATA_LOC = { GOLDSHELL_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {"web_setting": {"web": "setting"}}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction(
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, "get_mac", [WebAPICommand("web_setting", "setting")]
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"web_status": {"web": "status"}}}, ),
"hostname": {"cmd": "get_hostname", "kwargs": {}}, str(DataOptions.MODEL): DataFunction("get_model"),
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, str(DataOptions.API_VERSION): DataFunction(
"expected_hashrate": { "get_api_ver", [RPCAPICommand("api_version", "version")]
"cmd": "get_expected_hashrate", ),
"kwargs": {"api_stats": {"api": "stats"}}, str(DataOptions.FW_VERSION): DataFunction(
}, "get_fw_ver", [WebAPICommand("web_status", "status")]
"hashboards": { ),
"cmd": "get_hashboards", str(DataOptions.HOSTNAME): DataFunction("get_hostname"),
"kwargs": { str(DataOptions.HASHRATE): DataFunction(
"api_devs": {"api": "devs"}, "get_hashrate", [RPCAPICommand("api_summary", "summary")]
"api_devdetails": {"api": "devdetails"}, ),
}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, ),
"wattage": {"cmd": "get_wattage", "kwargs": {}}, str(DataOptions.HASHBOARDS): DataFunction(
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, "get_hashboards",
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, [
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, RPCAPICommand("api_devs", "devs"),
"errors": {"cmd": "get_errors", "kwargs": {}}, RPCAPICommand("api_devdetails", "devdetails"),
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, ],
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, ),
"is_mining": {"cmd": "is_mining", "kwargs": {}}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"uptime": {"cmd": "get_uptime", "kwargs": {}}, str(DataOptions.WATTAGE): DataFunction("get_wattage"),
"config": { str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
"cmd": "get_config", str(DataOptions.FANS): DataFunction(
"kwargs": {}, "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): class BFGMinerGoldshell(BFGMiner):

View File

@@ -23,32 +23,50 @@ from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner from pyasic.miners.base import (
BaseMiner,
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
)
BMMINER_DATA_LOC = { BMMINER_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction("get_mac"),
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.MODEL): DataFunction("get_model"),
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.API_VERSION): DataFunction(
"hostname": {"cmd": "get_hostname", "kwargs": {}}, "get_api_ver", [RPCAPICommand("api_version", "version")]
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, ),
"expected_hashrate": { str(DataOptions.FW_VERSION): DataFunction(
"cmd": "get_expected_hashrate", "get_fw_ver", [RPCAPICommand("api_version", "version")]
"kwargs": {"api_stats": {"api": "stats"}}, ),
}, str(DataOptions.HOSTNAME): DataFunction("get_hostname"),
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.HASHRATE): DataFunction(
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, "get_hashrate", [RPCAPICommand("api_summary", "summary")]
"wattage": {"cmd": "get_wattage", "kwargs": {}}, ),
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, ),
"errors": {"cmd": "get_errors", "kwargs": {}}, str(DataOptions.HASHBOARDS): DataFunction(
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, "get_hashboards", [RPCAPICommand("api_stats", "stats")]
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, ),
"is_mining": {"cmd": "is_mining", "kwargs": {}}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"uptime": {"cmd": "get_uptime", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.WATTAGE): DataFunction("get_wattage"),
"config": {"cmd": "get_config", "kwargs": {}}, 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): class BMMiner(BaseMiner):
@@ -315,32 +333,6 @@ class BMMiner(BaseMiner):
return fans 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]: async def get_errors(self) -> List[MinerErrorData]:
return [] return []

View File

@@ -27,53 +27,69 @@ from pyasic.config.mining import MiningModePowerTune
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
from pyasic.errors import APIError 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 from pyasic.web.bosminer import BOSMinerWebAPI
BOSMINER_DATA_LOC = { BOSMINER_DATA_LOC = DataLocations(
"mac": { **{
"cmd": "get_mac", str(DataOptions.MAC): DataFunction(
"kwargs": { "get_mac",
"web_net_conf": {"web": "/cgi-bin/luci/admin/network/iface_status/lan"} [
}, WebAPICommand(
}, "web_net_conf", "/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", str(DataOptions.MODEL): DataFunction("get_model"),
"kwargs": { str(DataOptions.API_VERSION): DataFunction(
"graphql_version": {"web": {"bos": {"info": {"version": {"full": None}}}}} "get_api_ver", [RPCAPICommand("api_version", "version")]
}, ),
}, str(DataOptions.FW_VERSION): DataFunction(
"hostname": { "get_fw_ver",
"cmd": "get_hostname", [
"kwargs": {"graphql_hostname": {"web": {"bos": {"hostname": None}}}}, GraphQLCommand(
}, "graphql_version", {"bos": {"info": {"version": {"full": None}}}}
"hashrate": { )
"cmd": "get_hashrate", ],
"kwargs": { ),
"api_summary": {"api": "summary"}, str(DataOptions.HOSTNAME): DataFunction(
"graphql_hashrate": { "get_hostname",
"web": { [GraphQLCommand("graphql_hostname", {"bos": {"hostname": None}})],
),
str(DataOptions.HASHRATE): DataFunction(
"get_hashrate",
[
RPCAPICommand("api_summary", "summary"),
GraphQLCommand(
"graphql_hashrate",
{
"bosminer": { "bosminer": {
"info": {"workSolver": {"realHashrate": {"mhs1M": None}}} "info": {"workSolver": {"realHashrate": {"mhs1M": None}}}
} }
}, },
}, ),
}, ],
}, ),
"expected_hashrate": { str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"cmd": "get_expected_hashrate", "get_expected_hashrate", [RPCAPICommand("api_devs", "devs")]
"kwargs": {"api_devs": {"api": "devs"}}, ),
}, str(DataOptions.HASHBOARDS): DataFunction(
"hashboards": { "get_hashboards",
"cmd": "get_hashboards", [
"kwargs": { RPCAPICommand("api_temps", "temps"),
"api_temps": {"api": "temps"}, RPCAPICommand("api_devdetails", "devdetails"),
"api_devdetails": {"api": "devdetails"}, RPCAPICommand("api_devs", "devs"),
"api_devs": {"api": "devs"}, GraphQLCommand(
"graphql_boards": { "graphql_boards",
"web": { {
"bosminer": { "bosminer": {
"info": { "info": {
"workSolver": { "workSolver": {
@@ -87,50 +103,54 @@ BOSMINER_DATA_LOC = {
} }
} }
}, },
}, ),
}, ],
}, ),
"wattage": { str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"cmd": "get_wattage", str(DataOptions.WATTAGE): DataFunction(
"kwargs": { "get_wattage",
"api_tunerstatus": {"api": "tunerstatus"}, [
"graphql_wattage": { RPCAPICommand("api_tunerstatus", "tunerstatus"),
"web": { GraphQLCommand(
"graphql_wattage",
{
"bosminer": { "bosminer": {
"info": {"workSolver": {"power": {"approxConsumptionW": None}}} "info": {
"workSolver": {"power": {"approxConsumptionW": None}}
} }
} }
}, },
}, ),
}, ],
"wattage_limit": { ),
"cmd": "get_wattage_limit", str(DataOptions.WATTAGE_LIMIT): DataFunction(
"kwargs": { "get_wattage_limit",
"api_tunerstatus": {"api": "tunerstatus"}, [
"graphql_wattage_limit": { RPCAPICommand("api_tunerstatus", "tunerstatus"),
"web": { GraphQLCommand(
"bosminer": {"info": {"workSolver": {"power": {"limitW": None}}}} "graphql_wattage_limit",
} {"bosminer": {"info": {"workSolver": {"power": {"limitW": None}}}}},
}, ),
}, ],
}, ),
"fans": { str(DataOptions.FANS): DataFunction(
"cmd": "get_fans", "get_fans",
"kwargs": { [
"api_fans": {"api": "fans"}, RPCAPICommand("api_fans", "fans"),
"graphql_fans": { GraphQLCommand(
"web": {"bosminer": {"info": {"fans": {"name": None, "rpm": None}}}} "graphql_fans",
}, {"bosminer": {"info": {"fans": {"name": None, "rpm": None}}}},
}, ),
}, ],
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, ),
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"),
"errors": { str(DataOptions.ERRORS): DataFunction(
"cmd": "get_errors", "get_errors",
"kwargs": { [
"api_tunerstatus": {"api": "tunerstatus"}, RPCAPICommand("api_tunerstatus", "tunerstatus"),
"graphql_errors": { GraphQLCommand(
"web": { "graphql_errors",
{
"bosminer": { "bosminer": {
"info": { "info": {
"workSolver": { "workSolver": {
@@ -141,43 +161,23 @@ BOSMINER_DATA_LOC = {
} }
} }
} }
},
),
],
),
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"),
} }
}, )
},
},
"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": {}},
}
class BOSMiner(BaseMiner): class BOSMiner(BaseMiner):
@@ -231,7 +231,6 @@ class BOSMiner(BaseMiner):
return result return result
async def fault_light_on(self) -> bool: 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.") logging.debug(f"{self}: Sending fault_light on command.")
ret = await self.send_ssh_command("miner fault_light on") ret = await self.send_ssh_command("miner fault_light on")
logging.debug(f"{self}: fault_light on command completed.") logging.debug(f"{self}: fault_light on command completed.")
@@ -241,7 +240,6 @@ class BOSMiner(BaseMiner):
return False return False
async def fault_light_off(self) -> bool: 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.") logging.debug(f"{self}: Sending fault_light off command.")
self.light = False self.light = False
ret = await self.send_ssh_command("miner fault_light off") ret = await self.send_ssh_command("miner fault_light off")
@@ -252,11 +250,9 @@ class BOSMiner(BaseMiner):
return False return False
async def restart_backend(self) -> bool: 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() return await self.restart_bosminer()
async def restart_bosminer(self) -> bool: async def restart_bosminer(self) -> bool:
"""Restart bosminer hashing process."""
logging.debug(f"{self}: Sending bosminer restart command.") logging.debug(f"{self}: Sending bosminer restart command.")
ret = await self.send_ssh_command("/etc/init.d/bosminer restart") ret = await self.send_ssh_command("/etc/init.d/bosminer restart")
logging.debug(f"{self}: bosminer restart command completed.") logging.debug(f"{self}: bosminer restart command completed.")
@@ -285,7 +281,6 @@ class BOSMiner(BaseMiner):
return False return False
async def reboot(self) -> bool: async def reboot(self) -> bool:
"""Reboots power to the physical miner."""
logging.debug(f"{self}: Sending reboot command.") logging.debug(f"{self}: Sending reboot command.")
ret = await self.send_ssh_command("/sbin/reboot") ret = await self.send_ssh_command("/sbin/reboot")
logging.debug(f"{self}: Reboot command completed.") logging.debug(f"{self}: Reboot command completed.")
@@ -537,7 +532,7 @@ class BOSMiner(BaseMiner):
pass pass
try: try:
async with (await self._get_ssh_connection()) as conn: async with await self._get_ssh_connection() as conn:
if conn is not None: if conn is not None:
data = await conn.run("cat /proc/sys/kernel/hostname") data = await conn.run("cat /proc/sys/kernel/hostname")
host = data.stdout.strip() host = data.stdout.strip()
@@ -833,95 +828,6 @@ class BOSMiner(BaseMiner):
async def get_fan_psu(self) -> Optional[int]: async def get_fan_psu(self) -> Optional[int]:
return None 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( async def get_errors(
self, api_tunerstatus: dict = None, graphql_errors: dict = None self, api_tunerstatus: dict = None, graphql_errors: dict = None
) -> List[MinerErrorData]: ) -> List[MinerErrorData]:

View File

@@ -136,9 +136,6 @@ class BOSMinerOld(BOSMiner):
async def get_fw_ver(self, *args, **kwargs) -> Optional[str]: async def get_fw_ver(self, *args, **kwargs) -> Optional[str]:
return None return None
async def get_pools(self, *args, **kwargs) -> List[dict]:
return []
async def get_errors(self, *args, **kwargs) -> List[MinerErrorData]: async def get_errors(self, *args, **kwargs) -> List[MinerErrorData]:
return [] return []

View File

@@ -23,74 +23,86 @@ from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, WhatsminerError from pyasic.data.error_codes import MinerErrorData, WhatsminerError
from pyasic.errors import APIError 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 = { BTMINER_DATA_LOC = DataLocations(
"mac": { **{
"cmd": "get_mac", str(DataOptions.MAC): DataFunction(
"kwargs": { "get_mac",
"api_summary": {"api": "summary"}, [
"api_get_miner_info": {"api": "get_miner_info"}, RPCAPICommand("api_summary", "summary"),
}, RPCAPICommand("api_get_miner_info", "get_miner_info"),
}, ],
"model": {"cmd": "get_model", "kwargs": {}}, ),
"api_ver": { str(DataOptions.MODEL): DataFunction("get_model"),
"cmd": "get_api_ver", str(DataOptions.API_VERSION): DataFunction(
"kwargs": {"api_get_version": {"api": "get_version"}}, "get_api_ver", [RPCAPICommand("api_get_version", "get_version")]
}, ),
"fw_ver": { str(DataOptions.FW_VERSION): DataFunction(
"cmd": "get_fw_ver", "get_fw_ver",
"kwargs": { [
"api_get_version": {"api": "get_version"}, RPCAPICommand("api_get_version", "get_version"),
"api_summary": {"api": "summary"}, RPCAPICommand("api_summary", "summary"),
}, ],
}, ),
"hostname": { str(DataOptions.HOSTNAME): DataFunction(
"cmd": "get_hostname", "get_hostname", [RPCAPICommand("api_get_miner_info", "get_miner_info")]
"kwargs": {"api_get_miner_info": {"api": "get_miner_info"}}, ),
}, str(DataOptions.HASHRATE): DataFunction(
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, "get_hashrate", [RPCAPICommand("api_summary", "summary")]
"expected_hashrate": { ),
"cmd": "get_expected_hashrate", str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"kwargs": {"api_summary": {"api": "summary"}}, "get_expected_hashrate", [RPCAPICommand("api_summary", "summary")]
}, ),
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_devs": {"api": "devs"}}}, str(DataOptions.HASHBOARDS): DataFunction(
"env_temp": {"cmd": "get_env_temp", "kwargs": {"api_summary": {"api": "summary"}}}, "get_hashboards", [RPCAPICommand("api_devs", "devs")]
"wattage": {"cmd": "get_wattage", "kwargs": {"api_summary": {"api": "summary"}}}, ),
"wattage_limit": { str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"cmd": "get_wattage_limit", "get_env_temp", [RPCAPICommand("api_summary", "summary")]
"kwargs": {"api_summary": {"api": "summary"}}, ),
}, str(DataOptions.WATTAGE): DataFunction(
"fans": { "get_wattage", [RPCAPICommand("api_summary", "summary")]
"cmd": "get_fans", ),
"kwargs": { str(DataOptions.WATTAGE_LIMIT): DataFunction(
"api_summary": {"api": "summary"}, "get_wattage_limit", [RPCAPICommand("api_summary", "summary")]
"api_get_psu": {"api": "get_psu"}, ),
}, str(DataOptions.FANS): DataFunction(
}, "get_fans",
"fan_psu": { [
"cmd": "get_fan_psu", RPCAPICommand("api_summary", "summary"),
"kwargs": { RPCAPICommand("api_get_psu", "get_psu"),
"api_summary": {"api": "summary"}, ],
"api_get_psu": {"api": "get_psu"}, ),
}, str(DataOptions.FAN_PSU): DataFunction(
}, "get_fan_psu",
"errors": { [
"cmd": "get_errors", RPCAPICommand("api_summary", "summary"),
"kwargs": { RPCAPICommand("api_get_psu", "get_psu"),
"api_summary": {"api": "summary"}, ],
"api_get_error_code": {"api": "get_error_code"}, ),
}, str(DataOptions.ERRORS): DataFunction(
}, "get_errors", [RPCAPICommand("api_get_error_code", "get_error_code")]
"fault_light": { ),
"cmd": "get_fault_light", str(DataOptions.FAULT_LIGHT): DataFunction(
"kwargs": {"api_get_miner_info": {"api": "get_miner_info"}}, "get_fault_light",
}, [RPCAPICommand("api_get_miner_info", "get_miner_info")],
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, ),
"is_mining": {"cmd": "is_mining", "kwargs": {"api_status": {"api": "status"}}}, str(DataOptions.IS_MINING): DataFunction(
"uptime": {"cmd": "get_uptime", "kwargs": {"api_summary": {"api": "summary"}}}, "is_mining", [RPCAPICommand("api_status", "status")]
"config": {"cmd": "get_config", "kwargs": {}}, ),
str(DataOptions.UPTIME): DataFunction(
"get_uptime", [RPCAPICommand("api_summary", "summary")]
),
str(DataOptions.CONFIG): DataFunction("get_config"),
} }
)
class BTMiner(BaseMiner): class BTMiner(BaseMiner):
@@ -434,6 +446,7 @@ class BTMiner(BaseMiner):
float(board["MHS 1m"] / 1000000), 2 float(board["MHS 1m"] / 1000000), 2
) )
hashboards[board["ASC"]].chips = board["Effective Chips"] hashboards[board["ASC"]].chips = board["Effective Chips"]
hashboards[board["ASC"]].serial_number = board["PCB SN"]
hashboards[board["ASC"]].missing = False hashboards[board["ASC"]].missing = False
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
@@ -529,32 +542,6 @@ class BTMiner(BaseMiner):
except (KeyError, TypeError): except (KeyError, TypeError):
pass 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( async def get_errors(
self, api_summary: dict = None, api_get_error_code: dict = None self, api_summary: dict = None, api_get_error_code: dict = None
) -> List[MinerErrorData]: ) -> List[MinerErrorData]:
@@ -600,8 +587,8 @@ class BTMiner(BaseMiner):
if api_summary: if api_summary:
try: try:
expected_hashrate = api_summary["SUMMARY"][0]["Factory GHS"] expected_hashrate = api_summary["SUMMARY"][0]["Factory GHS"]
if nominal_hashrate: if expected_hashrate:
return round(nominal_hashrate / 1000, 2) return round(expected_hashrate / 1000, 2)
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass

View File

@@ -23,32 +23,50 @@ from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner from pyasic.miners.base import (
BaseMiner,
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
)
CGMINER_DATA_LOC = { CGMINER_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction("get_mac"),
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.MODEL): DataFunction("get_model"),
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, str(DataOptions.API_VERSION): DataFunction(
"hostname": {"cmd": "get_hostname", "kwargs": {}}, "get_api_ver", [RPCAPICommand("api_version", "version")]
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, ),
"expected_hashrate": { str(DataOptions.FW_VERSION): DataFunction(
"cmd": "get_expected_hashrate", "get_fw_ver", [RPCAPICommand("api_version", "version")]
"kwargs": {"api_stats": {"api": "stats"}}, ),
}, str(DataOptions.HOSTNAME): DataFunction("get_hostname"),
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.HASHRATE): DataFunction(
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, "get_hashrate", [RPCAPICommand("api_summary", "summary")]
"wattage": {"cmd": "get_wattage", "kwargs": {}}, ),
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, ),
"errors": {"cmd": "get_errors", "kwargs": {}}, str(DataOptions.HASHBOARDS): DataFunction(
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, "get_hashboards", [RPCAPICommand("api_stats", "stats")]
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, ),
"is_mining": {"cmd": "is_mining", "kwargs": {}}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"uptime": {"cmd": "get_uptime", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.WATTAGE): DataFunction("get_wattage"),
"config": {"cmd": "get_config", "kwargs": {}}, 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): class CGMiner(BaseMiner):
@@ -94,11 +112,9 @@ class CGMiner(BaseMiner):
return result return result
async def restart_backend(self) -> bool: 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() return await self.restart_cgminer()
async def restart_cgminer(self) -> bool: async def restart_cgminer(self) -> bool:
"""Restart cgminer hashing process."""
commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"] commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"]
commands = ";".join(commands) commands = ";".join(commands)
ret = await self.send_ssh_command(commands) ret = await self.send_ssh_command(commands)
@@ -107,7 +123,6 @@ class CGMiner(BaseMiner):
return True return True
async def reboot(self) -> bool: async def reboot(self) -> bool:
"""Reboots power to the physical miner."""
logging.debug(f"{self}: Sending reboot command.") logging.debug(f"{self}: Sending reboot command.")
ret = await self.send_ssh_command("reboot") ret = await self.send_ssh_command("reboot")
if ret is None: if ret is None:
@@ -328,32 +343,6 @@ class CGMiner(BaseMiner):
async def get_fan_psu(self) -> Optional[int]: async def get_fan_psu(self) -> Optional[int]:
return None 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]: async def get_errors(self) -> List[MinerErrorData]:
return [] return []

View File

@@ -23,37 +23,52 @@ from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.backends import CGMiner from pyasic.miners.backends import CGMiner
from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPICommand
AVALON_DATA_LOC = { AVALON_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {"api_version": {"api": "version"}}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction(
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, "get_mac", [RPCAPICommand("api_version", "version")]
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, ),
"hostname": {"cmd": "get_hostname", "kwargs": {"mac": {"api": "version"}}}, str(DataOptions.MODEL): DataFunction("get_model"),
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_devs": {"api": "devs"}}}, str(DataOptions.API_VERSION): DataFunction(
"expected_hashrate": { "get_api_ver", [RPCAPICommand("api_version", "version")]
"cmd": "get_expected_hashrate", ),
"kwargs": {"api_stats": {"api": "stats"}}, str(DataOptions.FW_VERSION): DataFunction(
}, "get_fw_ver", [RPCAPICommand("api_version", "version")]
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, ),
"env_temp": {"cmd": "get_env_temp", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.HOSTNAME): DataFunction(
"wattage": {"cmd": "get_wattage", "kwargs": {}}, "get_hostname", [RPCAPICommand("api_version", "version")]
"wattage_limit": { ),
"cmd": "get_wattage_limit", str(DataOptions.HASHRATE): DataFunction(
"kwargs": {"api_stats": {"api": "stats"}}, "get_hashrate", [RPCAPICommand("api_devs", "devs")]
}, ),
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"errors": {"cmd": "get_errors", "kwargs": {}}, ),
"fault_light": { str(DataOptions.HASHBOARDS): DataFunction(
"cmd": "get_fault_light", "get_hashboards", [RPCAPICommand("api_stats", "stats")]
"kwargs": {"api_stats": {"api": "stats"}}, ),
}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, "get_env_temp", [RPCAPICommand("api_stats", "stats")]
"is_mining": {"cmd": "is_mining", "kwargs": {}}, ),
"uptime": {"cmd": "get_uptime", "kwargs": {}}, str(DataOptions.WATTAGE): DataFunction("get_wattage"),
"config": {"cmd": "get_config", "kwargs": {}}, 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): class CGMinerAvalon(CGMiner):
@@ -116,7 +131,7 @@ class CGMinerAvalon(CGMiner):
@staticmethod @staticmethod
def parse_stats(stats): def parse_stats(stats):
_stats_items = re.findall(".+?\[*?]", stats) _stats_items = re.findall(".+?\\[*?]", stats)
stats_items = [] stats_items = []
stats_dict = {} stats_dict = {}
for item in _stats_items: for item in _stats_items:
@@ -318,32 +333,6 @@ class CGMinerAvalon(CGMiner):
pass pass
return fans_data 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]: async def get_errors(self) -> List[MinerErrorData]:
return [] return []

View File

@@ -14,51 +14,74 @@
# limitations under the License. - # 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 import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, X19Error from pyasic.data.error_codes import MinerErrorData, X19Error
from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.logger import logger 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 from pyasic.web.epic import ePICWebAPI
EPIC_DATA_LOC = { EPIC_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {"web_summary": {"web": "network"}}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction(
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, "get_mac", [WebAPICommand("web_network", "network")]
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"web_summary": {"web": "summary"}}}, ),
"hostname": {"cmd": "get_hostname", "kwargs": {"web_summary": {"web": "summary"}}}, str(DataOptions.MODEL): DataFunction("get_model"),
"hashrate": {"cmd": "get_hashrate", "kwargs": {"web_summary": {"web": "summary"}}}, str(DataOptions.API_VERSION): DataFunction("get_api_ver"),
"expected_hashrate": { str(DataOptions.FW_VERSION): DataFunction(
"cmd": "get_nominal_hashrate", "get_fw_ver", [WebAPICommand("web_summary", "summary")]
"kwargs": {"web_summary": {"web": "summary"}}, ),
}, str(DataOptions.HOSTNAME): DataFunction(
"hashboards": { "get_hostname", [WebAPICommand("web_summary", "summary")]
"cmd": "get_hashboards", ),
"kwargs": { str(DataOptions.HASHRATE): DataFunction(
"web_summary": {"web": "summary"}, "get_hashrate", [WebAPICommand("web_summary", "summary")]
"web_hashrate": {"web": "hashrate"}, ),
}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
}, "get_expected_hashrate", [WebAPICommand("web_summary", "summary")]
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, ),
"wattage": {"cmd": "get_wattage", "kwargs": {"web_summary": {"web": "summary"}}}, str(DataOptions.HASHBOARDS): DataFunction(
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, "get_hashboards",
"fans": {"cmd": "get_fans", "kwargs": {"web_summary": {"web": "summary"}}}, [
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, WebAPICommand("web_summary", "summary"),
"fault_light": { WebAPICommand("web_hashrate", "hashrate"),
"cmd": "get_fault_light", ],
"kwargs": {"web_summary": {"web": "summary"}}, ),
}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"pools": {"cmd": "get_pools", "kwargs": {"web_summary": {"web": "summary"}}}, str(DataOptions.WATTAGE): DataFunction(
"is_mining": {"cmd": "is_mining", "kwargs": {}}, "get_wattage", [WebAPICommand("web_summary", "summary")]
"uptime": {"cmd": "get_uptime", "kwargs": {"web_summary": {"web": "summary"}}}, ),
"errors": {"cmd": "get_errors", "kwargs": {"web_summary": {"web": "summary"}}}, str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
"config": {"cmd": "get_config", "kwargs": {}}, 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: def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
# interfaces # interfaces
@@ -74,6 +97,23 @@ class ePIC(BMMiner):
return self.model + " (ePIC)" return self.model + " (ePIC)"
return "? (ePIC)" return "? (ePIC)"
async def get_config(self) -> MinerConfig:
summary = None
try:
summary = await self.web.summary()
except APIError as e:
logger.warning(e)
except LookupError:
pass
if summary is not None:
cfg = MinerConfig.from_epic(summary)
else:
cfg = MinerConfig()
self.config = cfg
return self.config
async def restart_backend(self) -> bool: async def restart_backend(self) -> bool:
data = await self.web.restart_epic() data = await self.web.restart_epic()
if data: if data:
@@ -110,13 +150,13 @@ class ePIC(BMMiner):
pass pass
return False return False
async def get_mac(self, web_summary: dict = None) -> str: async def get_mac(self, web_network: dict = None) -> str:
if not web_summary: if not web_network:
web_summary = await self.web.network() web_network = await self.web.network()
if web_summary: if web_network:
try: try:
for network in web_summary: for network in web_network:
mac = web_summary[network]["mac_address"] mac = web_network[network]["mac_address"]
return mac return mac
except KeyError: except KeyError:
pass pass
@@ -249,32 +289,6 @@ class ePIC(BMMiner):
async def is_mining(self, *args, **kwargs) -> Optional[bool]: async def is_mining(self, *args, **kwargs) -> Optional[bool]:
return None 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]: async def get_uptime(self, web_summary: dict = None) -> Optional[int]:
if not web_summary: if not web_summary:
web_summary = await self.web.summary() web_summary = await self.web.summary()
@@ -310,3 +324,33 @@ class ePIC(BMMiner):
except KeyError: except KeyError:
pass pass
return errors 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

View File

@@ -26,30 +26,52 @@ from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
from pyasic.errors import APIError 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 from pyasic.web.bosminer import BOSMinerWebAPI
LUXMINER_DATA_LOC = { LUXMINER_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {"api_config": {"api": "config"}}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction(
"api_ver": {"cmd": "get_api_ver", "kwargs": {}}, "get_mac", [RPCAPICommand("api_config", "config")]
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {}}, ),
"hostname": {"cmd": "get_hostname", "kwargs": {}}, str(DataOptions.MODEL): DataFunction("get_model"),
"hashrate": {"cmd": "get_hashrate", "kwargs": {}}, str(DataOptions.API_VERSION): DataFunction("get_api_ver"),
"expected_hashrate": {"cmd": "get_nominal_hashrate", "kwargs": {}}, str(DataOptions.FW_VERSION): DataFunction("get_fw_ver"),
"hashboards": {"cmd": "get_hashboards", "kwargs": {}}, str(DataOptions.HOSTNAME): DataFunction("get_hostname"),
"wattage": {"cmd": "get_wattage", "kwargs": {}}, str(DataOptions.HASHRATE): DataFunction(
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, "get_hashrate", [RPCAPICommand("api_summary", "summary")]
"fans": {"cmd": "get_fans", "kwargs": {}}, ),
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"errors": {"cmd": "get_errors", "kwargs": {}}, ),
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, str(DataOptions.HASHBOARDS): DataFunction(
"pools": {"cmd": "get_pools", "kwargs": {}}, "get_hashboards", [RPCAPICommand("api_stats", "stats")]
"is_mining": {"cmd": "is_mining", "kwargs": {}}, ),
"uptime": {"cmd": "get_uptime", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"config": {"cmd": "get_config", "kwargs": {}}, 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): class LUXMiner(BaseMiner):
@@ -85,7 +107,6 @@ class LUXMiner(BaseMiner):
return return
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
"""Sends command to turn on fault light on the miner."""
try: try:
session_id = await self._get_session() session_id = await self._get_session()
if session_id: if session_id:
@@ -96,7 +117,6 @@ class LUXMiner(BaseMiner):
return False return False
async def fault_light_off(self) -> bool: async def fault_light_off(self) -> bool:
"""Sends command to turn off fault light on the miner."""
try: try:
session_id = await self._get_session() session_id = await self._get_session()
if session_id: if session_id:
@@ -107,11 +127,9 @@ class LUXMiner(BaseMiner):
return False return False
async def restart_backend(self) -> bool: 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() return await self.restart_luxminer()
async def restart_luxminer(self) -> bool: async def restart_luxminer(self) -> bool:
"""Restart luxminer hashing process."""
try: try:
session_id = await self._get_session() session_id = await self._get_session()
if session_id: if session_id:
@@ -141,7 +159,6 @@ class LUXMiner(BaseMiner):
pass pass
async def reboot(self) -> bool: async def reboot(self) -> bool:
"""Reboots power to the physical miner."""
try: try:
session_id = await self._get_session() session_id = await self._get_session()
if session_id: if session_id:
@@ -303,56 +320,6 @@ class LUXMiner(BaseMiner):
async def get_fan_psu(self) -> Optional[int]: async def get_fan_psu(self) -> Optional[int]:
return None 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]: async def get_errors(self) -> List[MinerErrorData]:
pass pass

View File

@@ -16,38 +16,61 @@
from typing import Optional from typing import Optional
from pyasic import MinerConfig
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.logger import logger from pyasic.logger import logger
from pyasic.miners.backends.bmminer import BMMiner from pyasic.miners.backends.bmminer import BMMiner
from pyasic.miners.base import (
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
WebAPICommand,
)
from pyasic.web.vnish import VNishWebAPI from pyasic.web.vnish import VNishWebAPI
VNISH_DATA_LOC = { VNISH_DATA_LOC = DataLocations(
"mac": {"cmd": "get_mac", "kwargs": {"web_summary": {"web": "summary"}}}, **{
"model": {"cmd": "get_model", "kwargs": {}}, str(DataOptions.MAC): DataFunction(
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, "get_mac", [WebAPICommand("web_summary", "summary")]
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"web_summary": {"web": "summary"}}}, ),
"hostname": {"cmd": "get_hostname", "kwargs": {"web_summary": {"web": "summary"}}}, str(DataOptions.MODEL): DataFunction("get_model"),
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, str(DataOptions.API_VERSION): DataFunction(
"expected_hashrate": { "get_api_ver", [RPCAPICommand("api_version", "version")]
"cmd": "get_nominal_hashrate", ),
"kwargs": {"api_stats": {"api": "stats"}}, str(DataOptions.FW_VERSION): DataFunction(
}, "get_fw_ver", [WebAPICommand("web_summary", "summary")]
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, ),
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, str(DataOptions.HOSTNAME): DataFunction(
"wattage": {"cmd": "get_wattage", "kwargs": {"web_summary": {"web": "summary"}}}, "get_hostname", [WebAPICommand("web_summary", "summary")]
"wattage_limit": { ),
"cmd": "get_wattage_limit", str(DataOptions.HASHRATE): DataFunction(
"kwargs": {"web_settings": {"web": "settings"}}, "get_hashrate", [RPCAPICommand("api_summary", "summary")]
}, ),
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
"errors": {"cmd": "get_errors", "kwargs": {}}, ),
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, str(DataOptions.HASHBOARDS): DataFunction(
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, "get_hashboards", [RPCAPICommand("api_stats", "stats")]
"is_mining": {"cmd": "is_mining", "kwargs": {}}, ),
"uptime": {"cmd": "get_uptime", "kwargs": {}}, str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"),
"config": {"cmd": "get_config", "kwargs": {}}, 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", [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 VNish(BMMiner): class VNish(BMMiner):
@@ -197,3 +220,11 @@ class VNish(BMMiner):
async def get_uptime(self, *args, **kwargs) -> Optional[int]: async def get_uptime(self, *args, **kwargs) -> Optional[int]:
return None return None
async def get_config(self) -> MinerConfig:
try:
web_settings = await self.web.settings()
except APIError:
return self.config
self.config = MinerConfig.from_vnish(web_settings)
return self.config

View File

@@ -17,7 +17,9 @@ import asyncio
import ipaddress import ipaddress
import logging import logging
from abc import ABC, abstractmethod 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 import asyncssh
@@ -27,6 +29,70 @@ from pyasic.data.error_codes import MinerErrorData
from pyasic.logger import logger 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): class BaseMiner(ABC):
def __init__(self, ip: str, *args, **kwargs) -> None: def __init__(self, ip: str, *args, **kwargs) -> None:
# interfaces # interfaces
@@ -46,7 +112,7 @@ class BaseMiner(ABC):
self.expected_chips = 0 self.expected_chips = 0
self.fan_count = 2 self.fan_count = 2
# data gathering locations # data gathering locations
self.data_locations = None self.data_locations: DataLocations = None
# autotuning/shutdown support # autotuning/shutdown support
self.supports_autotuning = False self.supports_autotuning = False
self.supports_shutdown = False self.supports_shutdown = False
@@ -359,15 +425,6 @@ class BaseMiner(ABC):
""" """
pass 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 @abstractmethod
async def get_errors(self, *args, **kwargs) -> List[MinerErrorData]: async def get_errors(self, *args, **kwargs) -> List[MinerErrorData]:
"""Get a list of the errors the miner is experiencing. """Get a list of the errors the miner is experiencing.
@@ -414,28 +471,33 @@ class BaseMiner(ABC):
pass pass
async def _get_data( 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: ) -> dict:
if include is None: if include is not None:
include = [str(i) for i in include]
else:
# everything # everything
include = list(self.data_locations.keys()) include = [str(enum_value.value) for enum_value in DataOptions]
if exclude is not None: if exclude is not None:
for item in exclude: for item in exclude:
if item in include: if str(item) in include:
include.remove(item) include.remove(str(item))
api_multicommand = set() api_multicommand = set()
web_multicommand = [] web_multicommand = []
for data_name in include: for data_name in include:
try: try:
fn_args = self.data_locations[data_name]["kwargs"] fn_args = getattr(self.data_locations, data_name).kwargs
for arg_name in fn_args: for arg in fn_args:
if fn_args[arg_name].get("api"): if isinstance(arg, RPCAPICommand):
api_multicommand.add(fn_args[arg_name]["api"]) api_multicommand.add(arg.cmd)
if fn_args[arg_name].get("web"): if isinstance(arg, WebAPICommand):
if not fn_args[arg_name]["web"] in web_multicommand: if arg.cmd not in web_multicommand:
web_multicommand.append(fn_args[arg_name]["web"]) web_multicommand.append(arg.cmd)
except KeyError as e: except KeyError as e:
logger.error(e, data_name) logger.error(e, data_name)
continue continue
@@ -465,37 +527,36 @@ class BaseMiner(ABC):
for data_name in include: for data_name in include:
try: try:
fn_args = self.data_locations[data_name]["kwargs"] fn_args = getattr(self.data_locations, data_name).kwargs
args_to_send = {k: None for k in fn_args} args_to_send = {k.name: None for k in fn_args}
for arg_name in fn_args: for arg in fn_args:
try: try:
if fn_args[arg_name].get("api"): if isinstance(arg, RPCAPICommand):
if api_command_data.get("multicommand"): if api_command_data.get("multicommand"):
args_to_send[arg_name] = api_command_data[ args_to_send[arg.name] = api_command_data[arg.cmd][0]
fn_args[arg_name]["api"]
][0]
else: else:
args_to_send[arg_name] = api_command_data args_to_send[arg.name] = api_command_data
if fn_args[arg_name].get("web"): if isinstance(arg, WebAPICommand):
if web_command_data is not None: if web_command_data is not None:
if web_command_data.get("multicommand"): if web_command_data.get("multicommand"):
args_to_send[arg_name] = web_command_data[ args_to_send[arg.name] = web_command_data[arg.cmd]
fn_args[arg_name]["web"]
]
else: else:
if not web_command_data == {"multicommand": False}: 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: except LookupError:
args_to_send[arg_name] = None args_to_send[arg.name] = None
except LookupError: except LookupError:
continue 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) miner_data[data_name] = await function(**args_to_send)
return miner_data return miner_data
async def get_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: ) -> MinerData:
"""Get data from the miner in the form of [`MinerData`][pyasic.data.MinerData]. """Get data from the miner in the form of [`MinerData`][pyasic.data.MinerData].

View File

@@ -267,32 +267,6 @@ class CGMinerA10X(CGMiner, A10X):
return fans 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( async def get_errors(
self, web_get_error_detail: dict = None self, web_get_error_detail: dict = None
) -> List[MinerErrorData]: # noqa: named this way for automatic functionality ) -> List[MinerErrorData]: # noqa: named this way for automatic functionality

View File

@@ -246,32 +246,6 @@ class CGMinerT3HPlus(CGMiner, T3HPlus):
return fans 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( async def get_errors(
self, web_get_error_detail: dict = None self, web_get_error_detail: dict = None
) -> List[MinerErrorData]: # noqa: named this way for automatic functionality ) -> List[MinerErrorData]: # noqa: named this way for automatic functionality

View File

@@ -92,6 +92,7 @@ MINER_CLASSES = {
"ANTMINER S19 XP": BMMinerS19XP, "ANTMINER S19 XP": BMMinerS19XP,
"ANTMINER S19A": BMMinerS19a, "ANTMINER S19A": BMMinerS19a,
"ANTMINER S19A PRO": BMMinerS19aPro, "ANTMINER S19A PRO": BMMinerS19aPro,
"ANTMINER S19 PRO HYD.": BMMinerS19ProHydro,
"ANTMINER T19": BMMinerT19, "ANTMINER T19": BMMinerT19,
}, },
MinerTypes.WHATSMINER: { MinerTypes.WHATSMINER: {

View File

@@ -31,7 +31,7 @@ class S19NoPIC(AntMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "S19 No PIC" self.model = "S19 No PIC"
self.nominal_chips = 88 self.expected_chips = 88
self.fan_count = 4 self.fan_count = 4
@@ -49,7 +49,7 @@ class S19i(AntMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "S19i" self.model = "S19i"
self.nominal_chips = 80 self.expected_chips = 80
self.fan_count = 4 self.fan_count = 4
@@ -58,7 +58,7 @@ class S19Plus(AntMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "S19+" self.model = "S19+"
self.nominal_chips = 80 self.expected_chips = 80
self.fan_count = 4 self.fan_count = 4
@@ -76,7 +76,7 @@ class S19XP(AntMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "S19 XP" self.model = "S19 XP"
self.nominal_chips = 110 self.expected_chips = 110
self.fan_count = 4 self.fan_count = 4
@@ -94,7 +94,7 @@ class S19aPro(AntMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "S19a Pro" self.model = "S19a Pro"
self.nominal_chips = 100 self.expected_chips = 100
self.fan_count = 4 self.fan_count = 4
@@ -103,7 +103,7 @@ class S19j(AntMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "S19j" self.model = "S19j"
self.nominal_chips = 114 self.expected_chips = 114
self.fan_count = 4 self.fan_count = 4
@@ -112,7 +112,7 @@ class S19jNoPIC(AntMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "S19j No PIC" self.model = "S19j No PIC"
self.nominal_chips = 88 self.expected_chips = 88
self.fan_count = 4 self.fan_count = 4
@@ -121,7 +121,7 @@ class S19jPro(AntMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "S19j Pro" self.model = "S19j Pro"
self.nominal_chips = 126 self.expected_chips = 126
self.fan_count = 4 self.fan_count = 4
@@ -130,7 +130,7 @@ class S19jProPlus(AntMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "S19j Pro+" self.model = "S19j Pro+"
self.nominal_chips = 120 self.expected_chips = 120
self.fan_count = 4 self.fan_count = 4
@@ -139,7 +139,7 @@ class S19kPro(AntMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "S19k Pro" self.model = "S19k Pro"
self.nominal_chips = 77 self.expected_chips = 77
self.fan_count = 4 self.fan_count = 4
@@ -157,5 +157,15 @@ class S19kProNoPIC(AntMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "S19k Pro No PIC" self.model = "S19k Pro No PIC"
self.nominal_chips = 77 self.expected_chips = 77
self.fan_count = 4 self.fan_count = 4
class S19ProHydro(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver)
self.ip = ip
self.model = "S19 Pro Hydro"
self.expected_chips = 180
self.expected_hashboards = 4
self.fan_count = 0

View File

@@ -30,6 +30,7 @@ from .S19 import (
S19NoPIC, S19NoPIC,
S19Plus, S19Plus,
S19Pro, S19Pro,
S19ProHydro,
S19ProPlus, S19ProPlus,
) )
from .T19 import T19 from .T19 import T19

View File

@@ -40,5 +40,5 @@ class S9j(AntMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "S9j" self.model = "S9j"
self.nominal_chips = 63 self.expected_chips = 63
self.fan_count = 2 self.fan_count = 2

View File

@@ -33,7 +33,7 @@ class M20SV20(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M20S V20" self.model = "M20S V20"
self.nominal_chips = 111 self.expected_chips = 111
self.fan_count = 2 self.fan_count = 2

View File

@@ -33,5 +33,5 @@ class M30V20(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30 V20" self.model = "M30 V20"
self.nominal_chips = 111 self.expected_chips = 111
self.fan_count = 2 self.fan_count = 2

View File

@@ -33,7 +33,7 @@ class M30SV20(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S V20" self.model = "M30S V20"
self.nominal_chips = 156 self.expected_chips = 156
self.fan_count = 2 self.fan_count = 2
@@ -42,7 +42,7 @@ class M30SV30(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S V30" self.model = "M30S V30"
self.nominal_chips = 164 self.expected_chips = 164
self.fan_count = 2 self.fan_count = 2
@@ -60,7 +60,7 @@ class M30SV50(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S V50" self.model = "M30S V50"
self.nominal_chips = 156 self.expected_chips = 156
self.fan_count = 2 self.fan_count = 2
@@ -69,7 +69,7 @@ class M30SV60(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S V60" self.model = "M30S V60"
self.nominal_chips = 164 self.expected_chips = 164
self.fan_count = 2 self.fan_count = 2
@@ -78,7 +78,7 @@ class M30SV70(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S V70" self.model = "M30S V70"
self.nominal_chips = 0 self.expected_chips = 0
warnings.warn( warnings.warn(
"Unknown chip count for miner type M30SV70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." "Unknown chip count for miner type M30SV70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
) )
@@ -90,7 +90,7 @@ class M30SV80(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S V80" self.model = "M30S V80"
self.nominal_chips = 129 self.expected_chips = 129
self.fan_count = 2 self.fan_count = 2
@@ -108,7 +108,7 @@ class M30SVE20(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S VE20" self.model = "M30S VE20"
self.nominal_chips = 111 self.expected_chips = 111
self.fan_count = 2 self.fan_count = 2
@@ -117,7 +117,7 @@ class M30SVE30(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S VE30" self.model = "M30S VE30"
self.nominal_chips = 117 self.expected_chips = 117
self.fan_count = 2 self.fan_count = 2
@@ -126,7 +126,7 @@ class M30SVE40(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S VE40" self.model = "M30S VE40"
self.nominal_chips = 123 self.expected_chips = 123
self.fan_count = 2 self.fan_count = 2
@@ -135,7 +135,7 @@ class M30SVE50(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S VE50" self.model = "M30S VE50"
self.nominal_chips = 129 self.expected_chips = 129
self.fan_count = 2 self.fan_count = 2
@@ -144,7 +144,7 @@ class M30SVE60(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S VE60" self.model = "M30S VE60"
self.nominal_chips = 0 self.expected_chips = 0
warnings.warn( warnings.warn(
"Unknown chip count for miner type M30SVE60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." "Unknown chip count for miner type M30SVE60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
) )
@@ -156,7 +156,7 @@ class M30SVE70(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S VE70" self.model = "M30S VE70"
self.nominal_chips = 0 self.expected_chips = 0
warnings.warn( warnings.warn(
"Unknown chip count for miner type M30SVE70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." "Unknown chip count for miner type M30SVE70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
) )
@@ -168,7 +168,7 @@ class M30SVF10(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S VF10" self.model = "M30S VF10"
self.nominal_chips = 70 self.expected_chips = 70
self.fan_count = 2 self.fan_count = 2
@@ -186,7 +186,7 @@ class M30SVF30(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S VF30" self.model = "M30S VF30"
self.nominal_chips = 78 self.expected_chips = 78
self.fan_count = 2 self.fan_count = 2
@@ -195,7 +195,7 @@ class M30SVG10(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S VG10" self.model = "M30S VG10"
self.nominal_chips = 66 self.expected_chips = 66
self.fan_count = 2 self.fan_count = 2
@@ -213,7 +213,7 @@ class M30SVG30(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S VG30" self.model = "M30S VG30"
self.nominal_chips = 74 self.expected_chips = 74
self.fan_count = 2 self.fan_count = 2
@@ -222,7 +222,7 @@ class M30SVG40(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S VG40" self.model = "M30S VG40"
self.nominal_chips = 78 self.expected_chips = 78
self.fan_count = 2 self.fan_count = 2
@@ -240,7 +240,7 @@ class M30SVH20(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S VH20" self.model = "M30S VH20"
self.nominal_chips = 66 self.expected_chips = 66
self.fan_count = 2 self.fan_count = 2
@@ -249,7 +249,7 @@ class M30SVH30(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S VH30" self.model = "M30S VH30"
self.nominal_chips = 0 self.expected_chips = 0
warnings.warn( warnings.warn(
"Unknown chip count for miner type M30SVH30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." "Unknown chip count for miner type M30SVH30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
) )
@@ -261,7 +261,7 @@ class M30SVH40(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S VH40" self.model = "M30S VH40"
self.nominal_chips = 64 self.expected_chips = 64
self.fan_count = 2 self.fan_count = 2
@@ -270,7 +270,7 @@ class M30SVH50(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S VH50" self.model = "M30S VH50"
self.nominal_chips = 66 self.expected_chips = 66
self.fan_count = 2 self.fan_count = 2
@@ -291,5 +291,5 @@ class M30SVI20(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S VI20" self.model = "M30S VI20"
self.nominal_chips = 70 self.expected_chips = 70
self.fan_count = 2 self.fan_count = 2

View File

@@ -42,7 +42,7 @@ class M30SPlusV30(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ V30" self.model = "M30S+ V30"
self.nominal_chips = 0 self.expected_chips = 0
warnings.warn( warnings.warn(
"Unknown chip count for miner type M30S+ V30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." "Unknown chip count for miner type M30S+ V30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
) )
@@ -54,7 +54,7 @@ class M30SPlusV40(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ V40" self.model = "M30S+ V40"
self.nominal_chips = 235 self.expected_chips = 235
self.fan_count = 2 self.fan_count = 2
@@ -63,7 +63,7 @@ class M30SPlusV50(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ V50" self.model = "M30S+ V50"
self.nominal_chips = 225 self.expected_chips = 225
self.fan_count = 2 self.fan_count = 2
@@ -72,7 +72,7 @@ class M30SPlusV60(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ V60" self.model = "M30S+ V60"
self.nominal_chips = 245 self.expected_chips = 245
self.fan_count = 2 self.fan_count = 2
@@ -81,7 +81,7 @@ class M30SPlusV70(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ V70" self.model = "M30S+ V70"
self.nominal_chips = 235 self.expected_chips = 235
self.fan_count = 2 self.fan_count = 2
@@ -99,7 +99,7 @@ class M30SPlusV90(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ V90" self.model = "M30S+ V90"
self.nominal_chips = 225 self.expected_chips = 225
self.fan_count = 2 self.fan_count = 2
@@ -108,7 +108,7 @@ class M30SPlusV100(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ V100" self.model = "M30S+ V100"
self.nominal_chips = 215 self.expected_chips = 215
self.fan_count = 2 self.fan_count = 2
@@ -126,7 +126,7 @@ class M30SPlusVE40(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ VE40" self.model = "M30S+ VE40"
self.nominal_chips = 156 self.expected_chips = 156
self.fan_count = 2 self.fan_count = 2
@@ -135,7 +135,7 @@ class M30SPlusVE50(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ VE50" self.model = "M30S+ VE50"
self.nominal_chips = 164 self.expected_chips = 164
self.fan_count = 2 self.fan_count = 2
@@ -153,7 +153,7 @@ class M30SPlusVE70(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ VE70" self.model = "M30S+ VE70"
self.nominal_chips = 0 self.expected_chips = 0
warnings.warn( warnings.warn(
"Unknown chip count for miner type M30S+ VE70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." "Unknown chip count for miner type M30S+ VE70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
) )
@@ -165,7 +165,7 @@ class M30SPlusVE80(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ VE80" self.model = "M30S+ VE80"
self.nominal_chips = 0 self.expected_chips = 0
warnings.warn( warnings.warn(
"Unknown chip count for miner type M30S+ VE80, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." "Unknown chip count for miner type M30S+ VE80, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
) )
@@ -177,7 +177,7 @@ class M30SPlusVE90(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ VE90" self.model = "M30S+ VE90"
self.nominal_chips = 0 self.expected_chips = 0
warnings.warn( warnings.warn(
"Unknown chip count for miner type M30S+ VE90, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." "Unknown chip count for miner type M30S+ VE90, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
) )
@@ -189,7 +189,7 @@ class M30SPlusVE100(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ VE100" self.model = "M30S+ VE100"
self.nominal_chips = 0 self.expected_chips = 0
warnings.warn( warnings.warn(
"Unknown chip count for miner type M30S+ VE100, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." "Unknown chip count for miner type M30S+ VE100, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
) )
@@ -201,7 +201,7 @@ class M30SPlusVF20(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ VF20" self.model = "M30S+ VF20"
self.nominal_chips = 111 self.expected_chips = 111
self.fan_count = 2 self.fan_count = 2
@@ -219,7 +219,7 @@ class M30SPlusVG20(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ VG20" self.model = "M30S+ VG20"
self.nominal_chips = 82 self.expected_chips = 82
self.fan_count = 2 self.fan_count = 2
@@ -228,7 +228,7 @@ class M30SPlusVG30(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ VG30" self.model = "M30S+ VG30"
self.nominal_chips = 78 self.expected_chips = 78
self.fan_count = 2 self.fan_count = 2
@@ -246,7 +246,7 @@ class M30SPlusVG50(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ VG50" self.model = "M30S+ VG50"
self.nominal_chips = 111 self.expected_chips = 111
self.fan_count = 2 self.fan_count = 2
@@ -255,7 +255,7 @@ class M30SPlusVG60(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ VG60" self.model = "M30S+ VG60"
self.nominal_chips = 86 self.expected_chips = 86
self.fan_count = 2 self.fan_count = 2
@@ -273,7 +273,7 @@ class M30SPlusVH20(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ VH20" self.model = "M30S+ VH20"
self.nominal_chips = 66 self.expected_chips = 66
self.fan_count = 2 self.fan_count = 2
@@ -282,7 +282,7 @@ class M30SPlusVH30(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ VH30" self.model = "M30S+ VH30"
self.nominal_chips = 70 self.expected_chips = 70
self.fan_count = 2 self.fan_count = 2
@@ -291,7 +291,7 @@ class M30SPlusVH40(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ VH40" self.model = "M30S+ VH40"
self.nominal_chips = 74 self.expected_chips = 74
self.fan_count = 2 self.fan_count = 2
@@ -300,7 +300,7 @@ class M30SPlusVH50(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S+ VH50" self.model = "M30S+ VH50"
self.nominal_chips = 64 self.expected_chips = 64
self.fan_count = 2 self.fan_count = 2

View File

@@ -44,7 +44,7 @@ class M30SPlusPlusVE30(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S++ VE30" self.model = "M30S++ VE30"
self.nominal_chips = 215 self.expected_chips = 215
self.fan_count = 2 self.fan_count = 2
@@ -53,7 +53,7 @@ class M30SPlusPlusVE40(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S++ VE40" self.model = "M30S++ VE40"
self.nominal_chips = 225 self.expected_chips = 225
self.fan_count = 2 self.fan_count = 2
@@ -71,7 +71,7 @@ class M30SPlusPlusVF40(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S++ VF40" self.model = "M30S++ VF40"
self.nominal_chips = 156 self.expected_chips = 156
self.fan_count = 2 self.fan_count = 2
@@ -80,7 +80,7 @@ class M30SPlusPlusVG30(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S++ VG30" self.model = "M30S++ VG30"
self.nominal_chips = 111 self.expected_chips = 111
self.fan_count = 2 self.fan_count = 2
@@ -89,7 +89,7 @@ class M30SPlusPlusVG40(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S++ VG40" self.model = "M30S++ VG40"
self.nominal_chips = 117 self.expected_chips = 117
self.fan_count = 2 self.fan_count = 2
@@ -98,7 +98,7 @@ class M30SPlusPlusVG50(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S++ VG50" self.model = "M30S++ VG50"
self.nominal_chips = 0 self.expected_chips = 0
warnings.warn( warnings.warn(
"Unknown chip count for miner type M30S++ VG50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." "Unknown chip count for miner type M30S++ VG50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
) )
@@ -110,7 +110,7 @@ class M30SPlusPlusVH10(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S++ VH10" self.model = "M30S++ VH10"
self.nominal_chips = 82 self.expected_chips = 82
self.fan_count = 2 self.fan_count = 2
@@ -128,7 +128,7 @@ class M30SPlusPlusVH30(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S++ VH30" self.model = "M30S++ VH30"
self.nominal_chips = 111 self.expected_chips = 111
self.fan_count = 2 self.fan_count = 2
@@ -137,7 +137,7 @@ class M30SPlusPlusVH40(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S++ VH40" self.model = "M30S++ VH40"
self.nominal_chips = 70 self.expected_chips = 70
self.fan_count = 2 self.fan_count = 2
@@ -146,7 +146,7 @@ class M30SPlusPlusVH50(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S++ VH50" self.model = "M30S++ VH50"
self.nominal_chips = 74 self.expected_chips = 74
self.fan_count = 2 self.fan_count = 2
@@ -155,7 +155,7 @@ class M30SPlusPlusVH60(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S++ VH60" self.model = "M30S++ VH60"
self.nominal_chips = 78 self.expected_chips = 78
self.fan_count = 2 self.fan_count = 2
@@ -173,7 +173,7 @@ class M30SPlusPlusVH80(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S++ VH80" self.model = "M30S++ VH80"
self.nominal_chips = 74 self.expected_chips = 74
self.fan_count = 2 self.fan_count = 2
@@ -182,7 +182,7 @@ class M30SPlusPlusVH90(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S++ VH90" self.model = "M30S++ VH90"
self.nominal_chips = 78 self.expected_chips = 78
self.fan_count = 2 self.fan_count = 2
@@ -200,7 +200,7 @@ class M30SPlusPlusVJ20(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S++ VJ20" self.model = "M30S++ VJ20"
self.nominal_chips = 0 self.expected_chips = 0
warnings.warn( warnings.warn(
"Unknown chip count for miner type M30S++ VJ20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." "Unknown chip count for miner type M30S++ VJ20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
) )
@@ -212,7 +212,7 @@ class M30SPlusPlusVJ30(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M30S++ VJ30" self.model = "M30S++ VJ30"
self.nominal_chips = 0 self.expected_chips = 0
warnings.warn( warnings.warn(
"Unknown chip count for miner type M30S++ VJ30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." "Unknown chip count for miner type M30S++ VJ30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
) )

View File

@@ -33,5 +33,5 @@ class M31V20(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M31 V20" self.model = "M31 V20"
self.nominal_chips = 74 self.expected_chips = 74
self.fan_count = 2 self.fan_count = 2

View File

@@ -51,7 +51,7 @@ class M31SPlusV40(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M31S+ V40" self.model = "M31S+ V40"
self.nominal_chips = 123 self.expected_chips = 123
self.fan_count = 2 self.fan_count = 2
@@ -60,7 +60,7 @@ class M31SPlusV50(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M31S+ V50" self.model = "M31S+ V50"
self.nominal_chips = 148 self.expected_chips = 148
self.fan_count = 2 self.fan_count = 2
@@ -69,7 +69,7 @@ class M31SPlusV60(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M31S+ V60" self.model = "M31S+ V60"
self.nominal_chips = 156 self.expected_chips = 156
self.fan_count = 2 self.fan_count = 2
@@ -78,7 +78,7 @@ class M31SPlusV80(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M31S+ V80" self.model = "M31S+ V80"
self.nominal_chips = 129 self.expected_chips = 129
self.fan_count = 2 self.fan_count = 2
@@ -87,7 +87,7 @@ class M31SPlusV90(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M31S+ V90" self.model = "M31S+ V90"
self.nominal_chips = 117 self.expected_chips = 117
self.fan_count = 2 self.fan_count = 2
@@ -96,7 +96,7 @@ class M31SPlusV100(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M31S+ V100" self.model = "M31S+ V100"
self.nominal_chips = 111 self.expected_chips = 111
self.fan_count = 2 self.fan_count = 2
@@ -105,7 +105,7 @@ class M31SPlusVE10(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M31S+ VE10" self.model = "M31S+ VE10"
self.nominal_chips = 82 self.expected_chips = 82
self.fan_count = 2 self.fan_count = 2
@@ -114,7 +114,7 @@ class M31SPlusVE20(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M31S+ VE20" self.model = "M31S+ VE20"
self.nominal_chips = 78 self.expected_chips = 78
self.fan_count = 2 self.fan_count = 2

View File

@@ -61,7 +61,7 @@ class M50VH30(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M50 VH30" self.model = "M50 VH30"
self.nominal_chips = 117 self.expected_chips = 117
self.fan_count = 2 self.fan_count = 2
@@ -70,7 +70,7 @@ class M50VH40(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M50 VH40" self.model = "M50 VH40"
self.nominal_chips = 84 self.expected_chips = 84
self.fan_count = 2 self.fan_count = 2
@@ -79,7 +79,7 @@ class M50VH50(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M50 VH50" self.model = "M50 VH50"
self.nominal_chips = 105 self.expected_chips = 105
self.fan_count = 2 self.fan_count = 2
@@ -88,7 +88,7 @@ class M50VH60(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M50 VH60" self.model = "M50 VH60"
self.nominal_chips = 84 self.expected_chips = 84
self.fan_count = 2 self.fan_count = 2
@@ -97,7 +97,7 @@ class M50VH70(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M50 VH70" self.model = "M50 VH70"
self.nominal_chips = 0 self.expected_chips = 0
warnings.warn( warnings.warn(
"Unknown chip count for miner type M50 VH70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." "Unknown chip count for miner type M50 VH70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
) )
@@ -109,7 +109,7 @@ class M50VH80(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M50 VH80" self.model = "M50 VH80"
self.nominal_chips = 111 self.expected_chips = 111
self.fan_count = 2 self.fan_count = 2
@@ -118,7 +118,7 @@ class M50VJ10(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M50 VJ10" self.model = "M50 VJ10"
self.nominal_chips = 0 self.expected_chips = 0
warnings.warn( warnings.warn(
"Unknown chip count for miner type M50 VJ10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." "Unknown chip count for miner type M50 VJ10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
) )
@@ -130,7 +130,7 @@ class M50VJ20(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M50 VJ20" self.model = "M50 VJ20"
self.nominal_chips = 0 self.expected_chips = 0
warnings.warn( warnings.warn(
"Unknown chip count for miner type M50 VJ20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." "Unknown chip count for miner type M50 VJ20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
) )

View File

@@ -60,7 +60,7 @@ class M60VK40(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "M60 VK40" self.model = "M60 VK40"
self.nominal_chips = 0 self.expected_chips = 0
warnings.warn( warnings.warn(
"Unknown chip count for miner type M60 VK40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." "Unknown chip count for miner type M60 VK40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
) )

View File

@@ -113,32 +113,6 @@ class UnknownMiner(BaseMiner):
async def get_fw_ver(self) -> Optional[str]: async def get_fw_ver(self) -> Optional[str]:
return None 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]: async def get_errors(self) -> List[MinerErrorData]:
return [] return []
@@ -155,6 +129,6 @@ class UnknownMiner(BaseMiner):
return None return None
async def get_data( 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: ) -> MinerData:
return MinerData(ip=str(self.ip)) return MinerData(ip=str(self.ip))

View File

@@ -123,9 +123,8 @@ class MinerNetwork:
# clear cached miners # clear cached miners
miner_factory.clear_cached_miners() miner_factory.clear_cached_miners()
limit = asyncio.Semaphore(settings.get("network_scan_threads", 300))
miners = await asyncio.gather( 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 # remove all None from the miner list
@@ -148,12 +147,8 @@ class MinerNetwork:
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
# create a list of scan tasks # create a list of scan tasks
limit = asyncio.Semaphore(settings.get("network_scan_threads", 300))
miners = asyncio.as_completed( miners = asyncio.as_completed(
[ [loop.create_task(self.ping_and_get_miner(host)) for host in self.hosts]
loop.create_task(self.ping_and_get_miner(host, limit))
for host in self.hosts
]
) )
for miner in miners: for miner in miners:
try: try:
@@ -162,16 +157,11 @@ class MinerNetwork:
yield None yield None
@staticmethod @staticmethod
async def ping_and_get_miner( async def ping_and_get_miner(ip: ipaddress.ip_address) -> Union[None, AnyMiner]:
ip: ipaddress.ip_address, semaphore: asyncio.Semaphore
) -> Union[None, AnyMiner]:
async with semaphore:
try: try:
return await ping_and_get_miner(ip) return await ping_and_get_miner(ip)
except ConnectionRefusedError: except ConnectionRefusedError:
tasks = [ tasks = [ping_and_get_miner(ip, port=port) for port in [4028, 4029, 8889]]
ping_and_get_miner(ip, port=port) for port in [4028, 4029, 8889]
]
for miner in asyncio.as_completed(tasks): for miner in asyncio.as_completed(tasks):
try: try:
return await miner return await miner

View File

@@ -24,7 +24,6 @@ from httpx import AsyncHTTPTransport
_settings = { # defaults _settings = { # defaults
"network_ping_retries": 1, "network_ping_retries": 1,
"network_ping_timeout": 3, "network_ping_timeout": 3,
"network_scan_threads": 300,
"factory_get_retries": 1, "factory_get_retries": 1,
"factory_get_timeout": 3, "factory_get_timeout": 3,
"get_data_retries": 1, "get_data_retries": 1,

View File

@@ -156,6 +156,8 @@ class BOSMinerGQLAPI:
) -> dict: ) -> dict:
url = f"http://{self.ip}/graphql" url = f"http://{self.ip}/graphql"
query = command query = command
if command is None:
return {}
if command.get("query") is None: if command.get("query") is None:
query = {"query": self.parse_command(command)} query = {"query": self.parse_command(command)}
try: try:
@@ -189,11 +191,13 @@ class BOSMinerGQLAPI:
await client.post( await client.post(
url, url,
json={ json={
"query": 'mutation{auth{login(username:"' "query": (
'mutation{auth{login(username:"'
+ "root" + "root"
+ '", password:"' + '", password:"'
+ self.pwd + self.pwd
+ '"){__typename}}}' + '"){__typename}}}'
)
}, },
) )
@@ -233,7 +237,9 @@ class BOSMinerLuCIAPI:
login = {"luci_username": self.username, "luci_password": self.pwd} login = {"luci_username": self.username, "luci_password": self.pwd}
url = f"http://{self.ip}/cgi-bin/luci" url = f"http://{self.ip}/cgi-bin/luci"
headers = { headers = {
"User-Agent": "BTC Tools v0.1", # only seems to respond if this user-agent is set "User-Agent": (
"BTC Tools v0.1"
), # only seems to respond if this user-agent is set
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
} }
await session.post(url, headers=headers, data=login) await session.post(url, headers=headers, data=login)

View File

@@ -2357,17 +2357,21 @@ class ActionsServiceBase(ServiceBase):
RebootRequest, RebootRequest,
RebootResponse, RebootResponse,
), ),
"/braiins.bos.v1.ActionsService/SetLocateDeviceStatus": grpclib.const.Handler( "/braiins.bos.v1.ActionsService/SetLocateDeviceStatus": (
grpclib.const.Handler(
self.__rpc_set_locate_device_status, self.__rpc_set_locate_device_status,
grpclib.const.Cardinality.UNARY_UNARY, grpclib.const.Cardinality.UNARY_UNARY,
SetLocateDeviceStatusRequest, SetLocateDeviceStatusRequest,
LocateDeviceStatusResponse, LocateDeviceStatusResponse,
)
), ),
"/braiins.bos.v1.ActionsService/GetLocateDeviceStatus": grpclib.const.Handler( "/braiins.bos.v1.ActionsService/GetLocateDeviceStatus": (
grpclib.const.Handler(
self.__rpc_get_locate_device_status, self.__rpc_get_locate_device_status,
grpclib.const.Cardinality.UNARY_UNARY, grpclib.const.Cardinality.UNARY_UNARY,
GetLocateDeviceStatusRequest, GetLocateDeviceStatusRequest,
LocateDeviceStatusResponse, LocateDeviceStatusResponse,
)
), ),
} }
@@ -2644,17 +2648,21 @@ class PerformanceServiceBase(ServiceBase):
GetTunerStateRequest, GetTunerStateRequest,
GetTunerStateResponse, GetTunerStateResponse,
), ),
"/braiins.bos.v1.PerformanceService/ListTargetProfiles": grpclib.const.Handler( "/braiins.bos.v1.PerformanceService/ListTargetProfiles": (
grpclib.const.Handler(
self.__rpc_list_target_profiles, self.__rpc_list_target_profiles,
grpclib.const.Cardinality.UNARY_UNARY, grpclib.const.Cardinality.UNARY_UNARY,
ListTargetProfilesRequest, ListTargetProfilesRequest,
ListTargetProfilesResponse, ListTargetProfilesResponse,
)
), ),
"/braiins.bos.v1.PerformanceService/SetDefaultPowerTarget": grpclib.const.Handler( "/braiins.bos.v1.PerformanceService/SetDefaultPowerTarget": (
grpclib.const.Handler(
self.__rpc_set_default_power_target, self.__rpc_set_default_power_target,
grpclib.const.Cardinality.UNARY_UNARY, grpclib.const.Cardinality.UNARY_UNARY,
SetDefaultPowerTargetRequest, SetDefaultPowerTargetRequest,
SetPowerTargetResponse, SetPowerTargetResponse,
)
), ),
"/braiins.bos.v1.PerformanceService/SetPowerTarget": grpclib.const.Handler( "/braiins.bos.v1.PerformanceService/SetPowerTarget": grpclib.const.Handler(
self.__rpc_set_power_target, self.__rpc_set_power_target,
@@ -2662,41 +2670,53 @@ class PerformanceServiceBase(ServiceBase):
SetPowerTargetRequest, SetPowerTargetRequest,
SetPowerTargetResponse, SetPowerTargetResponse,
), ),
"/braiins.bos.v1.PerformanceService/IncrementPowerTarget": grpclib.const.Handler( "/braiins.bos.v1.PerformanceService/IncrementPowerTarget": (
grpclib.const.Handler(
self.__rpc_increment_power_target, self.__rpc_increment_power_target,
grpclib.const.Cardinality.UNARY_UNARY, grpclib.const.Cardinality.UNARY_UNARY,
IncrementPowerTargetRequest, IncrementPowerTargetRequest,
SetPowerTargetResponse, SetPowerTargetResponse,
)
), ),
"/braiins.bos.v1.PerformanceService/DecrementPowerTarget": grpclib.const.Handler( "/braiins.bos.v1.PerformanceService/DecrementPowerTarget": (
grpclib.const.Handler(
self.__rpc_decrement_power_target, self.__rpc_decrement_power_target,
grpclib.const.Cardinality.UNARY_UNARY, grpclib.const.Cardinality.UNARY_UNARY,
DecrementPowerTargetRequest, DecrementPowerTargetRequest,
SetPowerTargetResponse, SetPowerTargetResponse,
)
), ),
"/braiins.bos.v1.PerformanceService/SetDefaultHashrateTarget": grpclib.const.Handler( "/braiins.bos.v1.PerformanceService/SetDefaultHashrateTarget": (
grpclib.const.Handler(
self.__rpc_set_default_hashrate_target, self.__rpc_set_default_hashrate_target,
grpclib.const.Cardinality.UNARY_UNARY, grpclib.const.Cardinality.UNARY_UNARY,
SetDefaultHashrateTargetRequest, SetDefaultHashrateTargetRequest,
SetHashrateTargetResponse, SetHashrateTargetResponse,
)
), ),
"/braiins.bos.v1.PerformanceService/SetHashrateTarget": grpclib.const.Handler( "/braiins.bos.v1.PerformanceService/SetHashrateTarget": (
grpclib.const.Handler(
self.__rpc_set_hashrate_target, self.__rpc_set_hashrate_target,
grpclib.const.Cardinality.UNARY_UNARY, grpclib.const.Cardinality.UNARY_UNARY,
SetHashrateTargetRequest, SetHashrateTargetRequest,
SetHashrateTargetResponse, SetHashrateTargetResponse,
)
), ),
"/braiins.bos.v1.PerformanceService/IncrementHashrateTarget": grpclib.const.Handler( "/braiins.bos.v1.PerformanceService/IncrementHashrateTarget": (
grpclib.const.Handler(
self.__rpc_increment_hashrate_target, self.__rpc_increment_hashrate_target,
grpclib.const.Cardinality.UNARY_UNARY, grpclib.const.Cardinality.UNARY_UNARY,
IncrementHashrateTargetRequest, IncrementHashrateTargetRequest,
SetHashrateTargetResponse, SetHashrateTargetResponse,
)
), ),
"/braiins.bos.v1.PerformanceService/DecrementHashrateTarget": grpclib.const.Handler( "/braiins.bos.v1.PerformanceService/DecrementHashrateTarget": (
grpclib.const.Handler(
self.__rpc_decrement_hashrate_target, self.__rpc_decrement_hashrate_target,
grpclib.const.Cardinality.UNARY_UNARY, grpclib.const.Cardinality.UNARY_UNARY,
DecrementHashrateTargetRequest, DecrementHashrateTargetRequest,
SetHashrateTargetResponse, SetHashrateTargetResponse,
)
), ),
"/braiins.bos.v1.PerformanceService/SetDPS": grpclib.const.Handler( "/braiins.bos.v1.PerformanceService/SetDPS": grpclib.const.Handler(
self.__rpc_set_dps, self.__rpc_set_dps,
@@ -2704,23 +2724,29 @@ class PerformanceServiceBase(ServiceBase):
SetDpsRequest, SetDpsRequest,
SetDpsResponse, SetDpsResponse,
), ),
"/braiins.bos.v1.PerformanceService/SetPerformanceMode": grpclib.const.Handler( "/braiins.bos.v1.PerformanceService/SetPerformanceMode": (
grpclib.const.Handler(
self.__rpc_set_performance_mode, self.__rpc_set_performance_mode,
grpclib.const.Cardinality.UNARY_UNARY, grpclib.const.Cardinality.UNARY_UNARY,
SetPerformanceModeRequest, SetPerformanceModeRequest,
PerformanceMode, PerformanceMode,
)
), ),
"/braiins.bos.v1.PerformanceService/GetActivePerformanceMode": grpclib.const.Handler( "/braiins.bos.v1.PerformanceService/GetActivePerformanceMode": (
grpclib.const.Handler(
self.__rpc_get_active_performance_mode, self.__rpc_get_active_performance_mode,
grpclib.const.Cardinality.UNARY_UNARY, grpclib.const.Cardinality.UNARY_UNARY,
GetPerformanceModeRequest, GetPerformanceModeRequest,
PerformanceMode, PerformanceMode,
)
), ),
"/braiins.bos.v1.PerformanceService/RemoveTunedProfiles": grpclib.const.Handler( "/braiins.bos.v1.PerformanceService/RemoveTunedProfiles": (
grpclib.const.Handler(
self.__rpc_remove_tuned_profiles, self.__rpc_remove_tuned_profiles,
grpclib.const.Cardinality.UNARY_UNARY, grpclib.const.Cardinality.UNARY_UNARY,
RemoveTunedProfilesRequest, RemoveTunedProfilesRequest,
RemoveTunedProfilesResponse, RemoveTunedProfilesResponse,
)
), ),
} }
@@ -2836,17 +2862,21 @@ class ConfigurationServiceBase(ServiceBase):
def __mapping__(self) -> Dict[str, grpclib.const.Handler]: def __mapping__(self) -> Dict[str, grpclib.const.Handler]:
return { return {
"/braiins.bos.v1.ConfigurationService/GetMinerConfiguration": grpclib.const.Handler( "/braiins.bos.v1.ConfigurationService/GetMinerConfiguration": (
grpclib.const.Handler(
self.__rpc_get_miner_configuration, self.__rpc_get_miner_configuration,
grpclib.const.Cardinality.UNARY_UNARY, grpclib.const.Cardinality.UNARY_UNARY,
GetMinerConfigurationRequest, GetMinerConfigurationRequest,
GetMinerConfigurationResponse, GetMinerConfigurationResponse,
)
), ),
"/braiins.bos.v1.ConfigurationService/GetConstraints": grpclib.const.Handler( "/braiins.bos.v1.ConfigurationService/GetConstraints": (
grpclib.const.Handler(
self.__rpc_get_constraints, self.__rpc_get_constraints,
grpclib.const.Cardinality.UNARY_UNARY, grpclib.const.Cardinality.UNARY_UNARY,
GetConstraintsRequest, GetConstraintsRequest,
GetConstraintsResponse, GetConstraintsResponse,
)
), ),
} }

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "pyasic" name = "pyasic"
version = "0.44.0" version = "0.46.1"
description = "A simplified and standardized interface for Bitcoin ASICs." description = "A simplified and standardized interface for Bitcoin ASICs."
authors = ["UpstreamData <brett@upstreamdata.ca>"] authors = ["UpstreamData <brett@upstreamdata.ca>"]
repository = "https://github.com/UpstreamData/pyasic" repository = "https://github.com/UpstreamData/pyasic"
@@ -9,11 +9,11 @@ readme = "README.md"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.8" python = "^3.8"
httpx = "^0.25.2" httpx = "^0.26.0"
asyncssh = "^2.14.1" asyncssh = "^2.14.2"
grpc-requests = "^0.1.12" grpc-requests = "^0.1.13"
passlib = "^1.7.4" passlib = "^1.7.4"
pyaml = "^23.9.7" pyaml = "^23.12.0"
toml = "^0.10.2" toml = "^0.10.2"
betterproto = "2.0.0b6" betterproto = "2.0.0b6"

View File

@@ -18,6 +18,7 @@ import inspect
import sys import sys
import unittest import unittest
import warnings import warnings
from dataclasses import asdict
from pyasic.miners.backends import CGMiner # noqa from pyasic.miners.backends import CGMiner # noqa
from pyasic.miners.base import BaseMiner from pyasic.miners.base import BaseMiner
@@ -57,7 +58,6 @@ class MinersTest(unittest.TestCase):
"mac", "mac",
"model", "model",
"expected_hashrate", "expected_hashrate",
"pools",
"uptime", "uptime",
"wattage", "wattage",
"wattage_limit", "wattage_limit",
@@ -72,7 +72,9 @@ class MinersTest(unittest.TestCase):
miner_api=miner_api, miner_api=miner_api,
): ):
miner = MINER_CLASSES[miner_model][miner_api]("127.0.0.1") 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) self.assertEqual(miner_keys, keys)