Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39e3e249f8 | ||
|
|
118c5b056e | ||
|
|
2c3b5599fe | ||
|
|
e421eaa324 | ||
|
|
53f3fc5ee9 | ||
|
|
1b36de4131 | ||
|
|
6f0c6f6284 | ||
|
|
b7dda5bf87 | ||
|
|
53a3bbf531 | ||
|
|
50586f1ce7 | ||
|
|
9f6235a0fc | ||
|
|
4d21f150ce | ||
|
|
7c0dfc49dd | ||
|
|
269b13f6c1 | ||
|
|
a9bb7d2e5a | ||
|
|
11295f27a7 | ||
|
|
55aa3dd85b | ||
|
|
20272d4360 | ||
|
|
623dc92ef2 | ||
|
|
2d59394b1e | ||
|
|
26c2095ff1 | ||
|
|
ec7d241caa | ||
|
|
d0432ed1aa | ||
|
|
8c5503d002 | ||
|
|
6d6f950c95 | ||
|
|
30745e54ba | ||
|
|
c3fd94e79e | ||
|
|
2924a8d67b | ||
|
|
9f4c4bb9cf | ||
|
|
3d6eebf06e | ||
|
|
b3d9b6ff7e | ||
|
|
60facacc48 | ||
|
|
b8a6063838 | ||
|
|
bcba2be524 | ||
|
|
f7187d2017 | ||
|
|
d91b7c4406 | ||
|
|
248a7e6d69 | ||
|
|
4f2c3e772a | ||
|
|
95f7146eef | ||
|
|
9d5d19cc6b | ||
|
|
cc38129571 | ||
|
|
3dfd9f237d | ||
|
|
f3fe478dbb | ||
|
|
e10f32ae3d | ||
|
|
4e0924aa0e | ||
|
|
d0d3fd3117 | ||
|
|
4de950d8f4 | ||
|
|
03f2a1f9ba | ||
|
|
2653db90e3 | ||
|
|
ddc8c53eb9 | ||
|
|
eb5d1a24ea | ||
|
|
6c0e80265b | ||
|
|
ad3a4ae414 | ||
|
|
3484d43510 | ||
|
|
dd7e352391 | ||
|
|
a32b61fe5d | ||
|
|
597a178009 | ||
|
|
409b2527f0 | ||
|
|
58234fcf7f | ||
|
|
1bf863cca8 | ||
|
|
6482d04185 | ||
|
|
3b58b11501 | ||
|
|
7485b8ef77 | ||
|
|
d2bea227db | ||
|
|
1b7afaaf7e | ||
|
|
96898d639c | ||
|
|
eb439f4dcf | ||
|
|
69f4349393 | ||
|
|
e371bb577c | ||
|
|
2500ec3869 | ||
|
|
5be3187eec | ||
|
|
be1e9127b0 | ||
|
|
13572c4770 | ||
|
|
08fa3961fe | ||
|
|
b5d2809e9c | ||
|
|
aa538d3079 | ||
|
|
e1500bb75c | ||
|
|
7f00a65598 | ||
|
|
64c473a7d4 | ||
|
|
96d9fe8e6c | ||
|
|
0b27400d27 |
@@ -1,5 +1,5 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
*A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH.*
|
*A simplified and standardized interface for Bitcoin ASICs.*
|
||||||
|
|
||||||
[](https://github.com/psf/black)
|
[](https://github.com/psf/black)
|
||||||
[](https://pypi.org/project/pyasic/)
|
[](https://pypi.org/project/pyasic/)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Use these instead -
|
|||||||
#### [BOSMiner API][pyasic.API.bosminer.BOSMinerAPI]
|
#### [BOSMiner API][pyasic.API.bosminer.BOSMinerAPI]
|
||||||
#### [BTMiner API][pyasic.API.btminer.BTMinerAPI]
|
#### [BTMiner API][pyasic.API.btminer.BTMinerAPI]
|
||||||
#### [CGMiner API][pyasic.API.cgminer.CGMinerAPI]
|
#### [CGMiner API][pyasic.API.cgminer.CGMinerAPI]
|
||||||
|
#### [LUXMiner API][pyasic.API.luxminer.LUXMinerAPI]
|
||||||
#### [Unknown API][pyasic.API.unknown.UnknownAPI]
|
#### [Unknown API][pyasic.API.unknown.UnknownAPI]
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
7
docs/API/luxminer.md
Normal file
7
docs/API/luxminer.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# pyasic
|
||||||
|
## LUXMinerAPI
|
||||||
|
::: pyasic.API.luxminer.LUXMinerAPI
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
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))
|
||||||
@@ -76,13 +76,14 @@ This function will return an instance of the dataclass [`MinerData`][pyasic.data
|
|||||||
Each piece of data in a [`MinerData`][pyasic.data.MinerData] instance can be referenced by getting it as an attribute, such as [`MinerData().hashrate`][pyasic.data.MinerData].
|
Each piece of data in a [`MinerData`][pyasic.data.MinerData] instance can be referenced by getting it as an attribute, such as [`MinerData().hashrate`][pyasic.data.MinerData].
|
||||||
```python
|
```python
|
||||||
import asyncio
|
import asyncio
|
||||||
from pyasic.miners.miner_factory import MinerFactory
|
from pyasic import get_miner
|
||||||
|
|
||||||
async def gather_miner_data():
|
async def gather_miner_data():
|
||||||
miner = await MinerFactory().get_miner("192.168.1.75")
|
miner = await get_miner("192.168.1.75")
|
||||||
miner_data = await miner.get_data()
|
if miner is not None:
|
||||||
print(miner_data) # all data from the dataclass
|
miner_data = await miner.get_data()
|
||||||
print(miner_data.hashrate) # hashrate of the miner in TH/s
|
print(miner_data) # all data from the dataclass
|
||||||
|
print(miner_data.hashrate) # hashrate of the miner in TH/s
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(gather_miner_data())
|
asyncio.run(gather_miner_data())
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
8
docs/miners/backends/luxminer.md
Normal file
8
docs/miners/backends/luxminer.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# pyasic
|
||||||
|
## LUXMiner Backend
|
||||||
|
|
||||||
|
::: pyasic.miners.backends.luxminer.LUXMiner
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
8
docs/miners/backends/vnish.md
Normal file
8
docs/miners/backends/vnish.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# pyasic
|
||||||
|
## VNish Backend
|
||||||
|
|
||||||
|
::: pyasic.miners.backends.vnish.VNish
|
||||||
|
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>
|
||||||
|
|||||||
10
mkdocs.yml
10
mkdocs.yml
@@ -20,6 +20,7 @@ nav:
|
|||||||
- BOSMiner: "API/bosminer.md"
|
- BOSMiner: "API/bosminer.md"
|
||||||
- BTMiner: "API/btminer.md"
|
- BTMiner: "API/btminer.md"
|
||||||
- CGMiner: "API/cgminer.md"
|
- CGMiner: "API/cgminer.md"
|
||||||
|
- LUXMiner: "API/luxminer.md"
|
||||||
- Unknown: "API/unknown.md"
|
- Unknown: "API/unknown.md"
|
||||||
- Backends:
|
- Backends:
|
||||||
- BMMiner: "miners/backends/bmminer.md"
|
- BMMiner: "miners/backends/bmminer.md"
|
||||||
@@ -27,6 +28,8 @@ nav:
|
|||||||
- BFGMiner: "miners/backends/bfgminer.md"
|
- BFGMiner: "miners/backends/bfgminer.md"
|
||||||
- BTMiner: "miners/backends/btminer.md"
|
- BTMiner: "miners/backends/btminer.md"
|
||||||
- CGMiner: "miners/backends/cgminer.md"
|
- CGMiner: "miners/backends/cgminer.md"
|
||||||
|
- LUXMiner: "miners/backends/luxminer.md"
|
||||||
|
- VNish: "miners/backends/vnish.md"
|
||||||
- Hiveon: "miners/backends/hiveon.md"
|
- Hiveon: "miners/backends/hiveon.md"
|
||||||
- Classes:
|
- Classes:
|
||||||
- Antminer X3: "miners/antminer/X3.md"
|
- Antminer X3: "miners/antminer/X3.md"
|
||||||
@@ -40,14 +43,15 @@ nav:
|
|||||||
- Avalon 8X: "miners/avalonminer/A8X.md"
|
- Avalon 8X: "miners/avalonminer/A8X.md"
|
||||||
- Avalon 9X: "miners/avalonminer/A9X.md"
|
- Avalon 9X: "miners/avalonminer/A9X.md"
|
||||||
- Avalon 10X: "miners/avalonminer/A10X.md"
|
- Avalon 10X: "miners/avalonminer/A10X.md"
|
||||||
|
- Avalon 11X: "miners/avalonminer/A11X.md"
|
||||||
|
- Avalon 12X: "miners/avalonminer/A12X.md"
|
||||||
- Whatsminer M2X: "miners/whatsminer/M2X.md"
|
- Whatsminer M2X: "miners/whatsminer/M2X.md"
|
||||||
- Whatsminer M3X: "miners/whatsminer/M3X.md"
|
- Whatsminer M3X: "miners/whatsminer/M3X.md"
|
||||||
- Whatsminer M5X: "miners/whatsminer/M5X.md"
|
- Whatsminer M5X: "miners/whatsminer/M5X.md"
|
||||||
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
||||||
- Innosilicon A10X: "miners/innosilicon/A10X.md"
|
- Innosilicon A10X: "miners/innosilicon/A10X.md"
|
||||||
- Goldshell CKX: "miners/goldshell/CKX.md"
|
- Goldshell X5: "miners/goldshell/X5.md"
|
||||||
- Goldshell HSX: "miners/goldshell/HSX.md"
|
- Goldshell XMax: "miners/goldshell/XMax.md"
|
||||||
- Goldshell KDX: "miners/goldshell/KDX.md"
|
|
||||||
- Base Miner: "miners/base_miner.md"
|
- Base Miner: "miners/base_miner.md"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
from typing import Union
|
from typing import Tuple, Union
|
||||||
|
|
||||||
from pyasic.errors import APIError, APIWarning
|
from pyasic.errors import APIError, APIWarning
|
||||||
|
|
||||||
@@ -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")
|
||||||
@@ -126,6 +128,18 @@ class BaseMinerAPI:
|
|||||||
data["multicommand"] = True
|
data["multicommand"] = True
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
async def _handle_multicommand(self, command: str, allow_warning: bool = True):
|
||||||
|
try:
|
||||||
|
data = await self.send_command(command, allow_warning=allow_warning)
|
||||||
|
if not "+" in command:
|
||||||
|
return {command: [data]}
|
||||||
|
return data
|
||||||
|
|
||||||
|
except APIError:
|
||||||
|
if "+" in command:
|
||||||
|
return {command: [{}] for command in command.split("+")}
|
||||||
|
return {command: [{}]}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def commands(self) -> list:
|
def commands(self) -> list:
|
||||||
return self.get_commands()
|
return self.get_commands()
|
||||||
@@ -169,7 +183,11 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
)
|
)
|
||||||
return return_commands
|
return return_commands
|
||||||
|
|
||||||
async def _send_bytes(self, data: bytes, timeout: int = 100) -> bytes:
|
async def _send_bytes(
|
||||||
|
self,
|
||||||
|
data: bytes,
|
||||||
|
timeout: int = 100,
|
||||||
|
) -> bytes:
|
||||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Sending")
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Sending")
|
||||||
try:
|
try:
|
||||||
# get reader and writer streams
|
# get reader and writer streams
|
||||||
@@ -240,9 +258,12 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
return False, data["Msg"]
|
return False, data["Msg"]
|
||||||
else:
|
else:
|
||||||
# make sure the command succeeded
|
# make sure the command succeeded
|
||||||
if type(data["STATUS"]) == str:
|
if isinstance(data["STATUS"], str):
|
||||||
if data["STATUS"] in ["RESTART"]:
|
if data["STATUS"] in ["RESTART"]:
|
||||||
return True, None
|
return True, None
|
||||||
|
elif isinstance(data["STATUS"], dict):
|
||||||
|
if data["STATUS"].get("STATUS") in ["S", "I"]:
|
||||||
|
return True, None
|
||||||
elif data["STATUS"][0]["STATUS"] not in ("S", "I"):
|
elif data["STATUS"][0]["STATUS"] not in ("S", "I"):
|
||||||
# this is an error
|
# this is an error
|
||||||
if data["STATUS"][0]["STATUS"] not in ("S", "I"):
|
if data["STATUS"][0]["STATUS"] not in ("S", "I"):
|
||||||
|
|||||||
@@ -13,7 +13,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 asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pyasic.API import APIError, BaseMinerAPI
|
from pyasic.API import APIError, BaseMinerAPI
|
||||||
@@ -56,19 +56,19 @@ class BFGMinerAPI(BaseMinerAPI):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
async def _x19_multicommand(self, *commands) -> dict:
|
async def _x19_multicommand(self, *commands) -> dict:
|
||||||
data = None
|
tasks = []
|
||||||
try:
|
# send all commands individually
|
||||||
data = {}
|
for cmd in commands:
|
||||||
# send all commands individually
|
tasks.append(
|
||||||
for cmd in commands:
|
asyncio.create_task(self._handle_multicommand(cmd, allow_warning=True))
|
||||||
data[cmd] = []
|
|
||||||
data[cmd].append(await self.send_command(cmd, allow_warning=True))
|
|
||||||
except APIError:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning(
|
|
||||||
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
all_data = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
for item in all_data:
|
||||||
|
data.update(item)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def version(self) -> dict:
|
async def version(self) -> dict:
|
||||||
|
|||||||
@@ -13,6 +13,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 asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pyasic.API import APIError, BaseMinerAPI
|
from pyasic.API import APIError, BaseMinerAPI
|
||||||
@@ -57,21 +58,19 @@ class BMMinerAPI(BaseMinerAPI):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
async def _x19_multicommand(self, *commands, allow_warning: bool = True) -> dict:
|
async def _x19_multicommand(self, *commands, allow_warning: bool = True) -> dict:
|
||||||
data = None
|
tasks = []
|
||||||
try:
|
# send all commands individually
|
||||||
data = {}
|
for cmd in commands:
|
||||||
# send all commands individually
|
tasks.append(
|
||||||
for cmd in commands:
|
asyncio.create_task(self._handle_multicommand(cmd, allow_warning=True))
|
||||||
data[cmd] = []
|
|
||||||
data[cmd].append(
|
|
||||||
await self.send_command(cmd, allow_warning=allow_warning)
|
|
||||||
)
|
|
||||||
except APIError:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning(
|
|
||||||
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
all_data = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
for item in all_data:
|
||||||
|
data.update(item)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def version(self) -> dict:
|
async def version(self) -> dict:
|
||||||
|
|||||||
@@ -22,15 +22,15 @@ import hashlib
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from typing import Union
|
from typing import Literal, Union
|
||||||
|
|
||||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
from passlib.handlers.md5_crypt import md5_crypt
|
from passlib.handlers.md5_crypt import md5_crypt
|
||||||
|
|
||||||
|
from pyasic import settings
|
||||||
from pyasic.API import BaseMinerAPI
|
from pyasic.API import BaseMinerAPI
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.misc import api_min_version
|
from pyasic.misc import api_min_version
|
||||||
from pyasic.settings import PyasicSettings
|
|
||||||
|
|
||||||
### IMPORTANT ###
|
### IMPORTANT ###
|
||||||
# you need to change the password of the miners using the Whatsminer
|
# you need to change the password of the miners using the Whatsminer
|
||||||
@@ -40,6 +40,12 @@ from pyasic.settings import PyasicSettings
|
|||||||
# you change the password, you can pass that to this class as pwd,
|
# you change the password, you can pass that to this class as pwd,
|
||||||
# or add it as the Whatsminer_pwd in the settings.toml file.
|
# or add it as the Whatsminer_pwd in the settings.toml file.
|
||||||
|
|
||||||
|
PrePowerOnMessage = Union[
|
||||||
|
Literal["wait for adjust temp"],
|
||||||
|
Literal["adjust complete"],
|
||||||
|
Literal["adjust continue"],
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _crypt(word: str, salt: str) -> str:
|
def _crypt(word: str, salt: str) -> str:
|
||||||
"""Encrypts a word with a salt, using a standard salt format.
|
"""Encrypts a word with a salt, using a standard salt format.
|
||||||
@@ -186,7 +192,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
ip: str,
|
ip: str,
|
||||||
api_ver: str = "0.0.0",
|
api_ver: str = "0.0.0",
|
||||||
port: int = 4028,
|
port: int = 4028,
|
||||||
pwd: str = PyasicSettings().global_whatsminer_password,
|
pwd: str = settings.get("default_whatsminer_password", "admin"),
|
||||||
):
|
):
|
||||||
super().__init__(ip, port)
|
super().__init__(ip, port)
|
||||||
self.pwd = pwd
|
self.pwd = pwd
|
||||||
@@ -203,27 +209,35 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
# 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)
|
||||||
# standard multicommand format is "command1+command2"
|
# standard multicommand format is "command1+command2"
|
||||||
# commands starting with "get_" aren't supported, but we can fake that
|
# commands starting with "get_" and the "status" command aren't supported, but we can fake that
|
||||||
get_commands_data = {}
|
|
||||||
|
tasks = []
|
||||||
|
|
||||||
for command in list(commands):
|
for command in list(commands):
|
||||||
if command.startswith("get_"):
|
if command.startswith("get_") or command == "status":
|
||||||
commands.remove(command)
|
commands.remove(command)
|
||||||
# send seperately and append later
|
# send seperately and append later
|
||||||
try:
|
tasks.append(
|
||||||
get_commands_data[command] = [
|
asyncio.create_task(
|
||||||
await self.send_command(command, allow_warning=allow_warning)
|
self._handle_multicommand(command, allow_warning=allow_warning)
|
||||||
]
|
)
|
||||||
except APIError:
|
)
|
||||||
get_commands_data[command] = [{}]
|
|
||||||
|
|
||||||
command = "+".join(commands)
|
command = "+".join(commands)
|
||||||
try:
|
tasks.append(
|
||||||
main_data = await self.send_command(command, allow_warning=allow_warning)
|
asyncio.create_task(
|
||||||
except APIError:
|
self._handle_multicommand(command, allow_warning=allow_warning)
|
||||||
main_data = {command: [{}] for command in commands}
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
all_data = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
logging.debug(f"{self} - (Multicommand) - Received data")
|
logging.debug(f"{self} - (Multicommand) - Received data")
|
||||||
|
|
||||||
data = dict(**main_data, **get_commands_data)
|
data = {}
|
||||||
|
for item in all_data:
|
||||||
|
data.update(item)
|
||||||
|
|
||||||
data["multicommand"] = True
|
data["multicommand"] = True
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@@ -685,7 +699,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
)
|
)
|
||||||
return await self.send_privileged_command("set_power_pct", percent=str(percent))
|
return await self.send_privileged_command("set_power_pct", percent=str(percent))
|
||||||
|
|
||||||
async def pre_power_on(self, complete: bool, msg: str) -> dict:
|
async def pre_power_on(self, complete: bool, msg: PrePowerOnMessage) -> dict:
|
||||||
"""Configure or check status of pre power on.
|
"""Configure or check status of pre power on.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -705,7 +719,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not msg == "wait for adjust temp" or "adjust complete" or "adjust continue":
|
if msg not in ("wait for adjust temp", "adjust complete", "adjust continue"):
|
||||||
raise APIError(
|
raise APIError(
|
||||||
"Message is incorrect, please choose one of "
|
"Message is incorrect, please choose one of "
|
||||||
'["wait for adjust temp", '
|
'["wait for adjust temp", '
|
||||||
@@ -721,6 +735,34 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
)
|
)
|
||||||
|
|
||||||
### ADDED IN V2.0.5 Whatsminer API ###
|
### ADDED IN V2.0.5 Whatsminer API ###
|
||||||
|
|
||||||
|
@api_min_version("2.0.5")
|
||||||
|
async def set_power_pct_v2(self, percent: int) -> dict:
|
||||||
|
"""Set the power percentage of the miner based on current power. Used for temporary adjustment. Added in API v2.0.5.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Set the power percentage of the miner, only works after changing
|
||||||
|
the password of the miner using the Whatsminer tool.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
percent: The power percentage to set.
|
||||||
|
Returns:
|
||||||
|
A reply informing of the status of setting the power percentage.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not 0 < percent < 100:
|
||||||
|
raise APIError(
|
||||||
|
f"Power PCT % is outside of the allowed "
|
||||||
|
f"range. Please set a % between 0 and "
|
||||||
|
f"100"
|
||||||
|
)
|
||||||
|
return await self.send_privileged_command(
|
||||||
|
"set_power_pct_v2", percent=str(percent)
|
||||||
|
)
|
||||||
|
|
||||||
@api_min_version("2.0.5")
|
@api_min_version("2.0.5")
|
||||||
async def set_temp_offset(self, temp_offset: int) -> dict:
|
async def set_temp_offset(self, temp_offset: int) -> dict:
|
||||||
"""Set the offset of miner hash board target temperature.
|
"""Set the offset of miner hash board target temperature.
|
||||||
|
|||||||
@@ -13,7 +13,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 asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pyasic.API import APIError, BaseMinerAPI
|
from pyasic.API import APIError, BaseMinerAPI
|
||||||
@@ -56,19 +56,19 @@ class CGMinerAPI(BaseMinerAPI):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
async def _x19_multicommand(self, *commands) -> dict:
|
async def _x19_multicommand(self, *commands) -> dict:
|
||||||
data = None
|
tasks = []
|
||||||
try:
|
# send all commands individually
|
||||||
data = {}
|
for cmd in commands:
|
||||||
# send all commands individually
|
tasks.append(
|
||||||
for cmd in commands:
|
asyncio.create_task(self._handle_multicommand(cmd, allow_warning=True))
|
||||||
data[cmd] = []
|
|
||||||
data[cmd].append(await self.send_command(cmd, allow_warning=True))
|
|
||||||
except APIError:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning(
|
|
||||||
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
all_data = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
for item in all_data:
|
||||||
|
data.update(item)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def version(self) -> dict:
|
async def version(self) -> dict:
|
||||||
|
|||||||
@@ -13,6 +13,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. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
from pyasic import settings
|
||||||
from pyasic.API.bmminer import BMMinerAPI
|
from pyasic.API.bmminer import BMMinerAPI
|
||||||
from pyasic.API.bosminer import BOSMinerAPI
|
from pyasic.API.bosminer import BOSMinerAPI
|
||||||
from pyasic.API.btminer import BTMinerAPI
|
from pyasic.API.btminer import BTMinerAPI
|
||||||
@@ -32,7 +33,6 @@ from pyasic.miners.base import AnyMiner
|
|||||||
from pyasic.miners.miner_factory import MinerFactory
|
from pyasic.miners.miner_factory import MinerFactory
|
||||||
from pyasic.miners.miner_listener import MinerListener
|
from pyasic.miners.miner_listener import MinerListener
|
||||||
from pyasic.network import MinerNetwork
|
from pyasic.network import MinerNetwork
|
||||||
from pyasic.settings import PyasicSettings
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"BMMinerAPI",
|
"BMMinerAPI",
|
||||||
@@ -53,5 +53,5 @@ __all__ = [
|
|||||||
"MinerFactory",
|
"MinerFactory",
|
||||||
"MinerListener",
|
"MinerListener",
|
||||||
"MinerNetwork",
|
"MinerNetwork",
|
||||||
"PyasicSettings",
|
"settings",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -560,9 +560,6 @@ class MinerConfig:
|
|||||||
if self.fan_speed:
|
if self.fan_speed:
|
||||||
cfg["bitmain-fan-pwn"] = str(self.fan_speed)
|
cfg["bitmain-fan-pwn"] = str(self.fan_speed)
|
||||||
|
|
||||||
if self.miner_mode == X19PowerMode.Sleep:
|
|
||||||
cfg["freq-level"] = "0"
|
|
||||||
|
|
||||||
return cfg
|
return cfg
|
||||||
|
|
||||||
def as_x17(self, user_suffix: str = None) -> dict:
|
def as_x17(self, user_suffix: str = None) -> dict:
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import logging
|
|||||||
import time
|
import time
|
||||||
from dataclasses import asdict, dataclass, field, fields
|
from dataclasses import asdict, dataclass, field, fields
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import List, Union
|
from typing import Any, List, Union
|
||||||
|
|
||||||
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
|
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
|
||||||
|
|
||||||
@@ -40,13 +40,28 @@ class HashBoard:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
slot: int = 0
|
slot: int = 0
|
||||||
hashrate: float = 0.0
|
hashrate: float = None
|
||||||
temp: int = -1
|
temp: int = None
|
||||||
chip_temp: int = -1
|
chip_temp: int = None
|
||||||
chips: int = 0
|
chips: int = None
|
||||||
expected_chips: int = 0
|
expected_chips: int = None
|
||||||
missing: bool = True
|
missing: bool = True
|
||||||
|
|
||||||
|
def get(self, __key: str, default: Any = None):
|
||||||
|
try:
|
||||||
|
val = self.__getitem__(__key)
|
||||||
|
if val is None:
|
||||||
|
return default
|
||||||
|
return val
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def __getitem__(self, item: str):
|
||||||
|
try:
|
||||||
|
return getattr(self, item)
|
||||||
|
except AttributeError:
|
||||||
|
raise KeyError(f"{item}")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Fan:
|
class Fan:
|
||||||
@@ -56,7 +71,22 @@ class Fan:
|
|||||||
speed: The speed of the fan.
|
speed: The speed of the fan.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
speed: int = -1
|
speed: int = None
|
||||||
|
|
||||||
|
def get(self, __key: str, default: Any = None):
|
||||||
|
try:
|
||||||
|
val = self.__getitem__(__key)
|
||||||
|
if val is None:
|
||||||
|
return default
|
||||||
|
return val
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def __getitem__(self, item: str):
|
||||||
|
try:
|
||||||
|
return getattr(self, item)
|
||||||
|
except AttributeError:
|
||||||
|
raise KeyError(f"{item}")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -102,26 +132,26 @@ class MinerData:
|
|||||||
|
|
||||||
ip: str
|
ip: str
|
||||||
datetime: datetime = None
|
datetime: datetime = None
|
||||||
uptime: int = 0
|
uptime: int = None
|
||||||
mac: str = "00:00:00:00:00:00"
|
mac: str = None
|
||||||
model: str = "Unknown"
|
model: str = None
|
||||||
make: str = "Unknown"
|
make: str = None
|
||||||
api_ver: str = "Unknown"
|
api_ver: str = None
|
||||||
fw_ver: str = "Unknown"
|
fw_ver: str = None
|
||||||
hostname: str = "Unknown"
|
hostname: str = None
|
||||||
hashrate: float = field(init=False)
|
hashrate: float = field(init=False)
|
||||||
_hashrate: float = 0
|
_hashrate: float = None
|
||||||
nominal_hashrate: float = 0
|
nominal_hashrate: float = None
|
||||||
hashboards: List[HashBoard] = field(default_factory=list)
|
hashboards: List[HashBoard] = field(default_factory=list)
|
||||||
ideal_hashboards: int = 1
|
ideal_hashboards: int = None
|
||||||
temperature_avg: int = field(init=False)
|
temperature_avg: int = field(init=False)
|
||||||
env_temp: float = -1.0
|
env_temp: float = None
|
||||||
wattage: int = -1
|
wattage: int = None
|
||||||
wattage_limit: int = -1
|
wattage_limit: int = None
|
||||||
fans: List[Fan] = field(default_factory=list)
|
fans: List[Fan] = field(default_factory=list)
|
||||||
fan_psu: int = -1
|
fan_psu: int = None
|
||||||
total_chips: int = field(init=False)
|
total_chips: int = field(init=False)
|
||||||
ideal_chips: int = 1
|
ideal_chips: int = None
|
||||||
percent_ideal_chips: float = field(init=False)
|
percent_ideal_chips: float = field(init=False)
|
||||||
percent_ideal_hashrate: float = field(init=False)
|
percent_ideal_hashrate: float = field(init=False)
|
||||||
percent_ideal_wattage: float = field(init=False)
|
percent_ideal_wattage: float = field(init=False)
|
||||||
@@ -145,7 +175,16 @@ class MinerData:
|
|||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
self.datetime = datetime.now(timezone.utc).astimezone()
|
self.datetime = datetime.now(timezone.utc).astimezone()
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def get(self, __key: str, default: Any = None):
|
||||||
|
try:
|
||||||
|
val = self.__getitem__(__key)
|
||||||
|
if val is None:
|
||||||
|
return default
|
||||||
|
return val
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def __getitem__(self, item: str):
|
||||||
try:
|
try:
|
||||||
return getattr(self, item)
|
return getattr(self, item)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -197,7 +236,12 @@ class MinerData:
|
|||||||
@property
|
@property
|
||||||
def hashrate(self): # noqa - Skip PyCharm inspection
|
def hashrate(self): # noqa - Skip PyCharm inspection
|
||||||
if len(self.hashboards) > 0:
|
if len(self.hashboards) > 0:
|
||||||
return round(sum(map(lambda x: x.hashrate, self.hashboards)), 2)
|
hr_data = []
|
||||||
|
for item in self.hashboards:
|
||||||
|
if item.hashrate is not None:
|
||||||
|
hr_data.append(item.hashrate)
|
||||||
|
if len(hr_data) > 0:
|
||||||
|
return sum(hr_data)
|
||||||
return self._hashrate
|
return self._hashrate
|
||||||
|
|
||||||
@hashrate.setter
|
@hashrate.setter
|
||||||
@@ -206,7 +250,14 @@ class MinerData:
|
|||||||
|
|
||||||
@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])
|
if len(self.hashboards) > 0:
|
||||||
|
chip_data = []
|
||||||
|
for item in self.hashboards:
|
||||||
|
if item.chips is not None:
|
||||||
|
chip_data.append(item.chips)
|
||||||
|
if len(chip_data) > 0:
|
||||||
|
return sum(chip_data)
|
||||||
|
return None
|
||||||
|
|
||||||
@total_chips.setter
|
@total_chips.setter
|
||||||
def total_chips(self, val):
|
def total_chips(self, val):
|
||||||
@@ -214,6 +265,8 @@ class MinerData:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def nominal(self): # noqa - Skip PyCharm inspection
|
def nominal(self): # noqa - Skip PyCharm inspection
|
||||||
|
if self.total_chips is None or self.ideal_chips is None:
|
||||||
|
return None
|
||||||
return self.ideal_chips == self.total_chips
|
return self.ideal_chips == self.total_chips
|
||||||
|
|
||||||
@nominal.setter
|
@nominal.setter
|
||||||
@@ -222,6 +275,8 @@ class MinerData:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def percent_ideal_chips(self): # noqa - Skip PyCharm inspection
|
def percent_ideal_chips(self): # noqa - Skip PyCharm inspection
|
||||||
|
if self.total_chips is None or self.ideal_chips is None:
|
||||||
|
return None
|
||||||
if self.total_chips == 0 or self.ideal_chips == 0:
|
if self.total_chips == 0 or self.ideal_chips == 0:
|
||||||
return 0
|
return 0
|
||||||
return round((self.total_chips / self.ideal_chips) * 100)
|
return round((self.total_chips / self.ideal_chips) * 100)
|
||||||
@@ -232,6 +287,8 @@ class MinerData:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def percent_ideal_hashrate(self): # noqa - Skip PyCharm inspection
|
def percent_ideal_hashrate(self): # noqa - Skip PyCharm inspection
|
||||||
|
if self.hashrate is None or self.nominal_hashrate is None:
|
||||||
|
return None
|
||||||
if self.hashrate == 0 or self.nominal_hashrate == 0:
|
if self.hashrate == 0 or self.nominal_hashrate == 0:
|
||||||
return 0
|
return 0
|
||||||
return round((self.hashrate / self.nominal_hashrate) * 100)
|
return round((self.hashrate / self.nominal_hashrate) * 100)
|
||||||
@@ -242,6 +299,8 @@ class MinerData:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def percent_ideal_wattage(self): # noqa - Skip PyCharm inspection
|
def percent_ideal_wattage(self): # noqa - Skip PyCharm inspection
|
||||||
|
if self.wattage_limit is None or self.wattage is None:
|
||||||
|
return None
|
||||||
if self.wattage_limit == 0 or self.wattage == 0:
|
if self.wattage_limit == 0 or self.wattage == 0:
|
||||||
return 0
|
return 0
|
||||||
return round((self.wattage / self.wattage_limit) * 100)
|
return round((self.wattage / self.wattage_limit) * 100)
|
||||||
@@ -255,11 +314,11 @@ class MinerData:
|
|||||||
total_temp = 0
|
total_temp = 0
|
||||||
temp_count = 0
|
temp_count = 0
|
||||||
for hb in self.hashboards:
|
for hb in self.hashboards:
|
||||||
if hb.temp and not hb.temp == -1:
|
if hb.temp is not None:
|
||||||
total_temp += hb.temp
|
total_temp += hb.temp
|
||||||
temp_count += 1
|
temp_count += 1
|
||||||
if not temp_count > 0:
|
if not temp_count > 0:
|
||||||
return 0
|
return None
|
||||||
return round(total_temp / temp_count)
|
return round(total_temp / temp_count)
|
||||||
|
|
||||||
@temperature_avg.setter
|
@temperature_avg.setter
|
||||||
@@ -268,7 +327,9 @@ class MinerData:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def efficiency(self): # noqa - Skip PyCharm inspection
|
def efficiency(self): # noqa - Skip PyCharm inspection
|
||||||
if self.hashrate == 0 or self.wattage == -1:
|
if self.hashrate is None or self.wattage is None:
|
||||||
|
return None
|
||||||
|
if self.hashrate == 0 or self.wattage == 0:
|
||||||
return 0
|
return 0
|
||||||
return round(self.wattage / self.hashrate)
|
return round(self.wattage / self.hashrate)
|
||||||
|
|
||||||
@@ -277,13 +338,16 @@ class MinerData:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def asdict(self) -> dict:
|
def asdict(self) -> dict:
|
||||||
|
logging.debug(f"MinerData - (To Dict) - Dumping Dict data")
|
||||||
|
return asdict(self)
|
||||||
|
|
||||||
|
def as_dict(self) -> dict:
|
||||||
"""Get this dataclass as a dictionary.
|
"""Get this dataclass as a dictionary.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A dictionary version of this class.
|
A dictionary version of this class.
|
||||||
"""
|
"""
|
||||||
logging.debug(f"MinerData - (To Dict) - Dumping Dict data")
|
return self.asdict()
|
||||||
return asdict(self)
|
|
||||||
|
|
||||||
def as_json(self) -> str:
|
def as_json(self) -> str:
|
||||||
"""Get this dataclass as JSON.
|
"""Get this dataclass as JSON.
|
||||||
@@ -328,7 +392,7 @@ class MinerData:
|
|||||||
tags = ["ip", "mac", "model", "hostname"]
|
tags = ["ip", "mac", "model", "hostname"]
|
||||||
for attribute in self:
|
for attribute in self:
|
||||||
if attribute in tags:
|
if attribute in tags:
|
||||||
escaped_data = self[attribute].replace(" ", "\\ ")
|
escaped_data = self.get(attribute, "Unknown").replace(" ", "\\ ")
|
||||||
tag_data.append(f"{attribute}={escaped_data}")
|
tag_data.append(f"{attribute}={escaped_data}")
|
||||||
continue
|
continue
|
||||||
elif str(attribute).startswith("_"):
|
elif str(attribute).startswith("_"):
|
||||||
@@ -345,26 +409,28 @@ class MinerData:
|
|||||||
elif isinstance(self[attribute], float):
|
elif isinstance(self[attribute], float):
|
||||||
field_data.append(f"{attribute}={self[attribute]}")
|
field_data.append(f"{attribute}={self[attribute]}")
|
||||||
continue
|
continue
|
||||||
elif attribute == "fault_light" and not self[attribute]:
|
|
||||||
field_data.append(f"{attribute}=false")
|
|
||||||
continue
|
|
||||||
elif attribute == "errors":
|
elif attribute == "errors":
|
||||||
for idx, item in enumerate(self[attribute]):
|
for idx, item in enumerate(self[attribute]):
|
||||||
field_data.append(f'error_{idx+1}="{item.error_message}"')
|
field_data.append(f'error_{idx+1}="{item.error_message}"')
|
||||||
elif attribute == "hashboards":
|
elif attribute == "hashboards":
|
||||||
for idx, item in enumerate(self[attribute]):
|
for idx, item in enumerate(self[attribute]):
|
||||||
field_data.append(f"hashboard_{idx+1}_hashrate={item.hashrate}")
|
|
||||||
field_data.append(f"hashboard_{idx+1}_temperature={item.temp}")
|
|
||||||
field_data.append(
|
field_data.append(
|
||||||
f"hashboard_{idx+1}_chip_temperature={item.chip_temp}"
|
f"hashboard_{idx+1}_hashrate={item.get('hashrate', 0.0)}"
|
||||||
)
|
)
|
||||||
field_data.append(f"hashboard_{idx+1}_chips={item.chips}")
|
|
||||||
field_data.append(
|
field_data.append(
|
||||||
f"hashboard_{idx+1}_expected_chips={item.expected_chips}"
|
f"hashboard_{idx+1}_temperature={item.get('temp', 0)}"
|
||||||
|
)
|
||||||
|
field_data.append(
|
||||||
|
f"hashboard_{idx+1}_chip_temperature={item.get('chip_temp', 0)}"
|
||||||
|
)
|
||||||
|
field_data.append(f"hashboard_{idx+1}_chips={item.get('chips', 0)}")
|
||||||
|
field_data.append(
|
||||||
|
f"hashboard_{idx+1}_expected_chips={item.get('expected_chips', 0)}"
|
||||||
)
|
)
|
||||||
elif attribute == "fans":
|
elif attribute == "fans":
|
||||||
for idx, item in enumerate(self[attribute]):
|
for idx, item in enumerate(self[attribute]):
|
||||||
field_data.append(f"fan_{idx+1}={item.speed}")
|
if item.speed is not None:
|
||||||
|
field_data.append(f"fan_{idx+1}={item.speed}")
|
||||||
|
|
||||||
tags_str = ",".join(tag_data)
|
tags_str = ",".join(tag_data)
|
||||||
field_str = ",".join(field_data)
|
field_str = ",".join(field_data)
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
from dataclasses import asdict, dataclass, field, fields
|
from dataclasses import asdict, dataclass, field, fields
|
||||||
|
|
||||||
C_N_CODES = ["52", "53", "54", "55", "56"]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class WhatsminerError:
|
class WhatsminerError:
|
||||||
@@ -37,10 +35,8 @@ class WhatsminerError:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def error_message(self): # noqa - Skip PyCharm inspection
|
def error_message(self): # noqa - Skip PyCharm inspection
|
||||||
if len(str(self.error_code)) > 3 and str(self.error_code)[:2] in C_N_CODES:
|
if len(str(self.error_code)) == 6 and not str(self.error_code)[:1] == "1":
|
||||||
# 55 error code base has chip numbers, so the format is
|
err_type = int(str(self.error_code)[:2])
|
||||||
# 55 -> board num len 1 -> chip num len 3
|
|
||||||
err_type = 55
|
|
||||||
err_subtype = int(str(self.error_code)[2:3])
|
err_subtype = int(str(self.error_code)[2:3])
|
||||||
err_value = int(str(self.error_code)[3:])
|
err_value = int(str(self.error_code)[3:])
|
||||||
else:
|
else:
|
||||||
@@ -88,7 +84,9 @@ class WhatsminerError:
|
|||||||
|
|
||||||
ERROR_CODES = {
|
ERROR_CODES = {
|
||||||
1: { # Fan error
|
1: { # Fan error
|
||||||
0: {0: "Fan unknown."},
|
0: {
|
||||||
|
0: "Fan unknown.",
|
||||||
|
},
|
||||||
1: { # Fan speed error of 1000+
|
1: { # Fan speed error of 1000+
|
||||||
0: "Intake fan speed error.",
|
0: "Intake fan speed error.",
|
||||||
1: "Exhaust fan speed error.",
|
1: "Exhaust fan speed error.",
|
||||||
@@ -101,7 +99,9 @@ ERROR_CODES = {
|
|||||||
0: "Intake fan speed error. Fan speed deviates by more than 3000.",
|
0: "Intake fan speed error. Fan speed deviates by more than 3000.",
|
||||||
1: "Exhaust fan speed error. Fan speed deviates by more than 3000.",
|
1: "Exhaust fan speed error. Fan speed deviates by more than 3000.",
|
||||||
},
|
},
|
||||||
4: {0: "Fan speed too high."}, # High speed
|
4: {
|
||||||
|
0: "Fan speed too high.",
|
||||||
|
}, # High speed
|
||||||
},
|
},
|
||||||
2: { # Power error
|
2: { # Power error
|
||||||
0: {
|
0: {
|
||||||
@@ -126,6 +126,7 @@ ERROR_CODES = {
|
|||||||
6: "Power remained unchanged for a long time.",
|
6: "Power remained unchanged for a long time.",
|
||||||
7: "Power set enable error.",
|
7: "Power set enable error.",
|
||||||
8: "Power input voltage is lower than 230V for high power mode.",
|
8: "Power input voltage is lower than 230V for high power mode.",
|
||||||
|
9: "Power input current is incorrect.",
|
||||||
},
|
},
|
||||||
3: {
|
3: {
|
||||||
3: "Power output high temperature protection error.",
|
3: "Power output high temperature protection error.",
|
||||||
@@ -159,6 +160,8 @@ ERROR_CODES = {
|
|||||||
6: {
|
6: {
|
||||||
3: "Power communication warning.",
|
3: "Power communication warning.",
|
||||||
4: "Power communication error.",
|
4: "Power communication error.",
|
||||||
|
5: "Power unknown error.",
|
||||||
|
6: "Power unknown error.",
|
||||||
7: "Power watchdog protection.",
|
7: "Power watchdog protection.",
|
||||||
8: "Power output high current protection.",
|
8: "Power output high current protection.",
|
||||||
9: "Power input high current protection.",
|
9: "Power input high current protection.",
|
||||||
@@ -170,57 +173,134 @@ ERROR_CODES = {
|
|||||||
3: "Power input too high warning.",
|
3: "Power input too high warning.",
|
||||||
4: "Power fan warning.",
|
4: "Power fan warning.",
|
||||||
5: "Power high temperature warning.",
|
5: "Power high temperature warning.",
|
||||||
|
6: "Power unknown error.",
|
||||||
|
7: "Power unknown error.",
|
||||||
|
8: "Power unknown error.",
|
||||||
|
9: "Power unknown error.",
|
||||||
|
},
|
||||||
|
8: {
|
||||||
|
0: "Power unknown error.",
|
||||||
|
1: "Power vendor status 1 bit 0 error.",
|
||||||
|
2: "Power vendor status 1 bit 1 error.",
|
||||||
|
3: "Power vendor status 1 bit 2 error.",
|
||||||
|
4: "Power vendor status 1 bit 3 error.",
|
||||||
|
5: "Power vendor status 1 bit 4 error.",
|
||||||
|
6: "Power vendor status 1 bit 5 error.",
|
||||||
|
7: "Power vendor status 1 bit 6 error.",
|
||||||
|
8: "Power vendor status 1 bit 7 error.",
|
||||||
|
9: "Power vendor status 2 bit 0 error.",
|
||||||
|
},
|
||||||
|
9: {
|
||||||
|
0: "Power vendor status 2 bit 1 error.",
|
||||||
|
1: "Power vendor status 2 bit 2 error.",
|
||||||
|
2: "Power vendor status 2 bit 3 error.",
|
||||||
|
3: "Power vendor status 2 bit 4 error.",
|
||||||
|
4: "Power vendor status 2 bit 5 error.",
|
||||||
|
5: "Power vendor status 2 bit 6 error.",
|
||||||
|
6: "Power vendor status 2 bit 7 error.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
3: { # temperature error
|
3: { # temperature error
|
||||||
0: { # sensor detection error
|
0: { # sensor detection error
|
||||||
"n": "Slot {n} temperature sensor detection error."
|
"n": "Slot {n} temperature sensor detection error.",
|
||||||
},
|
},
|
||||||
2: { # temperature reading error
|
2: { # temperature reading error
|
||||||
"n": "Slot {n} temperature reading error.",
|
"n": "Slot {n} temperature reading error.",
|
||||||
9: "Control board temperature sensor communication error.",
|
9: "Control board temperature sensor communication error.",
|
||||||
},
|
},
|
||||||
5: {"n": "Slot {n} temperature protecting."}, # temperature protection
|
5: {
|
||||||
6: {0: "Hashboard high temperature error."}, # high temp
|
"n": "Slot {n} temperature protecting.",
|
||||||
|
}, # temperature protection
|
||||||
|
6: {
|
||||||
|
0: "Hashboard high temperature error.",
|
||||||
|
1: "Hashboard high temperature error.",
|
||||||
|
2: "Hashboard high temperature error.",
|
||||||
|
3: "Hashboard high temperature error.",
|
||||||
|
}, # high temp
|
||||||
|
7: {
|
||||||
|
0: "The environment temperature fluctuates too much.",
|
||||||
|
}, # env temp
|
||||||
8: {
|
8: {
|
||||||
0: "Humidity sensor not found.",
|
0: "Humidity sensor not found.",
|
||||||
1: "Humidity sensor read error.",
|
1: "Humidity sensor read error.",
|
||||||
2: "Humidity sensor read error.",
|
2: "Humidity sensor read error.",
|
||||||
3: "Humidity sensor protecting.",
|
3: "Humidity sensor protecting.",
|
||||||
},
|
}, # humidity
|
||||||
},
|
},
|
||||||
4: { # EEPROM error
|
4: { # EEPROM error
|
||||||
0: {0: "Eeprom unknown error."},
|
0: {
|
||||||
1: {"n": "Slot {n} eeprom detection error."}, # EEPROM detection error
|
0: "Eeprom unknown error.",
|
||||||
2: {"n": "Slot {n} eeprom parsing error."}, # EEPROM parsing error
|
},
|
||||||
3: {"n": "Slot {n} chip bin type error."}, # chip bin error
|
1: {
|
||||||
4: {"n": "Slot {n} eeprom chip number X error."}, # EEPROM chip number error
|
"n": "Slot {n} eeprom detection error.",
|
||||||
5: {"n": "Slot {n} eeprom xfer error."}, # EEPROM xfer error
|
}, # EEPROM detection error
|
||||||
|
2: {
|
||||||
|
"n": "Slot {n} eeprom parsing error.",
|
||||||
|
}, # EEPROM parsing error
|
||||||
|
3: {
|
||||||
|
"n": "Slot {n} chip bin type error.",
|
||||||
|
}, # chip bin error
|
||||||
|
4: {
|
||||||
|
"n": "Slot {n} eeprom chip number X error.",
|
||||||
|
}, # EEPROM chip number error
|
||||||
|
5: {
|
||||||
|
"n": "Slot {n} eeprom xfer error.",
|
||||||
|
}, # EEPROM xfer error
|
||||||
},
|
},
|
||||||
5: { # hashboard error
|
5: { # hashboard error
|
||||||
0: {0: "Board unknown error."},
|
0: {
|
||||||
1: {"n": "Slot {n} miner type error."}, # board miner type error
|
0: "Board unknown error.",
|
||||||
2: {"n": "Slot {n} bin type error."}, # chip bin type error
|
},
|
||||||
3: {"n": "Slot {n} not found."}, # board not found error
|
1: {
|
||||||
4: {"n": "Slot {n} error reading chip id."}, # reading chip id error
|
"n": "Slot {n} miner type error.",
|
||||||
5: {"n": "Slot {n} has bad chips."}, # board has bad chips error
|
}, # board miner type error
|
||||||
6: {"n": "Slot {n} loss of balance error."}, # loss of balance error
|
2: {
|
||||||
7: {"n": "Slot {n} xfer error chip."}, # xfer error
|
"n": "Slot {n} bin type error.",
|
||||||
8: {"n": "Slot {n} reset error."}, # reset error
|
}, # chip bin type error
|
||||||
9: {"n": "Slot {n} frequency too low."}, # freq error
|
3: {
|
||||||
|
"n": "Slot {n} not found.",
|
||||||
|
}, # board not found error
|
||||||
|
4: {
|
||||||
|
"n": "Slot {n} error reading chip id.",
|
||||||
|
}, # reading chip id error
|
||||||
|
5: {
|
||||||
|
"n": "Slot {n} has bad chips.",
|
||||||
|
}, # board has bad chips error
|
||||||
|
6: {
|
||||||
|
"n": "Slot {n} loss of balance error.",
|
||||||
|
}, # loss of balance error
|
||||||
|
7: {
|
||||||
|
"n": "Slot {n} xfer error chip.",
|
||||||
|
}, # xfer error
|
||||||
|
8: {
|
||||||
|
"n": "Slot {n} reset error.",
|
||||||
|
}, # reset error
|
||||||
|
9: {
|
||||||
|
"n": "Slot {n} frequency too low.",
|
||||||
|
}, # freq error
|
||||||
},
|
},
|
||||||
6: { # env temp error
|
6: { # env temp error
|
||||||
0: {0: "Environment temperature is too high."}, # normal env temp error
|
0: {
|
||||||
|
0: "Environment temperature is too high.",
|
||||||
|
}, # normal env temp error
|
||||||
1: { # high power env temp error
|
1: { # high power env temp error
|
||||||
0: "Environment temperature is too high for high performance mode."
|
0: "Environment temperature is too high for high performance mode.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
7: { # control board error
|
7: { # control board error
|
||||||
0: {0: "MAC address invalid", 1: "Control board no support chip."},
|
0: {
|
||||||
|
0: "MAC address invalid",
|
||||||
|
1: "Control board no support chip.",
|
||||||
|
},
|
||||||
1: {
|
1: {
|
||||||
0: "Control board rebooted as an exception.",
|
0: "Control board rebooted as an exception.",
|
||||||
1: "Control board rebooted as exception and cpufreq reduced, please upgrade the firmware",
|
1: "Control board rebooted as exception and cpufreq reduced, please upgrade the firmware",
|
||||||
2: "Control board rebooted as an exception.",
|
2: "Control board rebooted as an exception.",
|
||||||
|
3: "The network is unstable, change time.",
|
||||||
|
4: "Unknown error.",
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
"n": "Control board slot {n} frame error.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
8: { # checksum error
|
8: { # checksum error
|
||||||
@@ -228,63 +308,152 @@ ERROR_CODES = {
|
|||||||
0: "CGMiner checksum error.",
|
0: "CGMiner checksum error.",
|
||||||
1: "System monitor checksum error.",
|
1: "System monitor checksum error.",
|
||||||
2: "Remote daemon checksum error.",
|
2: "Remote daemon checksum error.",
|
||||||
}
|
},
|
||||||
|
1: {0: "Air to liquid PCB serial # does not match."},
|
||||||
},
|
},
|
||||||
9: {0: {1: "Power rate error."}}, # power rate error
|
9: {
|
||||||
|
0: {0: "Unknown error.", 1: "Power rate error.", 2: "Unknown error."}
|
||||||
|
}, # power rate error
|
||||||
20: { # pool error
|
20: { # pool error
|
||||||
1: {0: "All pools are disabled."}, # all disabled error
|
0: {
|
||||||
2: {"n": "Pool {n} connection failed."}, # pool connection failed error
|
0: "No pool information configured.",
|
||||||
3: {0: "High rejection rate on pool."}, # rejection rate error
|
},
|
||||||
|
1: {
|
||||||
|
0: "All pools are disabled.",
|
||||||
|
}, # all disabled error
|
||||||
|
2: {
|
||||||
|
"n": "Pool {n} connection failed.",
|
||||||
|
}, # pool connection failed error
|
||||||
|
3: {
|
||||||
|
0: "High rejection rate on pool.",
|
||||||
|
}, # rejection rate error
|
||||||
4: { # asicboost not supported error
|
4: { # asicboost not supported error
|
||||||
0: "The pool does not support asicboost mode."
|
0: "The pool does not support asicboost mode.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
21: {1: {"n": "Slot {n} factory test step failed."}},
|
21: {
|
||||||
|
1: {
|
||||||
|
"n": "Slot {n} factory test step failed.",
|
||||||
|
}
|
||||||
|
},
|
||||||
23: { # hashrate error
|
23: { # hashrate error
|
||||||
1: {0: "Hashrate is too low."},
|
1: {
|
||||||
2: {0: "Hashrate is too low."},
|
0: "Hashrate is too low.",
|
||||||
3: {0: "Hashrate loss is too high."},
|
},
|
||||||
4: {0: "Hashrate loss is too high."},
|
2: {
|
||||||
5: {0: "Hashrate loss."},
|
0: "Hashrate is too low.",
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
0: "Hashrate loss is too high.",
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
0: "Hashrate loss is too high.",
|
||||||
|
},
|
||||||
|
5: {
|
||||||
|
0: "Hashrate loss.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
50: { # water velocity error/voltage error
|
50: { # water velocity error/voltage error
|
||||||
1: {"n": "Slot {n} chip voltage too low."},
|
1: {
|
||||||
2: {"n": "Slot {n} chip voltage changed."},
|
"n": "Slot {n} chip voltage too low.",
|
||||||
3: {"n": "Slot {n} chip temperature difference is too large."},
|
},
|
||||||
4: {"n": "Slot {n} chip hottest temperature difference is too large."},
|
2: {
|
||||||
7: {"n": "Slot {n} water velocity is abnormal."}, # abnormal water velocity
|
"n": "Slot {n} chip voltage changed.",
|
||||||
8: {0: "Chip temp calibration failed, please restore factory settings."},
|
},
|
||||||
9: {"n": "Slot {n} chip temp calibration check no balance."},
|
3: {
|
||||||
|
"n": "Slot {n} chip temperature difference is too large.",
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
"n": "Slot {n} chip hottest temperature difference is too large.",
|
||||||
|
},
|
||||||
|
5: {"n": "Slot {n} stopped hashing, chips temperature protecting."},
|
||||||
|
7: {
|
||||||
|
"n": "Slot {n} water velocity is abnormal.",
|
||||||
|
}, # abnormal water velocity
|
||||||
|
8: {
|
||||||
|
0: "Chip temp calibration failed, please restore factory settings.",
|
||||||
|
},
|
||||||
|
9: {
|
||||||
|
"n": "Slot {n} chip temp calibration check no balance.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
51: { # frequency error
|
51: { # frequency error
|
||||||
1: {"n": "Slot {n} frequency up timeout."}, # frequency up timeout
|
1: {
|
||||||
7: {"n": "Slot {n} frequency up timeout."}, # frequency up timeout
|
"n": "Slot {n} frequency up timeout.",
|
||||||
|
}, # frequency up timeout
|
||||||
|
2: {"n": "Slot {n} too many CRC errors."},
|
||||||
|
3: {"n": "Slot {n} unstable."},
|
||||||
|
7: {
|
||||||
|
"n": "Slot {n} frequency up timeout.",
|
||||||
|
}, # frequency up timeout
|
||||||
|
},
|
||||||
|
52: {
|
||||||
|
"n": {
|
||||||
|
"c": "Slot {n} chip {c} error nonce.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
53: {
|
||||||
|
"n": {
|
||||||
|
"c": "Slot {n} chip {c} too few nonce.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
54: {
|
||||||
|
"n": {
|
||||||
|
"c": "Slot {n} chip {c} temp protected.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
55: {
|
||||||
|
"n": {
|
||||||
|
"c": "Slot {n} chip {c} has been reset.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
56: {
|
||||||
|
"n": {
|
||||||
|
"c": "Slot {n} chip {c} zero nonce.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
52: {"n": {"c": "Slot {n} chip {c} error nonce."}},
|
|
||||||
53: {"n": {"c": "Slot {n} chip {c} too few nonce."}},
|
|
||||||
54: {"n": {"c": "Slot {n} chip {c} temp protected."}},
|
|
||||||
55: {"n": {"c": "Slot {n} chip {c} has been reset."}},
|
|
||||||
56: {"n": {"c": "Slot {n} chip {c} does not return to the nonce."}},
|
|
||||||
80: {
|
80: {
|
||||||
0: {0: "The tool version is too low, please update."},
|
0: {
|
||||||
1: {0: "Low freq."},
|
0: "The tool version is too low, please update.",
|
||||||
2: {0: "Low hashrate."},
|
},
|
||||||
3: {5: "High env temp."},
|
1: {
|
||||||
|
0: "Low freq.",
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
0: "Low hashrate.",
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
5: "High env temp.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
81: {
|
81: {
|
||||||
0: {0: "Chip data error."},
|
0: {
|
||||||
|
0: "Chip data error.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
82: {
|
82: {
|
||||||
0: {0: "Power version error."},
|
0: {
|
||||||
1: {0: "Miner type error."},
|
0: "Power version error.",
|
||||||
2: {0: "Version info error."},
|
},
|
||||||
|
1: {
|
||||||
|
0: "Miner type error.",
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
0: "Version info error.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
83: {
|
83: {
|
||||||
0: {0: "Empty level error."},
|
0: {
|
||||||
|
0: "Empty level error.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
84: {
|
84: {
|
||||||
0: {0: "Old firmware."},
|
0: {
|
||||||
1: {0: "Software version error."},
|
0: "Old firmware.",
|
||||||
|
},
|
||||||
|
1: {
|
||||||
|
0: "Software version error.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
85: {
|
85: {
|
||||||
"n": {
|
"n": {
|
||||||
@@ -296,8 +465,12 @@ ERROR_CODES = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
86: {
|
86: {
|
||||||
0: {0: "Missing product serial #."},
|
0: {
|
||||||
1: {0: "Missing product type."},
|
0: "Missing product serial #.",
|
||||||
|
},
|
||||||
|
1: {
|
||||||
|
0: "Missing product type.",
|
||||||
|
},
|
||||||
2: {
|
2: {
|
||||||
0: "Missing miner serial #.",
|
0: "Missing miner serial #.",
|
||||||
1: "Wrong miner serial # length.",
|
1: "Wrong miner serial # length.",
|
||||||
@@ -314,12 +487,34 @@ ERROR_CODES = {
|
|||||||
3: "Wrong power model rate.",
|
3: "Wrong power model rate.",
|
||||||
4: "Wrong power model format.",
|
4: "Wrong power model format.",
|
||||||
},
|
},
|
||||||
5: {0: "Wrong hash board struct."},
|
5: {
|
||||||
6: {0: "Wrong miner cooling type."},
|
0: "Wrong hash board struct.",
|
||||||
7: {0: "Missing PCB serial #."},
|
},
|
||||||
|
6: {
|
||||||
|
0: "Wrong miner cooling type.",
|
||||||
|
},
|
||||||
|
7: {
|
||||||
|
0: "Missing PCB serial #.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
87: {
|
||||||
|
0: {
|
||||||
|
0: "Miner power mismatch.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
90: {
|
||||||
|
0: {
|
||||||
|
0: "Process error, exited with signal: 3.",
|
||||||
|
},
|
||||||
|
1: {
|
||||||
|
0: "Process error, exited with signal: 3.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
99: {
|
||||||
|
9: {
|
||||||
|
9: "Miner unknown error.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
87: {0: {0: "Miner power mismatch."}},
|
|
||||||
99: {9: {9: "Miner unknown error."}},
|
|
||||||
1000: {
|
1000: {
|
||||||
0: {
|
0: {
|
||||||
0: "Security library error, please upgrade firmware",
|
0: "Security library error, please upgrade firmware",
|
||||||
@@ -328,7 +523,11 @@ ERROR_CODES = {
|
|||||||
3: "/antiv/dig/pf_partial.dig illegal.",
|
3: "/antiv/dig/pf_partial.dig illegal.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
1001: {0: {0: "Security BTMiner removed, please upgrade firmware."}},
|
1001: {
|
||||||
|
0: {
|
||||||
|
0: "Security BTMiner removed, please upgrade firmware.",
|
||||||
|
},
|
||||||
|
},
|
||||||
1100: {
|
1100: {
|
||||||
0: {
|
0: {
|
||||||
0: "Security illegal file, please upgrade firmware.",
|
0: "Security illegal file, please upgrade firmware.",
|
||||||
|
|||||||
@@ -149,10 +149,10 @@ class _MinerPhaseBalancer:
|
|||||||
not self.miners[data_point.ip]["shutdown"]
|
not self.miners[data_point.ip]["shutdown"]
|
||||||
):
|
):
|
||||||
# cant do anything with it so need to find a semi-accurate power limit
|
# cant do anything with it so need to find a semi-accurate power limit
|
||||||
if not data_point.wattage_limit == -1:
|
if not data_point.wattage_limit == None:
|
||||||
self.miners[data_point.ip]["max"] = int(data_point.wattage_limit)
|
self.miners[data_point.ip]["max"] = int(data_point.wattage_limit)
|
||||||
self.miners[data_point.ip]["min"] = int(data_point.wattage_limit)
|
self.miners[data_point.ip]["min"] = int(data_point.wattage_limit)
|
||||||
elif not data_point.wattage == -1:
|
elif not data_point.wattage == None:
|
||||||
self.miners[data_point.ip]["max"] = int(data_point.wattage)
|
self.miners[data_point.ip]["max"] = int(data_point.wattage)
|
||||||
self.miners[data_point.ip]["min"] = int(data_point.wattage)
|
self.miners[data_point.ip]["min"] = int(data_point.wattage)
|
||||||
|
|
||||||
|
|||||||
@@ -16,31 +16,29 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pyasic.settings import PyasicSettings
|
|
||||||
|
|
||||||
|
|
||||||
def init_logger():
|
def init_logger():
|
||||||
if PyasicSettings().logfile:
|
# if PyasicSettings().logfile:
|
||||||
logging.basicConfig(
|
# logging.basicConfig(
|
||||||
filename="logfile.txt",
|
# filename="logfile.txt",
|
||||||
filemode="a",
|
# filemode="a",
|
||||||
format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
# format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
||||||
datefmt="%x %X",
|
# datefmt="%x %X",
|
||||||
)
|
# )
|
||||||
else:
|
# else:
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
||||||
datefmt="%x %X",
|
datefmt="%x %X",
|
||||||
)
|
)
|
||||||
|
|
||||||
_logger = logging.getLogger()
|
_logger = logging.getLogger()
|
||||||
|
|
||||||
if PyasicSettings().debug:
|
# if PyasicSettings().debug:
|
||||||
_logger.setLevel(logging.DEBUG)
|
# _logger.setLevel(logging.DEBUG)
|
||||||
logging.getLogger("asyncssh").setLevel(logging.DEBUG)
|
# logging.getLogger("asyncssh").setLevel(logging.DEBUG)
|
||||||
else:
|
# else:
|
||||||
_logger.setLevel(logging.WARNING)
|
_logger.setLevel(logging.WARNING)
|
||||||
logging.getLogger("asyncssh").setLevel(logging.WARNING)
|
logging.getLogger("asyncssh").setLevel(logging.WARNING)
|
||||||
|
|
||||||
return _logger
|
return _logger
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,11 @@ from pyasic.miners.types import (
|
|||||||
S19XP,
|
S19XP,
|
||||||
S19a,
|
S19a,
|
||||||
S19aPro,
|
S19aPro,
|
||||||
|
S19i,
|
||||||
S19j,
|
S19j,
|
||||||
S19jNoPIC,
|
S19jNoPIC,
|
||||||
S19jPro,
|
S19jPro,
|
||||||
|
S19Plus,
|
||||||
S19Pro,
|
S19Pro,
|
||||||
S19ProPlus,
|
S19ProPlus,
|
||||||
)
|
)
|
||||||
@@ -33,6 +35,14 @@ class BMMinerS19(AntminerModern, S19):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BMMinerS19Plus(AntminerModern, S19Plus):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BMMinerS19i(AntminerModern, S19i):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BMMinerS19Pro(AntminerModern, S19Pro):
|
class BMMinerS19Pro(AntminerModern, S19Pro):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -18,10 +18,12 @@ from .S19 import (
|
|||||||
BMMinerS19,
|
BMMinerS19,
|
||||||
BMMinerS19a,
|
BMMinerS19a,
|
||||||
BMMinerS19aPro,
|
BMMinerS19aPro,
|
||||||
|
BMMinerS19i,
|
||||||
BMMinerS19j,
|
BMMinerS19j,
|
||||||
BMMinerS19jNoPIC,
|
BMMinerS19jNoPIC,
|
||||||
BMMinerS19jPro,
|
BMMinerS19jPro,
|
||||||
BMMinerS19L,
|
BMMinerS19L,
|
||||||
|
BMMinerS19Plus,
|
||||||
BMMinerS19Pro,
|
BMMinerS19Pro,
|
||||||
BMMinerS19ProPlus,
|
BMMinerS19ProPlus,
|
||||||
BMMinerS19XP,
|
BMMinerS19XP,
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class HiveonT9(Hiveon, T9):
|
|||||||
hashrate = 0
|
hashrate = 0
|
||||||
chips = 0
|
chips = 0
|
||||||
for chipset in board_map[board]:
|
for chipset in board_map[board]:
|
||||||
if hashboard.chip_temp == -1:
|
if hashboard.chip_temp == None:
|
||||||
try:
|
try:
|
||||||
hashboard.board_temp = api_stats["STATS"][1][f"temp{chipset}"]
|
hashboard.board_temp = api_stats["STATS"][1][f"temp{chipset}"]
|
||||||
hashboard.chip_temp = api_stats["STATS"][1][f"temp2_{chipset}"]
|
hashboard.chip_temp = api_stats["STATS"][1][f"temp2_{chipset}"]
|
||||||
|
|||||||
@@ -26,11 +26,17 @@ from pyasic.miners.backends.cgminer import CGMiner
|
|||||||
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
|
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
|
||||||
|
|
||||||
ANTMINER_MODERN_DATA_LOC = {
|
ANTMINER_MODERN_DATA_LOC = {
|
||||||
"mac": {"cmd": "get_mac", "kwargs": {}},
|
"mac": {
|
||||||
|
"cmd": "get_mac",
|
||||||
|
"kwargs": {"web_get_system_info": {"web": "get_system_info"}},
|
||||||
|
},
|
||||||
"model": {"cmd": "get_model", "kwargs": {}},
|
"model": {"cmd": "get_model", "kwargs": {}},
|
||||||
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}},
|
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||||
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||||
"hostname": {"cmd": "get_hostname", "kwargs": {}},
|
"hostname": {
|
||||||
|
"cmd": "get_hostname",
|
||||||
|
"kwargs": {"web_get_system_info": {"web": "get_system_info"}},
|
||||||
|
},
|
||||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||||
"nominal_hashrate": {
|
"nominal_hashrate": {
|
||||||
"cmd": "get_nominal_hashrate",
|
"cmd": "get_nominal_hashrate",
|
||||||
@@ -42,8 +48,11 @@ ANTMINER_MODERN_DATA_LOC = {
|
|||||||
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}},
|
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}},
|
||||||
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}},
|
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||||
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
||||||
"errors": {"cmd": "get_errors", "kwargs": {}},
|
"errors": {"cmd": "get_errors", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||||
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
"fault_light": {
|
||||||
|
"cmd": "get_fault_light",
|
||||||
|
"kwargs": {"web_get_blink_status": {"web": "get_blink_status"}},
|
||||||
|
},
|
||||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||||
"is_mining": {
|
"is_mining": {
|
||||||
"cmd": "is_mining",
|
"cmd": "is_mining",
|
||||||
@@ -100,7 +109,7 @@ class AntminerModern(BMMiner):
|
|||||||
data = await self.web.blink(blink=False)
|
data = await self.web.blink(blink=False)
|
||||||
if data:
|
if data:
|
||||||
if data.get("code") == "B100":
|
if data.get("code") == "B100":
|
||||||
self.light = True
|
self.light = False
|
||||||
return self.light
|
return self.light
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
@@ -121,21 +130,31 @@ class AntminerModern(BMMiner):
|
|||||||
await self.send_config(cfg)
|
await self.send_config(cfg)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def get_hostname(self) -> Union[str, None]:
|
async def get_hostname(self, web_get_system_info: dict = None) -> Union[str, None]:
|
||||||
try:
|
if not web_get_system_info:
|
||||||
data = await self.web.get_system_info()
|
try:
|
||||||
if data:
|
web_get_system_info = await self.web.get_system_info()
|
||||||
return data["hostname"]
|
except APIError:
|
||||||
except KeyError:
|
pass
|
||||||
pass
|
|
||||||
|
|
||||||
async def get_mac(self) -> Union[str, None]:
|
if web_get_system_info:
|
||||||
try:
|
try:
|
||||||
data = await self.web.get_system_info()
|
return web_get_system_info["hostname"]
|
||||||
if data:
|
except KeyError:
|
||||||
return data["macaddr"]
|
pass
|
||||||
except KeyError:
|
|
||||||
pass
|
async def get_mac(self, web_get_system_info: dict = None) -> Union[str, None]:
|
||||||
|
if not web_get_system_info:
|
||||||
|
try:
|
||||||
|
web_get_system_info = await self.web.get_system_info()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_get_system_info:
|
||||||
|
try:
|
||||||
|
return web_get_system_info["macaddr"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = await self.web.get_network_info()
|
data = await self.web.get_network_info()
|
||||||
@@ -144,12 +163,17 @@ class AntminerModern(BMMiner):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_errors(self) -> List[MinerErrorData]:
|
async def get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
||||||
errors = []
|
if not web_summary:
|
||||||
data = await self.web.summary()
|
|
||||||
if data:
|
|
||||||
try:
|
try:
|
||||||
for item in data["SUMMARY"][0]["status"]:
|
web_summary = await self.web.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
if web_summary:
|
||||||
|
try:
|
||||||
|
for item in web_summary["SUMMARY"][0]["status"]:
|
||||||
try:
|
try:
|
||||||
if not item["status"] == "s":
|
if not item["status"] == "s":
|
||||||
errors.append(X19Error(item["msg"]))
|
errors.append(X19Error(item["msg"]))
|
||||||
@@ -159,15 +183,21 @@ class AntminerModern(BMMiner):
|
|||||||
pass
|
pass
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
async def get_fault_light(self) -> bool:
|
async def get_fault_light(self, web_get_blink_status: dict = None) -> bool:
|
||||||
if self.light:
|
if self.light:
|
||||||
return self.light
|
return self.light
|
||||||
try:
|
|
||||||
data = await self.web.get_blink_status()
|
if not web_get_blink_status:
|
||||||
if data:
|
try:
|
||||||
self.light = data["blink"]
|
web_get_blink_status = await self.web.get_blink_status()
|
||||||
except KeyError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if web_get_blink_status:
|
||||||
|
try:
|
||||||
|
self.light = web_get_blink_status["blink"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
return self.light
|
return self.light
|
||||||
|
|
||||||
async def get_nominal_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
async def get_nominal_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||||
@@ -257,7 +287,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 +532,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
|
||||||
|
|
||||||
|
|||||||
@@ -235,7 +235,22 @@ class BMMiner(BaseMiner):
|
|||||||
if board_offset == -1:
|
if board_offset == -1:
|
||||||
board_offset = 1
|
board_offset = 1
|
||||||
|
|
||||||
for i in range(board_offset, board_offset + self.ideal_hashboards):
|
real_slots = []
|
||||||
|
|
||||||
|
for i in range(board_offset, board_offset + 4):
|
||||||
|
try:
|
||||||
|
key = f"chain_acs{i}"
|
||||||
|
if boards[1].get(key, "") != "":
|
||||||
|
real_slots.append(i)
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if len(real_slots) < 3:
|
||||||
|
real_slots = list(
|
||||||
|
range(board_offset, board_offset + self.ideal_hashboards)
|
||||||
|
)
|
||||||
|
|
||||||
|
for i in real_slots:
|
||||||
hashboard = HashBoard(
|
hashboard = HashBoard(
|
||||||
slot=i - board_offset, expected_chips=self.nominal_chips
|
slot=i - board_offset, expected_chips=self.nominal_chips
|
||||||
)
|
)
|
||||||
@@ -259,7 +274,7 @@ class BMMiner(BaseMiner):
|
|||||||
if (not chips) or (not chips > 0):
|
if (not chips) or (not chips > 0):
|
||||||
hashboard.missing = True
|
hashboard.missing = True
|
||||||
hashboards.append(hashboard)
|
hashboards.append(hashboard)
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
@@ -370,6 +385,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",
|
||||||
@@ -303,17 +303,12 @@ class BOSMiner(BaseMiner):
|
|||||||
The config from `self.config`.
|
The config from `self.config`.
|
||||||
"""
|
"""
|
||||||
logging.debug(f"{self}: Getting config.")
|
logging.debug(f"{self}: Getting config.")
|
||||||
conn = None
|
|
||||||
try:
|
try:
|
||||||
conn = await self._get_ssh_connection()
|
conn = await self._get_ssh_connection()
|
||||||
except ConnectionError:
|
except ConnectionError:
|
||||||
try:
|
conn = None
|
||||||
pools = await self.api.pools()
|
|
||||||
except APIError:
|
|
||||||
return self.config
|
|
||||||
if pools:
|
|
||||||
self.config = MinerConfig().from_api(pools["POOLS"])
|
|
||||||
return self.config
|
|
||||||
if conn:
|
if conn:
|
||||||
async with conn:
|
async with conn:
|
||||||
# good ol' BBB compatibility :/
|
# good ol' BBB compatibility :/
|
||||||
@@ -365,6 +360,8 @@ class BOSMiner(BaseMiner):
|
|||||||
async def set_power_limit(self, wattage: int) -> bool:
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
try:
|
try:
|
||||||
cfg = await self.get_config()
|
cfg = await self.get_config()
|
||||||
|
if cfg is None:
|
||||||
|
return False
|
||||||
cfg.autotuning_wattage = wattage
|
cfg.autotuning_wattage = wattage
|
||||||
await self.send_config(cfg)
|
await self.send_config(cfg)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -373,6 +370,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 +429,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 +491,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 +520,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 +560,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 +614,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 +729,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 +762,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 +798,7 @@ class BOSMiner(BaseMiner):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except KeyError:
|
except (LookupError, TypeError):
|
||||||
pass
|
pass
|
||||||
return fans
|
return fans
|
||||||
|
|
||||||
@@ -897,7 +938,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 +1031,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 +1072,18 @@ 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
|
||||||
|
|||||||
@@ -179,11 +179,12 @@ class CGMinerAvalon(CGMiner):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_hostname(self, mac: str = None) -> Optional[str]:
|
async def get_hostname(self, mac: str = None) -> Optional[str]:
|
||||||
if not mac:
|
return None
|
||||||
mac = await self.get_mac()
|
# if not mac:
|
||||||
|
# mac = await self.get_mac()
|
||||||
if mac:
|
#
|
||||||
return f"Avalon{mac.replace(':', '')[-6:]}"
|
# if mac:
|
||||||
|
# return f"Avalon{mac.replace(':', '')[-6:]}"
|
||||||
|
|
||||||
async def get_hashrate(self, api_devs: dict = None) -> Optional[float]:
|
async def get_hashrate(self, api_devs: dict = None) -> Optional[float]:
|
||||||
if not api_devs:
|
if not api_devs:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -73,6 +74,24 @@ class VNish(BMMiner):
|
|||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
async def stop_mining(self) -> bool:
|
||||||
|
data = await self.web.stop_mining()
|
||||||
|
if data:
|
||||||
|
try:
|
||||||
|
return data["success"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def resume_mining(self) -> bool:
|
||||||
|
data = await self.web.resume_mining()
|
||||||
|
if data:
|
||||||
|
try:
|
||||||
|
return data["success"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
data = await self.web.reboot()
|
data = await self.web.reboot()
|
||||||
if data:
|
if data:
|
||||||
@@ -144,7 +163,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]:
|
||||||
|
|||||||
@@ -13,7 +13,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 asyncio
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
@@ -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):
|
||||||
@@ -32,6 +33,8 @@ class BaseMiner(ABC):
|
|||||||
self.api = None
|
self.api = None
|
||||||
self.web = None
|
self.web = None
|
||||||
|
|
||||||
|
self.ssh_pwd = "root"
|
||||||
|
|
||||||
# static data
|
# static data
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.api_type = None
|
self.api_type = None
|
||||||
@@ -71,6 +74,53 @@ 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):
|
||||||
|
self.ssh_pwd = 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:
|
||||||
@@ -78,7 +128,7 @@ class BaseMiner(ABC):
|
|||||||
str(self.ip),
|
str(self.ip),
|
||||||
known_hosts=None,
|
known_hosts=None,
|
||||||
username="root",
|
username="root",
|
||||||
password="root",
|
password=self.ssh_pwd,
|
||||||
server_host_key_algs=["ssh-rsa"],
|
server_host_key_algs=["ssh-rsa"],
|
||||||
)
|
)
|
||||||
return conn
|
return conn
|
||||||
@@ -363,86 +413,81 @@ class BaseMiner(ABC):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_data(self, allow_warning: bool, data_to_get: list = None) -> dict:
|
async def _get_data(
|
||||||
if not data_to_get:
|
self, allow_warning: bool, include: list = None, exclude: list = None
|
||||||
|
) -> dict:
|
||||||
|
if include is None:
|
||||||
# everything
|
# everything
|
||||||
data_to_get = [
|
include = list(self.data_locations.keys())
|
||||||
"mac",
|
|
||||||
"model",
|
if exclude is not None:
|
||||||
"api_ver",
|
for item in exclude:
|
||||||
"fw_ver",
|
if item in include:
|
||||||
"hostname",
|
include.remove(item)
|
||||||
"hashrate",
|
|
||||||
"nominal_hashrate",
|
api_multicommand = set()
|
||||||
"hashboards",
|
|
||||||
"env_temp",
|
|
||||||
"wattage",
|
|
||||||
"wattage_limit",
|
|
||||||
"fans",
|
|
||||||
"fan_psu",
|
|
||||||
"errors",
|
|
||||||
"fault_light",
|
|
||||||
"pools",
|
|
||||||
"is_mining",
|
|
||||||
"uptime",
|
|
||||||
]
|
|
||||||
api_multicommand = []
|
|
||||||
web_multicommand = []
|
web_multicommand = []
|
||||||
for data_name in data_to_get:
|
for data_name in include:
|
||||||
try:
|
try:
|
||||||
fn_args = self.data_locations[data_name]["kwargs"]
|
fn_args = self.data_locations[data_name]["kwargs"]
|
||||||
for arg_name in fn_args:
|
for arg_name in fn_args:
|
||||||
if fn_args[arg_name].get("api"):
|
if fn_args[arg_name].get("api"):
|
||||||
api_multicommand.append(fn_args[arg_name]["api"])
|
api_multicommand.add(fn_args[arg_name]["api"])
|
||||||
if fn_args[arg_name].get("web"):
|
if fn_args[arg_name].get("web"):
|
||||||
web_multicommand.append(fn_args[arg_name]["web"])
|
if not fn_args[arg_name]["web"] in web_multicommand:
|
||||||
|
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))
|
|
||||||
_web_multicommand = web_multicommand
|
|
||||||
for item in web_multicommand:
|
|
||||||
if item not in _web_multicommand:
|
|
||||||
_web_multicommand.append(item)
|
|
||||||
web_multicommand = _web_multicommand
|
|
||||||
if len(api_multicommand) > 0:
|
if len(api_multicommand) > 0:
|
||||||
api_command_data = await self.api.multicommand(
|
api_command_task = asyncio.create_task(
|
||||||
*api_multicommand, allow_warning=allow_warning
|
self.api.multicommand(*api_multicommand, allow_warning=allow_warning)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
api_command_data = {}
|
api_command_task = asyncio.sleep(0)
|
||||||
if len(web_multicommand) > 0:
|
if len(web_multicommand) > 0:
|
||||||
web_command_data = await self.web.multicommand(
|
web_command_task = asyncio.create_task(
|
||||||
*web_multicommand, allow_warning=allow_warning
|
self.web.multicommand(*web_multicommand, allow_warning=allow_warning)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
web_command_task = asyncio.sleep(0)
|
||||||
|
|
||||||
|
web_command_data = await web_command_task
|
||||||
|
if web_command_data is None:
|
||||||
web_command_data = {}
|
web_command_data = {}
|
||||||
|
|
||||||
|
api_command_data = await api_command_task
|
||||||
|
if api_command_data is None:
|
||||||
|
api_command_data = {}
|
||||||
|
|
||||||
miner_data = {}
|
miner_data = {}
|
||||||
|
|
||||||
for data_name in data_to_get:
|
for data_name in include:
|
||||||
try:
|
try:
|
||||||
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:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
function = getattr(self, self.data_locations[data_name]["cmd"])
|
function = getattr(self, self.data_locations[data_name]["cmd"])
|
||||||
@@ -457,8 +502,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']}"
|
||||||
@@ -472,13 +517,14 @@ class BaseMiner(ABC):
|
|||||||
return miner_data
|
return miner_data
|
||||||
|
|
||||||
async def get_data(
|
async def get_data(
|
||||||
self, allow_warning: bool = False, data_to_get: list = None
|
self, allow_warning: bool = False, include: list = None, exclude: list = None
|
||||||
) -> MinerData:
|
) -> 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.
|
include: Names of data items you want to gather. Defaults to all data.
|
||||||
|
exclude: Names of data items to exclude. Exclusion happens after considering included items.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A [`MinerData`][pyasic.data.MinerData] instance containing data from the miner.
|
A [`MinerData`][pyasic.data.MinerData] instance containing data from the miner.
|
||||||
@@ -494,7 +540,9 @@ class BaseMiner(ABC):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
gathered_data = await self._get_data(allow_warning, data_to_get=data_to_get)
|
gathered_data = await self._get_data(
|
||||||
|
allow_warning, include=include, exclude=exclude
|
||||||
|
)
|
||||||
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])
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import json
|
|||||||
import re
|
import re
|
||||||
from typing import Callable, List, Optional, Tuple, Union
|
from typing import Callable, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import aiohttp
|
import httpx
|
||||||
|
|
||||||
from pyasic.logger import logger
|
from pyasic.logger import logger
|
||||||
from pyasic.miners.antminer import *
|
from pyasic.miners.antminer import *
|
||||||
@@ -85,6 +85,8 @@ MINER_CLASSES = {
|
|||||||
"ANTMINER S19L": BMMinerS19L,
|
"ANTMINER S19L": BMMinerS19L,
|
||||||
"ANTMINER S19 PRO": BMMinerS19Pro,
|
"ANTMINER S19 PRO": BMMinerS19Pro,
|
||||||
"ANTMINER S19J": BMMinerS19j,
|
"ANTMINER S19J": BMMinerS19j,
|
||||||
|
"ANTMINER S19I": BMMinerS19i,
|
||||||
|
"ANTMINER S19+": BMMinerS19Plus,
|
||||||
"ANTMINER S19J88NOPIC": BMMinerS19jNoPIC,
|
"ANTMINER S19J88NOPIC": BMMinerS19jNoPIC,
|
||||||
"ANTMINER S19PRO+": BMMinerS19ProPlus,
|
"ANTMINER S19PRO+": BMMinerS19ProPlus,
|
||||||
"ANTMINER S19J PRO": BMMinerS19jPro,
|
"ANTMINER S19J PRO": BMMinerS19jPro,
|
||||||
@@ -99,6 +101,8 @@ MINER_CLASSES = {
|
|||||||
"M20SV10": BTMinerM20SV10,
|
"M20SV10": BTMinerM20SV10,
|
||||||
"M20SV20": BTMinerM20SV20,
|
"M20SV20": BTMinerM20SV20,
|
||||||
"M20SV30": BTMinerM20SV30,
|
"M20SV30": BTMinerM20SV30,
|
||||||
|
"M20PV10": BTMinerM20PV10,
|
||||||
|
"M20PV30": BTMinerM20PV30,
|
||||||
"M20S+V30": BTMinerM20SPlusV30,
|
"M20S+V30": BTMinerM20SPlusV30,
|
||||||
"M21V10": BTMinerM21V10,
|
"M21V10": BTMinerM21V10,
|
||||||
"M21SV20": BTMinerM21SV20,
|
"M21SV20": BTMinerM21SV20,
|
||||||
@@ -319,6 +323,7 @@ MINER_CLASSES = {
|
|||||||
"ANTMINER S19J": BOSMinerS19j,
|
"ANTMINER S19J": BOSMinerS19j,
|
||||||
"ANTMINER S19J88NOPIC": BOSMinerS19jNoPIC,
|
"ANTMINER S19J88NOPIC": BOSMinerS19jNoPIC,
|
||||||
"ANTMINER S19J PRO": BOSMinerS19jPro,
|
"ANTMINER S19J PRO": BOSMinerS19jPro,
|
||||||
|
"ANTMINER S19J PRO NOPIC": BOSMinerS19jPro,
|
||||||
"ANTMINER T19": BOSMinerT19,
|
"ANTMINER T19": BOSMinerT19,
|
||||||
},
|
},
|
||||||
MinerTypes.VNISH: {
|
MinerTypes.VNISH: {
|
||||||
@@ -455,7 +460,7 @@ class MinerFactory:
|
|||||||
|
|
||||||
async def _get_miner_web(self, ip: str):
|
async def _get_miner_web(self, ip: str):
|
||||||
urls = [f"http://{ip}/", f"https://{ip}/"]
|
urls = [f"http://{ip}/", f"https://{ip}/"]
|
||||||
async with aiohttp.ClientSession() as session:
|
async with httpx.AsyncClient(verify=False) as session:
|
||||||
tasks = [asyncio.create_task(self._web_ping(session, url)) for url in urls]
|
tasks = [asyncio.create_task(self._web_ping(session, url)) for url in urls]
|
||||||
|
|
||||||
text, resp = await concurrent_get_first_result(
|
text, resp = await concurrent_get_first_result(
|
||||||
@@ -466,26 +471,26 @@ class MinerFactory:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _web_ping(
|
async def _web_ping(
|
||||||
session: aiohttp.ClientSession, url: str
|
session: httpx.AsyncClient, url: str
|
||||||
) -> Tuple[Optional[str], Optional[aiohttp.ClientResponse]]:
|
) -> Tuple[Optional[str], Optional[httpx.Response]]:
|
||||||
try:
|
try:
|
||||||
resp = await session.get(url, allow_redirects=False)
|
resp = await session.get(url, follow_redirects=False)
|
||||||
return await resp.text(), resp
|
return resp.text, resp
|
||||||
except (aiohttp.ClientError, asyncio.TimeoutError):
|
except (httpx.HTTPError, asyncio.TimeoutError):
|
||||||
pass
|
pass
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse_web_type(web_text: str, web_resp: aiohttp.ClientResponse) -> MinerTypes:
|
def _parse_web_type(web_text: str, web_resp: httpx.Response) -> MinerTypes:
|
||||||
if web_resp.status == 401 and 'realm="antMiner' in web_resp.headers.get(
|
if web_resp.status_code == 401 and 'realm="antMiner' in web_resp.headers.get(
|
||||||
"www-authenticate", ""
|
"www-authenticate", ""
|
||||||
):
|
):
|
||||||
return MinerTypes.ANTMINER
|
return MinerTypes.ANTMINER
|
||||||
if web_resp.status == 307 and "https://" in web_resp.headers.get(
|
if web_resp.status_code == 307 and "https://" in web_resp.headers.get(
|
||||||
"location", ""
|
"location", ""
|
||||||
):
|
):
|
||||||
return MinerTypes.WHATSMINER
|
return MinerTypes.WHATSMINER
|
||||||
if "Braiins OS" in web_text or 'href="/cgi-bin/luci"' in web_text:
|
if "Braiins OS" in web_text:
|
||||||
return MinerTypes.BRAIINS_OS
|
return MinerTypes.BRAIINS_OS
|
||||||
if "cloud-box" in web_text:
|
if "cloud-box" in web_text:
|
||||||
return MinerTypes.GOLDSHELL
|
return MinerTypes.GOLDSHELL
|
||||||
@@ -576,26 +581,26 @@ class MinerFactory:
|
|||||||
self,
|
self,
|
||||||
ip: Union[ipaddress.ip_address, str],
|
ip: Union[ipaddress.ip_address, str],
|
||||||
location: str,
|
location: str,
|
||||||
auth: Optional[aiohttp.BasicAuth] = None,
|
auth: Optional[httpx.DigestAuth] = None,
|
||||||
) -> Optional[dict]:
|
) -> Optional[dict]:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with httpx.AsyncClient(verify=False) as session:
|
||||||
try:
|
try:
|
||||||
data = await session.get(
|
data = await session.get(
|
||||||
f"http://{str(ip)}{location}",
|
f"http://{str(ip)}{location}",
|
||||||
auth=auth,
|
auth=auth,
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
except (aiohttp.ClientError, asyncio.TimeoutError):
|
except (httpx.HTTPError, asyncio.TimeoutError):
|
||||||
logger.info(f"{ip}: Web command timeout.")
|
logger.info(f"{ip}: Web command timeout.")
|
||||||
return
|
return
|
||||||
if data is None:
|
if data is None:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
json_data = await data.json()
|
json_data = data.json()
|
||||||
except (aiohttp.ContentTypeError, asyncio.TimeoutError):
|
except (json.JSONDecodeError, asyncio.TimeoutError):
|
||||||
try:
|
try:
|
||||||
return json.loads(await data.text())
|
return json.loads(data.text)
|
||||||
except (json.JSONDecodeError, aiohttp.ClientError):
|
except (json.JSONDecodeError, httpx.HTTPError):
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
return json_data
|
return json_data
|
||||||
@@ -691,6 +696,28 @@ class MinerFactory:
|
|||||||
return UnknownMiner(str(ip))
|
return UnknownMiner(str(ip))
|
||||||
|
|
||||||
async def get_miner_model_antminer(self, ip: str):
|
async def get_miner_model_antminer(self, ip: str):
|
||||||
|
tasks = [
|
||||||
|
asyncio.create_task(self._get_model_antminer_web(ip)),
|
||||||
|
asyncio.create_task(self._get_model_antminer_sock(ip)),
|
||||||
|
]
|
||||||
|
|
||||||
|
return await concurrent_get_first_result(tasks, lambda x: x is not None)
|
||||||
|
|
||||||
|
async def _get_model_antminer_web(self, ip: str):
|
||||||
|
# last resort, this is slow
|
||||||
|
auth = httpx.DigestAuth("root", "root")
|
||||||
|
web_json_data = await self.send_web_command(
|
||||||
|
ip, "/cgi-bin/get_system_info.cgi", auth=auth
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
miner_model = web_json_data["minertype"]
|
||||||
|
|
||||||
|
return miner_model
|
||||||
|
except (TypeError, LookupError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_model_antminer_sock(self, ip: str):
|
||||||
sock_json_data = await self.send_api_command(ip, "version")
|
sock_json_data = await self.send_api_command(ip, "version")
|
||||||
try:
|
try:
|
||||||
miner_model = sock_json_data["VERSION"][0]["Type"]
|
miner_model = sock_json_data["VERSION"][0]["Type"]
|
||||||
@@ -715,19 +742,6 @@ class MinerFactory:
|
|||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# last resort, this is slow
|
|
||||||
auth = aiohttp.BasicAuth("root", "root")
|
|
||||||
web_json_data = await self.send_web_command(
|
|
||||||
ip, "/cgi-bin/get_system_info.cgi", auth=auth
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
miner_model = web_json_data["minertype"]
|
|
||||||
|
|
||||||
return miner_model
|
|
||||||
except (TypeError, LookupError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def get_miner_model_goldshell(self, ip: str):
|
async def get_miner_model_goldshell(self, ip: str):
|
||||||
json_data = await self.send_web_command(ip, "/mcb/status")
|
json_data = await self.send_web_command(ip, "/mcb/status")
|
||||||
|
|
||||||
@@ -741,7 +755,7 @@ class MinerFactory:
|
|||||||
async def get_miner_model_whatsminer(self, ip: str):
|
async def get_miner_model_whatsminer(self, ip: str):
|
||||||
sock_json_data = await self.send_api_command(ip, "devdetails")
|
sock_json_data = await self.send_api_command(ip, "devdetails")
|
||||||
try:
|
try:
|
||||||
miner_model = sock_json_data["DEVDETAILS"][0]["Model"]
|
miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace("_", "")
|
||||||
|
|
||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
@@ -760,14 +774,14 @@ class MinerFactory:
|
|||||||
|
|
||||||
async def get_miner_model_innosilicon(self, ip: str) -> Optional[str]:
|
async def get_miner_model_innosilicon(self, ip: str) -> Optional[str]:
|
||||||
try:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with httpx.AsyncClient(verify=False) as session:
|
||||||
auth_req = await session.post(
|
auth_req = await session.post(
|
||||||
f"http://{ip}/api/auth",
|
f"http://{ip}/api/auth",
|
||||||
data={"username": "admin", "password": "admin"},
|
data={"username": "admin", "password": "admin"},
|
||||||
)
|
)
|
||||||
auth = (await auth_req.json())["jwt"]
|
auth = auth_req.json()["jwt"]
|
||||||
|
|
||||||
web_data = await (
|
web_data = (
|
||||||
await session.post(
|
await session.post(
|
||||||
f"http://{ip}/api/type",
|
f"http://{ip}/api/type",
|
||||||
headers={"Authorization": "Bearer " + auth},
|
headers={"Authorization": "Bearer " + auth},
|
||||||
@@ -775,7 +789,7 @@ class MinerFactory:
|
|||||||
)
|
)
|
||||||
).json()
|
).json()
|
||||||
return web_data["type"]
|
return web_data["type"]
|
||||||
except (aiohttp.ClientError, LookupError):
|
except (httpx.HTTPError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_miner_model_braiins_os(self, ip: str) -> Optional[str]:
|
async def get_miner_model_braiins_os(self, ip: str) -> Optional[str]:
|
||||||
@@ -790,16 +804,16 @@ class MinerFactory:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with httpx.AsyncClient(verify=False) as session:
|
||||||
d = await session.post(
|
d = await session.post(
|
||||||
f"http://{ip}/graphql",
|
f"http://{ip}/graphql",
|
||||||
json={"query": "{bosminer {info{modelName}}}"},
|
json={"query": "{bosminer {info{modelName}}}"},
|
||||||
)
|
)
|
||||||
if d.status == 200:
|
if d.status_code == 200:
|
||||||
json_data = await d.json()
|
json_data = d.json()
|
||||||
miner_model = json_data["data"]["bosminer"]["info"]["modelName"]
|
miner_model = json_data["data"]["bosminer"]["info"]["modelName"]
|
||||||
return miner_model
|
return miner_model
|
||||||
except (aiohttp.ClientError, LookupError):
|
except (httpx.HTTPError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def get_miner_model_vnish(self, ip: str) -> Optional[str]:
|
async def get_miner_model_vnish(self, ip: str) -> Optional[str]:
|
||||||
@@ -813,6 +827,9 @@ class MinerFactory:
|
|||||||
if "(88)" in miner_model:
|
if "(88)" in miner_model:
|
||||||
miner_model = miner_model.replace("(88)", "NOPIC")
|
miner_model = miner_model.replace("(88)", "NOPIC")
|
||||||
|
|
||||||
|
if " AML" in miner_model:
|
||||||
|
miner_model = miner_model.replace(" AML", "")
|
||||||
|
|
||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -44,6 +44,24 @@ class S19Pro(AntMiner): # noqa - ignore ABC method implementation
|
|||||||
self.fan_count = 4
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
|
class S19i(AntMiner): # noqa - ignore ABC method implementation
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
|
super().__init__(ip, api_ver)
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "S19i"
|
||||||
|
self.nominal_chips = 80
|
||||||
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
|
class S19Plus(AntMiner): # noqa - ignore ABC method implementation
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
|
super().__init__(ip, api_ver)
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "S19+"
|
||||||
|
self.nominal_chips = 80
|
||||||
|
self.fan_count = 4
|
||||||
|
|
||||||
|
|
||||||
class S19ProPlus(AntMiner): # noqa - ignore ABC method implementation
|
class S19ProPlus(AntMiner): # noqa - ignore ABC method implementation
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
|
|||||||
@@ -20,10 +20,12 @@ from .S19 import (
|
|||||||
S19XP,
|
S19XP,
|
||||||
S19a,
|
S19a,
|
||||||
S19aPro,
|
S19aPro,
|
||||||
|
S19i,
|
||||||
S19j,
|
S19j,
|
||||||
S19jNoPIC,
|
S19jNoPIC,
|
||||||
S19jPro,
|
S19jPro,
|
||||||
S19NoPIC,
|
S19NoPIC,
|
||||||
|
S19Plus,
|
||||||
S19Pro,
|
S19Pro,
|
||||||
S19ProPlus,
|
S19ProPlus,
|
||||||
)
|
)
|
||||||
|
|||||||
35
pyasic/miners/types/whatsminer/M2X/M20P.py
Normal file
35
pyasic/miners/types/whatsminer/M2X/M20P.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from pyasic.miners.makes import WhatsMiner
|
||||||
|
|
||||||
|
|
||||||
|
class M20PV10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
|
super().__init__(ip, api_ver)
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "M20P V10"
|
||||||
|
self.nominal_chips = 156
|
||||||
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
|
class M20PV30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||||
|
super().__init__(ip, api_ver)
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "M20P V30"
|
||||||
|
self.nominal_chips = 148
|
||||||
|
self.fan_count = 2
|
||||||
@@ -42,8 +42,5 @@ class M20SV30(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M20S V30"
|
self.model = "M20S V30"
|
||||||
self.nominal_chips = 0
|
self.nominal_chips = 140
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M20SV30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -24,8 +24,5 @@ class M29V10(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M29 V10"
|
self.model = "M29 V10"
|
||||||
self.nominal_chips = 0
|
self.nominal_chips = 50
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M29V10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from .M20 import M20V10
|
from .M20 import M20V10
|
||||||
|
from .M20P import M20PV10, M20PV30
|
||||||
from .M20S import M20SV10, M20SV20, M20SV30
|
from .M20S import M20SV10, M20SV20, M20SV30
|
||||||
from .M20S_Plus import M20SPlusV30
|
from .M20S_Plus import M20SPlusV30
|
||||||
from .M21 import M21V10
|
from .M21 import M21V10
|
||||||
|
|||||||
@@ -165,10 +165,7 @@ class M30SPlusVE50(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M30S+ VE50"
|
self.model = "M30S+ VE50"
|
||||||
self.nominal_chips = 0
|
self.nominal_chips = 164
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ VE50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -87,10 +87,7 @@ class M50VH60(WhatsMiner): # noqa - ignore ABC method implementation
|
|||||||
super().__init__(ip, api_ver)
|
super().__init__(ip, api_ver)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.model = "M50 VH60"
|
self.model = "M50 VH60"
|
||||||
self.nominal_chips = 0
|
self.nominal_chips = 84
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M50 VH60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
26
pyasic/miners/whatsminer/btminer/M2X/M20P.py
Normal file
26
pyasic/miners/whatsminer/btminer/M2X/M20P.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from pyasic.miners.backends import M2X
|
||||||
|
from pyasic.miners.types import M20PV10, M20PV30
|
||||||
|
|
||||||
|
|
||||||
|
class BTMinerM20PV10(M2X, M20PV10):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BTMinerM20PV30(M2X, M20PV30):
|
||||||
|
pass
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from .M20 import BTMinerM20V10
|
from .M20 import BTMinerM20V10
|
||||||
|
from .M20P import BTMinerM20PV10, BTMinerM20PV30
|
||||||
from .M20S import BTMinerM20SV10, BTMinerM20SV20, BTMinerM20SV30
|
from .M20S import BTMinerM20SV10, BTMinerM20SV20, BTMinerM20SV30
|
||||||
from .M20S_Plus import BTMinerM20SPlusV30
|
from .M20S_Plus import BTMinerM20SPlusV30
|
||||||
from .M21 import BTMinerM21V10
|
from .M21 import BTMinerM21V10
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ import ipaddress
|
|||||||
import logging
|
import logging
|
||||||
from typing import AsyncIterator, List, Union
|
from typing import AsyncIterator, List, Union
|
||||||
|
|
||||||
|
from pyasic import settings
|
||||||
from pyasic.miners.miner_factory import AnyMiner, miner_factory
|
from pyasic.miners.miner_factory import AnyMiner, miner_factory
|
||||||
from pyasic.network.net_range import MinerNetworkRange
|
from pyasic.network.net_range import MinerNetworkRange
|
||||||
from pyasic.settings import PyasicSettings
|
|
||||||
|
|
||||||
|
|
||||||
class MinerNetwork:
|
class MinerNetwork:
|
||||||
@@ -108,7 +108,7 @@ class MinerNetwork:
|
|||||||
# clear cached miners
|
# clear cached miners
|
||||||
miner_factory.clear_cached_miners()
|
miner_factory.clear_cached_miners()
|
||||||
|
|
||||||
limit = asyncio.Semaphore(PyasicSettings().network_scan_threads)
|
limit = asyncio.Semaphore(settings.get("network_scan_threads", 300))
|
||||||
miners = await asyncio.gather(
|
miners = await asyncio.gather(
|
||||||
*[self.ping_and_get_miner(host, limit) for host in local_network.hosts()]
|
*[self.ping_and_get_miner(host, limit) for host in local_network.hosts()]
|
||||||
)
|
)
|
||||||
@@ -136,7 +136,7 @@ class MinerNetwork:
|
|||||||
local_network = self.get_network()
|
local_network = self.get_network()
|
||||||
|
|
||||||
# create a list of scan tasks
|
# create a list of scan tasks
|
||||||
limit = asyncio.Semaphore(PyasicSettings().network_scan_threads)
|
limit = asyncio.Semaphore(settings.get("network_scan_threads", 300))
|
||||||
miners = asyncio.as_completed(
|
miners = asyncio.as_completed(
|
||||||
[
|
[
|
||||||
loop.create_task(self.ping_and_get_miner(host, limit))
|
loop.create_task(self.ping_and_get_miner(host, limit))
|
||||||
@@ -191,12 +191,12 @@ class MinerNetwork:
|
|||||||
async def ping_miner(
|
async def ping_miner(
|
||||||
ip: ipaddress.ip_address, port=4028
|
ip: ipaddress.ip_address, port=4028
|
||||||
) -> Union[None, ipaddress.ip_address]:
|
) -> Union[None, ipaddress.ip_address]:
|
||||||
for i in range(PyasicSettings().network_ping_retries):
|
for i in range(settings.get("network_ping_retries", 1)):
|
||||||
try:
|
try:
|
||||||
connection_fut = asyncio.open_connection(str(ip), port)
|
connection_fut = asyncio.open_connection(str(ip), port)
|
||||||
# get the read and write streams from the connection
|
# get the read and write streams from the connection
|
||||||
reader, writer = await asyncio.wait_for(
|
reader, writer = await asyncio.wait_for(
|
||||||
connection_fut, timeout=PyasicSettings().network_ping_timeout
|
connection_fut, timeout=settings.get("network_ping_timeout", 3)
|
||||||
)
|
)
|
||||||
# immediately close connection, we know connection happened
|
# immediately close connection, we know connection happened
|
||||||
writer.close()
|
writer.close()
|
||||||
@@ -220,12 +220,12 @@ async def ping_miner(
|
|||||||
async def ping_and_get_miner(
|
async def ping_and_get_miner(
|
||||||
ip: ipaddress.ip_address, port=4028
|
ip: ipaddress.ip_address, port=4028
|
||||||
) -> Union[None, AnyMiner]:
|
) -> Union[None, AnyMiner]:
|
||||||
for i in range(PyasicSettings().network_ping_retries):
|
for i in range(settings.get("network_ping_retries", 1)):
|
||||||
try:
|
try:
|
||||||
connection_fut = asyncio.open_connection(str(ip), port)
|
connection_fut = asyncio.open_connection(str(ip), port)
|
||||||
# get the read and write streams from the connection
|
# get the read and write streams from the connection
|
||||||
reader, writer = await asyncio.wait_for(
|
reader, writer = await asyncio.wait_for(
|
||||||
connection_fut, timeout=PyasicSettings().network_ping_timeout
|
connection_fut, timeout=settings.get("network_ping_timeout", 3)
|
||||||
)
|
)
|
||||||
# immediately close connection, we know connection happened
|
# immediately close connection, we know connection happened
|
||||||
writer.close()
|
writer.close()
|
||||||
|
|||||||
@@ -14,27 +14,26 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from typing import Any
|
||||||
|
|
||||||
from pyasic.misc import Singleton
|
_settings = { # defaults
|
||||||
|
"network_ping_retries": 1,
|
||||||
|
"network_ping_timeout": 3,
|
||||||
|
"network_scan_threads": 300,
|
||||||
|
"factory_get_retries": 1,
|
||||||
|
"get_data_retries": 1,
|
||||||
|
"default_whatsminer_password": "admin",
|
||||||
|
"default_innosilicon_password": "admin",
|
||||||
|
"default_antminer_password": "root",
|
||||||
|
"default_bosminer_password": "root",
|
||||||
|
"default_vnish_password": "admin",
|
||||||
|
"default_goldshell_password": "123456789",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
def get(key: str, other: Any = None) -> Any:
|
||||||
class PyasicSettings(metaclass=Singleton):
|
return _settings.get(key, other)
|
||||||
network_ping_retries: int = 1
|
|
||||||
network_ping_timeout: int = 3
|
|
||||||
network_scan_threads: int = 300
|
|
||||||
|
|
||||||
miner_factory_get_version_retries: int = 1
|
|
||||||
|
|
||||||
miner_get_data_retries: int = 1
|
def update(key: str, val: Any) -> Any:
|
||||||
|
_settings[key] = val
|
||||||
global_whatsminer_password = "admin"
|
|
||||||
global_innosilicon_password = "admin"
|
|
||||||
global_antminer_password = "root"
|
|
||||||
global_bosminer_password = "root"
|
|
||||||
global_vnish_password = "admin"
|
|
||||||
global_goldshell_password = "123456789"
|
|
||||||
|
|
||||||
debug: bool = False
|
|
||||||
logfile: bool = False
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from pyasic.errors import APIWarning
|
|||||||
class BaseWebAPI(ABC):
|
class BaseWebAPI(ABC):
|
||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
# ip address of the miner
|
# ip address of the miner
|
||||||
self.ip = ipaddress.ip_address(ip)
|
self.ip = ip # ipaddress.ip_address(ip)
|
||||||
self.username = "root"
|
self.username = "root"
|
||||||
self.pwd = "root"
|
self.pwd = "root"
|
||||||
|
|
||||||
|
|||||||
@@ -13,19 +13,20 @@
|
|||||||
# 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 asyncio
|
||||||
import json
|
import json
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from pyasic.settings import PyasicSettings
|
from pyasic import settings
|
||||||
from pyasic.web import BaseWebAPI
|
from pyasic.web import BaseWebAPI
|
||||||
|
|
||||||
|
|
||||||
class AntminerModernWebAPI(BaseWebAPI):
|
class AntminerModernWebAPI(BaseWebAPI):
|
||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.pwd = PyasicSettings().global_antminer_password
|
self.pwd = settings.get("default_antminer_password", "root")
|
||||||
|
|
||||||
async def send_command(
|
async def send_command(
|
||||||
self,
|
self,
|
||||||
@@ -56,25 +57,37 @@ class AntminerModernWebAPI(BaseWebAPI):
|
|||||||
async def multicommand(
|
async def multicommand(
|
||||||
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
||||||
) -> dict:
|
) -> dict:
|
||||||
data = {k: None for k in commands}
|
|
||||||
data["multicommand"] = True
|
|
||||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
for command in commands:
|
tasks = [
|
||||||
try:
|
asyncio.create_task(self._handle_multicommand(client, command))
|
||||||
url = f"http://{self.ip}/cgi-bin/{command}.cgi"
|
for command in commands
|
||||||
ret = await client.get(url, auth=auth)
|
]
|
||||||
except httpx.HTTPError:
|
all_data = await asyncio.gather(*tasks)
|
||||||
pass
|
|
||||||
else:
|
data = {}
|
||||||
if ret.status_code == 200:
|
for item in all_data:
|
||||||
try:
|
data.update(item)
|
||||||
json_data = ret.json()
|
|
||||||
data[command] = json_data
|
data["multicommand"] = True
|
||||||
except json.decoder.JSONDecodeError:
|
|
||||||
pass
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
async def _handle_multicommand(self, client: httpx.AsyncClient, command: str):
|
||||||
|
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = f"http://{self.ip}/cgi-bin/{command}.cgi"
|
||||||
|
ret = await client.get(url, auth=auth)
|
||||||
|
except httpx.HTTPError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if ret.status_code == 200:
|
||||||
|
try:
|
||||||
|
json_data = ret.json()
|
||||||
|
return {command: json_data}
|
||||||
|
except json.decoder.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
return {command: {}}
|
||||||
|
|
||||||
async def get_miner_conf(self) -> dict:
|
async def get_miner_conf(self) -> dict:
|
||||||
return await self.send_command("get_miner_conf")
|
return await self.send_command("get_miner_conf")
|
||||||
|
|
||||||
@@ -124,7 +137,7 @@ class AntminerModernWebAPI(BaseWebAPI):
|
|||||||
class AntminerOldWebAPI(BaseWebAPI):
|
class AntminerOldWebAPI(BaseWebAPI):
|
||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.pwd = PyasicSettings().global_antminer_password
|
self.pwd = settings.get("default_antminer_password", "root")
|
||||||
|
|
||||||
async def send_command(
|
async def send_command(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -18,15 +18,14 @@ from typing import Union
|
|||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from pyasic import APIError
|
from pyasic import APIError, settings
|
||||||
from pyasic.settings import PyasicSettings
|
|
||||||
from pyasic.web import BaseWebAPI
|
from pyasic.web import BaseWebAPI
|
||||||
|
|
||||||
|
|
||||||
class BOSMinerWebAPI(BaseWebAPI):
|
class BOSMinerWebAPI(BaseWebAPI):
|
||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.pwd = PyasicSettings().global_bosminer_password
|
self.pwd = settings.get("default_bosminer_password", "root")
|
||||||
|
|
||||||
async def send_command(
|
async def send_command(
|
||||||
self,
|
self,
|
||||||
@@ -58,11 +57,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 +75,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 +87,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 +150,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:
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from typing import Union
|
|||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from pyasic.settings import PyasicSettings
|
from pyasic import settings
|
||||||
from pyasic.web import BaseWebAPI
|
from pyasic.web import BaseWebAPI
|
||||||
|
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ class GoldshellWebAPI(BaseWebAPI):
|
|||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.username = "admin"
|
self.username = "admin"
|
||||||
self.pwd = PyasicSettings().global_goldshell_password
|
self.pwd = settings.get("default_goldshell_password", "123456789")
|
||||||
self.jwt = None
|
self.jwt = None
|
||||||
|
|
||||||
async def auth(self):
|
async def auth(self):
|
||||||
@@ -72,7 +72,7 @@ class GoldshellWebAPI(BaseWebAPI):
|
|||||||
if not self.jwt:
|
if not self.jwt:
|
||||||
await self.auth()
|
await self.auth()
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
for i in range(PyasicSettings().miner_get_data_retries):
|
for i in range(settings.get("get_data_retries", 1)):
|
||||||
try:
|
try:
|
||||||
if parameters:
|
if parameters:
|
||||||
response = await client.put(
|
response = await client.put(
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ from typing import Union
|
|||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
|
from pyasic import settings
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.settings import PyasicSettings
|
|
||||||
from pyasic.web import BaseWebAPI
|
from pyasic.web import BaseWebAPI
|
||||||
|
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ class InnosiliconWebAPI(BaseWebAPI):
|
|||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.username = "admin"
|
self.username = "admin"
|
||||||
self.pwd = PyasicSettings().global_innosilicon_password
|
self.pwd = settings.get("default_innosilicon_password", "admin")
|
||||||
self.jwt = None
|
self.jwt = None
|
||||||
|
|
||||||
async def auth(self):
|
async def auth(self):
|
||||||
@@ -55,7 +55,7 @@ class InnosiliconWebAPI(BaseWebAPI):
|
|||||||
if not self.jwt:
|
if not self.jwt:
|
||||||
await self.auth()
|
await self.auth()
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
for i in range(PyasicSettings().miner_get_data_retries):
|
for i in range(settings.get("get_data_retries", 1)):
|
||||||
try:
|
try:
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
f"http://{self.ip}/api/{command}",
|
f"http://{self.ip}/api/{command}",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from typing import Union
|
|||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from pyasic.settings import PyasicSettings
|
from pyasic import settings
|
||||||
from pyasic.web import BaseWebAPI
|
from pyasic.web import BaseWebAPI
|
||||||
|
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ class VNishWebAPI(BaseWebAPI):
|
|||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.username = "admin"
|
self.username = "admin"
|
||||||
self.pwd = PyasicSettings().global_vnish_password
|
self.pwd = settings.get("default_vnish_password", "admin")
|
||||||
self.token = None
|
self.token = None
|
||||||
|
|
||||||
async def auth(self):
|
async def auth(self):
|
||||||
@@ -59,7 +59,7 @@ class VNishWebAPI(BaseWebAPI):
|
|||||||
if not self.token:
|
if not self.token:
|
||||||
await self.auth()
|
await self.auth()
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
for i in range(PyasicSettings().miner_get_data_retries):
|
for i in range(settings.get("get_data_retries", 1)):
|
||||||
try:
|
try:
|
||||||
auth = self.token
|
auth = self.token
|
||||||
if command.startswith("system"):
|
if command.startswith("system"):
|
||||||
@@ -116,8 +116,32 @@ class VNishWebAPI(BaseWebAPI):
|
|||||||
async def reboot(self) -> dict:
|
async def reboot(self) -> dict:
|
||||||
return await self.send_command("system/reboot", post=True)
|
return await self.send_command("system/reboot", post=True)
|
||||||
|
|
||||||
|
async def pause_mining(self) -> dict:
|
||||||
|
return await self.send_command("mining/pause", post=True)
|
||||||
|
|
||||||
|
async def resume_mining(self) -> dict:
|
||||||
|
return await self.send_command("mining/resume", post=True)
|
||||||
|
|
||||||
|
async def stop_mining(self) -> dict:
|
||||||
|
return await self.send_command("mining/stop", post=True)
|
||||||
|
|
||||||
|
async def start_mining(self) -> dict:
|
||||||
|
return await self.send_command("mining/start", post=True)
|
||||||
|
|
||||||
async def info(self):
|
async def info(self):
|
||||||
return await self.send_command("info")
|
return await self.send_command("info")
|
||||||
|
|
||||||
async def summary(self):
|
async def summary(self):
|
||||||
return await self.send_command("summary")
|
return await self.send_command("summary")
|
||||||
|
|
||||||
|
async def chips(self):
|
||||||
|
return await self.send_command("chips")
|
||||||
|
|
||||||
|
async def layout(self):
|
||||||
|
return await self.send_command("layout")
|
||||||
|
|
||||||
|
async def status(self):
|
||||||
|
return await self.send_command("status")
|
||||||
|
|
||||||
|
async def settings(self):
|
||||||
|
return await self.send_command("settings")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "pyasic"
|
name = "pyasic"
|
||||||
version = "0.36.2"
|
version = "0.39.0"
|
||||||
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"
|
||||||
@@ -14,7 +14,6 @@ httpx = "^0.24.0"
|
|||||||
passlib = "^1.7.4"
|
passlib = "^1.7.4"
|
||||||
pyaml = "^23.5.9"
|
pyaml = "^23.5.9"
|
||||||
toml = "^0.10.2"
|
toml = "^0.10.2"
|
||||||
aiohttp = "^3.8.4"
|
|
||||||
|
|
||||||
[tool.poetry.group.dev]
|
[tool.poetry.group.dev]
|
||||||
optional = true
|
optional = true
|
||||||
|
|||||||
Reference in New Issue
Block a user