Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d4bf4e847 | ||
|
|
774e3d1a62 | ||
|
|
ade3cd6fee | ||
|
|
28dc3ccb84 | ||
|
|
6ca8582ec3 | ||
|
|
74b4aeb44a | ||
|
|
9c7ab5ac57 | ||
|
|
65ecf1fea2 | ||
|
|
44142c658b | ||
|
|
25a205ce6c | ||
|
|
25094084cf | ||
|
|
4eac601153 | ||
|
|
f0d8d66b9b | ||
|
|
cfa550f8c0 | ||
|
|
91f6a5bf41 | ||
|
|
464bd6be65 | ||
|
|
031d7e2186 | ||
|
|
126b0d124c | ||
|
|
81c84a3e8f | ||
|
|
406d5bd549 | ||
|
|
cd5fe09fd9 | ||
|
|
766fc4efed | ||
|
|
b70fed40c8 | ||
|
|
255d98fd08 | ||
|
|
78304631f7 | ||
|
|
ab230844fc | ||
|
|
8a58cb9fd3 | ||
|
|
70b6ed73dc | ||
|
|
d2400bf44e |
@@ -68,20 +68,23 @@ class BOSMiner(BaseMiner):
|
|||||||
|
|
||||||
async def send_graphql_query(self, query) -> Union[dict, None]:
|
async def send_graphql_query(self, query) -> Union[dict, None]:
|
||||||
url = f"http://{self.ip}/graphql"
|
url = f"http://{self.ip}/graphql"
|
||||||
async with httpx.AsyncClient() as client:
|
try:
|
||||||
_auth = await client.post(
|
async with httpx.AsyncClient() as client:
|
||||||
url,
|
_auth = await client.post(
|
||||||
json={
|
url,
|
||||||
"query": 'mutation{auth{login(username:"'
|
json={
|
||||||
+ self.uname
|
"query": 'mutation{auth{login(username:"'
|
||||||
+ '", password:"'
|
+ self.uname
|
||||||
+ self.pwd
|
+ '", password:"'
|
||||||
+ '"){__typename}}}'
|
+ self.pwd
|
||||||
},
|
+ '"){__typename}}}'
|
||||||
)
|
},
|
||||||
d = await client.post(url, json={"query": query})
|
)
|
||||||
if d.status_code == 200:
|
d = await client.post(url, json={"query": query})
|
||||||
return d.json()
|
if d.status_code == 200:
|
||||||
|
return d.json()
|
||||||
|
except httpx.ReadError:
|
||||||
|
return None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
@@ -276,8 +279,11 @@ class BOSMiner(BaseMiner):
|
|||||||
return self.light
|
return self.light
|
||||||
# get light through GraphQL
|
# get light through GraphQL
|
||||||
if data := await self.send_graphql_query("{bos {faultLight}}"):
|
if data := await self.send_graphql_query("{bos {faultLight}}"):
|
||||||
self.light = data["data"]["bos"]["faultLight"]
|
try:
|
||||||
return self.light
|
self.light = data["data"]["bos"]["faultLight"]
|
||||||
|
return self.light
|
||||||
|
except (TypeError, KeyError, ValueError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
# get light via ssh if that fails (10x slower)
|
# get light via ssh if that fails (10x slower)
|
||||||
data = (
|
data = (
|
||||||
@@ -478,7 +484,7 @@ class BOSMiner(BaseMiner):
|
|||||||
wattage_limit = tuner[0].get("PowerLimit")
|
wattage_limit = tuner[0].get("PowerLimit")
|
||||||
if wattage_limit:
|
if wattage_limit:
|
||||||
data.wattage_limit = wattage_limit
|
data.wattage_limit = wattage_limit
|
||||||
if wattage:
|
if wattage is not None:
|
||||||
data.wattage = wattage
|
data.wattage = wattage
|
||||||
|
|
||||||
chain_status = tuner[0].get("TunerChainStatus")
|
chain_status = tuner[0].get("TunerChainStatus")
|
||||||
@@ -548,57 +554,116 @@ class BOSMiner(BaseMiner):
|
|||||||
|
|
||||||
data.mac = await self.get_mac()
|
data.mac = await self.get_mac()
|
||||||
data.model = await self.get_model()
|
data.model = await self.get_model()
|
||||||
data.hostname = query_data["bos"]["hostname"]
|
if query_data.get("bos"):
|
||||||
data.hashrate = round(
|
if query_data["bos"].get("hostname"):
|
||||||
query_data["bosminer"]["info"]["workSolver"]["realHashrate"]["mhs1M"]
|
data.hostname = query_data["bos"]["hostname"]
|
||||||
/ 1000000,
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
|
|
||||||
boards = query_data["bosminer"]["info"]["workSolver"]["childSolvers"]
|
try:
|
||||||
offset = 6 if int(boards[0]["name"]) in [6, 7, 8] else int(boards[0]["name"])
|
if query_data["bosminer"]["info"]["workSolver"]["realHashrate"].get("mhs1M"):
|
||||||
for hb in boards:
|
data.hashrate = round(
|
||||||
_id = int(hb["name"]) - offset
|
query_data["bosminer"]["info"]["workSolver"]["realHashrate"]["mhs1M"]
|
||||||
|
/ 1000000,
|
||||||
board = data.hashboards[_id]
|
2,
|
||||||
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"])
|
|
||||||
board.chips = hb["hwDetails"]["chips"]
|
|
||||||
board.missing = False
|
|
||||||
|
|
||||||
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]}")
|
|
||||||
)
|
)
|
||||||
|
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"]
|
||||||
|
try:
|
||||||
|
if len(temps) > 0:
|
||||||
|
board.temp = round(hb["temperatures"][0]["degreesC"])
|
||||||
|
if len(temps) > 1:
|
||||||
|
board.chip_temp = round(hb["temperatures"][1]["degreesC"])
|
||||||
|
except (TypeError, KeyError, ValueError, IndexError):
|
||||||
|
pass
|
||||||
|
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):
|
||||||
|
data.wattage = 0
|
||||||
|
try:
|
||||||
|
data.wattage_limit = query_data["bosminer"]["info"]["workSolver"]["power"]["limitW"]
|
||||||
|
except (TypeError, KeyError, ValueError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
data.wattage = query_data["bosminer"]["info"]["workSolver"]["power"]["approxConsumptionW"]
|
|
||||||
data.wattage_limit = query_data["bosminer"]["info"]["workSolver"]["power"]["limitW"]
|
|
||||||
|
|
||||||
for n in range(self.fan_count):
|
for n in range(self.fan_count):
|
||||||
setattr(data, f"fan_{n - 1}", query_data["bosminer"]["info"]["fans"][n]["rpm"])
|
try:
|
||||||
|
setattr(data, f"fan_{n + 1}", query_data["bosminer"]["info"]["fans"][n]["rpm"])
|
||||||
|
except (TypeError, KeyError, ValueError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
groups = query_data["bosminer"]["config"]["groups"]
|
groups = None
|
||||||
if len(groups) == 1:
|
if query_data.get("bosminer"):
|
||||||
data.pool_1_user = groups[0]["pools"][0]["user"]
|
if query_data["bosminer"].get("config"):
|
||||||
data.pool_1_url = groups[0]["pools"][0]["url"]
|
groups = query_data["bosminer"]["config"].get("groups")
|
||||||
data.pool_2_user = groups[0]["pools"][1]["user"]
|
if groups:
|
||||||
data.pool_2_url = groups[0]["pools"][1]["url"]
|
if len(groups) == 1:
|
||||||
data.quota = 0
|
try:
|
||||||
else:
|
data.pool_1_user = groups[0]["pools"][0]["user"]
|
||||||
data.pool_1_user = groups[0]["pools"][0]["user"]
|
except (TypeError, KeyError, ValueError, IndexError):
|
||||||
data.pool_1_url = groups[0]["pools"][0]["url"]
|
pass
|
||||||
data.pool_2_user = groups[1]["pools"][0]["user"]
|
try:
|
||||||
data.pool_2_url = groups[1]["pools"][0]["url"]
|
data.pool_1_url = groups[0]["pools"][0]["url"]
|
||||||
if groups[0]["strategy"].get("quota"):
|
except (TypeError, KeyError, ValueError, IndexError):
|
||||||
data.quota = groups[0]["strategy"]["quota"] + "/" + groups[1]["strategy"]["quota"]
|
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()
|
data.fault_light = await self.check_light()
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class BaseMiner(ABC):
|
|||||||
return object.__new__(cls)
|
return object.__new__(cls)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{'' if not self.api_type else self.api_type} {'' if not self.model else self.model}: {str(self.ip)}"
|
return f"{'' if not self.api_type else self.api_type}{'' if not self.model else ' ' + self.model}: {str(self.ip)}"
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
return ipaddress.ip_address(self.ip) < ipaddress.ip_address(other.ip)
|
return ipaddress.ip_address(self.ip) < ipaddress.ip_address(other.ip)
|
||||||
|
|||||||
@@ -337,7 +337,6 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
break
|
break
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
logging.warning(f"{ip}: Get Miner Timed Out")
|
logging.warning(f"{ip}: Get Miner Timed Out")
|
||||||
|
|
||||||
miner = self._select_miner_from_classes(ip, model, api, ver)
|
miner = self._select_miner_from_classes(ip, model, api, ver)
|
||||||
|
|
||||||
# save the miner to the cache at its IP if its not unknown
|
# save the miner to the cache at its IP if its not unknown
|
||||||
@@ -412,7 +411,9 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
# miner refused connection on API port, we wont be able to get data this way
|
# miner refused connection on API port, we wont be able to get data this way
|
||||||
# try ssh
|
# try ssh
|
||||||
try:
|
try:
|
||||||
_model = await self.__get_model_from_ssh(ip)
|
_model = await self.__get_model_from_graphql(ip)
|
||||||
|
if not _model:
|
||||||
|
_model = await self.__get_model_from_ssh(ip)
|
||||||
if _model:
|
if _model:
|
||||||
model = _model
|
model = _model
|
||||||
api = "BOSMiner+"
|
api = "BOSMiner+"
|
||||||
@@ -433,11 +434,6 @@ 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)
|
|
||||||
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()
|
||||||
@@ -445,15 +441,42 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
break
|
break
|
||||||
except KeyError:
|
except KeyError:
|
||||||
continue
|
continue
|
||||||
|
try:
|
||||||
|
if devdetails[0]["STATUS"][0]["Msg"]:
|
||||||
|
model = await self.__get_model_from_graphql(ip)
|
||||||
|
if model:
|
||||||
|
api = "BOSMiner+"
|
||||||
|
except (KeyError, TypeError, ValueError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
if not model:
|
if not model:
|
||||||
# braiins OS bug check just in case
|
# braiins OS bug check just in case
|
||||||
if "s9" in devdetails["STATUS"][0]["Description"]:
|
if "s9" in devdetails["STATUS"][0]["Description"]:
|
||||||
model = "ANTMINER S9"
|
model = "ANTMINER S9"
|
||||||
if "s17" in version["STATUS"][0]["Description"]:
|
if "s17" in version["STATUS"][0]["Description"]:
|
||||||
model = "ANTMINER S17"
|
model = "ANTMINER S17"
|
||||||
|
if not api:
|
||||||
|
if "boser" in version["STATUS"][0]["Description"]:
|
||||||
|
api = "BOSMiner+"
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
_model = await self.__get_model_from_graphql(ip)
|
||||||
|
if _model:
|
||||||
|
model = _model
|
||||||
|
api = "BOSMiner+"
|
||||||
|
except (KeyError, TypeError, ValueError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
# if we have version we can get API type from here
|
# if we have version we can get API type from here
|
||||||
if version:
|
if version:
|
||||||
|
try:
|
||||||
|
if version[0]["STATUS"][0]["Msg"]:
|
||||||
|
model = await self.__get_model_from_graphql(ip)
|
||||||
|
if model:
|
||||||
|
api = "BOSMiner+"
|
||||||
|
return model, api, ver
|
||||||
|
except (KeyError, TypeError, ValueError, IndexError):
|
||||||
|
pass
|
||||||
if "VERSION" in version:
|
if "VERSION" in version:
|
||||||
api_types = ["BMMiner", "CGMiner", "BTMiner"]
|
api_types = ["BMMiner", "CGMiner", "BTMiner"]
|
||||||
# check basic API types, BOSMiner needs a special check
|
# check basic API types, BOSMiner needs a special check
|
||||||
@@ -469,6 +492,8 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
api = "BOSMiner+"
|
api = "BOSMiner+"
|
||||||
if "BOSminer+" in version["VERSION"][0]:
|
if "BOSminer+" in version["VERSION"][0]:
|
||||||
api = "BOSMiner+"
|
api = "BOSMiner+"
|
||||||
|
if any("BOSer" in string for string in version["VERSION"][0]):
|
||||||
|
api = "BOSMiner+"
|
||||||
|
|
||||||
# check for avalonminers
|
# check for avalonminers
|
||||||
for _version_key in ["PROD", "MODEL"]:
|
for _version_key in ["PROD", "MODEL"]:
|
||||||
@@ -532,14 +557,11 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
# don't need "Bitmain", just "ANTMINER XX" as model
|
# don't need "Bitmain", just "ANTMINER XX" as model
|
||||||
if "BITMAIN " in model:
|
if "BITMAIN " in model:
|
||||||
model = model.replace("BITMAIN ", "")
|
model = model.replace("BITMAIN ", "")
|
||||||
|
|
||||||
return model, api, ver
|
return model, api, ver
|
||||||
|
|
||||||
async def __get_devdetails_and_version(
|
async def __get_devdetails_and_version(
|
||||||
self, ip
|
self, ip
|
||||||
) -> Tuple[Union[dict, None], Union[dict, None]]:
|
) -> Tuple[Union[dict, None], Union[dict, None]]:
|
||||||
return {"Msg": "Disconnected"}, None
|
|
||||||
|
|
||||||
version = None
|
version = None
|
||||||
try:
|
try:
|
||||||
# get device details and version data
|
# get device details and version data
|
||||||
@@ -549,7 +571,7 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
if not validation[0]:
|
if not validation[0]:
|
||||||
try:
|
try:
|
||||||
if data["version"][0]["STATUS"][0]["Msg"] == "Disconnected":
|
if data["version"][0]["STATUS"][0]["Msg"] == "Disconnected":
|
||||||
return {"Msg": "Disconnected"}, None
|
return data["devdetails"], data["version"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
raise APIError(validation[1])
|
raise APIError(validation[1])
|
||||||
@@ -606,7 +628,7 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
d = await client.post(url, json={"query": "{bosminer {info{modelName}}}"})
|
d = await client.post(url, json={"query": "{bosminer {info{modelName}}}"})
|
||||||
if d.status_code == 200:
|
if d.status_code == 200:
|
||||||
model = d.json()["data"]["bosminer"]["info"]["modelName"].upper()
|
model = (d.json()["data"]["bosminer"]["info"]["modelName"]).upper()
|
||||||
return model
|
return model
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "pyasic"
|
name = "pyasic"
|
||||||
version = "0.21.1"
|
version = "0.21.11"
|
||||||
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user