Compare commits

..

19 Commits

Author SHA1 Message Date
UpstreamData
7485b8ef77 version: bump version number. 2023-07-04 10:04:20 -06:00
UpstreamData
d2bea227db bug: fix an issue with a possible SSH command in BOS+. 2023-07-04 10:01:24 -06:00
UpstreamData
1b7afaaf7e version: bump version number. 2023-06-30 08:49:31 -06:00
UpstreamData
96898d639c bug: fix some handling errors with graphql. 2023-06-30 08:49:08 -06:00
UpstreamData
eb439f4dcf version: bump version number. 2023-06-30 08:44:01 -06:00
UpstreamData
69f4349393 feature: create pwd and username property in miner object that sets web and api passwords and usernames. 2023-06-30 08:43:30 -06:00
UpstreamData
e371bb577c version: bump version number. 2023-06-29 18:10:35 -06:00
UpstreamData
2500ec3869 bug: fix possible None return from some bosminer webcommands. 2023-06-29 18:10:10 -06:00
UpstreamData
5be3187eec version: bump version number. 2023-06-29 16:51:29 -06:00
UpstreamData
be1e9127b0 bug: fix weird pool info when multiple groups are defined. 2023-06-29 16:51:15 -06:00
UpstreamData
13572c4770 version: bump version number. 2023-06-29 16:48:09 -06:00
UpstreamData
08fa3961fe bug: fix bosminer on X19 not reporting pause mode correctly. 2023-06-29 16:47:27 -06:00
UpstreamData
b5d2809e9c format: remove random print statements. 2023-06-29 15:53:53 -06:00
UpstreamData
aa538d3079 docs: add miner docs generation file. 2023-06-28 11:21:19 -06:00
UpstreamData
e1500bb75c docs: update docs. 2023-06-28 11:20:22 -06:00
UpstreamData
7f00a65598 version: bump version number. 2023-06-27 16:23:46 -06:00
UpstreamData
64c473a7d4 bug: fix improper stats key when getting uptime. 2023-06-27 16:23:21 -06:00
UpstreamData
96d9fe8e6c version: bump version number. 2023-06-27 14:56:11 -06:00
UpstreamData
0b27400d27 feature: add set_static_ip and set_dhcp for bosminer. 2023-06-27 14:55:05 -06:00
15 changed files with 356 additions and 65 deletions

163
docs/generate_miners.py Normal file
View File

@@ -0,0 +1,163 @@
import asyncio
import importlib
import os
import warnings
from pyasic.miners.miner_factory import MINER_CLASSES, MinerTypes
warnings.filterwarnings("ignore")
def path(cls):
module = importlib.import_module(cls.__module__)
return module.__name__ + "." + cls.__name__
def make(cls):
p = path(cls)
return p.split(".")[2]
def model_type(cls):
p = path(cls)
return p.split(".")[4]
def backend_str(backend: MinerTypes) -> str:
match backend:
case MinerTypes.ANTMINER:
return "Stock Firmware Antminers"
case MinerTypes.AVALONMINER:
return "Stock Firmware Avalonminers"
case MinerTypes.VNISH:
return "Vnish Firmware Miners"
case MinerTypes.BRAIINS_OS:
return "BOS+ Firmware Miners"
case MinerTypes.HIVEON:
return "HiveOS Firmware Miners"
case MinerTypes.INNOSILICON:
return "Stock Firmware Innosilicons"
case MinerTypes.WHATSMINER:
return "Stock Firmware Whatsminers"
case MinerTypes.GOLDSHELL:
return "Stock Firmware Goldshells"
case MinerTypes.LUX_OS:
return "LuxOS Firmware Miners"
def create_url_str(mtype: str):
return (
mtype.lower()
.replace(" ", "-")
.replace("(", "")
.replace(")", "")
.replace("+", "_1")
)
HEADER_FORMAT = "# pyasic\n## {} Models\n\n"
MINER_HEADER_FORMAT = "## {}\n"
DATA_FORMAT = """::: {}
handler: python
options:
show_root_heading: false
heading_level: 4
"""
SUPPORTED_TYPES_HEADER = """# pyasic
## Supported Miners
Supported miner types are here on this list. If your miner (or miner version) is not on this list, please feel free to [open an issue on GitHub](https://github.com/UpstreamData/pyasic/issues) to get it added.
##### pyasic currently supports the following miners and subtypes:
<style>
details {
margin:0px;
padding-top:0px;
padding-bottom:0px;
}
</style>
"""
BACKEND_TYPE_HEADER = """
<details>
<summary>{}:</summary>
<ul>"""
MINER_TYPE_HEADER = """
<details>
<summary>{} Series:</summary>
<ul>"""
MINER_DETAILS = """
<li><a href="../{}/{}#{}">{}</a></li>"""
MINER_TYPE_CLOSER = """
</ul>
</details>"""
BACKEND_TYPE_CLOSER = """
</ul>
</details>"""
m_data = {}
for m in MINER_CLASSES:
for t in MINER_CLASSES[m]:
if t is not None:
miner = MINER_CLASSES[m][t]
if make(miner) not in m_data:
m_data[make(miner)] = {}
if model_type(miner) not in m_data[make(miner)]:
m_data[make(miner)][model_type(miner)] = []
m_data[make(miner)][model_type(miner)].append(miner)
async def create_directory_structure(directory, data):
if not os.path.exists(directory):
os.makedirs(directory)
for key, value in data.items():
subdirectory = os.path.join(directory, key)
if isinstance(value, dict):
await create_directory_structure(subdirectory, value)
elif isinstance(value, list):
file_path = os.path.join(subdirectory + ".md")
with open(file_path, "w") as file:
file.write(HEADER_FORMAT.format(key))
for item in value:
header = await item("1.1.1.1").get_model()
file.write(MINER_HEADER_FORMAT.format(header))
file.write(DATA_FORMAT.format(path(item)))
async def create_supported_types(directory):
with open(os.path.join(directory, "supported_types.md"), "w") as file:
file.write(SUPPORTED_TYPES_HEADER)
for mback in MINER_CLASSES:
backend_types = {}
file.write(BACKEND_TYPE_HEADER.format(backend_str(mback)))
for mtype in MINER_CLASSES[mback]:
if mtype is None:
continue
m = MINER_CLASSES[mback][mtype]
if model_type(m) not in backend_types:
backend_types[model_type(m)] = []
backend_types[model_type(m)].append(m)
for mtype in backend_types:
file.write(MINER_TYPE_HEADER.format(mtype))
for minstance in backend_types[mtype]:
model = await minstance("1.1.1.1").get_model()
file.write(
MINER_DETAILS.format(
make(minstance), mtype, create_url_str(model), model
)
)
file.write(MINER_TYPE_CLOSER)
file.write(BACKEND_TYPE_CLOSER)
root_directory = os.path.join(os.getcwd(), "miners")
asyncio.run(create_directory_structure(root_directory, m_data))
asyncio.run(create_supported_types(root_directory))

View File

@@ -127,6 +127,13 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S19 No PIC (VNish)
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19NoPIC
handler: python
options:
show_root_heading: false
heading_level: 4
## S19 Pro (VNish) ## S19 Pro (VNish)
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19Pro ::: pyasic.miners.antminer.vnish.X19.S19.VNishS19Pro
handler: python handler: python

View File

@@ -50,3 +50,10 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S9 (LuxOS)
::: pyasic.miners.antminer.luxos.X9.S9.LUXMinerS9
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -10,6 +10,9 @@ details {
padding-top:0px; padding-top:0px;
padding-bottom:0px; padding-bottom:0px;
} }
ul {
margin:0px;
}
</style> </style>
<details> <details>
@@ -419,6 +422,7 @@ details {
<summary>X19 Series:</summary> <summary>X19 Series:</summary>
<ul> <ul>
<li><a href="../antminer/X19#s19-vnish">S19 (VNish)</a></li> <li><a href="../antminer/X19#s19-vnish">S19 (VNish)</a></li>
<li><a href="../antminer/X19#s19-no-pic-vnish">S19 No PIC (VNish)</a></li>
<li><a href="../antminer/X19#s19-pro-vnish">S19 Pro (VNish)</a></li> <li><a href="../antminer/X19#s19-pro-vnish">S19 Pro (VNish)</a></li>
<li><a href="../antminer/X19#s19j-vnish">S19j (VNish)</a></li> <li><a href="../antminer/X19#s19j-vnish">S19j (VNish)</a></li>
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li> <li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
@@ -439,4 +443,15 @@ details {
</ul> </ul>
</details> </details>
</ul> </ul>
</details> </details>
<details>
<summary>LuxOS Firmware Miners:</summary>
<ul>
<details>
<summary>X9 Series:</summary>
<ul>
<li><a href="../antminer/X9#s9-luxos">S9 (LuxOS)</a></li>
</ul>
</details>
</ul>
</details>

View File

@@ -32,6 +32,8 @@ class BaseMinerAPI:
# ip address of the miner # ip address of the miner
self.ip = ipaddress.ip_address(ip) self.ip = ipaddress.ip_address(ip)
self.pwd = "admin"
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if cls is BaseMinerAPI: if cls is BaseMinerAPI:
raise TypeError(f"Only children of '{cls.__name__}' may be instantiated") raise TypeError(f"Only children of '{cls.__name__}' may be instantiated")

View File

@@ -257,7 +257,7 @@ class AntminerModern(BMMiner):
if api_stats: if api_stats:
try: try:
return int(api_stats["STATS"][0]["Elapsed"]) return int(api_stats["STATS"][1]["Elapsed"])
except LookupError: except LookupError:
pass pass
@@ -502,6 +502,6 @@ class AntminerOld(CGMiner):
if api_stats: if api_stats:
try: try:
return int(api_stats["STATS"][0]["Elapsed"]) return int(api_stats["STATS"][1]["Elapsed"])
except LookupError: except LookupError:
pass pass

View File

@@ -18,6 +18,7 @@ from typing import List, Optional
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import HashBoard from pyasic.data import HashBoard
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.logger import logger
from pyasic.miners.backends import BFGMiner from pyasic.miners.backends import BFGMiner
from pyasic.web.goldshell import GoldshellWebAPI from pyasic.web.goldshell import GoldshellWebAPI
@@ -138,7 +139,7 @@ class BFGMinerGoldshell(BFGMiner):
except KeyError: except KeyError:
pass pass
else: else:
print(self, api_devs) logger.error(self, api_devs)
if not api_devdetails: if not api_devdetails:
try: try:
@@ -156,7 +157,7 @@ class BFGMinerGoldshell(BFGMiner):
except KeyError: except KeyError:
pass pass
else: else:
print(self, api_devdetails) logger.error(self, api_devdetails)
return hashboards return hashboards

View File

@@ -370,6 +370,6 @@ class BMMiner(BaseMiner):
if api_stats: if api_stats:
try: try:
return int(api_stats["STATS"][0]["Elapsed"]) return int(api_stats["STATS"][1]["Elapsed"])
except LookupError: except LookupError:
pass pass

View File

@@ -174,7 +174,7 @@ BOSMINER_DATA_LOC = {
}, },
"is_mining": { "is_mining": {
"cmd": "is_mining", "cmd": "is_mining",
"kwargs": {"api_tunerstatus": {"api": "tunerstatus"}}, "kwargs": {"api_devdetails": {"api": "devdetails"}},
}, },
"uptime": { "uptime": {
"cmd": "get_uptime", "cmd": "get_uptime",
@@ -373,6 +373,52 @@ class BOSMiner(BaseMiner):
else: else:
return True return True
async def set_static_ip(
self,
ip: str,
dns: str,
gateway: str,
subnet_mask: str = "255.255.255.0",
):
cfg_data_lan = (
"config interface 'lan'\n\toption type 'bridge'\n\toption ifname 'eth0'\n\toption proto 'static'\n\toption ipaddr '"
+ ip
+ "'\n\toption netmask '"
+ subnet_mask
+ "'\n\toption gateway '"
+ gateway
+ "'\n\toption dns '"
+ dns
+ "'"
)
data = await self.send_ssh_command("cat /etc/config/network")
split_data = data.split("\n\n")
for idx in range(len(split_data)):
if "config interface 'lan'" in split_data[idx]:
split_data[idx] = cfg_data_lan
config = "\n\n".join(split_data)
conn = await self._get_ssh_connection()
async with conn:
await conn.run("echo '" + config + "' > /etc/config/network")
async def set_dhcp(self):
cfg_data_lan = "config interface 'lan'\n\toption type 'bridge'\n\toption ifname 'eth0'\n\toption proto 'dhcp'"
data = await self.send_ssh_command("cat /etc/config/network")
split_data = data.split("\n\n")
for idx in range(len(split_data)):
if "config interface 'lan'" in split_data[idx]:
split_data[idx] = cfg_data_lan
config = "\n\n".join(split_data)
conn = await self._get_ssh_connection()
async with conn:
await conn.run("echo '" + config + "' > /etc/config/network")
################################################## ##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
################################################## ##################################################
@@ -386,8 +432,6 @@ class BOSMiner(BaseMiner):
except APIError: except APIError:
pass pass
print(web_net_conf)
if isinstance(web_net_conf, dict): if isinstance(web_net_conf, dict):
if "/cgi-bin/luci/admin/network/iface_status/lan" in web_net_conf.keys(): if "/cgi-bin/luci/admin/network/iface_status/lan" in web_net_conf.keys():
web_net_conf = web_net_conf[ web_net_conf = web_net_conf[
@@ -450,7 +494,7 @@ class BOSMiner(BaseMiner):
if graphql_version: if graphql_version:
try: try:
fw_ver = graphql_version["data"]["bos"]["info"]["version"]["full"] fw_ver = graphql_version["data"]["bos"]["info"]["version"]["full"]
except KeyError: except (KeyError, TypeError):
pass pass
if not fw_ver: if not fw_ver:
@@ -479,7 +523,7 @@ class BOSMiner(BaseMiner):
try: try:
hostname = graphql_hostname["data"]["bos"]["hostname"] hostname = graphql_hostname["data"]["bos"]["hostname"]
return hostname return hostname
except KeyError: except (TypeError, KeyError):
pass pass
try: try:
@@ -519,7 +563,7 @@ class BOSMiner(BaseMiner):
), ),
2, 2,
) )
except (KeyError, IndexError, ValueError): except (LookupError, ValueError, TypeError):
pass pass
# get hr from API # get hr from API
@@ -573,7 +617,7 @@ class BOSMiner(BaseMiner):
boards = graphql_boards["data"]["bosminer"]["info"]["workSolver"][ boards = graphql_boards["data"]["bosminer"]["info"]["workSolver"][
"childSolvers" "childSolvers"
] ]
except (KeyError, IndexError): except (TypeError, LookupError):
boards = None boards = None
if boards: if boards:
@@ -688,7 +732,7 @@ class BOSMiner(BaseMiner):
return graphql_wattage["data"]["bosminer"]["info"]["workSolver"][ return graphql_wattage["data"]["bosminer"]["info"]["workSolver"][
"power" "power"
]["approxConsumptionW"] ]["approxConsumptionW"]
except (KeyError, TypeError): except (LookupError, TypeError):
pass pass
if not api_tunerstatus: if not api_tunerstatus:
@@ -721,7 +765,7 @@ class BOSMiner(BaseMiner):
return graphql_wattage_limit["data"]["bosminer"]["info"]["workSolver"][ return graphql_wattage_limit["data"]["bosminer"]["info"]["workSolver"][
"power" "power"
]["limitW"] ]["limitW"]
except (KeyError, TypeError): except (LookupError, TypeError):
pass pass
if not api_tunerstatus: if not api_tunerstatus:
@@ -757,7 +801,7 @@ class BOSMiner(BaseMiner):
] ]
) )
) )
except KeyError: except (LookupError, TypeError):
pass pass
return fans return fans
@@ -897,7 +941,7 @@ class BOSMiner(BaseMiner):
boards = graphql_errors["data"]["bosminer"]["info"]["workSolver"][ boards = graphql_errors["data"]["bosminer"]["info"]["workSolver"][
"childSolvers" "childSolvers"
] ]
except (KeyError, IndexError): except (LookupError, TypeError):
boards = None boards = None
if boards: if boards:
@@ -990,17 +1034,20 @@ class BOSMiner(BaseMiner):
try: try:
self.light = graphql_fault_light["data"]["bos"]["faultLight"] self.light = graphql_fault_light["data"]["bos"]["faultLight"]
return self.light return self.light
except (TypeError, KeyError, ValueError, IndexError): except (TypeError, ValueError, LookupError):
pass pass
# get light via ssh if that fails (10x slower) # get light via ssh if that fails (10x slower)
data = ( try:
await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off") data = (
).strip() await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off")
self.light = False ).strip()
if data == "50": self.light = False
self.light = True if data == "50":
return self.light self.light = True
return self.light
except TypeError:
return self.light
async def get_nominal_hashrate(self, api_devs: dict = None) -> Optional[float]: async def get_nominal_hashrate(self, api_devs: dict = None) -> Optional[float]:
if not api_devs: if not api_devs:
@@ -1028,22 +1075,16 @@ class BOSMiner(BaseMiner):
except (IndexError, KeyError): except (IndexError, KeyError):
pass pass
async def is_mining(self, api_tunerstatus: dict = None) -> Optional[bool]: async def is_mining(self, api_devdetails: dict = None) -> Optional[bool]:
if not api_tunerstatus: if not api_devdetails:
try: try:
api_tunerstatus = await self.api.tunerstatus() api_devdetails = await self.api.send_command("devdetails", ignore_errors=True, allow_warning=False)
except APIError: except APIError:
pass pass
if api_tunerstatus: if api_devdetails:
try: try:
running = any( return not api_devdetails["STATUS"][0]["Msg"] == "Unavailable"
[
d["TunerRunning"]
for d in api_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"]
]
)
return running
except LookupError: except LookupError:
pass pass

View File

@@ -393,6 +393,6 @@ class CGMiner(BaseMiner):
if api_stats: if api_stats:
try: try:
return int(api_stats["STATS"][0]["Elapsed"]) return int(api_stats["STATS"][1]["Elapsed"])
except LookupError: except LookupError:
pass pass

View File

@@ -248,7 +248,6 @@ class LUXMiner(BaseMiner):
pass pass
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]: async def get_hashrate(self, api_summary: dict = None) -> Optional[float]:
# get hr from API
if not api_summary: if not api_summary:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
@@ -443,6 +442,6 @@ class LUXMiner(BaseMiner):
if api_stats: if api_stats:
try: try:
return int(api_stats["STATS"][0]["Elapsed"]) return int(api_stats["STATS"][1]["Elapsed"])
except LookupError: except LookupError:
pass pass

View File

@@ -17,6 +17,7 @@
from typing import Optional from typing import Optional
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.logger import logger
from pyasic.miners.backends.bmminer import BMMiner from pyasic.miners.backends.bmminer import BMMiner
from pyasic.web.vnish import VNishWebAPI from pyasic.web.vnish import VNishWebAPI
@@ -144,7 +145,7 @@ class VNish(BMMiner):
float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2 float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
) )
except (IndexError, KeyError, ValueError, TypeError) as e: except (IndexError, KeyError, ValueError, TypeError) as e:
print(e) logger.error(e)
pass pass
async def get_wattage_limit(self, web_settings: dict = None) -> Optional[int]: async def get_wattage_limit(self, web_settings: dict = None) -> Optional[int]:

View File

@@ -24,6 +24,7 @@ import asyncssh
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import Fan, 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.logger import logger
class BaseMiner(ABC): class BaseMiner(ABC):
@@ -71,6 +72,52 @@ class BaseMiner(ABC):
def __eq__(self, other): def __eq__(self, other):
return ipaddress.ip_address(self.ip) == ipaddress.ip_address(other.ip) return ipaddress.ip_address(self.ip) == ipaddress.ip_address(other.ip)
@property
def pwd(self): # noqa - Skip PyCharm inspection
data = []
try:
if self.web is not None:
data.append(f"web={self.web.pwd}")
except TypeError:
pass
try:
if self.api is not None:
data.append(f"api={self.api.pwd}")
except TypeError:
pass
return ",".join(data)
@pwd.setter
def pwd(self, val):
try:
if self.web is not None:
self.web.pwd = val
except TypeError:
pass
try:
if self.api is not None:
self.api.pwd = val
except TypeError:
pass
@property
def username(self): # noqa - Skip PyCharm inspection
data = []
try:
if self.web is not None:
data.append(f"web={self.web.username}")
except TypeError:
pass
return ",".join(data)
@username.setter
def username(self, val):
try:
if self.web is not None:
self.web.username = val
except TypeError:
pass
async def _get_ssh_connection(self) -> asyncssh.connect: async def _get_ssh_connection(self) -> asyncssh.connect:
"""Create a new asyncssh connection""" """Create a new asyncssh connection"""
try: try:
@@ -397,7 +444,7 @@ class BaseMiner(ABC):
if fn_args[arg_name].get("web"): if fn_args[arg_name].get("web"):
web_multicommand.append(fn_args[arg_name]["web"]) web_multicommand.append(fn_args[arg_name]["web"])
except KeyError as e: except KeyError as e:
print(e, data_name) logger.error(e, data_name)
continue continue
api_multicommand = list(set(api_multicommand)) api_multicommand = list(set(api_multicommand))
@@ -426,23 +473,26 @@ class BaseMiner(ABC):
fn_args = self.data_locations[data_name]["kwargs"] fn_args = self.data_locations[data_name]["kwargs"]
args_to_send = {k: None for k in fn_args} args_to_send = {k: None for k in fn_args}
for arg_name in fn_args: for arg_name in fn_args:
if fn_args[arg_name].get("api"): try:
if api_command_data.get("multicommand"): if fn_args[arg_name].get("api"):
args_to_send[arg_name] = api_command_data[ if api_command_data.get("multicommand"):
fn_args[arg_name]["api"] args_to_send[arg_name] = api_command_data[
][0] fn_args[arg_name]["api"]
else: ][0]
args_to_send[arg_name] = api_command_data
if fn_args[arg_name].get("web"):
if web_command_data is not None:
if web_command_data.get("multicommand"):
args_to_send[arg_name] = web_command_data[
fn_args[arg_name]["web"]
]
else: else:
if not web_command_data == {"multicommand": False}: args_to_send[arg_name] = api_command_data
args_to_send[arg_name] = web_command_data if fn_args[arg_name].get("web"):
except (KeyError, IndexError): if web_command_data is not None:
if web_command_data.get("multicommand"):
args_to_send[arg_name] = web_command_data[
fn_args[arg_name]["web"]
]
else:
if not web_command_data == {"multicommand": False}:
args_to_send[arg_name] = web_command_data
except LookupError:
args_to_send[arg_name] = None
except LookupError as e:
continue continue
function = getattr(self, self.data_locations[data_name]["cmd"]) function = getattr(self, self.data_locations[data_name]["cmd"])
@@ -457,8 +507,8 @@ class BaseMiner(ABC):
except KeyError: except KeyError:
pass pass
if len(pools_data) > 1: if len(pools_data) > 1:
miner_data["pool_2_url"] = pools_data[1]["pool_2_url"] miner_data["pool_2_url"] = pools_data[1]["pool_1_url"]
miner_data["pool_2_user"] = pools_data[1]["pool_2_user"] miner_data["pool_2_user"] = pools_data[1]["pool_1_user"]
miner_data[ miner_data[
"pool_split" "pool_split"
] = f"{pools_data[0]['quota']}/{pools_data[1]['quota']}" ] = f"{pools_data[0]['quota']}/{pools_data[1]['quota']}"

View File

@@ -58,11 +58,13 @@ class BOSMinerWebAPI(BaseWebAPI):
command: dict, command: dict,
) -> dict: ) -> dict:
url = f"http://{self.ip}/graphql" url = f"http://{self.ip}/graphql"
query = self.parse_command(command) query = command
if command.get("query") is None:
query = {"query": self.parse_command(command)}
try: try:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
await self.auth(client) await self.auth(client)
data = await client.post(url, json={"query": query}) data = await client.post(url, json=query)
except httpx.HTTPError: except httpx.HTTPError:
pass pass
else: else:
@@ -74,7 +76,7 @@ class BOSMinerWebAPI(BaseWebAPI):
async def multicommand( async def multicommand(
self, *commands: Union[dict, str], allow_warning: bool = True self, *commands: Union[dict, str], allow_warning: bool = True
): ) -> dict:
luci_commands = [] luci_commands = []
gql_commands = [] gql_commands = []
for cmd in commands: for cmd in commands:
@@ -86,6 +88,11 @@ class BOSMinerWebAPI(BaseWebAPI):
luci_data = await self.luci_multicommand(*luci_commands) luci_data = await self.luci_multicommand(*luci_commands)
gql_data = await self.gql_multicommand(*gql_commands) gql_data = await self.gql_multicommand(*gql_commands)
if gql_data is None:
gql_data = {}
if luci_data is None:
luci_data = {}
data = dict(**luci_data, **gql_data) data = dict(**luci_data, **gql_data)
return data return data
@@ -144,8 +151,6 @@ class BOSMinerWebAPI(BaseWebAPI):
data = await client.get( data = await client.get(
f"http://{self.ip}{path}", headers={"User-Agent": "BTC Tools v0.1"} f"http://{self.ip}{path}", headers={"User-Agent": "BTC Tools v0.1"}
) )
print(data.status_code)
print(data.text)
if data.status_code == 200: if data.status_code == 200:
return data.json() return data.json()
if ignore_errors: if ignore_errors:

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "pyasic" name = "pyasic"
version = "0.36.2" version = "0.36.10"
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"