Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7485b8ef77 | ||
|
|
d2bea227db | ||
|
|
1b7afaaf7e | ||
|
|
96898d639c | ||
|
|
eb439f4dcf | ||
|
|
69f4349393 | ||
|
|
e371bb577c | ||
|
|
2500ec3869 | ||
|
|
5be3187eec | ||
|
|
be1e9127b0 | ||
|
|
13572c4770 | ||
|
|
08fa3961fe | ||
|
|
b5d2809e9c | ||
|
|
aa538d3079 | ||
|
|
e1500bb75c | ||
|
|
7f00a65598 | ||
|
|
64c473a7d4 | ||
|
|
96d9fe8e6c | ||
|
|
0b27400d27 |
163
docs/generate_miners.py
Normal file
163
docs/generate_miners.py
Normal 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))
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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]:
|
||||||
|
|||||||
@@ -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']}"
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user