Compare commits

...

20 Commits

Author SHA1 Message Date
Upstream Data
464bd6be65 version: bump version number. 2022-11-13 18:58:31 -07:00
Upstream Data
031d7e2186 bug: fix missing catching IndexError 2022-11-13 18:58:05 -07:00
Upstream Data
126b0d124c version: bump version number 2022-11-13 18:54:46 -07:00
Upstream Data
81c84a3e8f bug: fix a bug with no try except blocks on pools. 2022-11-13 18:54:26 -07:00
Upstream Data
406d5bd549 version: bump version number 2022-11-13 18:21:18 -07:00
Upstream Data
cd5fe09fd9 bug: fix wrong formatting for multiple error handling 2022-11-13 18:20:15 -07:00
Upstream Data
766fc4efed bug: fix check light not responding properly from graphQL. 2022-11-13 17:06:35 -07:00
Upstream Data
b70fed40c8 bug: add try except statements to get_data to fix bugs with getting data from graphql. 2022-11-13 17:03:41 -07:00
Upstream Data
255d98fd08 bug: fix some issues with validating data from graphql properly 2022-11-13 16:48:22 -07:00
Upstream Data
78304631f7 bug: fix errors not registering properly on braiins OS miners 2022-11-13 16:34:26 -07:00
Upstream Data
ab230844fc bug: fix an issue with chip count not receiving properly in some cases. 2022-11-13 16:31:41 -07:00
Upstream Data
8a58cb9fd3 format: remove extra print 2022-11-13 16:28:36 -07:00
Upstream Data
70b6ed73dc bug: fix manual return from __get_devdetails_and_version 2022-11-13 16:27:42 -07:00
Upstream Data
d2400bf44e bug: fix identifying stock as bosminer. 2022-11-13 16:22:57 -07:00
Upstream Data
db780fe876 version: bump version number 2022-11-13 15:14:16 -07:00
Upstream Data
cd84ae828a bug: fix a bug with some missing temperatures in graphql. 2022-11-13 15:13:56 -07:00
Upstream Data
970d5d1031 version: bump version number 2022-11-13 14:39:23 -07:00
Upstream Data
da0e327ec7 feature: add support for graphQL for BOS miners which may be having API issues, such as BBB. 2022-11-13 14:38:44 -07:00
Upstream Data
4c84a8d572 bug: ensure bosminer check_light backwards compatibility 2022-11-13 11:06:28 -07:00
Upstream Data
1cfd895deb feature: improve speed of bosminer check_light by 10x using graphql. 2022-11-13 11:01:22 -07:00
3 changed files with 199 additions and 4 deletions

View File

@@ -18,6 +18,7 @@ import logging
from typing import List, Union from typing import List, Union
import toml import toml
import httpx
from pyasic.API.bosminer import BOSMinerAPI from pyasic.API.bosminer import BOSMinerAPI
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
@@ -65,6 +66,24 @@ class BOSMiner(BaseMiner):
# return the result, either command output or None # return the result, either command output or None
return str(result) return str(result)
async def send_graphql_query(self, query) -> Union[dict, None]:
url = f"http://{self.ip}/graphql"
async with httpx.AsyncClient() as client:
_auth = await client.post(
url,
json={
"query": 'mutation{auth{login(username:"'
+ self.uname
+ '", password:"'
+ self.pwd
+ '"){__typename}}}'
},
)
d = await client.post(url, json={"query": query})
if d.status_code == 200:
return d.json()
return None
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
"""Sends command to turn on fault light on the miner.""" """Sends command to turn on fault light on the miner."""
logging.debug(f"{self}: Sending fault_light on command.") logging.debug(f"{self}: Sending fault_light on command.")
@@ -148,6 +167,11 @@ class BOSMiner(BaseMiner):
""" """
if self.hostname: if self.hostname:
return self.hostname return self.hostname
# get hostname through GraphQL
if data := await self.send_graphql_query("{bos {hostname}}"):
self.hostname = data["data"]["bos"]["hostname"]
return self.hostname
try: try:
async with (await self._get_ssh_connection()) as conn: async with (await self._get_ssh_connection()) as conn:
if conn is not None: if conn is not None:
@@ -211,9 +235,15 @@ class BOSMiner(BaseMiner):
if self.version: if self.version:
logging.debug(f"Found version for {self.ip}: {self.version}") logging.debug(f"Found version for {self.ip}: {self.version}")
return self.version return self.version
version_data = None
# try to get data from graphql
data = await self.send_graphql_query("{bos{info{version{full}}}}")
if data:
version_data = data["bos"]["info"]["version"]["full"]
# get output of bos version file if not version_data:
version_data = await self.send_ssh_command("cat /etc/bos_version") # try version data file
version_data = await self.send_ssh_command("cat /etc/bos_version")
# if we get the version data, parse it # if we get the version data, parse it
if version_data: if version_data:
@@ -244,6 +274,15 @@ class BOSMiner(BaseMiner):
async def check_light(self) -> bool: async def check_light(self) -> bool:
if self.light: if self.light:
return self.light return self.light
# get light through GraphQL
if data := await self.send_graphql_query("{bos {faultLight}}"):
try:
self.light = data["data"]["bos"]["faultLight"]
return self.light
except (TypeError, KeyError, ValueError, IndexError):
pass
# get light via ssh if that fails (10x slower)
data = ( data = (
await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off") await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off")
).strip() ).strip()
@@ -285,6 +324,7 @@ class BOSMiner(BaseMiner):
if board["Status"] not in [ if board["Status"] not in [
"Stable", "Stable",
"Testing performance profile", "Testing performance profile",
"Tuning individual chips"
]: ]:
_error = board["Status"].split(" {")[0] _error = board["Status"].split(" {")[0]
_error = _error[0].lower() + _error[1:] _error = _error[0].lower() + _error[1:]
@@ -299,6 +339,10 @@ class BOSMiner(BaseMiner):
Returns: Returns:
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data. A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
""" """
d = await self._graphql_get_data()
if d:
return d
data = MinerData( data = MinerData(
ip=str(self.ip), ip=str(self.ip),
ideal_chips=self.nominal_chips * self.ideal_hashboards, ideal_chips=self.nominal_chips * self.ideal_hashboards,
@@ -338,7 +382,7 @@ class BOSMiner(BaseMiner):
"devdetails", "devdetails",
"fans", "fans",
"devs", "devs",
allow_warning=allow_warning allow_warning=allow_warning,
) )
except APIError as e: except APIError as e:
if str(e.message) == "Not ready": if str(e.message) == "Not ready":
@@ -489,6 +533,136 @@ class BOSMiner(BaseMiner):
data.hashboards[_id].hashrate = hashrate data.hashboards[_id].hashrate = hashrate
return data return data
async def _graphql_get_data(self) -> Union[MinerData, None]:
data = MinerData(
ip=str(self.ip),
ideal_chips=self.nominal_chips * self.ideal_hashboards,
ideal_hashboards=self.ideal_hashboards,
hashboards=[
HashBoard(slot=i, expected_chips=self.nominal_chips, missing=True)
for i in range(self.ideal_hashboards)
],
)
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}}}}}}"
query_data = await self.send_graphql_query(query)
if not query_data:
return None
query_data = query_data["data"]
data.mac = await self.get_mac()
data.model = await self.get_model()
if query_data.get("bos"):
if query_data["bos"].get("hostname"):
data.hostname = query_data["bos"]["hostname"]
try:
if query_data["bosminer"]["info"]["workSolver"]["realHashrate"].get("mhs1M"):
data.hashrate = round(
query_data["bosminer"]["info"]["workSolver"]["realHashrate"]["mhs1M"]
/ 1000000,
2,
)
except (TypeError, KeyError, ValueError, IndexError):
pass
boards = None
if query_data.get("bosminer"):
if query_data["bosminer"].get("info"):
if query_data["bosminer"]["info"].get("workSolver"):
boards = query_data["bosminer"]["info"]["workSolver"].get("childSolvers")
if boards:
offset = 6 if int(boards[0]["name"]) in [6, 7, 8] else int(boards[0]["name"])
for hb in boards:
_id = int(hb["name"]) - offset
board = data.hashboards[_id]
board.hashrate = round(hb["realHashrate"]["mhs1M"] / 1000000, 2)
temps = hb["temperatures"]
if len(temps) > 0:
board.temp = round(hb["temperatures"][0]["degreesC"])
if len(temps) > 1:
board.chip_temp = round(hb["temperatures"][1]["degreesC"])
details = hb.get("hwDetails")
if details:
if chips := details["chips"]:
board.chips = chips
board.missing = False
tuner = hb.get("tuner")
if tuner:
if msg := tuner.get("statusMessages"):
if len(msg) > 0:
if hb["tuner"]["statusMessages"][0] not in [
"Stable",
"Testing performance profile",
"Tuning individual chips"
]:
data.errors.append(
BraiinsOSError(f"Slot {_id} {hb['tuner']['statusMessages'][0]}")
)
try:
data.wattage = query_data["bosminer"]["info"]["workSolver"]["power"]["approxConsumptionW"]
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.wattage_limit = query_data["bosminer"]["info"]["workSolver"]["power"]["limitW"]
except (TypeError, KeyError, ValueError, IndexError):
pass
for n in range(self.fan_count):
try:
setattr(data, f"fan_{n + 1}", query_data["bosminer"]["info"]["fans"][n]["rpm"])
except (TypeError, KeyError, ValueError, IndexError):
pass
groups = None
if query_data.get("bosminer"):
if query_data["bosminer"].get("config"):
groups = query_data["bosminer"]["config"].get("groups")
if groups:
if len(groups) == 1:
try:
data.pool_1_user = groups[0]["pools"][0]["user"]
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.pool_1_url = groups[0]["pools"][0]["url"]
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.pool_2_user = groups[0]["pools"][1]["user"]
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.pool_2_url = groups[0]["pools"][1]["url"]
except (TypeError, KeyError, ValueError, IndexError):
pass
data.quota = 0
else:
try:
data.pool_1_user = groups[0]["pools"][0]["user"]
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.pool_1_url = groups[0]["pools"][0]["url"]
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.pool_2_user = groups[1]["pools"][0]["user"]
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.pool_2_url = groups[1]["pools"][0]["url"]
except (TypeError, KeyError, ValueError, IndexError):
pass
if groups[0]["strategy"].get("quota"):
data.quota = groups[0]["strategy"]["quota"] + "/" + groups[1]["strategy"]["quota"]
data.fault_light = await self.check_light()
return data
async def get_mac(self): async def get_mac(self):
result = await self.send_ssh_command("cat /sys/class/net/eth0/address") result = await self.send_ssh_command("cat /sys/class/net/eth0/address")
return result.upper().strip() return result.upper().strip()

View File

@@ -433,6 +433,12 @@ class MinerFactory(metaclass=Singleton):
# if we have devdetails, we can get model data from there # if we have devdetails, we can get model data from there
if devdetails: if devdetails:
if devdetails == {"Msg": "Disconnected"}:
model = await self.__get_model_from_graphql(ip)
if model:
api = "BOSMiner+"
return model, api, ver
for _devdetails_key in ["Model", "Driver"]: for _devdetails_key in ["Model", "Driver"]:
try: try:
model = devdetails["DEVDETAILS"][0][_devdetails_key].upper() model = devdetails["DEVDETAILS"][0][_devdetails_key].upper()
@@ -540,6 +546,11 @@ class MinerFactory(metaclass=Singleton):
# validate success # validate success
validation = await self._validate_command(data) validation = await self._validate_command(data)
if not validation[0]: if not validation[0]:
try:
if data["version"][0]["STATUS"][0]["Msg"] == "Disconnected":
return {"Msg": "Disconnected"}, None
except KeyError:
pass
raise APIError(validation[1]) raise APIError(validation[1])
# copy each part of the main command to devdetails and version # copy each part of the main command to devdetails and version
devdetails = data["devdetails"][0] devdetails = data["devdetails"][0]
@@ -587,6 +598,16 @@ class MinerFactory(metaclass=Singleton):
model = "ANTMINER S17" model = "ANTMINER S17"
return model return model
@staticmethod
async def __get_model_from_graphql(ip: ipaddress.ip_address) -> Union[str, None]:
model = None
url = f"http://{ip}/graphql"
async with httpx.AsyncClient() as client:
d = await client.post(url, json={"query": "{bosminer {info{modelName}}}"})
if d.status_code == 200:
model = d.json()["data"]["bosminer"]["info"]["modelName"].upper()
return model
@staticmethod @staticmethod
async def __get_system_info_from_web(ip) -> dict: async def __get_system_info_from_web(ip) -> dict:
url = f"http://{ip}/cgi-bin/get_system_info.cgi" url = f"http://{ip}/cgi-bin/get_system_info.cgi"

View File

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