feature: allow get_data to pick which data to gather in the first place to allow for speed optimizations.

This commit is contained in:
UpstreamData
2023-02-07 13:40:09 -07:00
parent 33b4ae2f2f
commit 4ff32a8081
15 changed files with 416 additions and 1005 deletions

View File

@@ -95,7 +95,9 @@ class BaseMinerAPI:
Parameters: Parameters:
*commands: The commands to send as a multicommand to the miner. *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. allow_warning: A boolean to supress APIWarnings.
""" """
# make sure we can actually run each command, otherwise they will fail # make sure we can actually run each command, otherwise they will fail
commands = self._check_commands(*commands) commands = self._check_commands(*commands)

View File

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

View File

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

View File

@@ -46,6 +46,17 @@ class HashBoard:
missing: bool = True missing: bool = True
@dataclass
class Fan:
"""A Dataclass to standardize fan data.
Attributes:
speed: The speed of the fan.
"""
speed: int = -1
@dataclass @dataclass
class MinerData: class MinerData:
"""A Dataclass to standardize data returned from miners (specifically `AnyMiner().get_data()`) """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) right_board_chip_temp: int = field(init=False)
wattage: int = -1 wattage: int = -1
wattage_limit: int = -1 wattage_limit: int = -1
fan_1: int = -1 fans: List[Fan] = field(default_factory=list)
fan_2: int = -1 fan_1: int = field(init=False)
fan_3: int = -1 fan_2: int = field(init=False)
fan_4: int = -1 fan_3: int = field(init=False)
fan_4: int = field(init=False)
fan_psu: int = -1 fan_psu: int = -1
left_chips: int = field(init=False) left_chips: int = field(init=False)
center_chips: int = field(init=False) center_chips: int = field(init=False)
@@ -199,6 +211,42 @@ class MinerData:
setattr(cp, key, item & other_item) setattr(cp, key, item & other_item)
return cp 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 @property
def total_chips(self): # noqa - Skip PyCharm inspection def total_chips(self): # noqa - Skip PyCharm inspection
return sum([hb.chips for hb in self.hashboards]) 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.API.bmminer import BMMinerAPI
from pyasic.config import MinerConfig 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.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
@@ -138,13 +138,10 @@ class BMMiner(BaseMiner):
logging.warning(f"Failed to get model for miner: {self}") logging.warning(f"Failed to get model for miner: {self}")
return None return None
async def get_version( async def get_api_ver(self, api_version: dict = None) -> Optional[str]:
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 # Check to see if the version info is already cached
if self.api_ver and self.fw_ver: if self.api_ver:
return miner_version(self.api_ver, self.fw_ver) return self.api_ver
if not api_version: if not api_version:
try: try:
@@ -157,12 +154,40 @@ class BMMiner(BaseMiner):
self.api_ver = api_version["VERSION"][0]["API"] self.api_ver = api_version["VERSION"][0]["API"]
except (KeyError, IndexError): except (KeyError, IndexError):
pass 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: try:
self.fw_ver = api_version["VERSION"][0]["CompileTime"] self.fw_ver = api_version["VERSION"][0]["CompileTime"]
except (KeyError, IndexError): except (KeyError, IndexError):
pass 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]: async def get_hostname(self) -> Optional[str]:
hn = await self.send_ssh_command("cat /proc/sys/kernel/hostname") 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]: async def get_wattage_limit(self) -> Optional[int]:
return None return None
async def get_fans( async def get_fans(self, api_stats: dict = None) -> List[Fan]:
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)
if not api_stats: if not api_stats:
try: try:
api_stats = await self.api.stats() 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}") fans_data[fan] = api_stats["STATS"][1].get(f"fan{fan_offset+fan}")
except (KeyError, IndexError): except (KeyError, IndexError):
pass 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]: async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = [] groups = []
@@ -339,108 +353,3 @@ class BMMiner(BaseMiner):
return round(ideal_rate, 2) return round(ideal_rate, 2)
except (KeyError, IndexError): except (KeyError, IndexError):
pass 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.API.bosminer import BOSMinerAPI
from pyasic.config import MinerConfig 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.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
@@ -287,10 +287,29 @@ class BOSMiner(BaseMiner):
) -> Tuple[Optional[str], Optional[str]]: ) -> Tuple[Optional[str], Optional[str]]:
# check if version is cached # check if version is cached
miner_version = namedtuple("MinerVersion", "api_ver fw_ver") miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
# if self.fw_ver and self.api_ver: api_ver = await self.get_api_ver(api_version)
# logging.debug(f"Found version for {self.ip}: {self.fw_ver}") fw_ver = await self.get_fw_ver(graphql_version)
# return miner_version(self.api_ver, self.fw_ver) 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: if not graphql_version:
try: try:
graphql_version = await self.send_graphql_query( graphql_version = await self.send_graphql_query(
@@ -299,11 +318,6 @@ class BOSMiner(BaseMiner):
except APIError: except APIError:
pass pass
if not api_version:
try:
api_version = await self.api.version()
except APIError:
pass
fw_ver = None fw_ver = None
if graphql_version: if graphql_version:
@@ -323,16 +337,7 @@ class BOSMiner(BaseMiner):
self.fw_ver = ver self.fw_ver = ver
logging.debug(f"Found version for {self.ip}: {self.fw_ver}") logging.debug(f"Found version for {self.ip}: {self.fw_ver}")
# Now get the API version return self.fw_ver
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)
async def get_hostname(self, graphql_hostname: dict = None) -> Union[str, None]: async def get_hostname(self, graphql_hostname: dict = None) -> Union[str, None]:
if self.hostname: if self.hostname:
@@ -584,16 +589,7 @@ class BOSMiner(BaseMiner):
async def get_fans( async def get_fans(
self, api_fans: dict = None, graphql_fans: dict = None self, api_fans: dict = None, graphql_fans: dict = None
) -> Tuple[ ) -> List[Fan]:
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")
if not graphql_fans and not api_fans: if not graphql_fans and not api_fans:
try: try:
graphql_fans = await self.send_graphql_query( graphql_fans = await self.send_graphql_query(
@@ -603,18 +599,15 @@ class BOSMiner(BaseMiner):
pass pass
if graphql_fans: 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): for n in range(self.fan_count):
try: try:
fans[f"fan_{n + 1}"] = graphql_fans["bosminer"]["info"]["fans"][n][ fans[f"fan_{n + 1}"].speed = graphql_fans["bosminer"]["info"][
"rpm" "fans"
] ][n]["rpm"]
except KeyError: except KeyError:
pass pass
return miner_fan_speeds( return [fans["fan_1"], fans["fan_2"], fans["fan_3"], fans["fan_4"]]
fan_speeds(fans["fan_1"], fans["fan_2"], fans["fan_3"], fans["fan_4"]),
psu_fan_speeds(psu_fan),
)
if not api_fans: if not api_fans:
try: try:
@@ -623,19 +616,17 @@ class BOSMiner(BaseMiner):
pass pass
if api_fans: 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): for n in range(self.fan_count):
try: try:
fans[f"fan_{n + 1}"] = api_fans["FANS"][n]["RPM"] fans[f"fan_{n + 1}"].speed = api_fans["FANS"][n]["RPM"]
except KeyError: except KeyError:
pass pass
return miner_fan_speeds( return [fans["fan_1"], fans["fan_2"], fans["fan_3"], fans["fan_4"]]
fan_speeds(fans["fan_1"], fans["fan_2"], fans["fan_3"], fans["fan_4"]), return [Fan(), Fan(), Fan(), Fan()]
psu_fan_speeds(psu_fan),
) async def get_fan_psu(self) -> Optional[int]:
return miner_fan_speeds( return None
fan_speeds(None, None, None, None), psu_fan_speeds(None)
)
async def get_pools( async def get_pools(
self, api_pools: dict = None, graphql_pools: dict = None self, api_pools: dict = None, graphql_pools: dict = None
@@ -851,144 +842,3 @@ class BOSMiner(BaseMiner):
) )
except (IndexError, KeyError): except (IndexError, KeyError):
pass 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.API.bosminer import BOSMinerAPI
from pyasic.config import MinerConfig 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.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
@@ -131,18 +131,17 @@ class BOSMinerOld(BaseMiner):
async def get_fans( async def get_fans(
self, self,
) -> Tuple[ ) -> List[Fan]:
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]], return [Fan(), Fan(), Fan(), Fan()]
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) async def get_fan_psu(self) -> Optional[int]:
psu_fans = psu_fan_speeds(None) 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]: async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = [] groups = []
@@ -179,8 +178,5 @@ class BOSMinerOld(BaseMiner):
async def get_nominal_hashrate(self) -> Optional[float]: async def get_nominal_hashrate(self) -> Optional[float]:
return None 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) -> MinerData:
return MinerData(ip=str(self.ip)) 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.API.btminer import BTMinerAPI
from pyasic.config import MinerConfig 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.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
@@ -225,11 +225,15 @@ class BTMiner(BaseMiner):
async def get_version( async def get_version(
self, api_version: dict = None, api_summary: dict = None self, api_version: dict = None, api_summary: dict = None
) -> Tuple[Optional[str], Optional[str]]: ) -> Tuple[Optional[str], Optional[str]]:
# check if version is cached
miner_version = namedtuple("MinerVersion", "api_ver fw_ver") miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
api_ver = await self.get_api_ver(api_version=api_version)
fw_ver = await self.get_fw_ver(api_version=api_version, api_summary=api_summary)
return miner_version(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 # Check to see if the version info is already cached
if self.api_ver and self.fw_ver: if self.api_ver:
return miner_version(self.api_ver, self.fw_ver) return self.api_ver
if not api_version: if not api_version:
try: try:
@@ -245,12 +249,36 @@ class BTMiner(BaseMiner):
if not isinstance(api_ver, str): if not isinstance(api_ver, str):
api_ver = api_ver["api_ver"] api_ver = api_ver["api_ver"]
self.api_ver = api_ver.replace("whatsminer v", "") self.api_ver = api_ver.replace("whatsminer v", "")
self.fw_ver = api_version["Msg"]["fw_ver"]
except (KeyError, TypeError): except (KeyError, TypeError):
pass pass
else: else:
self.api.api_ver = self.api_ver 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_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_version:
try:
api_version = await self.api.get_version()
except APIError:
pass
if api_version:
if "Code" in api_version.keys():
if api_version["Code"] == 131:
try:
self.fw_ver = api_version["Msg"]["fw_ver"]
except (KeyError, TypeError):
pass
else:
return self.fw_ver
if not api_summary: if not api_summary:
try: try:
@@ -266,7 +294,7 @@ class BTMiner(BaseMiner):
except (KeyError, IndexError): except (KeyError, IndexError):
pass 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_miner_info: dict = None) -> Optional[str]:
if self.hostname: if self.hostname:
@@ -376,17 +404,31 @@ class BTMiner(BaseMiner):
async def get_fans( async def get_fans(
self, api_summary: dict = None, api_psu: dict = None self, api_summary: dict = None, api_psu: dict = None
) -> Tuple[ ) -> List[Fan]:
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]], if not api_summary:
Tuple[Optional[int]], try:
]: api_summary = await self.api.summary()
fan_speeds = namedtuple("FanSpeeds", "fan_1 fan_2 fan_3 fan_4") except APIError:
psu_fan_speeds = namedtuple("PSUFanSpeeds", "psu_fan") pass
miner_fan_speeds = namedtuple("MinerFans", "fan_speeds psu_fan_speeds")
fans = fan_speeds(None, None, None, None) fans = [Fan(), Fan(), Fan(), Fan()]
psu_fans = psu_fan_speeds(None) 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_psu: dict = None
) -> Optional[int]:
if not api_summary: if not api_summary:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
@@ -395,33 +437,21 @@ class BTMiner(BaseMiner):
if api_summary: if api_summary:
try: try:
if self.fan_count > 0: return int(api_summary["SUMMARY"][0]["Power Fanspeed"])
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"])
)
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
if not psu_fans[0]: if not api_psu:
if not api_psu: try:
try: api_psu = await self.api.get_psu()
api_psu = await self.api.get_psu() except APIError:
except APIError: pass
pass
if api_psu: if api_psu:
try: try:
psu_fans = psu_fan_speeds(int(api_psu["Msg"]["fan_speed"])) return int(api_psu["Msg"]["fan_speed"])
except (KeyError, TypeError): except (KeyError, TypeError):
pass pass
return miner_fan_speeds(fans, psu_fans)
async def get_pools(self, api_pools: dict = None) -> List[dict]: async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = [] groups = []
@@ -516,117 +546,3 @@ class BTMiner(BaseMiner):
pass pass
return self.light if self.light else False 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.API.cgminer import CGMinerAPI
from pyasic.config import MinerConfig 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.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
@@ -185,9 +185,14 @@ class CGMiner(BaseMiner):
self, api_version: dict = None self, api_version: dict = None
) -> Tuple[Optional[str], Optional[str]]: ) -> Tuple[Optional[str], Optional[str]]:
miner_version = namedtuple("MinerVersion", "api_ver fw_ver") miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
# Check to see if the version info is already cached return miner_version(
if self.api_ver and self.fw_ver: api_ver=await self.get_api_ver(api_version=api_version),
return miner_version(self.api_ver, self.fw_ver) 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: if not api_version:
try: try:
@@ -200,12 +205,26 @@ class CGMiner(BaseMiner):
self.api_ver = api_version["VERSION"][0]["API"] self.api_ver = api_version["VERSION"][0]["API"]
except (KeyError, IndexError): except (KeyError, IndexError):
pass 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: try:
self.fw_ver = api_version["VERSION"][0]["CGMiner"] self.fw_ver = api_version["VERSION"][0]["CGMiner"]
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
return miner_version(self.api_ver, self.fw_ver) return self.fw_ver
async def get_hostname(self) -> Optional[str]: async def get_hostname(self) -> Optional[str]:
try: try:
@@ -293,25 +312,14 @@ class CGMiner(BaseMiner):
async def get_wattage_limit(self) -> Optional[int]: async def get_wattage_limit(self) -> Optional[int]:
return None return None
async def get_fans( async def get_fans(self, api_stats: dict = None) -> List[Fan]:
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)
if not api_stats: if not api_stats:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
fans_data = [None, None, None, None] fans_data = [Fan(), Fan(), Fan(), Fan()]
if api_stats: if api_stats:
try: try:
fan_offset = -1 fan_offset = -1
@@ -325,12 +333,15 @@ class CGMiner(BaseMiner):
fan_offset = 1 fan_offset = 1
for fan in range(self.fan_count): 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): except (KeyError, IndexError):
pass 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]: async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = [] groups = []
@@ -387,109 +398,3 @@ class CGMiner(BaseMiner):
return round(ideal_rate, 2) return round(ideal_rate, 2)
except (KeyError, IndexError): except (KeyError, IndexError):
pass 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.API.cgminer import CGMinerAPI
from pyasic.config import MinerConfig 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.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
@@ -206,25 +206,14 @@ class CGMinerAvalon(CGMiner):
async def get_wattage_limit(self) -> Optional[int]: async def get_wattage_limit(self) -> Optional[int]:
return None return None
async def get_fans( async def get_fans(self, api_stats: dict = None) -> List[Fan]:
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)
if not api_stats: if not api_stats:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
fans_data = [None, None, None, None] fans_data = [Fan(), Fan(), Fan(), Fan()]
if api_stats: if api_stats:
try: try:
stats_data = api_stats[0].get("STATS") stats_data = api_stats[0].get("STATS")
@@ -233,12 +222,11 @@ class CGMinerAvalon(CGMiner):
if key.startswith("MM ID"): if key.startswith("MM ID"):
raw_data = self.parse_stats(stats_data[0][key]) raw_data = self.parse_stats(stats_data[0][key])
for fan in range(self.fan_count): 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): except (KeyError, IndexError, ValueError, TypeError):
pass 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]: async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = [] groups = []
@@ -279,101 +267,3 @@ class CGMinerAvalon(CGMiner):
if data["STATUS"][0]["Msg"] == "ASC 0 set info: LED[1]": if data["STATUS"][0]["Msg"] == "ASC 0 set info: LED[1]":
return True return True
return False 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

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

View File

@@ -114,101 +114,3 @@ class HiveonT9(Hiveon, T9):
if not env_temp_list == []: if not env_temp_list == []:
return round(float(sum(env_temp_list) / len(env_temp_list)), 2) 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 # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import inspect
import ipaddress import ipaddress
import logging import logging
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
@@ -20,7 +21,7 @@ from typing import List, Optional, Tuple, TypeVar
import asyncssh import asyncssh
from pyasic.config import MinerConfig 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.data.error_codes import MinerErrorData
@@ -199,6 +200,24 @@ class BaseMiner(ABC):
""" """
pass 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 @abstractmethod
async def get_version(self, *args, **kwargs) -> Tuple[Optional[str], Optional[str]]: 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. """Get the API version and firmware version of the miner and return them as strings.
@@ -263,15 +282,20 @@ class BaseMiner(ABC):
pass pass
@abstractmethod @abstractmethod
async def get_fans( async def get_fans(self, *args, **kwargs) -> List[Fan]:
self, *args, **kwargs """Get fan data from the miner in the form [fan_1, fan_2, fan_3, fan_4].
) -> 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).
Returns: 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 pass
@@ -282,6 +306,7 @@ class BaseMiner(ABC):
Returns: Returns:
Pool groups and quotas in a list of dicts. 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]:
@@ -310,11 +335,104 @@ class BaseMiner(ABC):
""" """
pass 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]. """Get data from the miner in the form of [`MinerData`][pyasic.data.MinerData].
Parameters: Parameters:
allow_warning: Allow warning when an API command fails. 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: Returns:
A [`MinerData`][pyasic.data.MinerData] instance containing data from the miner. 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: for item in gathered_data:
if gathered_data[item] is not None: if gathered_data[item] is not None:
setattr(data, item, gathered_data[item]) setattr(data, item, gathered_data[item])
return data return data
@abstractmethod
async def _get_data(self, allow_warning: bool) -> dict:
pass
AnyMiner = TypeVar("AnyMiner", bound=BaseMiner) AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)

View File

@@ -20,7 +20,7 @@ from typing import List, Optional, Tuple, Union
import httpx import httpx
from pyasic.config import MinerConfig 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.data.error_codes import InnosiliconError, MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
@@ -133,8 +133,11 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
################################################## ##################################################
async def get_mac( 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]: ) -> Optional[str]:
web_all_data = web_getAll
if not web_all_data and not web_overview: if not web_all_data and not web_overview:
try: try:
web_overview = await self.send_web_command("overview") web_overview = await self.send_web_command("overview")
@@ -174,8 +177,11 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
pass pass
async def get_hashrate( 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]: ) -> Optional[float]:
web_all_data = web_getAll
if not api_summary and not web_all_data: if not api_summary and not web_all_data:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
@@ -197,8 +203,11 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
pass pass
async def get_hashboards( 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]: ) -> List[HashBoard]:
web_all_data = web_getAll
hashboards = [ hashboards = [
HashBoard(slot=i, expected_chips=self.nominal_chips) HashBoard(slot=i, expected_chips=self.nominal_chips)
for i in range(self.ideal_hashboards) for i in range(self.ideal_hashboards)
@@ -246,7 +255,10 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
return hashboards 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: if not web_all_data:
try: try:
web_all_data = await self.send_web_command("getAll") web_all_data = await self.send_web_command("getAll")
@@ -262,18 +274,10 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
pass pass
async def get_fans( async def get_fans(
self, web_all_data: dict = None self,
) -> Tuple[ web_getAll: dict = None, # noqa: named this way for automatic functionality
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]], ) -> List[Fan]:
Tuple[Optional[int]], web_all_data = web_getAll
]:
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)
if not web_all_data: if not web_all_data:
try: try:
web_all_data = await self.send_web_command("getAll") web_all_data = await self.send_web_command("getAll")
@@ -282,19 +286,18 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
else: else:
web_all_data = web_all_data["all"] web_all_data = web_all_data["all"]
fan_data = [Fan(), Fan(), Fan(), Fan()]
if web_all_data: if web_all_data:
try: try:
spd = web_all_data["fansSpeed"] spd = web_all_data["fansSpeed"]
except KeyError: except KeyError:
pass pass
else: else:
f = [None, None, None, None]
round((int(spd) * 6000) / 100) round((int(spd) * 6000) / 100)
for i in range(self.fan_count): for i in range(self.fan_count):
f[i] = spd fan_data[i] = Fan(spd)
fans = fan_speeds(*f)
return miner_fan_speeds(fans, psu_fans) return fan_data
async def get_pools(self, api_pools: dict = None) -> List[dict]: async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = [] groups = []
@@ -322,7 +325,10 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
pass pass
return groups 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 = [] errors = []
if not web_error_details: if not web_error_details:
try: try:
@@ -342,129 +348,3 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
if not err == 0: if not err == 0:
errors.append(InnosiliconError(error_code=err)) errors.append(InnosiliconError(error_code=err))
return errors 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.API.unknown import UnknownAPI
from pyasic.config import MinerConfig 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.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
@@ -98,18 +98,17 @@ class UnknownMiner(BaseMiner):
async def get_fans( async def get_fans(
self, self,
) -> Tuple[ ) -> List[Fan]:
Tuple[Optional[int], Optional[int], Optional[int], Optional[int]], return [Fan(), Fan(), Fan(), Fan()]
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) async def get_fan_psu(self) -> Optional[int]:
psu_fans = psu_fan_speeds(None) 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]: async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = [] groups = []
@@ -146,8 +145,7 @@ class UnknownMiner(BaseMiner):
async def get_nominal_hashrate(self) -> Optional[float]: async def get_nominal_hashrate(self) -> Optional[float]:
return None return None
async def _get_data(self, allow_warning: bool) -> dict: async def get_data(
return {} self, allow_warning: bool = False, data_to_get: list = None
) -> MinerData:
async def get_data(self, allow_warning: bool = False) -> MinerData:
return MinerData(ip=str(self.ip)) return MinerData(ip=str(self.ip))