Compare commits

..

5 Commits

Author SHA1 Message Date
UpstreamData
d632360932 version: bump version number. 2023-02-09 10:37:19 -07:00
UpstreamData
400001fa38 version: bump version number. 2023-02-07 13:41:06 -07:00
UpstreamData
4ff32a8081 feature: allow get_data to pick which data to gather in the first place to allow for speed optimizations. 2023-02-07 13:40:09 -07:00
UpstreamData
33b4ae2f2f version: bump version number. 2023-01-31 09:56:16 -07:00
UpstreamData
62194bd627 bug: add chip counts for M31S+ V30, V40, and V100. 2023-01-31 09:54:35 -07:00
17 changed files with 452 additions and 1048 deletions

View File

@@ -95,7 +95,9 @@ class BaseMinerAPI:
Parameters:
*commands: The commands to send as a multicommand to the miner.
ignore_errors: Whether to raise APIError when the command returns an error.
allow_warning: A boolean to supress APIWarnings.
"""
# make sure we can actually run each command, otherwise they will fail
commands = self._check_commands(*commands)

View File

@@ -63,8 +63,8 @@ class BMMinerAPI(BaseMinerAPI):
data[cmd].append(
await self.send_command(cmd, allow_warning=allow_warning)
)
except APIError as e:
raise APIError(e)
except APIError:
pass
except Exception as e:
logging.warning(
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"

View File

@@ -61,7 +61,7 @@ class CGMinerAPI(BaseMinerAPI):
data[cmd] = []
data[cmd].append(await self.send_command(cmd, allow_warning=True))
except APIError as e:
raise APIError(e)
pass
except Exception as e:
logging.warning(
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"

View File

@@ -46,6 +46,17 @@ class HashBoard:
missing: bool = True
@dataclass
class Fan:
"""A Dataclass to standardize fan data.
Attributes:
speed: The speed of the fan.
"""
speed: int = -1
@dataclass
class MinerData:
"""A Dataclass to standardize data returned from miners (specifically `AnyMiner().get_data()`)
@@ -120,10 +131,11 @@ class MinerData:
right_board_chip_temp: int = field(init=False)
wattage: int = -1
wattage_limit: int = -1
fan_1: int = -1
fan_2: int = -1
fan_3: int = -1
fan_4: int = -1
fans: List[Fan] = field(default_factory=list)
fan_1: int = field(init=False)
fan_2: int = field(init=False)
fan_3: int = field(init=False)
fan_4: int = field(init=False)
fan_psu: int = -1
left_chips: int = field(init=False)
center_chips: int = field(init=False)
@@ -199,6 +211,42 @@ class MinerData:
setattr(cp, key, item & other_item)
return cp
@property
def fan_1(self): # noqa - Skip PyCharm inspection
if len(self.fans) > 0:
return self.fans[0].speed
@fan_1.setter
def fan_1(self, val):
pass
@property
def fan_2(self): # noqa - Skip PyCharm inspection
if len(self.fans) > 1:
return self.fans[1].speed
@fan_2.setter
def fan_2(self, val):
pass
@property
def fan_3(self): # noqa - Skip PyCharm inspection
if len(self.fans) > 2:
return self.fans[2].speed
@fan_3.setter
def fan_3(self, val):
pass
@property
def fan_4(self): # noqa - Skip PyCharm inspection
if len(self.fans) > 3:
return self.fans[3].speed
@fan_4.setter
def fan_4(self, val):
pass
@property
def total_chips(self): # noqa - Skip PyCharm inspection
return sum([hb.chips for hb in self.hashboards])

View File

@@ -21,7 +21,7 @@ import asyncssh
from pyasic.API.bmminer import BMMinerAPI
from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.data import Fan, HashBoard, MinerData
from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner
@@ -138,13 +138,10 @@ class BMMiner(BaseMiner):
logging.warning(f"Failed to get model for miner: {self}")
return None
async def get_version(
self, api_version: dict = None
) -> Tuple[Optional[str], Optional[str]]:
miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
async def get_api_ver(self, api_version: dict = None) -> Optional[str]:
# Check to see if the version info is already cached
if self.api_ver and self.fw_ver:
return miner_version(self.api_ver, self.fw_ver)
if self.api_ver:
return self.api_ver
if not api_version:
try:
@@ -157,12 +154,40 @@ class BMMiner(BaseMiner):
self.api_ver = api_version["VERSION"][0]["API"]
except (KeyError, IndexError):
pass
return self.api_ver
async def get_fw_ver(self, api_version: dict = None) -> Optional[str]:
# Check to see if the version info is already cached
if self.fw_ver:
return self.fw_ver
if not api_version:
try:
api_version = await self.api.version()
except APIError:
pass
if api_version:
try:
self.fw_ver = api_version["VERSION"][0]["CompileTime"]
except (KeyError, IndexError):
pass
return miner_version(self.api_ver, self.fw_ver)
return self.fw_ver
async def get_version(
self, api_version: dict = None
) -> Tuple[Optional[str], Optional[str]]:
# check if version is cached
miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
return miner_version(
api_ver=await self.get_api_ver(api_version),
fw_ver=await self.get_fw_ver(api_version=api_version),
)
async def get_fan_psu(self):
return None
async def get_hostname(self) -> Optional[str]:
hn = await self.send_ssh_command("cat /proc/sys/kernel/hostname")
@@ -245,18 +270,7 @@ class BMMiner(BaseMiner):
async def get_wattage_limit(self) -> Optional[int]:
return None
async def get_fans(
self, api_stats: dict = None
) -> Tuple[
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]],
Tuple[Optional[int]],
]:
fan_speeds = namedtuple("FanSpeeds", "fan_1 fan_2 fan_3 fan_4")
psu_fan_speeds = namedtuple("PSUFanSpeeds", "psu_fan")
miner_fan_speeds = namedtuple("MinerFans", "fan_speeds psu_fan_speeds")
psu_fans = psu_fan_speeds(None)
async def get_fans(self, api_stats: dict = None) -> List[Fan]:
if not api_stats:
try:
api_stats = await self.api.stats()
@@ -280,9 +294,9 @@ class BMMiner(BaseMiner):
fans_data[fan] = api_stats["STATS"][1].get(f"fan{fan_offset+fan}")
except (KeyError, IndexError):
pass
fans = fan_speeds(*fans_data)
fans = [Fan(speed=d) if d else Fan() for d in fans_data]
return miner_fan_speeds(fans, psu_fans)
return fans
async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = []
@@ -339,108 +353,3 @@ class BMMiner(BaseMiner):
return round(ideal_rate, 2)
except (KeyError, IndexError):
pass
async def _get_data(self, allow_warning: bool) -> dict:
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
try:
miner_data = await self.api.multicommand(
"summary",
"pools",
"version",
"devdetails",
"stats",
allow_warning=allow_warning,
)
except APIError:
try:
miner_data = await self.api.multicommand(
"summary",
"version",
"pools",
"stats",
allow_warning=allow_warning,
)
except APIError:
pass
if miner_data:
break
if miner_data:
summary = miner_data.get("summary")
if summary:
summary = summary[0]
pools = miner_data.get("pools")
if pools:
pools = pools[0]
version = miner_data.get("version")
if version:
version = version[0]
devdetails = miner_data.get("devdetails")
if devdetails:
devdetails = devdetails[0]
stats = miner_data.get("stats")
if stats:
stats = stats[0]
else:
summary, pools, devdetails, version, stats = (None for _ in range(5))
data = { # noqa - Ignore dictionary could be re-written
# ip - Done at start
# datetime - Done auto
"mac": await self.get_mac(),
"model": await self.get_model(api_devdetails=devdetails),
# make - Done at start
"api_ver": None, # - Done at end
"fw_ver": None, # Done at end.
"hostname": await self.get_hostname(),
"hashrate": await self.get_hashrate(api_summary=summary),
"nominal_hashrate": await self.get_nominal_hashrate(api_stats=stats),
"hashboards": await self.get_hashboards(api_stats=stats),
# ideal_hashboards - Done at start
"env_temp": await self.get_env_temp(),
"wattage": await self.get_wattage(),
"wattage_limit": await self.get_wattage_limit(),
"fan_1": None, # - Done at end
"fan_2": None, # - Done at end
"fan_3": None, # - Done at end
"fan_4": None, # - Done at end
"fan_psu": None, # - Done at end
# ideal_chips - Done at start
"pool_split": None, # - Done at end
"pool_1_url": None, # - Done at end
"pool_1_user": None, # - Done at end
"pool_2_url": None, # - Done at end
"pool_2_user": None, # - Done at end
"errors": await self.get_errors(),
"fault_light": await self.get_fault_light(),
}
data["api_ver"], data["fw_ver"] = await self.get_version(api_version=version)
fan_data = await self.get_fans()
if fan_data:
data["fan_1"] = fan_data.fan_speeds.fan_1 # noqa
data["fan_2"] = fan_data.fan_speeds.fan_2 # noqa
data["fan_3"] = fan_data.fan_speeds.fan_3 # noqa
data["fan_4"] = fan_data.fan_speeds.fan_4 # noqa
data["fan_psu"] = fan_data.psu_fan_speeds.psu_fan # noqa
pools_data = await self.get_pools(api_pools=pools)
if pools_data:
data["pool_1_url"] = pools_data[0]["pool_1_url"]
data["pool_1_user"] = pools_data[0]["pool_1_user"]
if len(pools_data) > 1:
data["pool_2_url"] = pools_data[1]["pool_2_url"]
data["pool_2_user"] = pools_data[1]["pool_2_user"]
data[
"pool_split"
] = f"{pools_data[0]['quota']}/{pools_data[1]['quota']}"
else:
try:
data["pool_2_url"] = pools_data[0]["pool_2_url"]
data["pool_2_user"] = pools_data[0]["pool_2_user"]
data["quota"] = "0"
except KeyError:
pass
return data

View File

@@ -24,7 +24,7 @@ import toml
from pyasic.API.bosminer import BOSMinerAPI
from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.data import Fan, HashBoard, MinerData
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner
@@ -287,10 +287,29 @@ class BOSMiner(BaseMiner):
) -> Tuple[Optional[str], Optional[str]]:
# check if version is cached
miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
# if self.fw_ver and self.api_ver:
# logging.debug(f"Found version for {self.ip}: {self.fw_ver}")
# return miner_version(self.api_ver, self.fw_ver)
api_ver = await self.get_api_ver(api_version)
fw_ver = await self.get_fw_ver(graphql_version)
return miner_version(api_ver, fw_ver)
async def get_api_ver(self, api_version: dict = None) -> Optional[str]:
if not api_version:
try:
api_version = await self.api.version()
except APIError:
pass
# Now get the API version
if api_version:
try:
api_ver = api_version["VERSION"][0]["API"]
except (KeyError, IndexError):
api_ver = None
self.api_ver = api_ver
self.api.api_ver = self.api_ver
return self.api_ver
async def get_fw_ver(self, graphql_version: dict = None) -> Optional[str]:
if not graphql_version:
try:
graphql_version = await self.send_graphql_query(
@@ -299,11 +318,6 @@ class BOSMiner(BaseMiner):
except APIError:
pass
if not api_version:
try:
api_version = await self.api.version()
except APIError:
pass
fw_ver = None
if graphql_version:
@@ -323,16 +337,7 @@ class BOSMiner(BaseMiner):
self.fw_ver = ver
logging.debug(f"Found version for {self.ip}: {self.fw_ver}")
# Now get the API version
if api_version:
try:
api_ver = api_version["VERSION"][0]["API"]
except (KeyError, IndexError):
api_ver = None
self.api_ver = api_ver
self.api.api_ver = self.api_ver
return miner_version(self.api_ver, self.fw_ver)
return self.fw_ver
async def get_hostname(self, graphql_hostname: dict = None) -> Union[str, None]:
if self.hostname:
@@ -584,16 +589,7 @@ class BOSMiner(BaseMiner):
async def get_fans(
self, api_fans: dict = None, graphql_fans: dict = None
) -> Tuple[
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]],
Tuple[Optional[int]],
]:
psu_fan = None
fan_speeds = namedtuple("FanSpeeds", "fan_1 fan_2 fan_3 fan_4")
psu_fan_speeds = namedtuple("PSUFanSpeeds", "psu_fan")
miner_fan_speeds = namedtuple("MinerFans", "fan_speeds psu_fan_speeds")
) -> List[Fan]:
if not graphql_fans and not api_fans:
try:
graphql_fans = await self.send_graphql_query(
@@ -603,18 +599,15 @@ class BOSMiner(BaseMiner):
pass
if graphql_fans:
fans = {"fan_1": None, "fan_2": None, "fan_3": None, "fan_4": None}
fans = {"fan_1": Fan(), "fan_2": Fan(), "fan_3": Fan(), "fan_4": Fan()}
for n in range(self.fan_count):
try:
fans[f"fan_{n + 1}"] = graphql_fans["bosminer"]["info"]["fans"][n][
"rpm"
]
fans[f"fan_{n + 1}"].speed = graphql_fans["bosminer"]["info"][
"fans"
][n]["rpm"]
except KeyError:
pass
return miner_fan_speeds(
fan_speeds(fans["fan_1"], fans["fan_2"], fans["fan_3"], fans["fan_4"]),
psu_fan_speeds(psu_fan),
)
return [fans["fan_1"], fans["fan_2"], fans["fan_3"], fans["fan_4"]]
if not api_fans:
try:
@@ -623,19 +616,17 @@ class BOSMiner(BaseMiner):
pass
if api_fans:
fans = {"fan_1": None, "fan_2": None, "fan_3": None, "fan_4": None}
fans = {"fan_1": Fan(), "fan_2": Fan(), "fan_3": Fan(), "fan_4": Fan()}
for n in range(self.fan_count):
try:
fans[f"fan_{n + 1}"] = api_fans["FANS"][n]["RPM"]
fans[f"fan_{n + 1}"].speed = api_fans["FANS"][n]["RPM"]
except KeyError:
pass
return miner_fan_speeds(
fan_speeds(fans["fan_1"], fans["fan_2"], fans["fan_3"], fans["fan_4"]),
psu_fan_speeds(psu_fan),
)
return miner_fan_speeds(
fan_speeds(None, None, None, None), psu_fan_speeds(None)
)
return [fans["fan_1"], fans["fan_2"], fans["fan_3"], fans["fan_4"]]
return [Fan(), Fan(), Fan(), Fan()]
async def get_fan_psu(self) -> Optional[int]:
return None
async def get_pools(
self, api_pools: dict = None, graphql_pools: dict = None
@@ -851,144 +842,3 @@ class BOSMiner(BaseMiner):
)
except (IndexError, KeyError):
pass
async def _get_data(self, allow_warning: bool) -> dict:
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
try:
miner_data = await self.api.multicommand(
"summary",
"temps",
"tunerstatus",
"pools",
"devdetails",
"fans",
"devs",
"version",
allow_warning=allow_warning,
)
except APIError as e:
if str(e.message) == "Not ready":
try:
miner_data = await self.api.multicommand(
"summary", "tunerstatus", "pools", "devs", "version"
)
except APIError:
pass
if miner_data:
break
if miner_data:
summary = miner_data.get("summary")
if summary:
summary = summary[0]
version = miner_data.get("version")
if version:
version = version[0]
temps = miner_data.get("temps")
if temps:
temps = temps[0]
tunerstatus = miner_data.get("tunerstatus")
if tunerstatus:
tunerstatus = tunerstatus[0]
pools = miner_data.get("pools")
if pools:
pools = pools[0]
devdetails = miner_data.get("devdetails")
if devdetails:
devdetails = devdetails[0]
devs = miner_data.get("devs")
if devs:
devs = devs[0]
fans = miner_data.get("fans")
if fans:
fans = fans[0]
else:
summary, version, temps, tunerstatus, pools, devdetails, devs, fans = (
None for _ in range(8)
)
try:
gql_data = await self.send_graphql_query(
"{bos {hostname}, bosminer{config{... on BosminerConfig{groups{pools{url, user}, strategy{... on QuotaStrategy {quota}}}}}, info{fans{name, rpm}, workSolver{realHashrate{mhs1M}, temperatures{degreesC}, power{limitW, approxConsumptionW}, childSolvers{name, realHashrate{mhs1M}, hwDetails{chips}, tuner{statusMessages}, temperatures{degreesC}}}}}}"
)
except APIError:
gql_data = None
if gql_data:
if "data" in gql_data:
gql_data = gql_data["data"]
data = { # noqa - Ignore dictioonary could be re-written
# ip - Done at start
# datetime - Done auto
"mac": await self.get_mac(),
"model": await self.get_model(),
# make - Done at start
"api_ver": None, # - Done at end
"fw_ver": None, # - Done at end
"hostname": await self.get_hostname(graphql_hostname=gql_data),
"hashrate": await self.get_hashrate(
api_summary=summary, graphql_hashrate=gql_data
),
"nominal_hashrate": await self.get_nominal_hashrate(api_devs=devs),
"hashboards": await self.get_hashboards(
api_temps=temps,
api_devdetails=devdetails,
api_devs=devs,
graphql_boards=gql_data,
),
# ideal_hashboards - Done at start
"env_temp": await self.get_env_temp(),
"wattage": await self.get_wattage(
api_tunerstatus=tunerstatus, graphql_wattage=gql_data
),
"wattage_limit": await self.get_wattage_limit(
api_tunerstatus=tunerstatus, graphql_wattage_limit=gql_data
),
"fan_1": None, # - Done at end
"fan_2": None, # - Done at end
"fan_3": None, # - Done at end
"fan_4": None, # - Done at end
"fan_psu": None, # - Done at end
# ideal_chips - Done at start
"pool_split": None, # - Done at end
"pool_1_url": None, # - Done at end
"pool_1_user": None, # - Done at end
"pool_2_url": None, # - Done at end
"pool_2_user": None, # - Done at end
"errors": await self.get_errors(
api_tunerstatus=tunerstatus, graphql_errors=gql_data
),
"fault_light": await self.get_fault_light(),
}
data["api_ver"], data["fw_ver"] = await self.get_version(
api_version=version, graphql_version=gql_data
)
fan_data = await self.get_fans(api_fans=fans, graphql_fans=gql_data)
if fan_data:
data["fan_1"] = fan_data.fan_speeds.fan_1 # noqa
data["fan_2"] = fan_data.fan_speeds.fan_2 # noqa
data["fan_3"] = fan_data.fan_speeds.fan_3 # noqa
data["fan_4"] = fan_data.fan_speeds.fan_4 # noqa
data["fan_psu"] = fan_data.psu_fan_speeds.psu_fan # noqa
pools_data = await self.get_pools(api_pools=pools, graphql_pools=gql_data)
if pools_data:
data["pool_1_url"] = pools_data[0]["pool_1_url"]
data["pool_1_user"] = pools_data[0]["pool_1_user"]
if len(pools_data) > 1:
data["pool_2_url"] = pools_data[1]["pool_2_url"]
data["pool_2_user"] = pools_data[1]["pool_2_user"]
data[
"pool_split"
] = f"{pools_data[0]['quota']}/{pools_data[1]['quota']}"
else:
try:
data["pool_2_url"] = pools_data[0]["pool_2_url"]
data["pool_2_user"] = pools_data[0]["pool_2_user"]
data["quota"] = "0"
except KeyError:
pass
return data

View File

@@ -21,7 +21,7 @@ import asyncssh
from pyasic.API.bosminer import BOSMinerAPI
from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.data import Fan, HashBoard, MinerData
from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner
@@ -131,18 +131,17 @@ class BOSMinerOld(BaseMiner):
async def get_fans(
self,
) -> Tuple[
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]],
Tuple[Optional[int]],
]:
fan_speeds = namedtuple("FanSpeeds", "fan_1 fan_2 fan_3 fan_4")
psu_fan_speeds = namedtuple("PSUFanSpeeds", "psu_fan")
miner_fan_speeds = namedtuple("MinerFans", "fan_speeds psu_fan_speeds")
) -> List[Fan]:
return [Fan(), Fan(), Fan(), Fan()]
fans = fan_speeds(None, None, None, None)
psu_fans = psu_fan_speeds(None)
async def get_fan_psu(self) -> Optional[int]:
return None
return miner_fan_speeds(fans, psu_fans)
async def get_api_ver(self) -> Optional[str]:
return None
async def get_fw_ver(self) -> Optional[str]:
return None
async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = []
@@ -179,8 +178,5 @@ class BOSMinerOld(BaseMiner):
async def get_nominal_hashrate(self) -> Optional[float]:
return None
async def _get_data(self, allow_warning: bool) -> dict:
return {}
async def get_data(self, allow_warning: bool = False) -> MinerData:
return MinerData(ip=str(self.ip))

View File

@@ -19,7 +19,7 @@ from typing import List, Optional, Tuple, Union
from pyasic.API.btminer import BTMinerAPI
from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.data import Fan, HashBoard, MinerData
from pyasic.data.error_codes import MinerErrorData, WhatsminerError
from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner
@@ -172,17 +172,17 @@ class BTMiner(BaseMiner):
##################################################
async def get_mac(
self, api_summary: dict = None, api_miner_info: dict = None
self, api_summary: dict = None, api_get_miner_info: dict = None
) -> Optional[str]:
if not api_miner_info:
if not api_get_miner_info:
try:
api_miner_info = await self.api.get_miner_info()
api_get_miner_info = await self.api.get_miner_info()
except APIError:
pass
if api_miner_info:
if api_get_miner_info:
try:
mac = api_miner_info["Msg"]["mac"]
mac = api_get_miner_info["Msg"]["mac"]
return str(mac).upper()
except KeyError:
pass
@@ -223,34 +223,64 @@ class BTMiner(BaseMiner):
return None
async def get_version(
self, api_version: dict = None, api_summary: dict = None
self, api_get_version: dict = None, api_summary: dict = None
) -> Tuple[Optional[str], Optional[str]]:
# check if version is cached
miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
# Check to see if the version info is already cached
if self.api_ver and self.fw_ver:
return miner_version(self.api_ver, self.fw_ver)
api_ver = await self.get_api_ver(api_get_version=api_get_version)
fw_ver = await self.get_fw_ver(
api_get_version=api_get_version, api_summary=api_summary
)
return miner_version(api_ver, fw_ver)
if not api_version:
async def get_api_ver(self, api_get_version: dict = None) -> Optional[str]:
# Check to see if the version info is already cached
if self.api_ver:
return self.api_ver
if not api_get_version:
try:
api_version = await self.api.get_version()
api_get_version = await self.api.get_version()
except APIError:
pass
if api_version:
if "Code" in api_version.keys():
if api_version["Code"] == 131:
if api_get_version:
if "Code" in api_get_version.keys():
if api_get_version["Code"] == 131:
try:
api_ver = api_version["Msg"]
api_ver = api_get_version["Msg"]
if not isinstance(api_ver, str):
api_ver = api_ver["api_ver"]
self.api_ver = api_ver.replace("whatsminer v", "")
self.fw_ver = api_version["Msg"]["fw_ver"]
except (KeyError, TypeError):
pass
else:
self.api.api_ver = self.api_ver
return miner_version(self.api_ver, self.fw_ver)
return self.api_ver
return self.api_ver
async def get_fw_ver(
self, api_get_version: dict = None, api_summary: dict = None
) -> Optional[str]:
# Check to see if the version info is already cached
if self.fw_ver:
return self.fw_ver
if not api_get_version:
try:
api_get_version = await self.api.get_version()
except APIError:
pass
if api_get_version:
if "Code" in api_get_version.keys():
if api_get_version["Code"] == 131:
try:
self.fw_ver = api_get_version["Msg"]["fw_ver"]
except (KeyError, TypeError):
pass
else:
return self.fw_ver
if not api_summary:
try:
@@ -266,21 +296,21 @@ class BTMiner(BaseMiner):
except (KeyError, IndexError):
pass
return miner_version(self.api_ver, self.fw_ver)
return self.fw_ver
async def get_hostname(self, api_miner_info: dict = None) -> Optional[str]:
async def get_hostname(self, api_get_miner_info: dict = None) -> Optional[str]:
if self.hostname:
return self.hostname
if not api_miner_info:
if not api_get_miner_info:
try:
api_miner_info = await self.api.get_miner_info()
api_get_miner_info = await self.api.get_miner_info()
except APIError:
return None # only one way to get this
if api_miner_info:
if api_get_miner_info:
try:
self.hostname = api_miner_info["Msg"]["hostname"]
self.hostname = api_get_miner_info["Msg"]["hostname"]
except KeyError:
return None
@@ -375,18 +405,32 @@ class BTMiner(BaseMiner):
pass
async def get_fans(
self, api_summary: dict = None, api_psu: dict = None
) -> Tuple[
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]],
Tuple[Optional[int]],
]:
fan_speeds = namedtuple("FanSpeeds", "fan_1 fan_2 fan_3 fan_4")
psu_fan_speeds = namedtuple("PSUFanSpeeds", "psu_fan")
miner_fan_speeds = namedtuple("MinerFans", "fan_speeds psu_fan_speeds")
self, api_summary: dict = None, api_get_psu: dict = None
) -> List[Fan]:
if not api_summary:
try:
api_summary = await self.api.summary()
except APIError:
pass
fans = fan_speeds(None, None, None, None)
psu_fans = psu_fan_speeds(None)
fans = [Fan(), Fan(), Fan(), Fan()]
if api_summary:
try:
if self.fan_count > 0:
fans = [
Fan(api_summary["SUMMARY"][0]["Fan Speed In"]),
Fan(api_summary["SUMMARY"][0]["Fan Speed Out"]),
Fan(),
Fan(),
]
except (KeyError, IndexError):
pass
return fans
async def get_fan_psu(
self, api_summary: dict = None, api_get_psu: dict = None
) -> Optional[int]:
if not api_summary:
try:
api_summary = await self.api.summary()
@@ -395,33 +439,21 @@ class BTMiner(BaseMiner):
if api_summary:
try:
if self.fan_count > 0:
fans = fan_speeds(
api_summary["SUMMARY"][0]["Fan Speed In"],
api_summary["SUMMARY"][0]["Fan Speed Out"],
None,
None,
)
psu_fans = psu_fan_speeds(
int(api_summary["SUMMARY"][0]["Power Fanspeed"])
)
return int(api_summary["SUMMARY"][0]["Power Fanspeed"])
except (KeyError, IndexError):
pass
if not psu_fans[0]:
if not api_psu:
try:
api_psu = await self.api.get_psu()
except APIError:
pass
if not api_get_psu:
try:
api_get_psu = await self.api.get_psu()
except APIError:
pass
if api_psu:
try:
psu_fans = psu_fan_speeds(int(api_psu["Msg"]["fan_speed"]))
except (KeyError, TypeError):
pass
return miner_fan_speeds(fans, psu_fans)
if api_get_psu:
try:
return int(api_get_psu["Msg"]["fan_speed"])
except (KeyError, TypeError):
pass
async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = []
@@ -450,10 +482,10 @@ class BTMiner(BaseMiner):
return groups
async def get_errors(
self, api_summary: dict = None, api_error_codes: dict = None
self, api_summary: dict = None, api_get_error_code: dict = None
) -> List[MinerErrorData]:
errors = []
if not api_summary and not api_error_codes:
if not api_summary and not api_get_error_code:
try:
api_summary = await self.api.summary()
except APIError:
@@ -468,14 +500,14 @@ class BTMiner(BaseMiner):
except (KeyError, IndexError, ValueError, TypeError):
pass
if not api_error_codes:
if not api_get_error_code:
try:
api_error_codes = await self.api.get_error_code()
api_get_error_code = await self.api.get_error_code()
except APIError:
pass
if api_error_codes:
for err in api_error_codes["Msg"]["error_code"]:
if api_get_error_code:
for err in api_get_error_code["Msg"]["error_code"]:
if isinstance(err, dict):
for code in err:
errors.append(WhatsminerError(error_code=int(code)))
@@ -499,134 +531,20 @@ class BTMiner(BaseMiner):
except (KeyError, IndexError):
pass
async def get_fault_light(self, api_miner_info: dict = None) -> bool:
async def get_fault_light(self, api_get_miner_info: dict = None) -> bool:
data = None
if not api_miner_info:
if not api_get_miner_info:
try:
api_miner_info = await self.api.get_miner_info()
api_get_miner_info = await self.api.get_miner_info()
except APIError:
if not self.light:
self.light = False
if api_miner_info:
if api_get_miner_info:
try:
self.light = api_miner_info["Msg"]["ledstat"] == "auto"
self.light = api_get_miner_info["Msg"]["ledstat"] == "auto"
except KeyError:
pass
return self.light if self.light else False
async def _get_data(self, allow_warning: bool) -> dict:
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
try:
miner_data = await self.api.multicommand(
"summary",
"get_version",
"pools",
"devdetails",
"devs",
"get_psu",
"get_miner_info",
"get_error_code",
allow_warning=allow_warning,
)
except APIError:
pass
if miner_data:
break
if miner_data:
summary = miner_data.get("summary")
if summary:
summary = summary[0]
version = miner_data.get("get_version")
if version:
version = version[0]
pools = miner_data.get("pools")
if pools:
pools = pools[0]
devdetails = miner_data.get("devdetails")
if devdetails:
devdetails = devdetails[0]
devs = miner_data.get("devs")
if devs:
devs = devs[0]
psu = miner_data.get("get_psu")
if psu:
psu = psu[0]
miner_info = miner_data.get("get_miner_info")
if miner_info:
miner_info = miner_info[0]
error_codes = miner_data.get("get_error_codes")
if error_codes:
error_codes = error_codes[0]
else:
summary, version, pools, devdetails, devs, psu, miner_info, error_codes = (
None for _ in range(8)
)
data = { # noqa - Ignore dictionary could be re-written
# ip - Done at start
# datetime - Done auto
"mac": await self.get_mac(api_summary=summary, api_miner_info=miner_info),
"model": await self.get_model(api_devdetails=devdetails),
# make - Done at start
"api_ver": None, # - Done at end
"fw_ver": None, # - Done at end
"hostname": await self.get_hostname(api_miner_info=miner_info),
"hashrate": await self.get_hashrate(api_summary=summary),
"nominal_hashrate": await self.get_nominal_hashrate(api_summary=summary),
"hashboards": await self.get_hashboards(api_devs=devs),
# ideal_hashboards - Done at start
"env_temp": await self.get_env_temp(api_summary=summary),
"wattage": await self.get_wattage(api_summary=summary),
"wattage_limit": await self.get_wattage_limit(api_summary=summary),
"fan_1": None, # - Done at end
"fan_2": None, # - Done at end
"fan_3": None, # - Done at end
"fan_4": None, # - Done at end
"fan_psu": None, # - Done at end
# ideal_chips - Done at start
"pool_split": None, # - Done at end
"pool_1_url": None, # - Done at end
"pool_1_user": None, # - Done at end
"pool_2_url": None, # - Done at end
"pool_2_user": None, # - Done at end
"errors": await self.get_errors(
api_summary=summary, api_error_codes=error_codes
),
"fault_light": await self.get_fault_light(api_miner_info=miner_info),
}
data["api_ver"], data["fw_ver"] = await self.get_version(api_version=version)
fan_data = await self.get_fans()
if fan_data:
data["fan_1"] = fan_data.fan_speeds.fan_1 # noqa
data["fan_2"] = fan_data.fan_speeds.fan_2 # noqa
data["fan_3"] = fan_data.fan_speeds.fan_3 # noqa
data["fan_4"] = fan_data.fan_speeds.fan_4 # noqa
data["fan_psu"] = fan_data.psu_fan_speeds.psu_fan # noqa
pools_data = await self.get_pools(api_pools=pools)
if pools_data:
data["pool_1_url"] = pools_data[0]["pool_1_url"]
data["pool_1_user"] = pools_data[0]["pool_1_user"]
if len(pools_data) > 1:
data["pool_2_url"] = pools_data[1]["pool_2_url"]
data["pool_2_user"] = pools_data[1]["pool_2_user"]
data[
"pool_split"
] = f"{pools_data[0]['quota']}/{pools_data[1]['quota']}"
else:
try:
data["pool_2_url"] = pools_data[0]["pool_2_url"]
data["pool_2_user"] = pools_data[0]["pool_2_user"]
data["quota"] = "0"
except KeyError:
pass
return data

View File

@@ -21,7 +21,7 @@ import asyncssh
from pyasic.API.cgminer import CGMinerAPI
from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.data import Fan, HashBoard, MinerData
from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner
@@ -185,9 +185,14 @@ class CGMiner(BaseMiner):
self, api_version: dict = None
) -> Tuple[Optional[str], Optional[str]]:
miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
# Check to see if the version info is already cached
if self.api_ver and self.fw_ver:
return miner_version(self.api_ver, self.fw_ver)
return miner_version(
api_ver=await self.get_api_ver(api_version=api_version),
fw_ver=await self.get_fw_ver(api_version=api_version),
)
async def get_api_ver(self, api_version: dict = None) -> Optional[str]:
if self.api_ver:
return self.api_ver
if not api_version:
try:
@@ -200,12 +205,26 @@ class CGMiner(BaseMiner):
self.api_ver = api_version["VERSION"][0]["API"]
except (KeyError, IndexError):
pass
return self.api_ver
async def get_fw_ver(self, api_version: dict = None) -> Optional[str]:
if self.fw_ver:
return self.fw_ver
if not api_version:
try:
api_version = await self.api.version()
except APIError:
pass
if api_version:
try:
self.fw_ver = api_version["VERSION"][0]["CGMiner"]
except (KeyError, IndexError):
pass
return miner_version(self.api_ver, self.fw_ver)
return self.fw_ver
async def get_hostname(self) -> Optional[str]:
try:
@@ -293,25 +312,14 @@ class CGMiner(BaseMiner):
async def get_wattage_limit(self) -> Optional[int]:
return None
async def get_fans(
self, api_stats: dict = None
) -> Tuple[
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]],
Tuple[Optional[int]],
]:
fan_speeds = namedtuple("FanSpeeds", "fan_1 fan_2 fan_3 fan_4")
psu_fan_speeds = namedtuple("PSUFanSpeeds", "psu_fan")
miner_fan_speeds = namedtuple("MinerFans", "fan_speeds psu_fan_speeds")
psu_fans = psu_fan_speeds(None)
async def get_fans(self, api_stats: dict = None) -> List[Fan]:
if not api_stats:
try:
api_stats = await self.api.stats()
except APIError:
pass
fans_data = [None, None, None, None]
fans_data = [Fan(), Fan(), Fan(), Fan()]
if api_stats:
try:
fan_offset = -1
@@ -325,12 +333,15 @@ class CGMiner(BaseMiner):
fan_offset = 1
for fan in range(self.fan_count):
fans_data[fan] = api_stats["STATS"][1].get(f"fan{fan_offset+fan}")
fans_data[fan] = Fan(
api_stats["STATS"][1].get(f"fan{fan_offset+fan}")
)
except (KeyError, IndexError):
pass
fans = fan_speeds(*fans_data)
return fans_data
return miner_fan_speeds(fans, psu_fans)
async def get_fan_psu(self) -> Optional[int]:
return None
async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = []
@@ -387,109 +398,3 @@ class CGMiner(BaseMiner):
return round(ideal_rate, 2)
except (KeyError, IndexError):
pass
async def _get_data(self, allow_warning: bool) -> dict:
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
try:
miner_data = await self.api.multicommand(
"summary",
"pools",
"devdetails",
"stats",
allow_warning=allow_warning,
)
except APIError:
try:
miner_data = await self.api.multicommand(
"summary",
"pools",
"version",
"stats",
allow_warning=allow_warning,
)
except APIError:
pass
if miner_data:
break
if miner_data:
summary = miner_data.get("summary")
if summary:
summary = summary[0]
pools = miner_data.get("pools")
if pools:
pools = pools[0]
version = miner_data.get("version")
if version:
version = version[0]
devdetails = miner_data.get("devdetails")
if devdetails:
devdetails = devdetails[0]
stats = miner_data.get("stats")
if stats:
stats = stats[0]
else:
summary, pools, devdetails, version, stats = (None for _ in range(5))
data = { # noqa - Ignore dictionary could be re-written
# ip - Done at start
# datetime - Done auto
"mac": await self.get_mac(),
"model": await self.get_model(api_devdetails=devdetails),
# make - Done at start
"api_ver": None, # - Done at end
"fw_ver": None, # - Done at end
"hostname": await self.get_hostname(),
"hashrate": await self.get_hashrate(api_summary=summary),
"nominal_hashrate": await self.get_nominal_hashrate(api_stats=stats),
"hashboards": await self.get_hashboards(api_stats=stats),
# ideal_hashboards - Done at start
"env_temp": await self.get_env_temp(),
"wattage": await self.get_wattage(),
"wattage_limit": await self.get_wattage_limit(),
"fan_1": None, # - Done at end
"fan_2": None, # - Done at end
"fan_3": None, # - Done at end
"fan_4": None, # - Done at end
"fan_psu": None, # - Done at end
# ideal_chips - Done at start
"pool_split": None, # - Done at end
"pool_1_url": None, # - Done at end
"pool_1_user": None, # - Done at end
"pool_2_url": None, # - Done at end
"pool_2_user": None, # - Done at end
"errors": await self.get_errors(),
"fault_light": await self.get_fault_light(),
}
data["api_ver"], data["fw_ver"] = await self.get_version(api_version=version)
fan_data = await self.get_fans()
if fan_data:
data["fan_1"] = fan_data.fan_speeds.fan_1 # noqa
data["fan_2"] = fan_data.fan_speeds.fan_2 # noqa
data["fan_3"] = fan_data.fan_speeds.fan_3 # noqa
data["fan_4"] = fan_data.fan_speeds.fan_4 # noqa
data["fan_psu"] = fan_data.psu_fan_speeds.psu_fan # noqa
pools_data = await self.get_pools(api_pools=pools)
if pools_data:
data["pool_1_url"] = pools_data[0]["pool_1_url"]
data["pool_1_user"] = pools_data[0]["pool_1_user"]
if len(pools_data) > 1:
data["pool_2_url"] = pools_data[1]["pool_2_url"]
data["pool_2_user"] = pools_data[1]["pool_2_user"]
data[
"pool_split"
] = f"{pools_data[0]['quota']}/{pools_data[1]['quota']}"
else:
try:
data["pool_2_url"] = pools_data[0]["pool_2_url"]
data["pool_2_user"] = pools_data[0]["pool_2_user"]
data["quota"] = "0"
except KeyError:
pass
return data

View File

@@ -20,7 +20,7 @@ from typing import List, Optional, Tuple, Union
from pyasic.API.cgminer import CGMinerAPI
from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.data import Fan, HashBoard, MinerData
from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError
from pyasic.miners._backends import CGMiner
@@ -206,25 +206,14 @@ class CGMinerAvalon(CGMiner):
async def get_wattage_limit(self) -> Optional[int]:
return None
async def get_fans(
self, api_stats: dict = None
) -> Tuple[
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]],
Tuple[Optional[int]],
]:
fan_speeds = namedtuple("FanSpeeds", "fan_1 fan_2 fan_3 fan_4")
psu_fan_speeds = namedtuple("PSUFanSpeeds", "psu_fan")
miner_fan_speeds = namedtuple("MinerFans", "fan_speeds psu_fan_speeds")
psu_fans = psu_fan_speeds(None)
async def get_fans(self, api_stats: dict = None) -> List[Fan]:
if not api_stats:
try:
api_stats = await self.api.stats()
except APIError:
pass
fans_data = [None, None, None, None]
fans_data = [Fan(), Fan(), Fan(), Fan()]
if api_stats:
try:
stats_data = api_stats[0].get("STATS")
@@ -233,12 +222,11 @@ class CGMinerAvalon(CGMiner):
if key.startswith("MM ID"):
raw_data = self.parse_stats(stats_data[0][key])
for fan in range(self.fan_count):
fans_data[fan] = int(raw_data[f"Fan{fan + 1}"])
fans_data[fan] = Fan(int(raw_data[f"Fan{fan + 1}"]))
except (KeyError, IndexError, ValueError, TypeError):
pass
fans = fan_speeds(*fans_data)
return miner_fan_speeds(fans, psu_fans)
return fans_data
async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = []
@@ -279,101 +267,3 @@ class CGMinerAvalon(CGMiner):
if data["STATUS"][0]["Msg"] == "ASC 0 set info: LED[1]":
return True
return False
async def _get_data(self, allow_warning: bool) -> dict:
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
try:
miner_data = await self.api.multicommand(
"summary",
"pools",
"version",
"devdetails",
"stats",
allow_warning=allow_warning,
)
except APIError:
pass
if miner_data:
break
if miner_data:
summary = miner_data.get("summary")
if summary:
summary = summary[0]
pools = miner_data.get("pools")
if pools:
pools = pools[0]
version = miner_data.get("version")
if version:
version = version[0]
devdetails = miner_data.get("devdetails")
if devdetails:
devdetails = devdetails[0]
stats = miner_data.get("stats")
if stats:
stats = stats[0]
else:
summary, pools, devdetails, version, stats = (None for _ in range(5))
data = { # noqa - Ignore dictionary could be re-written
# ip - Done at start
# datetime - Done auto
"mac": await self.get_mac(),
"model": await self.get_model(api_devdetails=devdetails),
# make - Done at start
"api_ver": None, # - Done at end
"fw_ver": None, # - Done at end
"hostname": await self.get_hostname(),
"hashrate": await self.get_hashrate(api_summary=summary),
"nominal_hashrate": await self.get_nominal_hashrate(api_stats=stats),
"hashboards": await self.get_hashboards(api_stats=stats),
# ideal_hashboards - Done at start
"env_temp": await self.get_env_temp(),
"wattage": await self.get_wattage(),
"wattage_limit": await self.get_wattage_limit(),
"fan_1": None, # - Done at end
"fan_2": None, # - Done at end
"fan_3": None, # - Done at end
"fan_4": None, # - Done at end
"fan_psu": None, # - Done at end
# ideal_chips - Done at start
"pool_split": None, # - Done at end
"pool_1_url": None, # - Done at end
"pool_1_user": None, # - Done at end
"pool_2_url": None, # - Done at end
"pool_2_user": None, # - Done at end
"errors": await self.get_errors(),
"fault_light": await self.get_fault_light(),
}
data["api_ver"], data["fw_ver"] = await self.get_version(api_version=version)
fan_data = await self.get_fans()
if fan_data:
data["fan_1"] = fan_data.fan_speeds.fan_1 # noqa
data["fan_2"] = fan_data.fan_speeds.fan_2 # noqa
data["fan_3"] = fan_data.fan_speeds.fan_3 # noqa
data["fan_4"] = fan_data.fan_speeds.fan_4 # noqa
data["fan_psu"] = fan_data.psu_fan_speeds.psu_fan # noqa
pools_data = await self.get_pools(api_pools=pools)
if pools_data:
data["pool_1_url"] = pools_data[0]["pool_1_url"]
data["pool_1_user"] = pools_data[0]["pool_1_user"]
if len(pools_data) > 1:
data["pool_2_url"] = pools_data[1]["pool_2_url"]
data["pool_2_user"] = pools_data[1]["pool_2_user"]
data[
"pool_split"
] = f"{pools_data[0]['quota']}/{pools_data[1]['quota']}"
else:
try:
data["pool_2_url"] = pools_data[0]["pool_2_url"]
data["pool_2_user"] = pools_data[0]["pool_2_user"]
data["quota"] = "0"
except KeyError:
pass
return data

View File

@@ -46,10 +46,7 @@ class M31SPlusV30(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__()
self.ip = ip
self.model = "M31S+ V30"
self.nominal_chips = 0
warnings.warn(
"Unknown chip count for miner type M30S+ V30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
)
self.nominal_chips = 117
self.fan_count = 2
@@ -58,10 +55,7 @@ class M31SPlusV40(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__()
self.ip = ip
self.model = "M31S+ V40"
self.nominal_chips = 0
warnings.warn(
"Unknown chip count for miner type M30S+ V40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
)
self.nominal_chips = 123
self.fan_count = 2
@@ -109,10 +103,7 @@ class M31SPlusV100(WhatsMiner): # noqa - ignore ABC method implementation
super().__init__()
self.ip = ip
self.model = "M31S+ V100"
self.nominal_chips = 0
warnings.warn(
"Unknown chip count for miner type M30S+ V100, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
)
self.nominal_chips = 111
self.fan_count = 2

View File

@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from typing import Optional, Union
import httpx

View File

@@ -114,101 +114,3 @@ class HiveonT9(Hiveon, T9):
if not env_temp_list == []:
return round(float(sum(env_temp_list) / len(env_temp_list)), 2)
async def _get_data(self, allow_warning: bool) -> dict:
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
try:
miner_data = await self.api.multicommand(
"summary",
"pools",
"version",
"devdetails",
"stats",
allow_warning=allow_warning,
)
except APIError:
pass
if miner_data:
break
if miner_data:
summary = miner_data.get("summary")
if summary:
summary = summary[0]
pools = miner_data.get("pools")
if pools:
pools = pools[0]
version = miner_data.get("version")
if version:
version = version[0]
devdetails = miner_data.get("devdetails")
if devdetails:
devdetails = devdetails[0]
stats = miner_data.get("stats")
if stats:
stats = stats[0]
else:
summary, pools, devdetails, version, stats = (None for _ in range(5))
data = { # noqa - Ignore dictionary could be re-written
# ip - Done at start
# datetime - Done auto
"mac": await self.get_mac(),
"model": await self.get_model(api_devdetails=devdetails),
# make - Done at start
"api_ver": None, # - Done at end
"fw_ver": None, # - Done at end
"hostname": await self.get_hostname(),
"hashrate": await self.get_hashrate(api_summary=summary),
"nominal_hashrate": await self.get_nominal_hashrate(api_stats=stats),
"hashboards": await self.get_hashboards(api_stats=stats),
# ideal_hashboards - Done at start
"env_temp": await self.get_env_temp(api_stats=stats),
"wattage": await self.get_wattage(api_stats=stats),
"wattage_limit": await self.get_wattage_limit(),
"fan_1": None, # - Done at end
"fan_2": None, # - Done at end
"fan_3": None, # - Done at end
"fan_4": None, # - Done at end
"fan_psu": None, # - Done at end
# ideal_chips - Done at start
"pool_split": None, # - Done at end
"pool_1_url": None, # - Done at end
"pool_1_user": None, # - Done at end
"pool_2_url": None, # - Done at end
"pool_2_user": None, # - Done at end
"errors": await self.get_errors(),
"fault_light": await self.get_fault_light(),
}
data["api_ver"], data["fw_ver"] = await self.get_version(api_version=version)
fan_data = await self.get_fans()
if fan_data:
data["fan_1"] = fan_data.fan_speeds.fan_1 # noqa
data["fan_2"] = fan_data.fan_speeds.fan_2 # noqa
data["fan_3"] = fan_data.fan_speeds.fan_3 # noqa
data["fan_4"] = fan_data.fan_speeds.fan_4 # noqa
data["fan_psu"] = fan_data.psu_fan_speeds.psu_fan # noqa
pools_data = await self.get_pools(api_pools=pools)
if pools_data:
data["pool_1_url"] = pools_data[0]["pool_1_url"]
data["pool_1_user"] = pools_data[0]["pool_1_user"]
if len(pools_data) > 1:
data["pool_2_url"] = pools_data[1]["pool_2_url"]
data["pool_2_user"] = pools_data[1]["pool_2_user"]
data[
"pool_split"
] = f"{pools_data[0]['quota']}/{pools_data[1]['quota']}"
else:
try:
data["pool_2_url"] = pools_data[0]["pool_2_url"]
data["pool_2_user"] = pools_data[0]["pool_2_user"]
data["quota"] = "0"
except KeyError:
pass
return data

View File

@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import inspect
import ipaddress
import logging
from abc import ABC, abstractmethod
@@ -20,7 +21,7 @@ from typing import List, Optional, Tuple, TypeVar
import asyncssh
from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.data import Fan, HashBoard, MinerData
from pyasic.data.error_codes import MinerErrorData
@@ -199,6 +200,24 @@ class BaseMiner(ABC):
"""
pass
@abstractmethod
async def get_api_ver(self, *args, **kwargs) -> Optional[str]:
"""Get the API version of the miner and is as a string.
Returns:
API version as a string.
"""
pass
@abstractmethod
async def get_fw_ver(self, *args, **kwargs) -> Optional[str]:
"""Get the firmware version of the miner and is as a string.
Returns:
Firmware version as a string.
"""
pass
@abstractmethod
async def get_version(self, *args, **kwargs) -> Tuple[Optional[str], Optional[str]]:
"""Get the API version and firmware version of the miner and return them as strings.
@@ -263,15 +282,20 @@ class BaseMiner(ABC):
pass
@abstractmethod
async def get_fans(
self, *args, **kwargs
) -> Tuple[
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]], Optional[int]
]:
"""Get fan data from the miner in the form ((fan_1, fan_2, fan_3, fan_4), psu_fan).
async def get_fans(self, *args, **kwargs) -> List[Fan]:
"""Get fan data from the miner in the form [fan_1, fan_2, fan_3, fan_4].
Returns:
A list of error classes representing different errors.
A list of fan data.
"""
pass
@abstractmethod
async def get_fan_psu(self, *args, **kwargs) -> Optional[int]:
"""Get PSU fan speed from the miner.
Returns:
PSU fan speed.
"""
pass
@@ -282,6 +306,7 @@ class BaseMiner(ABC):
Returns:
Pool groups and quotas in a list of dicts.
"""
pass
@abstractmethod
async def get_errors(self, *args, **kwargs) -> List[MinerErrorData]:
@@ -310,11 +335,104 @@ class BaseMiner(ABC):
"""
pass
async def get_data(self, allow_warning: bool = False) -> MinerData:
async def _get_data(self, allow_warning: bool, data_to_get: list = None) -> dict:
if not data_to_get:
# everything
data_to_get = [
"mac",
"model",
"api_ver",
"fw_ver",
"hostname",
"hashrate",
"nominal_hashrate",
"hashboards",
"env_temp",
"wattage",
"wattage_limit",
"fans",
"fan_psu",
"errors",
"fault_light",
"pools",
]
api_params = []
web_params = []
gql = False
for data_name in data_to_get:
function = getattr(self, "get_" + data_name)
sig = inspect.signature(function)
for item in sig.parameters:
if item.startswith("api_"):
command = item.replace("api_", "")
api_params.append(command)
elif item.startswith("graphql_"):
gql = True
elif item.startswith("web_"):
web_params.append(item.replace("web_", ""))
api_params = list(set(api_params))
web_params = list(set(web_params))
miner_data = {}
command_data = await self.api.multicommand(
*api_params, allow_warning=allow_warning
)
if gql:
gql_data = await self.send_graphql_query( # noqa: bosminer only anyway
"{bos {hostname}, bosminer{config{... on BosminerConfig{groups{pools{url, user}, strategy{... on QuotaStrategy {quota}}}}}, info{fans{name, rpm}, workSolver{realHashrate{mhs1M}, temperatures{degreesC}, power{limitW, approxConsumptionW}, childSolvers{name, realHashrate{mhs1M}, hwDetails{chips}, tuner{statusMessages}, temperatures{degreesC}}}}}}"
)
web_data = {}
for command in web_params:
data = await self.send_web_command(command) # noqa: web only anyway
web_data[command] = data
for data_name in data_to_get:
function = getattr(self, "get_" + data_name)
sig = inspect.signature(function)
kwargs = {}
for arg_name in sig.parameters:
if arg_name.startswith("api_"):
try:
kwargs[arg_name] = command_data[arg_name.replace("api_", "")][0]
except (KeyError, IndexError):
kwargs[arg_name] = None
elif arg_name.startswith("graphql_"):
kwargs[
arg_name
] = gql_data # noqa: variable is not referenced before assignment
elif arg_name.startswith("web_"):
try:
kwargs[arg_name] = web_data[arg_name.replace("web_", "")]
except (KeyError, IndexError):
kwargs[arg_name] = None
if not data_name == "pools":
miner_data[data_name] = await function(**kwargs)
else:
pools_data = await function(**kwargs)
if pools_data:
miner_data["pool_1_url"] = pools_data[0]["pool_1_url"]
miner_data["pool_1_user"] = pools_data[0]["pool_1_user"]
if len(pools_data) > 1:
miner_data["pool_2_url"] = pools_data[1]["pool_2_url"]
miner_data["pool_2_user"] = pools_data[1]["pool_2_user"]
miner_data[
"pool_split"
] = f"{pools_data[0]['quota']}/{pools_data[1]['quota']}"
else:
try:
miner_data["pool_2_url"] = pools_data[0]["pool_2_url"]
miner_data["pool_2_user"] = pools_data[0]["pool_2_user"]
miner_data["quota"] = "0"
except KeyError:
pass
return miner_data
async def get_data(
self, allow_warning: bool = False, data_to_get: list = None
) -> MinerData:
"""Get data from the miner in the form of [`MinerData`][pyasic.data.MinerData].
Parameters:
allow_warning: Allow warning when an API command fails.
data_to_get: Names of data items you want to gather. Defaults to all data.
Returns:
A [`MinerData`][pyasic.data.MinerData] instance containing data from the miner.
@@ -330,16 +448,12 @@ class BaseMiner(ABC):
],
)
gathered_data = await self._get_data(allow_warning)
gathered_data = await self._get_data(allow_warning, data_to_get=data_to_get)
for item in gathered_data:
if gathered_data[item] is not None:
setattr(data, item, gathered_data[item])
return data
@abstractmethod
async def _get_data(self, allow_warning: bool) -> dict:
pass
AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)

View File

@@ -20,7 +20,7 @@ from typing import List, Optional, Tuple, Union
import httpx
from pyasic.config import MinerConfig
from pyasic.data import HashBoard
from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import InnosiliconError, MinerErrorData
from pyasic.errors import APIError
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
@@ -133,8 +133,11 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
##################################################
async def get_mac(
self, web_all_data: dict = None, web_overview: dict = None
self,
web_getAll: dict = None,
web_overview: dict = None, # noqa: named this way for automatic functionality
) -> Optional[str]:
web_all_data = web_getAll
if not web_all_data and not web_overview:
try:
web_overview = await self.send_web_command("overview")
@@ -174,8 +177,11 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
pass
async def get_hashrate(
self, api_summary: dict = None, web_all_data: dict = None
self,
api_summary: dict = None,
web_getAll: dict = None, # noqa: named this way for automatic functionality
) -> Optional[float]:
web_all_data = web_getAll
if not api_summary and not web_all_data:
try:
api_summary = await self.api.summary()
@@ -197,8 +203,11 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
pass
async def get_hashboards(
self, api_stats: dict = None, web_all_data: dict = None
self,
api_stats: dict = None,
web_getAll: dict = None, # noqa: named this way for automatic functionality
) -> List[HashBoard]:
web_all_data = web_getAll
hashboards = [
HashBoard(slot=i, expected_chips=self.nominal_chips)
for i in range(self.ideal_hashboards)
@@ -246,7 +255,10 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
return hashboards
async def get_wattage(self, web_all_data: dict = None) -> Optional[int]:
async def get_wattage(
self, web_getAll: dict = None
) -> Optional[int]: # noqa: named this way for automatic functionality
web_all_data = web_getAll
if not web_all_data:
try:
web_all_data = await self.send_web_command("getAll")
@@ -262,18 +274,10 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
pass
async def get_fans(
self, web_all_data: dict = None
) -> Tuple[
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]],
Tuple[Optional[int]],
]:
fan_speeds = namedtuple("FanSpeeds", "fan_1 fan_2 fan_3 fan_4")
psu_fan_speeds = namedtuple("PSUFanSpeeds", "psu_fan")
miner_fan_speeds = namedtuple("MinerFans", "fan_speeds psu_fan_speeds")
fans = fan_speeds(None, None, None, None)
psu_fans = psu_fan_speeds(None)
self,
web_getAll: dict = None, # noqa: named this way for automatic functionality
) -> List[Fan]:
web_all_data = web_getAll
if not web_all_data:
try:
web_all_data = await self.send_web_command("getAll")
@@ -282,19 +286,18 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
else:
web_all_data = web_all_data["all"]
fan_data = [Fan(), Fan(), Fan(), Fan()]
if web_all_data:
try:
spd = web_all_data["fansSpeed"]
except KeyError:
pass
else:
f = [None, None, None, None]
round((int(spd) * 6000) / 100)
for i in range(self.fan_count):
f[i] = spd
fans = fan_speeds(*f)
fan_data[i] = Fan(spd)
return miner_fan_speeds(fans, psu_fans)
return fan_data
async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = []
@@ -322,7 +325,10 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
pass
return groups
async def get_errors(self, web_error_details: dict = None) -> List[MinerErrorData]:
async def get_errors(
self, web_getErrorDetail: dict = None
) -> List[MinerErrorData]: # noqa: named this way for automatic functionality
web_error_details = web_getErrorDetail
errors = []
if not web_error_details:
try:
@@ -342,129 +348,3 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
if not err == 0:
errors.append(InnosiliconError(error_code=err))
return errors
async def _get_data(self, allow_warning: bool) -> dict:
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
try:
miner_data = await self.api.multicommand(
"summary",
"version",
"pools",
"stats",
allow_warning=allow_warning,
)
except APIError:
pass
if miner_data:
break
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
try:
miner_data = await self.api.multicommand(
"summary",
"pools",
"version",
"devdetails",
"stats",
allow_warning=allow_warning,
)
except APIError:
pass
if miner_data:
break
if miner_data:
summary = miner_data.get("summary")
if summary:
summary = summary[0]
pools = miner_data.get("pools")
if pools:
pools = pools[0]
version = miner_data.get("version")
if version:
version = version[0]
stats = miner_data.get("stats")
if stats:
stats = stats[0]
else:
summary, pools, version, stats = (None for _ in range(4))
try:
web_all_data = await self.send_web_command("getAll")
except APIError:
web_all_data = None
try:
web_type = await self.send_web_command("type")
except APIError:
web_type = None
try:
web_error_details = await self.send_web_command("getErrorDetail")
except APIError:
web_error_details = None
data = { # noqa - Ignore dictionary could be re-written
# ip - Done at start
# datetime - Done auto
"mac": await self.get_mac(web_all_data=web_all_data),
"model": await self.get_model(web_type=web_type),
# make - Done at start
"api_ver": None, # - Done at end
"fw_ver": None, # - Done at end
"hostname": await self.get_hostname(),
"hashrate": await self.get_hashrate(
api_summary=summary, web_all_data=web_all_data
),
"nominal_hashrate": await self.get_nominal_hashrate(api_stats=stats),
"hashboards": await self.get_hashboards(
api_stats=stats, web_all_data=web_all_data
),
# ideal_hashboards - Done at start
"env_temp": await self.get_env_temp(),
"wattage": await self.get_wattage(web_all_data=web_all_data),
"wattage_limit": await self.get_wattage_limit(),
"fan_1": None, # - Done at end
"fan_2": None, # - Done at end
"fan_3": None, # - Done at end
"fan_4": None, # - Done at end
"fan_psu": None, # - Done at end
# ideal_chips - Done at start
"pool_split": None, # - Done at end
"pool_1_url": None, # - Done at end
"pool_1_user": None, # - Done at end
"pool_2_url": None, # - Done at end
"pool_2_user": None, # - Done at end
"errors": await self.get_errors(web_error_details=web_error_details),
"fault_light": await self.get_fault_light(),
}
data["api_ver"], data["fw_ver"] = await self.get_version(api_version=version)
fan_data = await self.get_fans(web_all_data=web_all_data)
if fan_data:
data["fan_1"] = fan_data.fan_speeds.fan_1 # noqa
data["fan_2"] = fan_data.fan_speeds.fan_2 # noqa
data["fan_3"] = fan_data.fan_speeds.fan_3 # noqa
data["fan_4"] = fan_data.fan_speeds.fan_4 # noqa
data["fan_psu"] = fan_data.psu_fan_speeds.psu_fan # noqa
pools_data = await self.get_pools(api_pools=pools)
if pools_data:
data["pool_1_url"] = pools_data[0]["pool_1_url"]
data["pool_1_user"] = pools_data[0]["pool_1_user"]
if len(pools_data) > 1:
data["pool_2_url"] = pools_data[1]["pool_2_url"]
data["pool_2_user"] = pools_data[1]["pool_2_user"]
data[
"pool_split"
] = f"{pools_data[0]['quota']}/{pools_data[1]['quota']}"
else:
try:
data["pool_2_url"] = pools_data[0]["pool_2_url"]
data["pool_2_user"] = pools_data[0]["pool_2_user"]
data["quota"] = "0"
except KeyError:
pass
return data

View File

@@ -17,7 +17,7 @@ from typing import List, Optional, Tuple
from pyasic.API.unknown import UnknownAPI
from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.data import Fan, HashBoard, MinerData
from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner
@@ -98,18 +98,17 @@ class UnknownMiner(BaseMiner):
async def get_fans(
self,
) -> Tuple[
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]],
Tuple[Optional[int]],
]:
fan_speeds = namedtuple("FanSpeeds", "fan_1 fan_2 fan_3 fan_4")
psu_fan_speeds = namedtuple("PSUFanSpeeds", "psu_fan")
miner_fan_speeds = namedtuple("MinerFans", "fan_speeds psu_fan_speeds")
) -> List[Fan]:
return [Fan(), Fan(), Fan(), Fan()]
fans = fan_speeds(None, None, None, None)
psu_fans = psu_fan_speeds(None)
async def get_fan_psu(self) -> Optional[int]:
return None
return miner_fan_speeds(fans, psu_fans)
async def get_api_ver(self) -> Optional[str]:
return None
async def get_fw_ver(self) -> Optional[str]:
return None
async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = []
@@ -146,8 +145,7 @@ class UnknownMiner(BaseMiner):
async def get_nominal_hashrate(self) -> Optional[float]:
return None
async def _get_data(self, allow_warning: bool) -> dict:
return {}
async def get_data(self, allow_warning: bool = False) -> MinerData:
async def get_data(
self, allow_warning: bool = False, data_to_get: list = None
) -> MinerData:
return MinerData(ip=str(self.ip))

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "pyasic"
version = "0.26.1"
version = "0.27.1"
description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH."
authors = ["UpstreamData <brett@upstreamdata.ca>"]
repository = "https://github.com/UpstreamData/pyasic"