Compare commits
174 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbd73881d4 | ||
|
|
68c4dadb63 | ||
|
|
87016670d4 | ||
|
|
8701bbe4e2 | ||
|
|
7d1f125b0b | ||
|
|
e433902bd5 | ||
|
|
a653772968 | ||
|
|
d8b938cd5b | ||
|
|
47d76e325a | ||
|
|
7ee7868094 | ||
|
|
3f1183a4f9 | ||
|
|
2b443497ea | ||
|
|
c3972f9524 | ||
|
|
92bbb21c11 | ||
|
|
1980ff2563 | ||
|
|
93d09a1483 | ||
|
|
690d0d99df | ||
|
|
78f689eb2c | ||
|
|
e68f188e8f | ||
|
|
7eda611fe9 | ||
|
|
1d12817942 | ||
|
|
b24efd4c69 | ||
|
|
5533135b05 | ||
|
|
475054fbe0 | ||
|
|
06bad1bbe0 | ||
|
|
f3746ff756 | ||
|
|
9f16d37c8b | ||
|
|
8a13c7940a | ||
|
|
8bea76ff67 | ||
|
|
1504bd744c | ||
|
|
6449f10615 | ||
|
|
d79509bda7 | ||
|
|
630b847466 | ||
|
|
ed11611919 | ||
|
|
e2431c938d | ||
|
|
60f4b4a5ed | ||
|
|
d41097af20 | ||
|
|
8a5d505731 | ||
|
|
36e76c6f41 | ||
|
|
717b9421dd | ||
|
|
d2f71e8c94 | ||
|
|
697991f28f | ||
|
|
b0e18ab766 | ||
|
|
e39a6921d0 | ||
|
|
aac1be0565 | ||
|
|
683fcb2138 | ||
|
|
9fbbef9b18 | ||
|
|
6e0b9a0a7b | ||
|
|
7f472f6f4f | ||
|
|
b7d7b33ab9 | ||
|
|
da11c0bb1f | ||
|
|
eae433d2bd | ||
|
|
c16bc37aff | ||
|
|
255b06ac9e | ||
|
|
29ec619126 | ||
|
|
247def04ff | ||
|
|
4600e7d953 | ||
|
|
850c266555 | ||
|
|
ad374fe2fb | ||
|
|
5ca39b6fe7 | ||
|
|
b50dd26e6f | ||
|
|
53eaccaa9b | ||
|
|
91f207316a | ||
|
|
1e37418909 | ||
|
|
4c09ba5529 | ||
|
|
7bab4747ad | ||
|
|
fd8cc7378c | ||
|
|
8aeef4d5e7 | ||
|
|
4bafde9da7 | ||
|
|
5a3107aecf | ||
|
|
7e758720f0 | ||
|
|
39e3e249f8 | ||
|
|
118c5b056e | ||
|
|
2c3b5599fe | ||
|
|
e421eaa324 | ||
|
|
75d6bc6808 | ||
|
|
98c547e416 | ||
|
|
45250e36e4 | ||
|
|
fa7544d052 | ||
|
|
53f3fc5ee9 | ||
|
|
1b36de4131 | ||
|
|
6f0c6f6284 | ||
|
|
b7dda5bf87 | ||
|
|
14f33a40c3 | ||
|
|
5c904aced0 | ||
|
|
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 | ||
|
|
666b9dfe94 | ||
|
|
df3a080c9d | ||
|
|
bf3bd7c2b9 | ||
|
|
37fd60b462 | ||
|
|
2245904740 | ||
|
|
7b1b23016e | ||
|
|
b5fcd62e23 | ||
|
|
9057cde274 | ||
|
|
f6d35888fe | ||
|
|
f2abe9fd9e | ||
|
|
7d1a702804 | ||
|
|
65d1695ce4 | ||
|
|
65fd66b8bf | ||
|
|
5db52c46f3 | ||
|
|
d06cb19da3 | ||
|
|
4530d086da |
@@ -1,5 +1,5 @@
|
||||
# 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://pypi.org/project/pyasic/)
|
||||
|
||||
@@ -15,6 +15,7 @@ Use these instead -
|
||||
#### [BOSMiner API][pyasic.API.bosminer.BOSMinerAPI]
|
||||
#### [BTMiner API][pyasic.API.btminer.BTMinerAPI]
|
||||
#### [CGMiner API][pyasic.API.cgminer.CGMinerAPI]
|
||||
#### [LUXMiner API][pyasic.API.luxminer.LUXMinerAPI]
|
||||
#### [Unknown API][pyasic.API.unknown.UnknownAPI]
|
||||
|
||||
<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
|
||||
165
docs/generate_miners.py
Normal file
165
docs/generate_miners.py
Normal file
@@ -0,0 +1,165 @@
|
||||
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.EPIC:
|
||||
return "ePIC 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))
|
||||
@@ -19,7 +19,7 @@ Getting started with pyasic is easy. First, find your miner (or miners) on the
|
||||
|
||||
## Scanning for miners
|
||||
To scan for miners in pyasic, we use the class [`MinerNetwork`][pyasic.network.MinerNetwork], which abstracts the search, communication, identification, setup, and return of a miner to 1 command.
|
||||
The command [`MinerNetwork().scan_network_for_miners()`][pyasic.network.MinerNetwork.scan_network_for_miners] returns a list that contains any miners found.
|
||||
The command [`MinerNetwork.scan()`][pyasic.network.MinerNetwork.scan] returns a list that contains any miners found.
|
||||
```python
|
||||
import asyncio # asyncio for handling the async part
|
||||
from pyasic.network import MinerNetwork # miner network handles the scanning
|
||||
@@ -28,7 +28,7 @@ from pyasic.network import MinerNetwork # miner network handles the scanning
|
||||
async def scan_miners(): # define async scan function to allow awaiting
|
||||
# create a miner network
|
||||
# you can pass in any IP and it will use that in a subnet with a /24 mask (255 IPs).
|
||||
network = MinerNetwork("192.168.1.50") # this uses the 192.168.1.0-255 network
|
||||
network = MinerNetwork.from_subnet("192.168.1.50/24") # this uses the 192.168.1.0-255 network
|
||||
|
||||
# scan for miners asynchronously
|
||||
# this will return the correct type of miners if they are supported with all functionality.
|
||||
@@ -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].
|
||||
```python
|
||||
import asyncio
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
from pyasic import get_miner
|
||||
|
||||
async def gather_miner_data():
|
||||
miner = await MinerFactory().get_miner("192.168.1.75")
|
||||
miner_data = await miner.get_data()
|
||||
print(miner_data) # all data from the dataclass
|
||||
print(miner_data.hashrate) # hashrate of the miner in TH/s
|
||||
miner = await get_miner("192.168.1.75")
|
||||
if miner is not None:
|
||||
miner_data = await miner.get_data()
|
||||
print(miner_data) # all data from the dataclass
|
||||
print(miner_data.hashrate) # hashrate of the miner in TH/s
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(gather_miner_data())
|
||||
@@ -126,6 +127,7 @@ These functions are
|
||||
[`restart_backend`](#restart-backend),
|
||||
[`stop_mining`](#stop-mining),
|
||||
[`resume_mining`](#resume-mining),
|
||||
[`is_mining`](#is-mining),
|
||||
[`send_config`](#send-config), and
|
||||
[`set_power_limit`](#set-power-limit).
|
||||
|
||||
@@ -227,6 +229,14 @@ These functions are
|
||||
|
||||
<br>
|
||||
|
||||
### Is Mining
|
||||
::: pyasic.miners.BaseMiner.is_mining
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Send Config
|
||||
::: pyasic.miners.BaseMiner.send_config
|
||||
handler: python
|
||||
@@ -253,7 +263,7 @@ Pyasic implements a few dataclasses as helpers to make data return types consist
|
||||
|
||||
[`MinerData`][pyasic.data.MinerData] is a return from the [`get_data()`](#get-data) function, and is used to have a consistent dataset across all returns.
|
||||
|
||||
You can call [`MinerData.asdict()`][pyasic.data.MinerData.asdict] to get the dataclass as a dictionary, and there are many other helper functions contained in the class to convert to different data formats.
|
||||
You can call [`MinerData.as_dict()`][pyasic.data.MinerData.as_dict] to get the dataclass as a dictionary, and there are many other helper functions contained in the class to convert to different data formats.
|
||||
|
||||
[`MinerData`][pyasic.data.MinerData] instances can also be added to each other to combine their data and can be divided by a number to divide all their data, allowing you to get average data from many miners by doing -
|
||||
```python
|
||||
@@ -278,3 +288,28 @@ It is the return from [`get_config()`](#get-config).
|
||||
|
||||
Each miner has a unique way to convert the [`MinerConfig`][pyasic.config.MinerConfig] to their specific type, there are helper functions in the class.
|
||||
In most cases these helper functions should not be used, as [`send_config()`](#send-config) takes a [`MinerConfig`][pyasic.config.MinerConfig] and will do the conversion to the right type for you.
|
||||
|
||||
## Settings
|
||||
`pyasic` has settings designed to make using large groups of miners easier. You can set the default password for all types of miners using the [`pyasic.settings`][pyasic.settings] module, used as follows:
|
||||
|
||||
```python
|
||||
from pyasic import settings
|
||||
|
||||
settings.update("default_antminer_password", "my_pwd")
|
||||
```
|
||||
|
||||
Here are of all the settings, and their default values:
|
||||
```
|
||||
"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_epic_password": "letmein",
|
||||
"default_goldshell_password": "123456789",
|
||||
```
|
||||
|
||||
@@ -29,6 +29,20 @@
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19i
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19i
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19+
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j No PIC
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jNoPIC
|
||||
handler: python
|
||||
@@ -113,6 +127,13 @@
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T19 (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X19.T19.BOSMinerT19
|
||||
handler: python
|
||||
@@ -127,6 +148,13 @@
|
||||
show_root_heading: false
|
||||
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)
|
||||
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19Pro
|
||||
handler: python
|
||||
@@ -169,3 +197,38 @@
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 (ePIC)
|
||||
::: pyasic.miners.antminer.epic.X19.S19.ePICS19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro (ePIC)
|
||||
::: pyasic.miners.antminer.epic.X19.S19.ePICS19Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j (ePIC)
|
||||
::: pyasic.miners.antminer.epic.X19.S19.ePICS19j
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro (ePIC)
|
||||
::: pyasic.miners.antminer.epic.X19.S19.ePICS19jPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 XP (ePIC)
|
||||
::: pyasic.miners.antminer.epic.X19.S19.ePICS19XP
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
@@ -50,3 +50,10 @@
|
||||
show_root_heading: false
|
||||
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/epic.md
Normal file
8
docs/miners/backends/epic.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# pyasic
|
||||
## ePIC Backend
|
||||
|
||||
::: pyasic.miners.backends.epic.ePIC
|
||||
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
|
||||
@@ -1,6 +1,14 @@
|
||||
# pyasic
|
||||
## Miner Factory
|
||||
|
||||
[`MinerFactory`][pyasic.miners.miner_factory.MinerFactory] is the way to create miner types in `pyasic`. The most important method is [`get_miner()`][pyasic.get_miner], which is mapped to [`pyasic.get_miner()`][pyasic.get_miner], and should be used from there.
|
||||
|
||||
The instance used for [`pyasic.get_miner()`][pyasic.get_miner] is `pyasic.miner_factory`.
|
||||
|
||||
[`MinerFactory`][pyasic.MinerFactory] also keeps a cache, which can be cleared if needed with `pyasic.miner_factory.clear_cached_miners()`.
|
||||
|
||||
Finally, there is functionality to get multiple miners without using `asyncio.gather()` explicitly. Use `pyasic.miner_factory.get_multiple_miners()` with a list of IPs as strings to get a list of miner instances. You can also get multiple miners with an `AsyncGenerator` by using `pyasic.miner_factory.get_miner_generator()`.
|
||||
|
||||
::: pyasic.miners.miner_factory.MinerFactory
|
||||
handler: python
|
||||
options:
|
||||
|
||||
@@ -70,6 +70,8 @@ details {
|
||||
<li><a href="../antminer/X19#s19l">S19L</a></li>
|
||||
<li><a href="../antminer/X19#s19-pro">S19 Pro</a></li>
|
||||
<li><a href="../antminer/X19#s19j">S19j</a></li>
|
||||
<li><a href="../antminer/X19#s19i">S19i</a></li>
|
||||
<li><a href="../antminer/X19#s19_1">S19+</a></li>
|
||||
<li><a href="../antminer/X19#s19j-no-pic">S19j No PIC</a></li>
|
||||
<li><a href="../antminer/X19#s19-pro_1">S19 Pro+</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro">S19j Pro</a></li>
|
||||
@@ -91,6 +93,8 @@ details {
|
||||
<li><a href="../whatsminer/M2X#m20s-v10">M20S V10</a></li>
|
||||
<li><a href="../whatsminer/M2X#m20s-v20">M20S V20</a></li>
|
||||
<li><a href="../whatsminer/M2X#m20s-v30">M20S V30</a></li>
|
||||
<li><a href="../whatsminer/M2X#m20p-v10">M20P V10</a></li>
|
||||
<li><a href="../whatsminer/M2X#m20p-v30">M20P V30</a></li>
|
||||
<li><a href="../whatsminer/M2X#m20s_1-v30">M20S+ V30</a></li>
|
||||
<li><a href="../whatsminer/M2X#m21-v10">M21 V10</a></li>
|
||||
<li><a href="../whatsminer/M2X#m21s-v20">M21S V20</a></li>
|
||||
@@ -105,6 +109,8 @@ details {
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M3X#m30-v10">M30 V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30-v20">M30 V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30k-v10">M30K V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30l-v10">M30L V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v10">M30S V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v20">M30S V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v30">M30S V30</a></li>
|
||||
@@ -154,6 +160,7 @@ details {
|
||||
<li><a href="../whatsminer/M3X#m30s_1-ve100">M30S+ VE100</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vf20">M30S+ VF20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vf30">M30S+ VF30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vg20">M30S+ VG20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vg30">M30S+ VG30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vg40">M30S+ VG40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vg50">M30S+ VG50</a></li>
|
||||
@@ -187,6 +194,9 @@ details {
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-vj30">M30S++ VJ30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31-v10">M31 V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31-v20">M31 V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31h-v10">M31H V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31h-v40">M31H V40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30l-v10">M30L V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-v10">M31S V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-v20">M31S V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-v30">M31S V30</a></li>
|
||||
@@ -202,7 +212,6 @@ details {
|
||||
<li><a href="../whatsminer/M3X#m31se-v10">M31SE V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31se-v20">M31SE V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31se-v30">M31SE V30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31h-v40">M31H V40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-v10">M31S+ V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-v20">M31S+ V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-v30">M31S+ V30</a></li>
|
||||
@@ -229,6 +238,7 @@ details {
|
||||
<li><a href="../whatsminer/M3X#m33-v20">M33 V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33-v30">M33 V30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33s-vg30">M33S VG30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33s_1-vg20">M33S+ VG20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33s_1-vh20">M33S+ VH20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33s_1-vh30">M33S+ VH30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33s_1_1-vh20">M33S++ VH20</a></li>
|
||||
@@ -238,12 +248,15 @@ details {
|
||||
<li><a href="../whatsminer/M3X#m36s-ve10">M36S VE10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m36s_1-vg30">M36S+ VG30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m36s_1_1-vh30">M36S++ VH30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m39-v10">M39 V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m39-v20">M39 V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m39-v30">M39 V30</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>M5X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M5X#m50-ve30">M50 VE30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vg30">M50 VG30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh10">M50 VH10</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh20">M50 VH20</a></li>
|
||||
@@ -394,6 +407,7 @@ details {
|
||||
<li><a href="../antminer/X19#s19j-bos">S19j (BOS)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-no-pic-bos">S19j No PIC (BOS)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro-bos">S19j Pro (BOS)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro-bos">S19j Pro (BOS)</a></li>
|
||||
<li><a href="../antminer/X19#t19-bos">T19 (BOS)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
@@ -419,6 +433,7 @@ details {
|
||||
<summary>X19 Series:</summary>
|
||||
<ul>
|
||||
<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#s19j-vnish">S19j (VNish)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
|
||||
@@ -430,6 +445,21 @@ details {
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>ePIC Firmware Miners:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>X19 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X19#s19-epic">S19 (ePIC)</a></li>
|
||||
<li><a href="../antminer/X19#s19-pro-epic">S19 Pro (ePIC)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-epic">S19j (ePIC)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro-epic">S19j Pro (ePIC)</a></li>
|
||||
<li><a href="../antminer/X19#s19-xp-epic">S19 XP (ePIC)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>HiveOS Firmware Miners:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
@@ -439,4 +469,15 @@ details {
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</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>
|
||||
@@ -29,6 +29,20 @@
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M20P V10
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20P.BTMinerM20PV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M20P V30
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20P.BTMinerM20PV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M20S+ V30
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlusV30
|
||||
handler: python
|
||||
|
||||
@@ -15,6 +15,20 @@
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30K V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30K.BTMinerM30KV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30L V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30L.BTMinerM30LV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV10
|
||||
handler: python
|
||||
@@ -358,6 +372,13 @@
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VG20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VG30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG30
|
||||
handler: python
|
||||
@@ -589,6 +610,27 @@
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31H V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31H.BTMinerM31HV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31H V40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31H.BTMinerM31HV40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30L V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31L.BTMinerM31LV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV10
|
||||
handler: python
|
||||
@@ -694,13 +736,6 @@
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31H V40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31H.BTMinerM31HV40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV10
|
||||
handler: python
|
||||
@@ -883,6 +918,13 @@
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M33S+ VG20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M33S_Plus.BTMinerM33SPlusVG20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M33S+ VH20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M33S_Plus.BTMinerM33SPlusVH20
|
||||
handler: python
|
||||
@@ -946,6 +988,13 @@
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M39 V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M39.BTMinerM39V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M39 V20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M39.BTMinerM39V20
|
||||
handler: python
|
||||
@@ -953,3 +1002,10 @@
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M39 V30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M39.BTMinerM39V30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
# pyasic
|
||||
## M5X Models
|
||||
|
||||
## M50 VE30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VE30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50 VG30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VG30
|
||||
handler: python
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# pyasic
|
||||
## Miner Network Range
|
||||
|
||||
[`MinerNetworkRange`][pyasic.network.net_range.MinerNetworkRange] is a class used by [`MinerNetwork`][pyasic.network.MinerNetwork] to handle any constructor stings.
|
||||
The goal is to emulate what is produced by `ipaddress.ip_network` by allowing [`MinerNetwork`][pyasic.network.MinerNetwork] to get a list of hosts.
|
||||
This allows this class to be the [`MinerNetwork.network`][pyasic.network.MinerNetwork] and hence be used for scanning.
|
||||
|
||||
::: pyasic.network.net_range.MinerNetworkRange
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
35
docs/settings/settings.md
Normal file
35
docs/settings/settings.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# pyasic
|
||||
## settings
|
||||
|
||||
All settings here are global settings for all of pyasic. Set these settings with `update(key, value)`.
|
||||
|
||||
Settings options:
|
||||
- `network_ping_retries`
|
||||
- `network_ping_timeout`
|
||||
- `network_scan_threads`
|
||||
- `factory_get_retries`
|
||||
- `factory_get_timeout`
|
||||
- `get_data_retries`
|
||||
- `api_function_timeout`
|
||||
- `default_whatsminer_password`
|
||||
- `default_innosilicon_password`
|
||||
- `default_antminer_password`
|
||||
- `default_bosminer_password`
|
||||
- `default_vnish_password`
|
||||
- `default_goldshell_password`
|
||||
- `socket_linger_time`
|
||||
|
||||
|
||||
### get
|
||||
::: pyasic.settings.get
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
### update
|
||||
::: pyasic.settings.update
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
15
mkdocs.yml
15
mkdocs.yml
@@ -7,7 +7,6 @@ nav:
|
||||
- Miner Factory: "miners/miner_factory.md"
|
||||
- Network:
|
||||
- Miner Network: "network/miner_network.md"
|
||||
- Miner Network Range: "network/miner_network_range.md"
|
||||
- Dataclasses:
|
||||
- Miner Data: "data/miner_data.md"
|
||||
- Error Codes: "data/error_codes.md"
|
||||
@@ -20,6 +19,7 @@ nav:
|
||||
- BOSMiner: "API/bosminer.md"
|
||||
- BTMiner: "API/btminer.md"
|
||||
- CGMiner: "API/cgminer.md"
|
||||
- LUXMiner: "API/luxminer.md"
|
||||
- Unknown: "API/unknown.md"
|
||||
- Backends:
|
||||
- BMMiner: "miners/backends/bmminer.md"
|
||||
@@ -27,6 +27,9 @@ nav:
|
||||
- BFGMiner: "miners/backends/bfgminer.md"
|
||||
- BTMiner: "miners/backends/btminer.md"
|
||||
- CGMiner: "miners/backends/cgminer.md"
|
||||
- LUXMiner: "miners/backends/luxminer.md"
|
||||
- VNish: "miners/backends/vnish.md"
|
||||
- ePIC: "miners/backends/epic.md"
|
||||
- Hiveon: "miners/backends/hiveon.md"
|
||||
- Classes:
|
||||
- Antminer X3: "miners/antminer/X3.md"
|
||||
@@ -40,16 +43,18 @@ nav:
|
||||
- Avalon 8X: "miners/avalonminer/A8X.md"
|
||||
- Avalon 9X: "miners/avalonminer/A9X.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 M3X: "miners/whatsminer/M3X.md"
|
||||
- Whatsminer M5X: "miners/whatsminer/M5X.md"
|
||||
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
||||
- Innosilicon A10X: "miners/innosilicon/A10X.md"
|
||||
- Goldshell CKX: "miners/goldshell/CKX.md"
|
||||
- Goldshell HSX: "miners/goldshell/HSX.md"
|
||||
- Goldshell KDX: "miners/goldshell/KDX.md"
|
||||
- Goldshell X5: "miners/goldshell/X5.md"
|
||||
- Goldshell XMax: "miners/goldshell/XMax.md"
|
||||
- Base Miner: "miners/base_miner.md"
|
||||
|
||||
- Settings:
|
||||
- Settings: "settings/settings.md"
|
||||
|
||||
plugins:
|
||||
- mkdocstrings
|
||||
|
||||
@@ -32,6 +32,8 @@ class BaseMinerAPI:
|
||||
# ip address of the miner
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
|
||||
self.pwd = "admin"
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls is BaseMinerAPI:
|
||||
raise TypeError(f"Only children of '{cls.__name__}' may be instantiated")
|
||||
@@ -73,18 +75,23 @@ class BaseMinerAPI:
|
||||
# send the command
|
||||
data = await self._send_bytes(json.dumps(cmd).encode("utf-8"))
|
||||
|
||||
if data == b"Socket connect failed: Connection refused\n":
|
||||
if not ignore_errors:
|
||||
raise APIError(data.decode("utf-8"))
|
||||
return {}
|
||||
|
||||
data = self._load_api_data(data)
|
||||
|
||||
# check for if the user wants to allow errors to return
|
||||
if not ignore_errors:
|
||||
# validate the command succeeded
|
||||
validation = self._validate_command_output(data)
|
||||
if not validation[0]:
|
||||
if allow_warning:
|
||||
logging.warning(
|
||||
f"{self.ip}: API Command Error: {command}: {validation[1]}"
|
||||
)
|
||||
validation = self._validate_command_output(data)
|
||||
if not validation[0]:
|
||||
if not ignore_errors:
|
||||
# validate the command succeeded
|
||||
raise APIError(validation[1])
|
||||
if allow_warning:
|
||||
logging.warning(
|
||||
f"{self.ip}: API Command Error: {command}: {validation[1]}"
|
||||
)
|
||||
|
||||
logging.debug(f"{self} - (Send Command) - Received data.")
|
||||
return data
|
||||
@@ -111,16 +118,29 @@ class BaseMinerAPI:
|
||||
data = await self.send_command(command, allow_warning=allow_warning)
|
||||
except APIError as e:
|
||||
# try to identify the error
|
||||
if ":" in e.message:
|
||||
err_command = e.message.split(":")[0]
|
||||
if err_command in commands:
|
||||
commands.remove(err_command)
|
||||
continue
|
||||
if e.message is not None:
|
||||
if ":" in e.message:
|
||||
err_command = e.message.split(":")[0]
|
||||
if err_command in commands:
|
||||
commands.remove(err_command)
|
||||
continue
|
||||
return {command: [{}] for command in commands}
|
||||
logging.debug(f"{self} - (Multicommand) - Received data")
|
||||
data["multicommand"] = True
|
||||
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
|
||||
def commands(self) -> list:
|
||||
return self.get_commands()
|
||||
@@ -164,7 +184,11 @@ If you are sure you want to use this command please use API.send_command("{comma
|
||||
)
|
||||
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")
|
||||
try:
|
||||
# get reader and writer streams
|
||||
@@ -183,16 +207,18 @@ If you are sure you want to use this command please use API.send_command("{comma
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Draining")
|
||||
await writer.drain()
|
||||
try:
|
||||
ret_data = await asyncio.wait_for(reader.read(4096), timeout=timeout)
|
||||
except ConnectionAbortedError:
|
||||
return b"{}"
|
||||
try:
|
||||
# TO address a situation where a whatsminer has an unknown PW -AND-
|
||||
# Fix for stupid whatsminer bug, reboot/restart seem to not load properly in the loop
|
||||
# have to receive, save the data, check if there is more data by reading with a short timeout
|
||||
# append that data if there is more, and then onto the main loop.
|
||||
ret_data += await asyncio.wait_for(reader.read(1), timeout=1)
|
||||
except asyncio.TimeoutError:
|
||||
return ret_data
|
||||
# the password timeout might need to be longer than 1, but it seems to work for now.
|
||||
ret_data = await asyncio.wait_for(reader.read(1), timeout=1)
|
||||
except (asyncio.TimeoutError):
|
||||
return b"{}"
|
||||
try:
|
||||
ret_data += await asyncio.wait_for(reader.read(4096), timeout=timeout)
|
||||
except (ConnectionAbortedError):
|
||||
return b"{}"
|
||||
|
||||
# loop to receive all the data
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Receiving")
|
||||
@@ -231,13 +257,22 @@ If you are sure you want to use this command please use API.send_command("{comma
|
||||
# this is an error
|
||||
return False, f"{key}: " + data[key][0]["STATUS"][0]["Msg"]
|
||||
elif "id" not in data.keys():
|
||||
if isinstance(data["STATUS"], list):
|
||||
if data["STATUS"][0].get("STATUS", None) in ["S", "I"]:
|
||||
return True, None
|
||||
else:
|
||||
return False, data["STATUS"][0]["Msg"]
|
||||
|
||||
if data["STATUS"] not in ["S", "I"]:
|
||||
return False, data["Msg"]
|
||||
else:
|
||||
# make sure the command succeeded
|
||||
if type(data["STATUS"]) == str:
|
||||
if isinstance(data["STATUS"], str):
|
||||
if data["STATUS"] in ["RESTART"]:
|
||||
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"):
|
||||
# this is an error
|
||||
if data["STATUS"][0]["STATUS"] not in ("S", "I"):
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from pyasic.API import APIError, BaseMinerAPI
|
||||
@@ -56,19 +56,19 @@ class BFGMinerAPI(BaseMinerAPI):
|
||||
return data
|
||||
|
||||
async def _x19_multicommand(self, *commands) -> dict:
|
||||
data = None
|
||||
try:
|
||||
data = {}
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
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}"
|
||||
tasks = []
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
tasks.append(
|
||||
asyncio.create_task(self._handle_multicommand(cmd, allow_warning=True))
|
||||
)
|
||||
|
||||
all_data = await asyncio.gather(*tasks)
|
||||
|
||||
data = {}
|
||||
for item in all_data:
|
||||
data.update(item)
|
||||
|
||||
return data
|
||||
|
||||
async def version(self) -> dict:
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from pyasic.API import APIError, BaseMinerAPI
|
||||
@@ -57,21 +58,19 @@ class BMMinerAPI(BaseMinerAPI):
|
||||
return data
|
||||
|
||||
async def _x19_multicommand(self, *commands, allow_warning: bool = True) -> dict:
|
||||
data = None
|
||||
try:
|
||||
data = {}
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
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}"
|
||||
tasks = []
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
tasks.append(
|
||||
asyncio.create_task(self._handle_multicommand(cmd, allow_warning=True))
|
||||
)
|
||||
|
||||
all_data = await asyncio.gather(*tasks)
|
||||
|
||||
data = {}
|
||||
for item in all_data:
|
||||
data.update(item)
|
||||
|
||||
return data
|
||||
|
||||
async def version(self) -> dict:
|
||||
|
||||
@@ -22,15 +22,15 @@ import hashlib
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from typing import Union
|
||||
from typing import Literal, Union
|
||||
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from passlib.handlers.md5_crypt import md5_crypt
|
||||
|
||||
from pyasic import settings
|
||||
from pyasic.API import BaseMinerAPI
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.misc import api_min_version
|
||||
from pyasic.settings import PyasicSettings
|
||||
|
||||
### IMPORTANT ###
|
||||
# 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,
|
||||
# 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:
|
||||
"""Encrypts a word with a salt, using a standard salt format.
|
||||
@@ -186,7 +192,7 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
ip: str,
|
||||
api_ver: str = "0.0.0",
|
||||
port: int = 4028,
|
||||
pwd: str = PyasicSettings().global_whatsminer_password,
|
||||
pwd: str = settings.get("default_whatsminer_password", "admin"),
|
||||
):
|
||||
super().__init__(ip, port)
|
||||
self.pwd = pwd
|
||||
@@ -203,27 +209,35 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
# make sure we can actually run each command, otherwise they will fail
|
||||
commands = self._check_commands(*commands)
|
||||
# standard multicommand format is "command1+command2"
|
||||
# commands starting with "get_" aren't supported, but we can fake that
|
||||
get_commands_data = {}
|
||||
# commands starting with "get_" and the "status" command aren't supported, but we can fake that
|
||||
|
||||
tasks = []
|
||||
|
||||
for command in list(commands):
|
||||
if command.startswith("get_"):
|
||||
if command.startswith("get_") or command == "status":
|
||||
commands.remove(command)
|
||||
# send seperately and append later
|
||||
try:
|
||||
get_commands_data[command] = [
|
||||
await self.send_command(command, allow_warning=allow_warning)
|
||||
]
|
||||
except APIError:
|
||||
get_commands_data[command] = [{}]
|
||||
tasks.append(
|
||||
asyncio.create_task(
|
||||
self._handle_multicommand(command, allow_warning=allow_warning)
|
||||
)
|
||||
)
|
||||
|
||||
command = "+".join(commands)
|
||||
try:
|
||||
main_data = await self.send_command(command, allow_warning=allow_warning)
|
||||
except APIError:
|
||||
main_data = {command: [{}] for command in commands}
|
||||
tasks.append(
|
||||
asyncio.create_task(
|
||||
self._handle_multicommand(command, allow_warning=allow_warning)
|
||||
)
|
||||
)
|
||||
|
||||
all_data = await asyncio.gather(*tasks)
|
||||
|
||||
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
|
||||
return data
|
||||
|
||||
@@ -685,7 +699,7 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
)
|
||||
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.
|
||||
|
||||
<details>
|
||||
@@ -705,7 +719,7 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
</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(
|
||||
"Message is incorrect, please choose one of "
|
||||
'["wait for adjust temp", '
|
||||
@@ -721,6 +735,34 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
)
|
||||
|
||||
### 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")
|
||||
async def set_temp_offset(self, temp_offset: int) -> dict:
|
||||
"""Set the offset of miner hash board target temperature.
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from pyasic.API import APIError, BaseMinerAPI
|
||||
@@ -56,19 +56,19 @@ class CGMinerAPI(BaseMinerAPI):
|
||||
return data
|
||||
|
||||
async def _x19_multicommand(self, *commands) -> dict:
|
||||
data = None
|
||||
try:
|
||||
data = {}
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
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}"
|
||||
tasks = []
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
tasks.append(
|
||||
asyncio.create_task(self._handle_multicommand(cmd, allow_warning=True))
|
||||
)
|
||||
|
||||
all_data = await asyncio.gather(*tasks)
|
||||
|
||||
data = {}
|
||||
for item in all_data:
|
||||
data.update(item)
|
||||
|
||||
return data
|
||||
|
||||
async def version(self) -> dict:
|
||||
|
||||
759
pyasic/API/luxminer.py
Normal file
759
pyasic/API/luxminer.py
Normal file
@@ -0,0 +1,759 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 typing import Literal
|
||||
|
||||
from pyasic.API import BaseMinerAPI
|
||||
|
||||
|
||||
class LUXMinerAPI(BaseMinerAPI):
|
||||
"""An abstraction of the LUXMiner API.
|
||||
|
||||
Each method corresponds to an API command in LUXMiner.
|
||||
|
||||
[LUXMiner API documentation](https://docs.firmware.luxor.tech/API/intro)
|
||||
|
||||
This class abstracts use of the LUXMiner API, as well as the
|
||||
methods for sending commands to it. The `self.send_command()`
|
||||
function handles sending a command to the miner asynchronously, and
|
||||
as such is the base for many of the functions in this class, which
|
||||
rely on it to send the command for them.
|
||||
|
||||
Parameters:
|
||||
ip: The IP of the miner to reference the API on.
|
||||
port: The port to reference the API on. Default is 4028.
|
||||
"""
|
||||
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0", port: int = 4028) -> None:
|
||||
super().__init__(ip, port=port)
|
||||
self.api_ver = api_ver
|
||||
|
||||
async def addgroup(self, name: str, quota: int) -> dict:
|
||||
"""Add a pool group.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
name: The group name.
|
||||
quota: The group quota.
|
||||
|
||||
Returns:
|
||||
Confirmation of adding a pool group.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("addgroup", parameters=f"{name},{quota}")
|
||||
|
||||
async def addpool(
|
||||
self, url: str, user: str, pwd: str = "", group_id: str = None
|
||||
) -> dict:
|
||||
"""Add a pool.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
url: The pool url.
|
||||
user: The pool username.
|
||||
pwd: The pool password.
|
||||
group_id: The group ID to use.
|
||||
|
||||
Returns:
|
||||
Confirmation of adding a pool.
|
||||
</details>
|
||||
"""
|
||||
pool_data = [url, user, pwd]
|
||||
if group_id is not None:
|
||||
pool_data.append(group_id)
|
||||
return await self.send_command("addpool", parameters=",".join(pool_data))
|
||||
|
||||
async def asc(self, n: int) -> dict:
|
||||
"""Get data for ASC device n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The device to get data for.
|
||||
|
||||
Returns:
|
||||
The data for ASC device n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("asc", parameters=n)
|
||||
|
||||
async def asccount(self) -> dict:
|
||||
"""Get data on the number of ASC devices and their info.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on all ASC devices.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("asccount")
|
||||
|
||||
async def check(self, command: str) -> dict:
|
||||
"""Check if the command `command` exists in LUXMiner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
command: The command to check.
|
||||
|
||||
Returns:
|
||||
## Information about a command:
|
||||
* Exists (Y/N) <- the command exists in this version
|
||||
* Access (Y/N) <- you have access to use the command
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("check", parameters=command)
|
||||
|
||||
async def coin(self) -> dict:
|
||||
"""Get information on the current coin.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
## Information about the current coin being mined:
|
||||
* Hash Method <- the hashing algorithm
|
||||
* Current Block Time <- blocktime as a float, 0 means none
|
||||
* Current Block Hash <- the hash of the current block, blank means none
|
||||
* LP <- whether LP is in use on at least 1 pool
|
||||
* Network Difficulty: the current network difficulty
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("coin")
|
||||
|
||||
async def config(self) -> dict:
|
||||
"""Get some basic configuration info.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Miner configuration information.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("config")
|
||||
|
||||
async def curtail(self, session_id: str) -> dict:
|
||||
"""Put the miner into sleep mode. Requires a session_id from logon.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
session_id: Session id from the logon command.
|
||||
|
||||
Returns:
|
||||
A confirmation of putting the miner to sleep.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("curtail", parameters=session_id)
|
||||
|
||||
async def devdetails(self) -> dict:
|
||||
"""Get data on all devices with their static details.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on all devices with their static details.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("devdetails")
|
||||
|
||||
async def devs(self) -> dict:
|
||||
"""Get data on each PGA/ASC with their details.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on each PGA/ASC with their details.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("devs")
|
||||
|
||||
async def disablepool(self, n: int) -> dict:
|
||||
"""Disable a pool.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: Pool to disable.
|
||||
|
||||
Returns:
|
||||
A confirmation of diabling the pool.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("disablepool", parameters=n)
|
||||
|
||||
async def edevs(self) -> dict:
|
||||
"""Alias for devs"""
|
||||
return await self.devs()
|
||||
|
||||
async def enablepool(self, n: int) -> dict:
|
||||
"""Enable pool n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The pool to enable.
|
||||
|
||||
Returns:
|
||||
A confirmation of enabling pool n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("enablepool", parameters=n)
|
||||
|
||||
async def estats(self) -> dict:
|
||||
"""Alias for stats"""
|
||||
return await self.stats()
|
||||
|
||||
async def fans(self) -> dict:
|
||||
"""Get fan data.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on the fans of the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("fans")
|
||||
|
||||
async def fanset(self, session_id: str, speed: int, min_fans: int = None) -> dict:
|
||||
"""Set fan control. Requires a session_id from logon.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
session_id: Session id from the logon command.
|
||||
speed: The fan speed to set. Use -1 to set automatically.
|
||||
min_fans: The minimum number of fans to use. Optional.
|
||||
|
||||
Returns:
|
||||
A confirmation of setting fan control values.
|
||||
</details>
|
||||
"""
|
||||
fanset_data = [str(session_id), str(speed)]
|
||||
if min_fans is not None:
|
||||
fanset_data.append(str(min_fans))
|
||||
return await self.send_command("fanset", parameters=",".join(fanset_data))
|
||||
|
||||
async def frequencyget(self, board_n: int, chip_n: int = None) -> dict:
|
||||
"""Get frequency data for a board and chips.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
board_n: The board number to get frequency info from.
|
||||
chip_n: The chip number to get frequency info from. Optional.
|
||||
|
||||
Returns:
|
||||
Board and/or chip frequency values.
|
||||
</details>
|
||||
"""
|
||||
frequencyget_data = [str(board_n)]
|
||||
if chip_n is not None:
|
||||
frequencyget_data.append(str(chip_n))
|
||||
return await self.send_command(
|
||||
"frequencyget", parameters=",".join(frequencyget_data)
|
||||
)
|
||||
|
||||
async def frequencyset(self, session_id: str, board_n: int, freq: int) -> dict:
|
||||
"""Set frequency. Requires a session_id from logon.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
session_id: Session id from the logon command.
|
||||
board_n: The board number to set frequency on.
|
||||
freq: The frequency to set.
|
||||
|
||||
Returns:
|
||||
A confirmation of setting frequency values.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command(
|
||||
"frequencyset", parameters=f"{session_id},{board_n},{freq}"
|
||||
)
|
||||
|
||||
async def frequencystop(self, session_id: str, board_n: int) -> dict:
|
||||
"""Stop set frequency. Requires a session_id from logon.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
session_id: Session id from the logon command.
|
||||
board_n: The board number to set frequency on.
|
||||
|
||||
Returns:
|
||||
A confirmation of stopping frequencyset value.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command(
|
||||
"frequencystop", parameters=f"{session_id},{board_n}"
|
||||
)
|
||||
|
||||
async def groupquota(self, group_n: int, quota: int) -> dict:
|
||||
"""Set a group's quota.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
group_n: The group number to set quota on.
|
||||
quota: The quota to use.
|
||||
|
||||
Returns:
|
||||
A confirmation of setting quota value.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("groupquota", parameters=f"{group_n},{quota}")
|
||||
|
||||
async def groups(self) -> dict:
|
||||
"""Get pool group data.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on the pool groups on the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("groups")
|
||||
|
||||
async def healthchipget(self, board_n: int, chip_n: int = None) -> dict:
|
||||
"""Get chip health.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
board_n: The board number to get chip health of.
|
||||
chip_n: The chip number to get chip health of. Optional.
|
||||
|
||||
Returns:
|
||||
Chip health data.
|
||||
</details>
|
||||
"""
|
||||
healthchipget_data = [str(board_n)]
|
||||
if chip_n is not None:
|
||||
healthchipget_data.append(str(chip_n))
|
||||
return await self.send_command(
|
||||
"healthchipget", parameters=",".join(healthchipget_data)
|
||||
)
|
||||
|
||||
async def healthchipset(
|
||||
self, session_id: str, board_n: int, chip_n: int = None
|
||||
) -> dict:
|
||||
"""Select the next chip to have its health checked. Requires a session_id from logon.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
session_id: Session id from the logon command.
|
||||
board_n: The board number to next get chip health of.
|
||||
chip_n: The chip number to next get chip health of. Optional.
|
||||
|
||||
Returns:
|
||||
Confirmation of selecting the next health check chip.
|
||||
</details>
|
||||
"""
|
||||
healthchipset_data = [session_id, str(board_n)]
|
||||
if chip_n is not None:
|
||||
healthchipset_data.append(str(chip_n))
|
||||
return await self.send_command(
|
||||
"healthchipset", parameters=",".join(healthchipset_data)
|
||||
)
|
||||
|
||||
async def healthctrl(self) -> dict:
|
||||
"""Get health check config.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Health check config.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("healthctrl")
|
||||
|
||||
async def healthctrlset(
|
||||
self, session_id: str, num_readings: int, amplified_factor: float
|
||||
) -> dict:
|
||||
"""Set health control config. Requires a session_id from logon.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
session_id: Session id from the logon command.
|
||||
num_readings: The minimum number of readings for evaluation.
|
||||
amplified_factor: Performance factor of the evaluation.
|
||||
|
||||
Returns:
|
||||
A confirmation of setting health control config.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command(
|
||||
"healthctrlset",
|
||||
parameters=f"{session_id},{num_readings},{amplified_factor}",
|
||||
)
|
||||
|
||||
async def kill(self) -> dict:
|
||||
"""Forced session kill. Use logoff instead.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
A confirmation of killing the active session.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("kill")
|
||||
|
||||
async def lcd(self) -> dict:
|
||||
"""Get a general all-in-one status summary of the miner. Always zeros on LUXMiner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
An all-in-one status summary of the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("lcd")
|
||||
|
||||
async def ledset(
|
||||
self,
|
||||
session_id: str,
|
||||
color: Literal["red"],
|
||||
state: Literal["on", "off", "blink"],
|
||||
) -> dict:
|
||||
"""Set led. Requires a session_id from logon.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
session_id: Session id from the logon command.
|
||||
color: The color LED to set. Can be "red".
|
||||
state: The state to set the LED to. Can be "on", "off", or "blink".
|
||||
|
||||
Returns:
|
||||
A confirmation of setting LED.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command(
|
||||
"ledset", parameters=f"{session_id},{color},{state}"
|
||||
)
|
||||
|
||||
async def limits(self) -> dict:
|
||||
"""Get max and min values of config parameters.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on max and min values of config parameters.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("limits")
|
||||
|
||||
async def logoff(self, session_id: str) -> dict:
|
||||
"""Log off of a session. Requires a session id from an active session.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
session_id: Session id from the logon command.
|
||||
|
||||
Returns:
|
||||
Confirmation of logging off a session.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("logoff", parameters=session_id)
|
||||
|
||||
async def logon(self) -> dict:
|
||||
"""Get or create a session.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
The Session ID to be used.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("logon")
|
||||
|
||||
async def pools(self) -> dict:
|
||||
"""Get pool information.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Miner pool information.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pools")
|
||||
|
||||
async def power(self) -> dict:
|
||||
"""Get the estimated power usage in watts.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Estimated power usage in watts.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("power")
|
||||
|
||||
async def profiles(self) -> dict:
|
||||
"""Get the available profiles.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on available profiles.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("profiles")
|
||||
|
||||
async def profileset(self, session_id: str, board_n: int, profile: str) -> dict:
|
||||
"""Set active profile for a board. Requires a session_id from logon.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
session_id: Session id from the logon command.
|
||||
board_n: The board to set the profile on.
|
||||
profile: The profile name to use.
|
||||
|
||||
Returns:
|
||||
A confirmation of setting the profile on board_n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command(
|
||||
"profileset", parameters=f"{session_id},{board_n},{profile}"
|
||||
)
|
||||
|
||||
async def reboot(self, session_id: str, board_n: int, delay_s: int = None) -> dict:
|
||||
"""Reboot a board. Requires a session_id from logon.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
session_id: Session id from the logon command.
|
||||
board_n: The board to reboot.
|
||||
delay_s: The number of seconds to delay until startup. If it is 0, the board will just stop. Optional.
|
||||
|
||||
Returns:
|
||||
A confirmation of rebooting board_n.
|
||||
</details>
|
||||
"""
|
||||
reboot_data = [session_id, str(board_n)]
|
||||
if delay_s is not None:
|
||||
reboot_data.append(str(delay_s))
|
||||
return await self.send_command("reboot", parameters=",".join(reboot_data))
|
||||
|
||||
async def rebootdevice(self, session_id: str) -> dict:
|
||||
"""Reboot the miner. Requires a session_id from logon.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
session_id: Session id from the logon command.
|
||||
|
||||
Returns:
|
||||
A confirmation of rebooting the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("rebootdevice", parameters=session_id)
|
||||
|
||||
async def removegroup(self, group_id: str) -> dict:
|
||||
"""Remove a pool group.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
group_id: Group id to remove.
|
||||
|
||||
Returns:
|
||||
A confirmation of removing the pool group.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("removegroup", parameters=group_id)
|
||||
|
||||
async def resetminer(self, session_id: str) -> dict:
|
||||
"""Restart the mining process. Requires a session_id from logon.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
session_id: Session id from the logon command.
|
||||
|
||||
Returns:
|
||||
A confirmation of restarting the mining process.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("resetminer", parameters=session_id)
|
||||
|
||||
async def removepool(self, pool_id: int) -> dict:
|
||||
"""Remove a pool.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
pool_id: Pool to remove.
|
||||
|
||||
Returns:
|
||||
A confirmation of removing the pool.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("removepool", parameters=str(pool_id))
|
||||
|
||||
async def session(self) -> dict:
|
||||
"""Get the current session.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on the current session.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("session")
|
||||
|
||||
async def tempctrlset(self, target: int, hot: int, dangerous: int) -> dict:
|
||||
"""Set temp control values.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
target: Target temp.
|
||||
hot: Hot temp.
|
||||
dangerous: Dangerous temp.
|
||||
|
||||
Returns:
|
||||
A confirmation of setting the temp control config.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command(
|
||||
"tempctrlset", parameters=f"{target},{hot},{dangerous}"
|
||||
)
|
||||
|
||||
async def stats(self) -> dict:
|
||||
"""Get stats of each device/pool with more than 1 getwork.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Stats of each device/pool with more than 1 getwork.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("stats")
|
||||
|
||||
async def summary(self) -> dict:
|
||||
"""Get the status summary of the miner.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
The status summary of the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("summary")
|
||||
|
||||
async def switchpool(self, pool_id: int) -> dict:
|
||||
"""Switch to a pool.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
pool_id: Pool to switch to.
|
||||
|
||||
Returns:
|
||||
A confirmation of switching to the pool.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("switchpool", parameters=str(pool_id))
|
||||
|
||||
async def tempctrl(self) -> dict:
|
||||
"""Get temperature control data.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data about the temp control settings of the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("tempctrl")
|
||||
|
||||
async def temps(self) -> dict:
|
||||
"""Get temperature data.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on the temps of the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("temps")
|
||||
|
||||
async def version(self) -> dict:
|
||||
"""Get miner version info.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Miner version information.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("version")
|
||||
|
||||
async def voltageget(self, board_n: int) -> dict:
|
||||
"""Get voltage data for a board.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
board_n: The board number to get voltage info from.
|
||||
|
||||
Returns:
|
||||
Board voltage values.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("frequencyget", parameters=str(board_n))
|
||||
|
||||
async def voltageset(self, session_id: str, board_n: int, voltage: float) -> dict:
|
||||
"""Set voltage values.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
session_id: Session id from the logon command.
|
||||
board_n: The board to set the voltage on.
|
||||
voltage: The voltage to use.
|
||||
|
||||
Returns:
|
||||
A confirmation of setting the voltage.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command(
|
||||
"voltageset", parameters=f"{session_id},{board_n},{voltage}"
|
||||
)
|
||||
|
||||
async def wakeup(self, session_id: str) -> dict:
|
||||
"""Take the miner out of sleep mode. Requires a session_id from logon.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
session_id: Session id from the logon command.
|
||||
|
||||
Returns:
|
||||
A confirmation of resuming mining.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("wakeup", parameters=session_id)
|
||||
@@ -13,6 +13,7 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic import settings
|
||||
from pyasic.API.bmminer import BMMinerAPI
|
||||
from pyasic.API.bosminer import BOSMinerAPI
|
||||
from pyasic.API.btminer import BTMinerAPI
|
||||
@@ -29,10 +30,9 @@ from pyasic.data import (
|
||||
from pyasic.errors import APIError, APIWarning
|
||||
from pyasic.miners import get_miner
|
||||
from pyasic.miners.base import AnyMiner
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
from pyasic.miners.miner_factory import MinerFactory, miner_factory
|
||||
from pyasic.miners.miner_listener import MinerListener
|
||||
from pyasic.network import MinerNetwork
|
||||
from pyasic.settings import PyasicSettings
|
||||
|
||||
__all__ = [
|
||||
"BMMinerAPI",
|
||||
@@ -51,7 +51,8 @@ __all__ = [
|
||||
"get_miner",
|
||||
"AnyMiner",
|
||||
"MinerFactory",
|
||||
"miner_factory",
|
||||
"MinerListener",
|
||||
"MinerNetwork",
|
||||
"PyasicSettings",
|
||||
"settings",
|
||||
]
|
||||
|
||||
@@ -560,9 +560,6 @@ class MinerConfig:
|
||||
if self.fan_speed:
|
||||
cfg["bitmain-fan-pwn"] = str(self.fan_speed)
|
||||
|
||||
if self.miner_mode == X19PowerMode.Sleep:
|
||||
cfg["freq-level"] = "0"
|
||||
|
||||
return cfg
|
||||
|
||||
def as_x17(self, user_suffix: str = None) -> dict:
|
||||
|
||||
@@ -20,7 +20,7 @@ import logging
|
||||
import time
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Union
|
||||
from typing import Any, List, Union
|
||||
|
||||
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
|
||||
|
||||
@@ -40,13 +40,28 @@ class HashBoard:
|
||||
"""
|
||||
|
||||
slot: int = 0
|
||||
hashrate: float = 0.0
|
||||
temp: int = -1
|
||||
chip_temp: int = -1
|
||||
chips: int = 0
|
||||
expected_chips: int = 0
|
||||
hashrate: float = None
|
||||
temp: int = None
|
||||
chip_temp: int = None
|
||||
chips: int = None
|
||||
expected_chips: int = None
|
||||
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
|
||||
class Fan:
|
||||
@@ -56,7 +71,22 @@ class 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
|
||||
@@ -66,6 +96,7 @@ class MinerData:
|
||||
Attributes:
|
||||
ip: The IP of the miner as a str.
|
||||
datetime: The time and date this data was generated.
|
||||
uptime: The uptime of the miner in seconds.
|
||||
mac: The MAC address of the miner as a str.
|
||||
model: The model of the miner as a str.
|
||||
make: The make of the miner as a str.
|
||||
@@ -96,29 +127,31 @@ class MinerData:
|
||||
errors: A list of errors on the miner.
|
||||
fault_light: Whether the fault light is on as a boolean.
|
||||
efficiency: Efficiency of the miner in J/TH (Watts per TH/s). Calculated automatically.
|
||||
is_mining: Whether the miner is mining.
|
||||
"""
|
||||
|
||||
ip: str
|
||||
datetime: datetime = None
|
||||
mac: str = "00:00:00:00:00:00"
|
||||
model: str = "Unknown"
|
||||
make: str = "Unknown"
|
||||
api_ver: str = "Unknown"
|
||||
fw_ver: str = "Unknown"
|
||||
hostname: str = "Unknown"
|
||||
uptime: int = None
|
||||
mac: str = None
|
||||
model: str = None
|
||||
make: str = None
|
||||
api_ver: str = None
|
||||
fw_ver: str = None
|
||||
hostname: str = None
|
||||
hashrate: float = field(init=False)
|
||||
_hashrate: float = 0
|
||||
nominal_hashrate: float = 0
|
||||
_hashrate: float = None
|
||||
nominal_hashrate: float = None
|
||||
hashboards: List[HashBoard] = field(default_factory=list)
|
||||
ideal_hashboards: int = 1
|
||||
ideal_hashboards: int = None
|
||||
temperature_avg: int = field(init=False)
|
||||
env_temp: float = -1.0
|
||||
wattage: int = -1
|
||||
wattage_limit: int = -1
|
||||
env_temp: float = None
|
||||
wattage: int = None
|
||||
wattage_limit: int = None
|
||||
fans: List[Fan] = field(default_factory=list)
|
||||
fan_psu: int = -1
|
||||
fan_psu: int = None
|
||||
total_chips: int = field(init=False)
|
||||
ideal_chips: int = 1
|
||||
ideal_chips: int = None
|
||||
percent_ideal_chips: float = field(init=False)
|
||||
percent_ideal_hashrate: float = field(init=False)
|
||||
percent_ideal_wattage: float = field(init=False)
|
||||
@@ -133,6 +166,7 @@ class MinerData:
|
||||
] = field(default_factory=list)
|
||||
fault_light: Union[bool, None] = None
|
||||
efficiency: int = field(init=False)
|
||||
is_mining: bool = True
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
@@ -141,7 +175,16 @@ class MinerData:
|
||||
def __post_init__(self):
|
||||
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:
|
||||
return getattr(self, item)
|
||||
except AttributeError:
|
||||
@@ -193,7 +236,12 @@ class MinerData:
|
||||
@property
|
||||
def hashrate(self): # noqa - Skip PyCharm inspection
|
||||
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 round(sum(hr_data), 2)
|
||||
return self._hashrate
|
||||
|
||||
@hashrate.setter
|
||||
@@ -202,7 +250,14 @@ class MinerData:
|
||||
|
||||
@property
|
||||
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
|
||||
def total_chips(self, val):
|
||||
@@ -210,6 +265,8 @@ class MinerData:
|
||||
|
||||
@property
|
||||
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
|
||||
|
||||
@nominal.setter
|
||||
@@ -218,6 +275,8 @@ class MinerData:
|
||||
|
||||
@property
|
||||
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:
|
||||
return 0
|
||||
return round((self.total_chips / self.ideal_chips) * 100)
|
||||
@@ -228,6 +287,8 @@ class MinerData:
|
||||
|
||||
@property
|
||||
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:
|
||||
return 0
|
||||
return round((self.hashrate / self.nominal_hashrate) * 100)
|
||||
@@ -238,6 +299,8 @@ class MinerData:
|
||||
|
||||
@property
|
||||
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:
|
||||
return 0
|
||||
return round((self.wattage / self.wattage_limit) * 100)
|
||||
@@ -251,11 +314,11 @@ class MinerData:
|
||||
total_temp = 0
|
||||
temp_count = 0
|
||||
for hb in self.hashboards:
|
||||
if hb.temp and not hb.temp == -1:
|
||||
if hb.temp is not None:
|
||||
total_temp += hb.temp
|
||||
temp_count += 1
|
||||
if not temp_count > 0:
|
||||
return 0
|
||||
return None
|
||||
return round(total_temp / temp_count)
|
||||
|
||||
@temperature_avg.setter
|
||||
@@ -264,7 +327,9 @@ class MinerData:
|
||||
|
||||
@property
|
||||
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 round(self.wattage / self.hashrate)
|
||||
|
||||
@@ -273,13 +338,16 @@ class MinerData:
|
||||
pass
|
||||
|
||||
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.
|
||||
|
||||
Returns:
|
||||
A dictionary version of this class.
|
||||
"""
|
||||
logging.debug(f"MinerData - (To Dict) - Dumping Dict data")
|
||||
return asdict(self)
|
||||
return self.asdict()
|
||||
|
||||
def as_json(self) -> str:
|
||||
"""Get this dataclass as JSON.
|
||||
@@ -324,7 +392,7 @@ class MinerData:
|
||||
tags = ["ip", "mac", "model", "hostname"]
|
||||
for attribute in self:
|
||||
if attribute in tags:
|
||||
escaped_data = self[attribute].replace(" ", "\\ ")
|
||||
escaped_data = self.get(attribute, "Unknown").replace(" ", "\\ ")
|
||||
tag_data.append(f"{attribute}={escaped_data}")
|
||||
continue
|
||||
elif str(attribute).startswith("_"):
|
||||
@@ -341,26 +409,28 @@ class MinerData:
|
||||
elif isinstance(self[attribute], float):
|
||||
field_data.append(f"{attribute}={self[attribute]}")
|
||||
continue
|
||||
elif attribute == "fault_light" and not self[attribute]:
|
||||
field_data.append(f"{attribute}=false")
|
||||
continue
|
||||
elif attribute == "errors":
|
||||
for idx, item in enumerate(self[attribute]):
|
||||
field_data.append(f'error_{idx+1}="{item.error_message}"')
|
||||
elif attribute == "hashboards":
|
||||
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(
|
||||
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(
|
||||
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":
|
||||
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)
|
||||
field_str = ",".join(field_data)
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
|
||||
C_N_CODES = ["52", "53", "54", "55", "56"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class WhatsminerError:
|
||||
@@ -37,10 +35,8 @@ class WhatsminerError:
|
||||
|
||||
@property
|
||||
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:
|
||||
# 55 error code base has chip numbers, so the format is
|
||||
# 55 -> board num len 1 -> chip num len 3
|
||||
err_type = 55
|
||||
if len(str(self.error_code)) == 6 and not str(self.error_code)[:1] == "1":
|
||||
err_type = int(str(self.error_code)[:2])
|
||||
err_subtype = int(str(self.error_code)[2:3])
|
||||
err_value = int(str(self.error_code)[3:])
|
||||
else:
|
||||
@@ -88,7 +84,9 @@ class WhatsminerError:
|
||||
|
||||
ERROR_CODES = {
|
||||
1: { # Fan error
|
||||
0: {0: "Fan unknown."},
|
||||
0: {
|
||||
0: "Fan unknown.",
|
||||
},
|
||||
1: { # Fan speed error of 1000+
|
||||
0: "Intake 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.",
|
||||
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
|
||||
0: {
|
||||
@@ -126,6 +126,7 @@ ERROR_CODES = {
|
||||
6: "Power remained unchanged for a long time.",
|
||||
7: "Power set enable error.",
|
||||
8: "Power input voltage is lower than 230V for high power mode.",
|
||||
9: "Power input current is incorrect.",
|
||||
},
|
||||
3: {
|
||||
3: "Power output high temperature protection error.",
|
||||
@@ -159,6 +160,8 @@ ERROR_CODES = {
|
||||
6: {
|
||||
3: "Power communication warning.",
|
||||
4: "Power communication error.",
|
||||
5: "Power unknown error.",
|
||||
6: "Power unknown error.",
|
||||
7: "Power watchdog protection.",
|
||||
8: "Power output high current protection.",
|
||||
9: "Power input high current protection.",
|
||||
@@ -170,57 +173,134 @@ ERROR_CODES = {
|
||||
3: "Power input too high warning.",
|
||||
4: "Power fan 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
|
||||
0: { # sensor detection error
|
||||
"n": "Slot {n} temperature sensor detection error."
|
||||
"n": "Slot {n} temperature sensor detection error.",
|
||||
},
|
||||
2: { # temperature reading error
|
||||
"n": "Slot {n} temperature reading error.",
|
||||
9: "Control board temperature sensor communication error.",
|
||||
},
|
||||
5: {"n": "Slot {n} temperature protecting."}, # temperature protection
|
||||
6: {0: "Hashboard high temperature error."}, # high temp
|
||||
5: {
|
||||
"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: {
|
||||
0: "Humidity sensor not found.",
|
||||
1: "Humidity sensor read error.",
|
||||
2: "Humidity sensor read error.",
|
||||
3: "Humidity sensor protecting.",
|
||||
},
|
||||
}, # humidity
|
||||
},
|
||||
4: { # EEPROM error
|
||||
0: {0: "Eeprom unknown error."},
|
||||
1: {"n": "Slot {n} eeprom detection 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
|
||||
0: {
|
||||
0: "Eeprom unknown error.",
|
||||
},
|
||||
1: {
|
||||
"n": "Slot {n} eeprom detection 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
|
||||
0: {0: "Board unknown error."},
|
||||
1: {"n": "Slot {n} miner type error."}, # board miner type error
|
||||
2: {"n": "Slot {n} bin type error."}, # chip bin type 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
|
||||
0: {
|
||||
0: "Board unknown error.",
|
||||
},
|
||||
1: {
|
||||
"n": "Slot {n} miner type error.",
|
||||
}, # board miner type error
|
||||
2: {
|
||||
"n": "Slot {n} bin type error.",
|
||||
}, # chip bin type 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
|
||||
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
|
||||
0: "Environment temperature is too high for high performance mode."
|
||||
0: "Environment temperature is too high for high performance mode.",
|
||||
},
|
||||
},
|
||||
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: {
|
||||
0: "Control board rebooted as an exception.",
|
||||
1: "Control board rebooted as exception and cpufreq reduced, please upgrade the firmware",
|
||||
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
|
||||
@@ -228,63 +308,152 @@ ERROR_CODES = {
|
||||
0: "CGMiner checksum error.",
|
||||
1: "System monitor 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
|
||||
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
|
||||
0: {
|
||||
0: "No pool information configured.",
|
||||
},
|
||||
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
|
||||
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
|
||||
1: {0: "Hashrate is too low."},
|
||||
2: {0: "Hashrate is too low."},
|
||||
3: {0: "Hashrate loss is too high."},
|
||||
4: {0: "Hashrate loss is too high."},
|
||||
5: {0: "Hashrate loss."},
|
||||
1: {
|
||||
0: "Hashrate is too low.",
|
||||
},
|
||||
2: {
|
||||
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
|
||||
1: {"n": "Slot {n} chip voltage too low."},
|
||||
2: {"n": "Slot {n} chip voltage changed."},
|
||||
3: {"n": "Slot {n} chip temperature difference is too large."},
|
||||
4: {"n": "Slot {n} chip hottest temperature difference is too large."},
|
||||
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."},
|
||||
1: {
|
||||
"n": "Slot {n} chip voltage too low.",
|
||||
},
|
||||
2: {
|
||||
"n": "Slot {n} chip voltage changed.",
|
||||
},
|
||||
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
|
||||
1: {"n": "Slot {n} frequency up timeout."}, # frequency up timeout
|
||||
7: {"n": "Slot {n} frequency up timeout."}, # frequency up timeout
|
||||
1: {
|
||||
"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: {
|
||||
0: {0: "The tool version is too low, please update."},
|
||||
1: {0: "Low freq."},
|
||||
2: {0: "Low hashrate."},
|
||||
3: {5: "High env temp."},
|
||||
0: {
|
||||
0: "The tool version is too low, please update.",
|
||||
},
|
||||
1: {
|
||||
0: "Low freq.",
|
||||
},
|
||||
2: {
|
||||
0: "Low hashrate.",
|
||||
},
|
||||
3: {
|
||||
5: "High env temp.",
|
||||
},
|
||||
},
|
||||
81: {
|
||||
0: {0: "Chip data error."},
|
||||
0: {
|
||||
0: "Chip data error.",
|
||||
},
|
||||
},
|
||||
82: {
|
||||
0: {0: "Power version error."},
|
||||
1: {0: "Miner type error."},
|
||||
2: {0: "Version info error."},
|
||||
0: {
|
||||
0: "Power version error.",
|
||||
},
|
||||
1: {
|
||||
0: "Miner type error.",
|
||||
},
|
||||
2: {
|
||||
0: "Version info error.",
|
||||
},
|
||||
},
|
||||
83: {
|
||||
0: {0: "Empty level error."},
|
||||
0: {
|
||||
0: "Empty level error.",
|
||||
},
|
||||
},
|
||||
84: {
|
||||
0: {0: "Old firmware."},
|
||||
1: {0: "Software version error."},
|
||||
0: {
|
||||
0: "Old firmware.",
|
||||
},
|
||||
1: {
|
||||
0: "Software version error.",
|
||||
},
|
||||
},
|
||||
85: {
|
||||
"n": {
|
||||
@@ -296,8 +465,12 @@ ERROR_CODES = {
|
||||
},
|
||||
},
|
||||
86: {
|
||||
0: {0: "Missing product serial #."},
|
||||
1: {0: "Missing product type."},
|
||||
0: {
|
||||
0: "Missing product serial #.",
|
||||
},
|
||||
1: {
|
||||
0: "Missing product type.",
|
||||
},
|
||||
2: {
|
||||
0: "Missing miner serial #.",
|
||||
1: "Wrong miner serial # length.",
|
||||
@@ -314,12 +487,34 @@ ERROR_CODES = {
|
||||
3: "Wrong power model rate.",
|
||||
4: "Wrong power model format.",
|
||||
},
|
||||
5: {0: "Wrong hash board struct."},
|
||||
6: {0: "Wrong miner cooling type."},
|
||||
7: {0: "Missing PCB serial #."},
|
||||
5: {
|
||||
0: "Wrong hash board struct.",
|
||||
},
|
||||
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: {
|
||||
0: {
|
||||
0: "Security library error, please upgrade firmware",
|
||||
@@ -328,7 +523,11 @@ ERROR_CODES = {
|
||||
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: {
|
||||
0: {
|
||||
0: "Security illegal file, please upgrade firmware.",
|
||||
|
||||
@@ -149,10 +149,10 @@ class _MinerPhaseBalancer:
|
||||
not self.miners[data_point.ip]["shutdown"]
|
||||
):
|
||||
# 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]["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]["min"] = int(data_point.wattage)
|
||||
|
||||
|
||||
@@ -16,31 +16,29 @@
|
||||
|
||||
import logging
|
||||
|
||||
from pyasic.settings import PyasicSettings
|
||||
|
||||
|
||||
def init_logger():
|
||||
if PyasicSettings().logfile:
|
||||
logging.basicConfig(
|
||||
filename="logfile.txt",
|
||||
filemode="a",
|
||||
format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
||||
datefmt="%x %X",
|
||||
)
|
||||
else:
|
||||
logging.basicConfig(
|
||||
format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
||||
datefmt="%x %X",
|
||||
)
|
||||
# if PyasicSettings().logfile:
|
||||
# logging.basicConfig(
|
||||
# filename="logfile.txt",
|
||||
# filemode="a",
|
||||
# format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
||||
# datefmt="%x %X",
|
||||
# )
|
||||
# else:
|
||||
logging.basicConfig(
|
||||
format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
||||
datefmt="%x %X",
|
||||
)
|
||||
|
||||
_logger = logging.getLogger()
|
||||
|
||||
if PyasicSettings().debug:
|
||||
_logger.setLevel(logging.DEBUG)
|
||||
logging.getLogger("asyncssh").setLevel(logging.DEBUG)
|
||||
else:
|
||||
_logger.setLevel(logging.WARNING)
|
||||
logging.getLogger("asyncssh").setLevel(logging.WARNING)
|
||||
# if PyasicSettings().debug:
|
||||
# _logger.setLevel(logging.DEBUG)
|
||||
# logging.getLogger("asyncssh").setLevel(logging.DEBUG)
|
||||
# else:
|
||||
_logger.setLevel(logging.WARNING)
|
||||
logging.getLogger("asyncssh").setLevel(logging.WARNING)
|
||||
|
||||
return _logger
|
||||
|
||||
|
||||
@@ -18,4 +18,6 @@ from .bmminer import *
|
||||
from .bosminer import *
|
||||
from .cgminer import *
|
||||
from .hiveon import *
|
||||
from .luxos import *
|
||||
from .vnish import *
|
||||
from .epic import *
|
||||
|
||||
@@ -21,9 +21,11 @@ from pyasic.miners.types import (
|
||||
S19XP,
|
||||
S19a,
|
||||
S19aPro,
|
||||
S19i,
|
||||
S19j,
|
||||
S19jNoPIC,
|
||||
S19jPro,
|
||||
S19Plus,
|
||||
S19Pro,
|
||||
S19ProPlus,
|
||||
)
|
||||
@@ -33,6 +35,14 @@ class BMMinerS19(AntminerModern, S19):
|
||||
pass
|
||||
|
||||
|
||||
class BMMinerS19Plus(AntminerModern, S19Plus):
|
||||
pass
|
||||
|
||||
|
||||
class BMMinerS19i(AntminerModern, S19i):
|
||||
pass
|
||||
|
||||
|
||||
class BMMinerS19Pro(AntminerModern, S19Pro):
|
||||
pass
|
||||
|
||||
|
||||
@@ -18,10 +18,12 @@ from .S19 import (
|
||||
BMMinerS19,
|
||||
BMMinerS19a,
|
||||
BMMinerS19aPro,
|
||||
BMMinerS19i,
|
||||
BMMinerS19j,
|
||||
BMMinerS19jNoPIC,
|
||||
BMMinerS19jPro,
|
||||
BMMinerS19L,
|
||||
BMMinerS19Plus,
|
||||
BMMinerS19Pro,
|
||||
BMMinerS19ProPlus,
|
||||
BMMinerS19XP,
|
||||
|
||||
40
pyasic/miners/antminer/epic/X19/S19.py
Normal file
40
pyasic/miners/antminer/epic/X19/S19.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 ePIC
|
||||
from pyasic.miners.types import (
|
||||
S19,
|
||||
S19Pro,
|
||||
S19j,
|
||||
S19jPro,
|
||||
S19XP,
|
||||
)
|
||||
|
||||
|
||||
class ePICS19(ePIC, S19):
|
||||
pass
|
||||
|
||||
class ePICS19Pro(ePIC, S19Pro):
|
||||
pass
|
||||
|
||||
class ePICS19j(ePIC, S19j):
|
||||
pass
|
||||
|
||||
class ePICS19jPro(ePIC, S19jPro):
|
||||
pass
|
||||
|
||||
class ePICS19XP(ePIC, S19XP):
|
||||
pass
|
||||
23
pyasic/miners/antminer/epic/X19/__init__.py
Normal file
23
pyasic/miners/antminer/epic/X19/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 .S19 import (
|
||||
ePICS19,
|
||||
ePICS19Pro,
|
||||
ePICS19j,
|
||||
ePICS19jPro,
|
||||
ePICS19XP,
|
||||
)
|
||||
17
pyasic/miners/antminer/epic/__init__.py
Normal file
17
pyasic/miners/antminer/epic/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 .X19 import *
|
||||
@@ -58,7 +58,7 @@ class HiveonT9(Hiveon, T9):
|
||||
hashrate = 0
|
||||
chips = 0
|
||||
for chipset in board_map[board]:
|
||||
if hashboard.chip_temp == -1:
|
||||
if hashboard.chip_temp == None:
|
||||
try:
|
||||
hashboard.board_temp = api_stats["STATS"][1][f"temp{chipset}"]
|
||||
hashboard.chip_temp = api_stats["STATS"][1][f"temp2_{chipset}"]
|
||||
|
||||
22
pyasic/miners/antminer/luxos/X9/S9.py
Normal file
22
pyasic/miners/antminer/luxos/X9/S9.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 LUXMiner
|
||||
from pyasic.miners.types import S9
|
||||
|
||||
|
||||
class LUXMinerS9(LUXMiner, S9):
|
||||
pass
|
||||
17
pyasic/miners/antminer/luxos/X9/__init__.py
Normal file
17
pyasic/miners/antminer/luxos/X9/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 .S9 import LUXMinerS9
|
||||
17
pyasic/miners/antminer/luxos/__init__.py
Normal file
17
pyasic/miners/antminer/luxos/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 .X9 import *
|
||||
@@ -15,13 +15,26 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import VNish
|
||||
from pyasic.miners.types import S19, S19XP, S19a, S19aPro, S19j, S19jPro, S19Pro
|
||||
from pyasic.miners.types import (
|
||||
S19,
|
||||
S19XP,
|
||||
S19a,
|
||||
S19aPro,
|
||||
S19j,
|
||||
S19jPro,
|
||||
S19NoPIC,
|
||||
S19Pro,
|
||||
)
|
||||
|
||||
|
||||
class VNishS19(VNish, S19):
|
||||
pass
|
||||
|
||||
|
||||
class VNishS19NoPIC(VNish, S19NoPIC):
|
||||
pass
|
||||
|
||||
|
||||
class VNishS19Pro(VNish, S19Pro):
|
||||
pass
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ from .S19 import (
|
||||
VNishS19aPro,
|
||||
VNishS19j,
|
||||
VNishS19jPro,
|
||||
VNishS19NoPIC,
|
||||
VNishS19Pro,
|
||||
VNishS19XP,
|
||||
)
|
||||
|
||||
@@ -22,5 +22,7 @@ from .btminer import BTMiner
|
||||
from .cgminer import CGMiner
|
||||
from .cgminer_avalon import CGMinerAvalon
|
||||
from .hiveon import Hiveon
|
||||
from .luxminer import LUXMiner
|
||||
from .vnish import VNish
|
||||
from .epic import ePIC
|
||||
from .whatsminer import M2X, M3X, M5X
|
||||
|
||||
@@ -26,11 +26,17 @@ from pyasic.miners.backends.cgminer import CGMiner
|
||||
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
|
||||
|
||||
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": {}},
|
||||
"api_ver": {"cmd": "get_api_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"}}},
|
||||
"nominal_hashrate": {
|
||||
"cmd": "get_nominal_hashrate",
|
||||
@@ -42,9 +48,20 @@ ANTMINER_MODERN_DATA_LOC = {
|
||||
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}},
|
||||
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
||||
"errors": {"cmd": "get_errors", "kwargs": {}},
|
||||
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
||||
"errors": {"cmd": "get_errors", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||
"fault_light": {
|
||||
"cmd": "get_fault_light",
|
||||
"kwargs": {"web_get_blink_status": {"web": "get_blink_status"}},
|
||||
},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
"is_mining": {
|
||||
"cmd": "is_mining",
|
||||
"kwargs": {"web_get_conf": {"web": "get_miner_conf"}},
|
||||
},
|
||||
"uptime": {
|
||||
"cmd": "get_uptime",
|
||||
"kwargs": {"api_stats": {"api": "stats"}},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +109,7 @@ class AntminerModern(BMMiner):
|
||||
data = await self.web.blink(blink=False)
|
||||
if data:
|
||||
if data.get("code") == "B100":
|
||||
self.light = True
|
||||
self.light = False
|
||||
return self.light
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
@@ -113,21 +130,31 @@ class AntminerModern(BMMiner):
|
||||
await self.send_config(cfg)
|
||||
return True
|
||||
|
||||
async def get_hostname(self) -> Union[str, None]:
|
||||
try:
|
||||
data = await self.web.get_system_info()
|
||||
if data:
|
||||
return data["hostname"]
|
||||
except KeyError:
|
||||
pass
|
||||
async def get_hostname(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
|
||||
|
||||
async def get_mac(self) -> Union[str, None]:
|
||||
try:
|
||||
data = await self.web.get_system_info()
|
||||
if data:
|
||||
return data["macaddr"]
|
||||
except KeyError:
|
||||
pass
|
||||
if web_get_system_info:
|
||||
try:
|
||||
return web_get_system_info["hostname"]
|
||||
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:
|
||||
data = await self.web.get_network_info()
|
||||
@@ -136,12 +163,17 @@ class AntminerModern(BMMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def get_errors(self) -> List[MinerErrorData]:
|
||||
errors = []
|
||||
data = await self.web.summary()
|
||||
if data:
|
||||
async def get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
||||
if not web_summary:
|
||||
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:
|
||||
if not item["status"] == "s":
|
||||
errors.append(X19Error(item["msg"]))
|
||||
@@ -151,15 +183,21 @@ class AntminerModern(BMMiner):
|
||||
pass
|
||||
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:
|
||||
return self.light
|
||||
try:
|
||||
data = await self.web.get_blink_status()
|
||||
if data:
|
||||
self.light = data["blink"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if not web_get_blink_status:
|
||||
try:
|
||||
web_get_blink_status = await self.web.get_blink_status()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_blink_status:
|
||||
try:
|
||||
self.light = web_get_blink_status["blink"]
|
||||
except KeyError:
|
||||
pass
|
||||
return self.light
|
||||
|
||||
async def get_nominal_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||
@@ -227,6 +265,36 @@ class AntminerModern(BMMiner):
|
||||
protocol=protocol,
|
||||
)
|
||||
|
||||
async def is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
|
||||
if not web_get_conf:
|
||||
try:
|
||||
web_get_conf = await self.web.get_miner_conf()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_conf:
|
||||
try:
|
||||
if web_get_conf["bitmain-work-mode"].isdigit():
|
||||
return (
|
||||
False if int(web_get_conf["bitmain-work-mode"]) == 1 else True
|
||||
)
|
||||
return False
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
||||
if not api_stats:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
try:
|
||||
return int(api_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
|
||||
ANTMINER_OLD_DATA_LOC = {
|
||||
"mac": {"cmd": "get_mac", "kwargs": {}},
|
||||
@@ -257,6 +325,14 @@ ANTMINER_OLD_DATA_LOC = {
|
||||
"kwargs": {"web_get_blink_status": {"web": "get_blink_status"}},
|
||||
},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
"is_mining": {
|
||||
"cmd": "is_mining",
|
||||
"kwargs": {"web_get_conf": {"web": "get_miner_conf"}},
|
||||
},
|
||||
"uptime": {
|
||||
"cmd": "get_uptime",
|
||||
"kwargs": {"api_stats": {"api": "stats"}},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -425,3 +501,41 @@ class AntminerOld(CGMiner):
|
||||
pass
|
||||
|
||||
return hashboards
|
||||
|
||||
async def is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
|
||||
if not web_get_conf:
|
||||
try:
|
||||
web_get_conf = await self.web.get_miner_conf()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_conf:
|
||||
try:
|
||||
return False if int(web_get_conf["bitmain-work-mode"]) == 1 else True
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
api_summary = None
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary is not None:
|
||||
if not api_summary == {}:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
async def get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
||||
if not api_stats:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
try:
|
||||
return int(api_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
@@ -44,6 +44,8 @@ BFGMINER_DATA_LOC = {
|
||||
"errors": {"cmd": "get_errors", "kwargs": {}},
|
||||
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
||||
"uptime": {"cmd": "get_uptime", "kwargs": {}},
|
||||
}
|
||||
|
||||
|
||||
@@ -318,3 +320,9 @@ class BFGMiner(BaseMiner):
|
||||
return round(ideal_rate, 2)
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
|
||||
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
|
||||
return None
|
||||
|
||||
async def get_uptime(self, *args, **kwargs) -> Optional[int]:
|
||||
return None
|
||||
|
||||
@@ -13,11 +13,12 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import HashBoard
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.logger import logger
|
||||
from pyasic.miners.backends import BFGMiner
|
||||
from pyasic.web.goldshell import GoldshellWebAPI
|
||||
|
||||
@@ -47,6 +48,8 @@ GOLDSHELL_DATA_LOC = {
|
||||
"errors": {"cmd": "get_errors", "kwargs": {}},
|
||||
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
||||
"uptime": {"cmd": "get_uptime", "kwargs": {}},
|
||||
}
|
||||
|
||||
|
||||
@@ -136,7 +139,7 @@ class BFGMinerGoldshell(BFGMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
print(self, api_devs)
|
||||
logger.error(self, api_devs)
|
||||
|
||||
if not api_devdetails:
|
||||
try:
|
||||
@@ -154,6 +157,12 @@ class BFGMinerGoldshell(BFGMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
print(self, api_devdetails)
|
||||
logger.error(self, api_devdetails)
|
||||
|
||||
return hashboards
|
||||
|
||||
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
|
||||
return None
|
||||
|
||||
async def get_uptime(self, *args, **kwargs) -> Optional[int]:
|
||||
return None
|
||||
|
||||
@@ -45,6 +45,11 @@ BMMINER_DATA_LOC = {
|
||||
"errors": {"cmd": "get_errors", "kwargs": {}},
|
||||
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
||||
"uptime": {
|
||||
"cmd": "get_uptime",
|
||||
"kwargs": {"api_stats": {"api": "stats"}},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -230,7 +235,22 @@ class BMMiner(BaseMiner):
|
||||
if 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(
|
||||
slot=i - board_offset, expected_chips=self.nominal_chips
|
||||
)
|
||||
@@ -254,7 +274,7 @@ class BMMiner(BaseMiner):
|
||||
if (not chips) or (not chips > 0):
|
||||
hashboard.missing = True
|
||||
hashboards.append(hashboard)
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
return hashboards
|
||||
@@ -352,3 +372,19 @@ class BMMiner(BaseMiner):
|
||||
return round(ideal_rate, 2)
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
|
||||
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
|
||||
return None
|
||||
|
||||
async def get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
||||
if not api_stats:
|
||||
try:
|
||||
api_stats = await self.web.get_miner_conf()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
try:
|
||||
return int(api_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
from typing import List, Optional, Tuple, Union
|
||||
@@ -29,7 +29,12 @@ from pyasic.miners.base import BaseMiner
|
||||
from pyasic.web.bosminer import BOSMinerWebAPI
|
||||
|
||||
BOSMINER_DATA_LOC = {
|
||||
"mac": {"cmd": "get_mac", "kwargs": {}},
|
||||
"mac": {
|
||||
"cmd": "get_mac",
|
||||
"kwargs": {
|
||||
"web_net_conf": {"web": "/cgi-bin/luci/admin/network/iface_status/lan"}
|
||||
},
|
||||
},
|
||||
"model": {"cmd": "get_model", "kwargs": {}},
|
||||
"api_ver": {
|
||||
"cmd": "get_api_ver",
|
||||
@@ -167,15 +172,23 @@ BOSMINER_DATA_LOC = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"is_mining": {
|
||||
"cmd": "is_mining",
|
||||
"kwargs": {"api_devdetails": {"api": "devdetails"}},
|
||||
},
|
||||
"uptime": {
|
||||
"cmd": "get_uptime",
|
||||
"kwargs": {"api_summary": {"api": "summary"}},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class BOSMiner(BaseMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0", boser: bool = None) -> None:
|
||||
super().__init__(ip)
|
||||
# interfaces
|
||||
self.api = BOSMinerAPI(ip, api_ver)
|
||||
self.web = BOSMinerWebAPI(ip)
|
||||
self.web = BOSMinerWebAPI(ip, boser=boser)
|
||||
|
||||
# static data
|
||||
self.api_type = "BOSMiner"
|
||||
@@ -192,8 +205,8 @@ class BOSMiner(BaseMiner):
|
||||
result = None
|
||||
|
||||
try:
|
||||
conn = await self._get_ssh_connection()
|
||||
except ConnectionError:
|
||||
conn = await asyncio.wait_for(self._get_ssh_connection(), timeout=10)
|
||||
except (ConnectionError, asyncio.TimeoutError):
|
||||
return None
|
||||
|
||||
# open an ssh connection
|
||||
@@ -290,17 +303,12 @@ class BOSMiner(BaseMiner):
|
||||
The config from `self.config`.
|
||||
"""
|
||||
logging.debug(f"{self}: Getting config.")
|
||||
conn = None
|
||||
|
||||
try:
|
||||
conn = await self._get_ssh_connection()
|
||||
except ConnectionError:
|
||||
try:
|
||||
pools = await self.api.pools()
|
||||
except APIError:
|
||||
return self.config
|
||||
if pools:
|
||||
self.config = MinerConfig().from_api(pools["POOLS"])
|
||||
return self.config
|
||||
conn = None
|
||||
|
||||
if conn:
|
||||
async with conn:
|
||||
# good ol' BBB compatibility :/
|
||||
@@ -352,6 +360,8 @@ class BOSMiner(BaseMiner):
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
try:
|
||||
cfg = await self.get_config()
|
||||
if cfg is None:
|
||||
return False
|
||||
cfg.autotuning_wattage = wattage
|
||||
await self.send_config(cfg)
|
||||
except Exception as e:
|
||||
@@ -360,14 +370,80 @@ class BOSMiner(BaseMiner):
|
||||
else:
|
||||
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}) ###
|
||||
##################################################
|
||||
|
||||
async def get_mac(self) -> Optional[str]:
|
||||
result = await self.send_ssh_command("cat /sys/class/net/eth0/address")
|
||||
if result:
|
||||
return result.upper().strip()
|
||||
async def get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]:
|
||||
if not web_net_conf:
|
||||
try:
|
||||
web_net_conf = await self.web.send_command(
|
||||
"/cgi-bin/luci/admin/network/iface_status/lan"
|
||||
)
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if isinstance(web_net_conf, dict):
|
||||
if "/cgi-bin/luci/admin/network/iface_status/lan" in web_net_conf.keys():
|
||||
web_net_conf = web_net_conf[
|
||||
"/cgi-bin/luci/admin/network/iface_status/lan"
|
||||
]
|
||||
|
||||
if web_net_conf:
|
||||
try:
|
||||
return web_net_conf[0]["macaddr"]
|
||||
except LookupError:
|
||||
pass
|
||||
# could use ssh, but its slow and buggy
|
||||
# result = await self.send_ssh_command("cat /sys/class/net/eth0/address")
|
||||
# if result:
|
||||
# return result.upper().strip()
|
||||
|
||||
async def get_model(self) -> Optional[str]:
|
||||
if self.model is not None:
|
||||
@@ -415,7 +491,7 @@ class BOSMiner(BaseMiner):
|
||||
if graphql_version:
|
||||
try:
|
||||
fw_ver = graphql_version["data"]["bos"]["info"]["version"]["full"]
|
||||
except KeyError:
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
|
||||
if not fw_ver:
|
||||
@@ -444,7 +520,7 @@ class BOSMiner(BaseMiner):
|
||||
try:
|
||||
hostname = graphql_hostname["data"]["bos"]["hostname"]
|
||||
return hostname
|
||||
except KeyError:
|
||||
except (TypeError, KeyError):
|
||||
pass
|
||||
|
||||
try:
|
||||
@@ -484,7 +560,7 @@ class BOSMiner(BaseMiner):
|
||||
),
|
||||
2,
|
||||
)
|
||||
except (KeyError, IndexError, ValueError):
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# get hr from API
|
||||
@@ -538,7 +614,7 @@ class BOSMiner(BaseMiner):
|
||||
boards = graphql_boards["data"]["bosminer"]["info"]["workSolver"][
|
||||
"childSolvers"
|
||||
]
|
||||
except (KeyError, IndexError):
|
||||
except (TypeError, LookupError):
|
||||
boards = None
|
||||
|
||||
if boards:
|
||||
@@ -653,7 +729,7 @@ class BOSMiner(BaseMiner):
|
||||
return graphql_wattage["data"]["bosminer"]["info"]["workSolver"][
|
||||
"power"
|
||||
]["approxConsumptionW"]
|
||||
except (KeyError, TypeError):
|
||||
except (LookupError, TypeError):
|
||||
pass
|
||||
|
||||
if not api_tunerstatus:
|
||||
@@ -686,7 +762,7 @@ class BOSMiner(BaseMiner):
|
||||
return graphql_wattage_limit["data"]["bosminer"]["info"]["workSolver"][
|
||||
"power"
|
||||
]["limitW"]
|
||||
except (KeyError, TypeError):
|
||||
except (LookupError, TypeError):
|
||||
pass
|
||||
|
||||
if not api_tunerstatus:
|
||||
@@ -711,7 +787,7 @@ class BOSMiner(BaseMiner):
|
||||
)
|
||||
except APIError:
|
||||
pass
|
||||
if graphql_fans:
|
||||
if graphql_fans.get("data"):
|
||||
fans = []
|
||||
for n in range(self.fan_count):
|
||||
try:
|
||||
@@ -722,7 +798,7 @@ class BOSMiner(BaseMiner):
|
||||
]
|
||||
)
|
||||
)
|
||||
except KeyError:
|
||||
except (LookupError, TypeError):
|
||||
pass
|
||||
return fans
|
||||
|
||||
@@ -862,7 +938,7 @@ class BOSMiner(BaseMiner):
|
||||
boards = graphql_errors["data"]["bosminer"]["info"]["workSolver"][
|
||||
"childSolvers"
|
||||
]
|
||||
except (KeyError, IndexError):
|
||||
except (LookupError, TypeError):
|
||||
boards = None
|
||||
|
||||
if boards:
|
||||
@@ -955,17 +1031,20 @@ class BOSMiner(BaseMiner):
|
||||
try:
|
||||
self.light = graphql_fault_light["data"]["bos"]["faultLight"]
|
||||
return self.light
|
||||
except (TypeError, KeyError, ValueError, IndexError):
|
||||
except (TypeError, ValueError, LookupError):
|
||||
pass
|
||||
|
||||
# get light via ssh if that fails (10x slower)
|
||||
data = (
|
||||
await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off")
|
||||
).strip()
|
||||
self.light = False
|
||||
if data == "50":
|
||||
self.light = True
|
||||
return self.light
|
||||
try:
|
||||
data = (
|
||||
await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off")
|
||||
).strip()
|
||||
self.light = False
|
||||
if data == "50":
|
||||
self.light = True
|
||||
return self.light
|
||||
except (TypeError, AttributeError):
|
||||
return self.light
|
||||
|
||||
async def get_nominal_hashrate(self, api_devs: dict = None) -> Optional[float]:
|
||||
if not api_devs:
|
||||
@@ -992,3 +1071,31 @@ class BOSMiner(BaseMiner):
|
||||
)
|
||||
except (IndexError, KeyError):
|
||||
pass
|
||||
|
||||
async def is_mining(self, api_devdetails: dict = None) -> Optional[bool]:
|
||||
if not api_devdetails:
|
||||
try:
|
||||
api_devdetails = await self.api.send_command(
|
||||
"devdetails", ignore_errors=True, allow_warning=False
|
||||
)
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devdetails:
|
||||
try:
|
||||
return not api_devdetails["STATUS"][0]["Msg"] == "Unavailable"
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def get_uptime(self, api_summary: dict = None) -> Optional[int]:
|
||||
if not api_summary:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
try:
|
||||
return int(api_summary["SUMMARY"][0]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
@@ -150,3 +150,9 @@ class BOSMinerOld(BOSMiner):
|
||||
|
||||
async def get_data(self, allow_warning: bool = False, **kwargs) -> MinerData:
|
||||
return MinerData(ip=str(self.ip))
|
||||
|
||||
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
|
||||
return None
|
||||
|
||||
async def get_uptime(self, *args, **kwargs) -> Optional[int]:
|
||||
return None
|
||||
|
||||
@@ -88,6 +88,11 @@ BTMINER_DATA_LOC = {
|
||||
"kwargs": {"api_get_miner_info": {"api": "get_miner_info"}},
|
||||
},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
"is_mining": {"cmd": "is_mining", "kwargs": {"api_status": {"api": "status"}}},
|
||||
"uptime": {
|
||||
"cmd": "get_uptime",
|
||||
"kwargs": {"api_summary": {"api": "summary"}},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -437,7 +442,8 @@ class BTMiner(BaseMiner):
|
||||
|
||||
if api_summary:
|
||||
try:
|
||||
return api_summary["SUMMARY"][0]["Power"]
|
||||
wattage = api_summary["SUMMARY"][0]["Power"]
|
||||
return wattage if not wattage == -1 else None
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
|
||||
@@ -616,3 +622,35 @@ class BTMiner(BaseMiner):
|
||||
|
||||
async def set_hostname(self, hostname: str):
|
||||
await self.api.set_hostname(hostname)
|
||||
|
||||
async def is_mining(self, api_status: dict = None) -> Optional[bool]:
|
||||
if not api_status:
|
||||
try:
|
||||
api_status = await self.api.status()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_status:
|
||||
try:
|
||||
if api_status["Msg"].get("btmineroff"):
|
||||
try:
|
||||
await self.api.devdetails()
|
||||
except APIError:
|
||||
return False
|
||||
return True
|
||||
return True if api_status["Msg"]["mineroff"] == "false" else False
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def get_uptime(self, api_summary: dict = None) -> Optional[int]:
|
||||
if not api_summary:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
try:
|
||||
return int(api_summary["SUMMARY"][0]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
@@ -45,6 +45,11 @@ CGMINER_DATA_LOC = {
|
||||
"errors": {"cmd": "get_errors", "kwargs": {}},
|
||||
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
||||
"uptime": {
|
||||
"cmd": "get_uptime",
|
||||
"kwargs": {"api_stats": {"api": "stats"}},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -375,3 +380,19 @@ class CGMiner(BaseMiner):
|
||||
return round(ideal_rate, 2)
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
|
||||
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
|
||||
return None
|
||||
|
||||
async def get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
||||
if not api_stats:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
try:
|
||||
return int(api_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
@@ -50,6 +50,8 @@ AVALON_DATA_LOC = {
|
||||
"kwargs": {"api_stats": {"api": "stats"}},
|
||||
},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
||||
"uptime": {"cmd": "get_uptime", "kwargs": {}},
|
||||
}
|
||||
|
||||
|
||||
@@ -177,11 +179,12 @@ class CGMinerAvalon(CGMiner):
|
||||
pass
|
||||
|
||||
async def get_hostname(self, mac: str = None) -> Optional[str]:
|
||||
if not mac:
|
||||
mac = await self.get_mac()
|
||||
|
||||
if mac:
|
||||
return f"Avalon{mac.replace(':', '')[-6:]}"
|
||||
return None
|
||||
# if not mac:
|
||||
# mac = await self.get_mac()
|
||||
#
|
||||
# if mac:
|
||||
# return f"Avalon{mac.replace(':', '')[-6:]}"
|
||||
|
||||
async def get_hashrate(self, api_devs: dict = None) -> Optional[float]:
|
||||
if not api_devs:
|
||||
@@ -371,3 +374,6 @@ class CGMinerAvalon(CGMiner):
|
||||
except LookupError:
|
||||
pass
|
||||
return False
|
||||
|
||||
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
|
||||
return None
|
||||
|
||||
302
pyasic/miners/backends/epic.py
Normal file
302
pyasic/miners/backends/epic.py
Normal file
@@ -0,0 +1,302 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 typing import Optional
|
||||
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.logger import logger
|
||||
from pyasic.miners.backends.bmminer import BMMiner
|
||||
from pyasic.web.epic import ePICWebAPI
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from typing import List, Optional, Tuple, Union
|
||||
from pyasic.data.error_codes import MinerErrorData, X19Error
|
||||
|
||||
|
||||
EPIC_DATA_LOC = {
|
||||
"mac": {"cmd": "get_mac", "kwargs": {"web_summary": {"web": "network"}}},
|
||||
"model": {"cmd": "get_model", "kwargs": {}},
|
||||
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||
"hostname": {"cmd": "get_hostname", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||
"nominal_hashrate": {
|
||||
"cmd": "get_nominal_hashrate",
|
||||
"kwargs": {"web_summary": {"web": "summary"}},
|
||||
},
|
||||
"hashboards": {"cmd": "get_hashboards", "kwargs": {"web_summary": {"web": "summary"}, "web_hashrate": {"web": "hashrate"}}},
|
||||
"env_temp": {"cmd": "get_env_temp", "kwargs": {}},
|
||||
"wattage": {"cmd": "get_wattage", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||
"fans": {"cmd": "get_fans", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
||||
"fault_light": {"cmd": "get_fault_light", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
||||
"uptime": {"cmd": "get_uptime", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||
"errors": {"cmd": "get_errors", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||
}
|
||||
|
||||
|
||||
class ePIC(BMMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip, api_ver)
|
||||
# interfaces
|
||||
self.web = ePICWebAPI(ip)
|
||||
|
||||
# static data
|
||||
self.api_type = "ePIC"
|
||||
# data gathering locations
|
||||
self.data_locations = EPIC_DATA_LOC
|
||||
|
||||
async def get_model(self) -> Optional[str]:
|
||||
if self.model is not None:
|
||||
return self.model + " (ePIC)"
|
||||
return "? (ePIC)"
|
||||
|
||||
async def restart_backend(self) -> bool:
|
||||
data = await self.web.restart_epic()
|
||||
if data:
|
||||
try:
|
||||
return data["success"]
|
||||
except KeyError:
|
||||
pass
|
||||
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:
|
||||
data = await self.web.reboot()
|
||||
if data:
|
||||
try:
|
||||
return data["success"]
|
||||
except KeyError:
|
||||
pass
|
||||
return False
|
||||
|
||||
async def get_mac(self, web_summary: dict = None) -> str:
|
||||
if not web_summary:
|
||||
web_summary = await self.web.network()
|
||||
if web_summary:
|
||||
try:
|
||||
for network in web_summary:
|
||||
mac = web_summary[network]["mac_address"]
|
||||
return mac
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def get_hostname(self, web_summary: dict = None) -> str:
|
||||
if not web_summary:
|
||||
web_summary = await self.web.summary()
|
||||
if web_summary:
|
||||
try:
|
||||
hostname = web_summary["Hostname"]
|
||||
return hostname
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def get_wattage(self, web_summary: dict = None) -> Optional[int]:
|
||||
if not web_summary:
|
||||
web_summary = await self.web.summary()
|
||||
|
||||
if web_summary:
|
||||
try:
|
||||
wattage = web_summary["Power Supply Stats"]["Input Power"]
|
||||
wattage = round(wattage)
|
||||
return wattage
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def get_hashrate(self, web_summary: dict = None) -> Optional[float]:
|
||||
# get hr from API
|
||||
if not web_summary:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_summary:
|
||||
try:
|
||||
hashrate = 0
|
||||
if web_summary["HBs"] != None:
|
||||
for hb in web_summary["HBs"]:
|
||||
hashrate += hb["Hashrate"][0]
|
||||
return round(
|
||||
float(float(hashrate/ 1000000)), 2)
|
||||
except (LookupError, ValueError, TypeError) as e:
|
||||
logger.error(e)
|
||||
pass
|
||||
|
||||
async def get_nominal_hashrate(self, web_summary: dict = None) -> Optional[float]:
|
||||
# get hr from API
|
||||
if not web_summary:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_summary:
|
||||
try:
|
||||
hashrate = 0
|
||||
if web_summary["HBs"] != None:
|
||||
for hb in web_summary["HBs"]:
|
||||
if hb["Hashrate"][1] == 0:
|
||||
ideal = 1.0
|
||||
else:
|
||||
ideal = hb["Hashrate"][1]/100
|
||||
|
||||
hashrate += hb["Hashrate"][0]/ideal
|
||||
return round(
|
||||
float(float(hashrate/ 1000000)), 2)
|
||||
except (IndexError, KeyError, ValueError, TypeError) as e:
|
||||
logger.error(e)
|
||||
pass
|
||||
|
||||
|
||||
async def get_fw_ver(self, web_summary: dict = None) -> Optional[str]:
|
||||
if not web_summary:
|
||||
web_summary = await self.web.summary()
|
||||
|
||||
if web_summary:
|
||||
try:
|
||||
fw_ver = web_summary["Software"]
|
||||
fw_ver = fw_ver.split(" ")[1].replace("v", "")
|
||||
return fw_ver
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def get_fans(self, web_summary: dict = None) -> List[Fan]:
|
||||
if not web_summary:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
fans = []
|
||||
|
||||
if web_summary:
|
||||
for fan in web_summary["Fans Rpm"]:
|
||||
try:
|
||||
fans.append(Fan(web_summary["Fans Rpm"][fan]))
|
||||
except (LookupError, ValueError, TypeError):
|
||||
fans.append(Fan())
|
||||
return fans
|
||||
|
||||
async def get_hashboards(self, web_summary: dict = None, web_hashrate: dict= None) -> List[HashBoard]:
|
||||
if not web_summary:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
if not web_hashrate:
|
||||
try:
|
||||
web_hashrate = await self.web.hashrate()
|
||||
except APIError:
|
||||
pass
|
||||
hb_list = [HashBoard(slot=i, expected_chips=self.nominal_chips) for i in range(self.ideal_hashboards)]
|
||||
if web_summary["HBs"] != None:
|
||||
for hb in web_summary["HBs"]:
|
||||
for hr in web_hashrate:
|
||||
if hr["Index"] == hb["Index"]:
|
||||
num_of_chips = len(hr["Data"])
|
||||
hashrate = hb["Hashrate"][0]
|
||||
#Update the Hashboard object
|
||||
hb_list[hr["Index"]].expected_chips = num_of_chips
|
||||
hb_list[hr["Index"]].missing = False
|
||||
hb_list[hr["Index"]].hashrate = round(hashrate/1000000,2)
|
||||
hb_list[hr["Index"]].chips = num_of_chips
|
||||
hb_list[hr["Index"]].temp = hb["Temperature"]
|
||||
return hb_list
|
||||
|
||||
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
|
||||
return None
|
||||
|
||||
async def get_pools(self, web_summary: dict = None) -> List[dict]:
|
||||
groups = []
|
||||
|
||||
if not web_summary:
|
||||
try:
|
||||
web_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_summary:
|
||||
try:
|
||||
pools = {}
|
||||
for i, pool in enumerate(web_summary["StratumConfigs"]):
|
||||
pools[f"pool_{i + 1}_url"] = (
|
||||
pool["pool"]
|
||||
.replace("stratum+tcp://", "")
|
||||
.replace("stratum2+tcp://", "")
|
||||
)
|
||||
pools[f"pool_{i + 1}_user"] = pool["login"]
|
||||
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
|
||||
|
||||
groups.append(pools)
|
||||
except KeyError:
|
||||
pass
|
||||
return groups
|
||||
|
||||
|
||||
async def get_uptime(self, web_summary: dict = None) -> Optional[int]:
|
||||
if not web_summary:
|
||||
web_summary = await self.web.summary()
|
||||
if web_summary:
|
||||
try:
|
||||
uptime = web_summary["Session"]["Uptime"]
|
||||
return uptime
|
||||
except KeyError:
|
||||
pass
|
||||
return None
|
||||
|
||||
async def get_fault_light(self, web_summary: dict = None) -> bool:
|
||||
if not web_summary:
|
||||
web_summary = await self.web.summary()
|
||||
if web_summary:
|
||||
try:
|
||||
light = web_summary["Misc"]["Locate Miner State"]
|
||||
return light
|
||||
except KeyError:
|
||||
pass
|
||||
return False
|
||||
|
||||
async def get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
||||
if not web_summary:
|
||||
web_summary = await self.web.summary()
|
||||
errors = []
|
||||
if web_summary:
|
||||
try:
|
||||
error = web_summary["Status"]["Last Error"]
|
||||
if error != None:
|
||||
errors.append(X19Error(str(error)))
|
||||
return errors
|
||||
except KeyError:
|
||||
pass
|
||||
return errors
|
||||
447
pyasic/miners/backends/luxminer.py
Normal file
447
pyasic/miners/backends/luxminer.py
Normal file
@@ -0,0 +1,447 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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. -
|
||||
# ------------------------------------------------------------------------------
|
||||
import asyncio
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
from typing import List, Optional, Tuple, Union
|
||||
|
||||
import toml
|
||||
|
||||
from pyasic.API.bosminer import BOSMinerAPI
|
||||
from pyasic.API.luxminer import LUXMinerAPI
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.web.bosminer import BOSMinerWebAPI
|
||||
|
||||
LUXMINER_DATA_LOC = {
|
||||
"mac": {
|
||||
"cmd": "get_mac",
|
||||
"kwargs": {"api_config": {"api": "config"}},
|
||||
},
|
||||
"model": {"cmd": "get_model", "kwargs": {}},
|
||||
"api_ver": {
|
||||
"cmd": "get_api_ver",
|
||||
"kwargs": {},
|
||||
},
|
||||
"fw_ver": {
|
||||
"cmd": "get_fw_ver",
|
||||
"kwargs": {},
|
||||
},
|
||||
"hostname": {
|
||||
"cmd": "get_hostname",
|
||||
"kwargs": {},
|
||||
},
|
||||
"hashrate": {
|
||||
"cmd": "get_hashrate",
|
||||
"kwargs": {},
|
||||
},
|
||||
"nominal_hashrate": {
|
||||
"cmd": "get_nominal_hashrate",
|
||||
"kwargs": {},
|
||||
},
|
||||
"hashboards": {
|
||||
"cmd": "get_hashboards",
|
||||
"kwargs": {},
|
||||
},
|
||||
"wattage": {
|
||||
"cmd": "get_wattage",
|
||||
"kwargs": {},
|
||||
},
|
||||
"wattage_limit": {
|
||||
"cmd": "get_wattage_limit",
|
||||
"kwargs": {},
|
||||
},
|
||||
"fans": {
|
||||
"cmd": "get_fans",
|
||||
"kwargs": {},
|
||||
},
|
||||
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
||||
"env_temp": {"cmd": "get_env_temp", "kwargs": {}},
|
||||
"errors": {
|
||||
"cmd": "get_errors",
|
||||
"kwargs": {},
|
||||
},
|
||||
"fault_light": {
|
||||
"cmd": "get_fault_light",
|
||||
"kwargs": {},
|
||||
},
|
||||
"pools": {
|
||||
"cmd": "get_pools",
|
||||
"kwargs": {},
|
||||
},
|
||||
"is_mining": {
|
||||
"cmd": "is_mining",
|
||||
"kwargs": {},
|
||||
},
|
||||
"uptime": {
|
||||
"cmd": "get_uptime",
|
||||
"kwargs": {"api_stats": {"api": "stats"}},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class LUXMiner(BaseMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip)
|
||||
# interfaces
|
||||
self.api = LUXMinerAPI(ip, api_ver)
|
||||
# self.web = BOSMinerWebAPI(ip)
|
||||
|
||||
# static data
|
||||
self.api_type = "LUXMiner"
|
||||
# data gathering locations
|
||||
self.data_locations = LUXMINER_DATA_LOC
|
||||
# autotuning/shutdown support
|
||||
# self.supports_autotuning = True
|
||||
# self.supports_shutdown = True
|
||||
|
||||
# data storage
|
||||
self.api_ver = api_ver
|
||||
|
||||
async def _get_session(self) -> Optional[str]:
|
||||
try:
|
||||
data = await self.api.session()
|
||||
if not data["SESSION"][0]["SessionID"] == "":
|
||||
return data["SESSION"][0]["SessionID"]
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
try:
|
||||
data = await self.api.logon()
|
||||
return data["SESSION"][0]["SessionID"]
|
||||
except (LookupError, APIError):
|
||||
return
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
"""Sends command to turn on fault light on the miner."""
|
||||
try:
|
||||
session_id = await self._get_session()
|
||||
if session_id:
|
||||
await self.api.ledset(session_id, "red", "blink")
|
||||
return True
|
||||
except (APIError, LookupError):
|
||||
pass
|
||||
return False
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
"""Sends command to turn off fault light on the miner."""
|
||||
try:
|
||||
session_id = await self._get_session()
|
||||
if session_id:
|
||||
await self.api.ledset(session_id, "red", "off")
|
||||
return True
|
||||
except (APIError, LookupError):
|
||||
pass
|
||||
return False
|
||||
|
||||
async def restart_backend(self) -> bool:
|
||||
"""Restart luxminer hashing process. Wraps [`restart_luxminer`][pyasic.miners.backends.luxminer.LUXMiner.restart_luxminer] to standardize."""
|
||||
return await self.restart_luxminer()
|
||||
|
||||
async def restart_luxminer(self) -> bool:
|
||||
"""Restart luxminer hashing process."""
|
||||
try:
|
||||
session_id = await self._get_session()
|
||||
if session_id:
|
||||
await self.api.resetminer(session_id)
|
||||
return True
|
||||
except (APIError, LookupError):
|
||||
pass
|
||||
return False
|
||||
|
||||
async def stop_mining(self) -> bool:
|
||||
try:
|
||||
session_id = await self._get_session()
|
||||
if session_id:
|
||||
await self.api.curtail(session_id)
|
||||
return True
|
||||
except (APIError, LookupError):
|
||||
pass
|
||||
return False
|
||||
|
||||
async def resume_mining(self) -> bool:
|
||||
try:
|
||||
session_id = await self._get_session()
|
||||
if session_id:
|
||||
await self.api.wakeup(session_id)
|
||||
return True
|
||||
except (APIError, LookupError):
|
||||
pass
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
"""Reboots power to the physical miner."""
|
||||
try:
|
||||
session_id = await self._get_session()
|
||||
if session_id:
|
||||
await self.api.rebootdevice(session_id)
|
||||
return True
|
||||
except (APIError, LookupError):
|
||||
pass
|
||||
return False
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
"""Gets the config for the miner and sets it as `self.config`.
|
||||
|
||||
Returns:
|
||||
The config from `self.config`.
|
||||
"""
|
||||
return self.config
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
"""Configures miner with yaml config."""
|
||||
pass
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
return False
|
||||
|
||||
##################################################
|
||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||
##################################################
|
||||
|
||||
async def get_mac(self, api_config: dict = None) -> Optional[str]:
|
||||
mac = None
|
||||
if not api_config:
|
||||
try:
|
||||
api_config = await self.api.config()
|
||||
except APIError:
|
||||
return None
|
||||
|
||||
if api_config:
|
||||
try:
|
||||
mac = api_config["CONFIG"][0]["MACAddr"]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
return mac
|
||||
|
||||
async def get_model(self) -> Optional[str]:
|
||||
if self.model is not None:
|
||||
return self.model + " (LuxOS)"
|
||||
return "? (LuxOS)"
|
||||
|
||||
async def get_version(self) -> Tuple[Optional[str], Optional[str]]:
|
||||
pass
|
||||
|
||||
async def get_api_ver(self) -> Optional[str]:
|
||||
pass
|
||||
|
||||
async def get_fw_ver(self) -> Optional[str]:
|
||||
pass
|
||||
|
||||
async def get_hostname(self) -> Union[str, None]:
|
||||
pass
|
||||
|
||||
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
if not api_summary:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
try:
|
||||
return round(float(api_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2)
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||
hashboards = []
|
||||
|
||||
if not api_stats:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
try:
|
||||
board_offset = -1
|
||||
boards = api_stats["STATS"]
|
||||
if len(boards) > 1:
|
||||
for board_num in range(1, 16, 5):
|
||||
for _b_num in range(5):
|
||||
b = boards[1].get(f"chain_acn{board_num + _b_num}")
|
||||
|
||||
if b and not b == 0 and board_offset == -1:
|
||||
board_offset = board_num
|
||||
if board_offset == -1:
|
||||
board_offset = 1
|
||||
|
||||
for i in range(board_offset, board_offset + self.ideal_hashboards):
|
||||
hashboard = HashBoard(
|
||||
slot=i - board_offset, expected_chips=self.nominal_chips
|
||||
)
|
||||
|
||||
chip_temp = boards[1].get(f"temp{i}")
|
||||
if chip_temp:
|
||||
hashboard.chip_temp = round(chip_temp)
|
||||
|
||||
temp = boards[1].get(f"temp2_{i}")
|
||||
if temp:
|
||||
hashboard.temp = round(temp)
|
||||
|
||||
hashrate = boards[1].get(f"chain_rate{i}")
|
||||
if hashrate:
|
||||
hashboard.hashrate = round(float(hashrate) / 1000, 2)
|
||||
|
||||
chips = boards[1].get(f"chain_acn{i}")
|
||||
if chips:
|
||||
hashboard.chips = chips
|
||||
hashboard.missing = False
|
||||
if (not chips) or (not chips > 0):
|
||||
hashboard.missing = True
|
||||
hashboards.append(hashboard)
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
return hashboards
|
||||
|
||||
async def get_env_temp(self) -> Optional[float]:
|
||||
return None
|
||||
|
||||
async def get_wattage(self, api_power: dict) -> Optional[int]:
|
||||
if not api_power:
|
||||
try:
|
||||
api_power = await self.api.power()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_power:
|
||||
try:
|
||||
return api_power["POWER"][0]["Watts"]
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def get_wattage_limit(self) -> Optional[int]:
|
||||
return None
|
||||
|
||||
async def get_fans(self, api_fans: dict = None) -> List[Fan]:
|
||||
if not api_fans:
|
||||
try:
|
||||
api_fans = await self.api.fans()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
fans = []
|
||||
|
||||
if api_fans:
|
||||
for fan in range(self.fan_count):
|
||||
try:
|
||||
fans.append(Fan(api_fans["FANS"][0]["RPM"]))
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
fans.append(Fan())
|
||||
return fans
|
||||
|
||||
async def get_fan_psu(self) -> Optional[int]:
|
||||
return None
|
||||
|
||||
async def get_pools(self, api_pools: dict = None) -> List[dict]:
|
||||
if not api_pools:
|
||||
try:
|
||||
api_pools = await self.api.pools()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_pools:
|
||||
seen = []
|
||||
groups = [{"quota": "0"}]
|
||||
if api_pools.get("POOLS"):
|
||||
for i, pool in enumerate(api_pools["POOLS"]):
|
||||
if len(seen) == 0:
|
||||
seen.append(pool["User"])
|
||||
if not pool["User"] in seen:
|
||||
# need to use get_config, as this will never read perfectly as there are some bad edge cases
|
||||
groups = []
|
||||
cfg = await self.get_config()
|
||||
if cfg:
|
||||
for group in cfg.pool_groups:
|
||||
pools = {"quota": group.quota}
|
||||
for _i, _pool in enumerate(group.pools):
|
||||
pools[f"pool_{_i + 1}_url"] = _pool.url.replace(
|
||||
"stratum+tcp://", ""
|
||||
).replace("stratum2+tcp://", "")
|
||||
pools[f"pool_{_i + 1}_user"] = _pool.username
|
||||
groups.append(pools)
|
||||
return groups
|
||||
else:
|
||||
groups[0][f"pool_{i + 1}_url"] = (
|
||||
pool["URL"]
|
||||
.replace("stratum+tcp://", "")
|
||||
.replace("stratum2+tcp://", "")
|
||||
)
|
||||
groups[0][f"pool_{i + 1}_user"] = pool["User"]
|
||||
else:
|
||||
groups = []
|
||||
cfg = await self.get_config()
|
||||
if cfg:
|
||||
for group in cfg.pool_groups:
|
||||
pools = {"quota": group.quota}
|
||||
for _i, _pool in enumerate(group.pools):
|
||||
pools[f"pool_{_i + 1}_url"] = _pool.url.replace(
|
||||
"stratum+tcp://", ""
|
||||
).replace("stratum2+tcp://", "")
|
||||
pools[f"pool_{_i + 1}_user"] = _pool.username
|
||||
groups.append(pools)
|
||||
return groups
|
||||
return groups
|
||||
|
||||
async def get_errors(self) -> List[MinerErrorData]:
|
||||
pass
|
||||
|
||||
async def get_fault_light(self) -> bool:
|
||||
pass
|
||||
|
||||
async def get_nominal_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||
if not api_stats:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
try:
|
||||
ideal_rate = api_stats["STATS"][1]["total_rateideal"]
|
||||
try:
|
||||
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
||||
except KeyError:
|
||||
rate_unit = "GH"
|
||||
if rate_unit == "GH":
|
||||
return round(ideal_rate / 1000, 2)
|
||||
if rate_unit == "MH":
|
||||
return round(ideal_rate / 1000000, 2)
|
||||
else:
|
||||
return round(ideal_rate, 2)
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
|
||||
async def is_mining(self) -> Optional[bool]:
|
||||
pass
|
||||
|
||||
async def get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
||||
if not api_stats:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
try:
|
||||
return int(api_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
@@ -17,6 +17,7 @@
|
||||
from typing import Optional
|
||||
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.logger import logger
|
||||
from pyasic.miners.backends.bmminer import BMMiner
|
||||
from pyasic.web.vnish import VNishWebAPI
|
||||
|
||||
@@ -43,6 +44,8 @@ VNISH_DATA_LOC = {
|
||||
"errors": {"cmd": "get_errors", "kwargs": {}},
|
||||
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
"is_mining": {"cmd": "is_mining", "kwargs": {}},
|
||||
"uptime": {"cmd": "get_uptime", "kwargs": {}},
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +74,24 @@ class VNish(BMMiner):
|
||||
pass
|
||||
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:
|
||||
data = await self.web.reboot()
|
||||
if data:
|
||||
@@ -142,7 +163,7 @@ class VNish(BMMiner):
|
||||
float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
|
||||
)
|
||||
except (IndexError, KeyError, ValueError, TypeError) as e:
|
||||
print(e)
|
||||
logger.error(e)
|
||||
pass
|
||||
|
||||
async def get_wattage_limit(self, web_settings: dict = None) -> Optional[int]:
|
||||
@@ -169,3 +190,9 @@ class VNish(BMMiner):
|
||||
return fw_ver
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
|
||||
return None
|
||||
|
||||
async def get_uptime(self, *args, **kwargs) -> Optional[int]:
|
||||
return None
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
import ipaddress
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
@@ -24,6 +24,7 @@ import asyncssh
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard, MinerData
|
||||
from pyasic.data.error_codes import MinerErrorData
|
||||
from pyasic.logger import logger
|
||||
|
||||
|
||||
class BaseMiner(ABC):
|
||||
@@ -32,6 +33,8 @@ class BaseMiner(ABC):
|
||||
self.api = None
|
||||
self.web = None
|
||||
|
||||
self.ssh_pwd = "root"
|
||||
|
||||
# static data
|
||||
self.ip = ip
|
||||
self.api_type = None
|
||||
@@ -71,6 +74,53 @@ class BaseMiner(ABC):
|
||||
def __eq__(self, other):
|
||||
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:
|
||||
"""Create a new asyncssh connection"""
|
||||
try:
|
||||
@@ -78,7 +128,7 @@ class BaseMiner(ABC):
|
||||
str(self.ip),
|
||||
known_hosts=None,
|
||||
username="root",
|
||||
password="root",
|
||||
password=self.ssh_pwd,
|
||||
server_host_key_algs=["ssh-rsa"],
|
||||
)
|
||||
return conn
|
||||
@@ -345,83 +395,99 @@ class BaseMiner(ABC):
|
||||
"""
|
||||
pass
|
||||
|
||||
async def _get_data(self, allow_warning: bool, data_to_get: list = None) -> dict:
|
||||
if not data_to_get:
|
||||
@abstractmethod
|
||||
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
|
||||
"""Check whether the miner is mining.
|
||||
|
||||
Returns:
|
||||
A boolean value representing if the miner is mining.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_uptime(self, *args, **kwargs) -> Optional[int]:
|
||||
"""Get the uptime of the miner in seconds.
|
||||
|
||||
Returns:
|
||||
The uptime of the miner in seconds.
|
||||
"""
|
||||
pass
|
||||
|
||||
async def _get_data(
|
||||
self, allow_warning: bool, include: list = None, exclude: list = None
|
||||
) -> dict:
|
||||
if include is None:
|
||||
# everything
|
||||
data_to_get = [
|
||||
"mac",
|
||||
"model",
|
||||
"api_ver",
|
||||
"fw_ver",
|
||||
"hostname",
|
||||
"hashrate",
|
||||
"nominal_hashrate",
|
||||
"hashboards",
|
||||
"env_temp",
|
||||
"wattage",
|
||||
"wattage_limit",
|
||||
"fans",
|
||||
"fan_psu",
|
||||
"errors",
|
||||
"fault_light",
|
||||
"pools",
|
||||
]
|
||||
api_multicommand = []
|
||||
include = list(self.data_locations.keys())
|
||||
|
||||
if exclude is not None:
|
||||
for item in exclude:
|
||||
if item in include:
|
||||
include.remove(item)
|
||||
|
||||
api_multicommand = set()
|
||||
web_multicommand = []
|
||||
for data_name in data_to_get:
|
||||
for data_name in include:
|
||||
try:
|
||||
fn_args = self.data_locations[data_name]["kwargs"]
|
||||
for arg_name in fn_args:
|
||||
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"):
|
||||
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:
|
||||
print(e, data_name)
|
||||
logger.error(e, data_name)
|
||||
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:
|
||||
api_command_data = await self.api.multicommand(
|
||||
*api_multicommand, allow_warning=allow_warning
|
||||
api_command_task = asyncio.create_task(
|
||||
self.api.multicommand(*api_multicommand, allow_warning=allow_warning)
|
||||
)
|
||||
else:
|
||||
api_command_data = {}
|
||||
api_command_task = asyncio.sleep(0)
|
||||
if len(web_multicommand) > 0:
|
||||
web_command_data = await self.web.multicommand(
|
||||
*web_multicommand, allow_warning=allow_warning
|
||||
web_command_task = asyncio.create_task(
|
||||
self.web.multicommand(*web_multicommand, allow_warning=allow_warning)
|
||||
)
|
||||
else:
|
||||
web_command_task = asyncio.sleep(0)
|
||||
|
||||
web_command_data = await web_command_task
|
||||
if web_command_data is None:
|
||||
web_command_data = {}
|
||||
|
||||
api_command_data = await api_command_task
|
||||
if api_command_data is None:
|
||||
api_command_data = {}
|
||||
|
||||
miner_data = {}
|
||||
|
||||
for data_name in data_to_get:
|
||||
for data_name in include:
|
||||
try:
|
||||
fn_args = self.data_locations[data_name]["kwargs"]
|
||||
args_to_send = {k: None for k in fn_args}
|
||||
for arg_name in fn_args:
|
||||
if fn_args[arg_name].get("api"):
|
||||
if api_command_data.get("multicommand"):
|
||||
args_to_send[arg_name] = api_command_data[
|
||||
fn_args[arg_name]["api"]
|
||||
][0]
|
||||
else:
|
||||
args_to_send[arg_name] = api_command_data
|
||||
if fn_args[arg_name].get("web"):
|
||||
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 (KeyError, IndexError):
|
||||
try:
|
||||
if fn_args[arg_name].get("api"):
|
||||
if api_command_data.get("multicommand"):
|
||||
args_to_send[arg_name] = api_command_data[
|
||||
fn_args[arg_name]["api"]
|
||||
][0]
|
||||
else:
|
||||
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:
|
||||
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
|
||||
|
||||
function = getattr(self, self.data_locations[data_name]["cmd"])
|
||||
@@ -436,8 +502,8 @@ class BaseMiner(ABC):
|
||||
except KeyError:
|
||||
pass
|
||||
if len(pools_data) > 1:
|
||||
miner_data["pool_2_url"] = pools_data[1]["pool_2_url"]
|
||||
miner_data["pool_2_user"] = pools_data[1]["pool_2_user"]
|
||||
miner_data["pool_2_url"] = pools_data[1]["pool_1_url"]
|
||||
miner_data["pool_2_user"] = pools_data[1]["pool_1_user"]
|
||||
miner_data[
|
||||
"pool_split"
|
||||
] = f"{pools_data[0]['quota']}/{pools_data[1]['quota']}"
|
||||
@@ -451,13 +517,14 @@ class BaseMiner(ABC):
|
||||
return miner_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:
|
||||
"""Get data from the miner in the form of [`MinerData`][pyasic.data.MinerData].
|
||||
|
||||
Parameters:
|
||||
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:
|
||||
A [`MinerData`][pyasic.data.MinerData] instance containing data from the miner.
|
||||
@@ -473,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=allow_warning, include=include, exclude=exclude
|
||||
)
|
||||
for item in gathered_data:
|
||||
if gathered_data[item] is not None:
|
||||
setattr(data, item, gathered_data[item])
|
||||
|
||||
@@ -13,17 +13,17 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
|
||||
import asyncio
|
||||
import enum
|
||||
import ipaddress
|
||||
import json
|
||||
import re
|
||||
from typing import Callable, List, Optional, Tuple, Union
|
||||
from typing import AsyncGenerator, Callable, List, Optional, Tuple, Union
|
||||
|
||||
import aiohttp
|
||||
import anyio
|
||||
import httpx
|
||||
|
||||
from pyasic import settings
|
||||
from pyasic.logger import logger
|
||||
from pyasic.miners.antminer import *
|
||||
from pyasic.miners.avalonminer import *
|
||||
@@ -35,7 +35,9 @@ from pyasic.miners.backends import (
|
||||
CGMiner,
|
||||
CGMinerAvalon,
|
||||
Hiveon,
|
||||
LUXMiner,
|
||||
VNish,
|
||||
ePIC,
|
||||
)
|
||||
from pyasic.miners.base import AnyMiner
|
||||
from pyasic.miners.goldshell import *
|
||||
@@ -43,9 +45,6 @@ from pyasic.miners.innosilicon import *
|
||||
from pyasic.miners.unknown import UnknownMiner
|
||||
from pyasic.miners.whatsminer import *
|
||||
|
||||
TIMEOUT = 20
|
||||
RETRIES = 3
|
||||
|
||||
|
||||
class MinerTypes(enum.Enum):
|
||||
ANTMINER = 0
|
||||
@@ -56,6 +55,8 @@ class MinerTypes(enum.Enum):
|
||||
BRAIINS_OS = 5
|
||||
VNISH = 6
|
||||
HIVEON = 7
|
||||
LUX_OS = 8
|
||||
EPIC = 9
|
||||
|
||||
|
||||
MINER_CLASSES = {
|
||||
@@ -83,6 +84,8 @@ MINER_CLASSES = {
|
||||
"ANTMINER S19L": BMMinerS19L,
|
||||
"ANTMINER S19 PRO": BMMinerS19Pro,
|
||||
"ANTMINER S19J": BMMinerS19j,
|
||||
"ANTMINER S19I": BMMinerS19i,
|
||||
"ANTMINER S19+": BMMinerS19Plus,
|
||||
"ANTMINER S19J88NOPIC": BMMinerS19jNoPIC,
|
||||
"ANTMINER S19PRO+": BMMinerS19ProPlus,
|
||||
"ANTMINER S19J PRO": BMMinerS19jPro,
|
||||
@@ -97,6 +100,8 @@ MINER_CLASSES = {
|
||||
"M20SV10": BTMinerM20SV10,
|
||||
"M20SV20": BTMinerM20SV20,
|
||||
"M20SV30": BTMinerM20SV30,
|
||||
"M20PV10": BTMinerM20PV10,
|
||||
"M20PV30": BTMinerM20PV30,
|
||||
"M20S+V30": BTMinerM20SPlusV30,
|
||||
"M21V10": BTMinerM21V10,
|
||||
"M21SV20": BTMinerM21SV20,
|
||||
@@ -106,6 +111,8 @@ MINER_CLASSES = {
|
||||
"M29V10": BTMinerM29V10,
|
||||
"M30V10": BTMinerM30V10,
|
||||
"M30V20": BTMinerM30V20,
|
||||
"M30KV10": BTMinerM30KV10,
|
||||
"M30LV10": BTMinerM30LV10,
|
||||
"M30SV10": BTMinerM30SV10,
|
||||
"M30SV20": BTMinerM30SV20,
|
||||
"M30SV30": BTMinerM30SV30,
|
||||
@@ -155,6 +162,7 @@ MINER_CLASSES = {
|
||||
"M30S+VE100": BTMinerM30SPlusVE100,
|
||||
"M30S+VF20": BTMinerM30SPlusVF20,
|
||||
"M30S+VF30": BTMinerM30SPlusVF30,
|
||||
"M30S+VG20": BTMinerM30SPlusVG20,
|
||||
"M30S+VG30": BTMinerM30SPlusVG30,
|
||||
"M30S+VG40": BTMinerM30SPlusVG40,
|
||||
"M30S+VG50": BTMinerM30SPlusVG50,
|
||||
@@ -188,6 +196,9 @@ MINER_CLASSES = {
|
||||
"M30S++VJ30": BTMinerM30SPlusPlusVJ30,
|
||||
"M31V10": BTMinerM31V10,
|
||||
"M31V20": BTMinerM31V20,
|
||||
"M31HV10": BTMinerM31HV10,
|
||||
"M31HV40": BTMinerM31HV40,
|
||||
"M31LV10": BTMinerM31LV10,
|
||||
"M31SV10": BTMinerM31SV10,
|
||||
"M31SV20": BTMinerM31SV20,
|
||||
"M31SV30": BTMinerM31SV30,
|
||||
@@ -203,7 +214,6 @@ MINER_CLASSES = {
|
||||
"M31SEV10": BTMinerM31SEV10,
|
||||
"M31SEV20": BTMinerM31SEV20,
|
||||
"M31SEV30": BTMinerM31SEV30,
|
||||
"M31HV40": BTMinerM31HV40,
|
||||
"M31S+V10": BTMinerM31SPlusV10,
|
||||
"M31S+V20": BTMinerM31SPlusV20,
|
||||
"M31S+V30": BTMinerM31SPlusV30,
|
||||
@@ -230,6 +240,7 @@ MINER_CLASSES = {
|
||||
"M33V20": BTMinerM33V20,
|
||||
"M33V30": BTMinerM33V30,
|
||||
"M33SVG30": BTMinerM33SVG30,
|
||||
"M33S+VG20": BTMinerM33SPlusVG20,
|
||||
"M33S+VH20": BTMinerM33SPlusVH20,
|
||||
"M33S+VH30": BTMinerM33SPlusVH30,
|
||||
"M33S++VH20": BTMinerM33SPlusPlusVH20,
|
||||
@@ -239,7 +250,10 @@ MINER_CLASSES = {
|
||||
"M36SVE10": BTMinerM36SVE10,
|
||||
"M36S+VG30": BTMinerM36SPlusVG30,
|
||||
"M36S++VH30": BTMinerM36SPlusPlusVH30,
|
||||
"M39V10": BTMinerM39V10,
|
||||
"M39V20": BTMinerM39V20,
|
||||
"M39V30": BTMinerM39V30,
|
||||
"M50VE30": BTMinerM50VE30,
|
||||
"M50VG30": BTMinerM50VG30,
|
||||
"M50VH10": BTMinerM50VH10,
|
||||
"M50VH20": BTMinerM50VH20,
|
||||
@@ -317,6 +331,7 @@ MINER_CLASSES = {
|
||||
"ANTMINER S19J": BOSMinerS19j,
|
||||
"ANTMINER S19J88NOPIC": BOSMinerS19jNoPIC,
|
||||
"ANTMINER S19J PRO": BOSMinerS19jPro,
|
||||
"ANTMINER S19J PRO NOPIC": BOSMinerS19jPro,
|
||||
"ANTMINER T19": BOSMinerT19,
|
||||
},
|
||||
MinerTypes.VNISH: {
|
||||
@@ -325,6 +340,7 @@ MINER_CLASSES = {
|
||||
"ANTMINER S17+": VNishS17Plus,
|
||||
"ANTMINER S17 PRO": VNishS17Pro,
|
||||
"ANTMINER S19": VNishS19,
|
||||
"ANTMINER S19NOPIC": VNishS19NoPIC,
|
||||
"ANTMINER S19 PRO": VNishS19Pro,
|
||||
"ANTMINER S19J": VNishS19j,
|
||||
"ANTMINER S19J PRO": VNishS19jPro,
|
||||
@@ -332,10 +348,22 @@ MINER_CLASSES = {
|
||||
"ANTMINER S19A PRO": VNishS19aPro,
|
||||
"ANTMINER T19": VNishT19,
|
||||
},
|
||||
MinerTypes.EPIC: {
|
||||
None: ePIC,
|
||||
"ANTMINER S19": ePICS19,
|
||||
"ANTMINER S19 PRO": ePICS19Pro,
|
||||
"ANTMINER S19J": ePICS19j,
|
||||
"ANTMINER S19J PRO": ePICS19jPro,
|
||||
"ANTMINER S19 XP": ePICS19XP,
|
||||
},
|
||||
MinerTypes.HIVEON: {
|
||||
None: Hiveon,
|
||||
"ANTMINER T9": HiveonT9,
|
||||
},
|
||||
MinerTypes.LUX_OS: {
|
||||
None: LUXMiner,
|
||||
"ANTMINER S9": LUXMinerS9,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -367,7 +395,9 @@ class MinerFactory:
|
||||
def clear_cached_miners(self):
|
||||
self.cache = {}
|
||||
|
||||
async def get_multiple_miners(self, ips: List[str], limit: int = 200):
|
||||
async def get_multiple_miners(
|
||||
self, ips: List[str], limit: int = 200
|
||||
) -> List[AnyMiner]:
|
||||
results = []
|
||||
|
||||
async for miner in self.get_miner_generator(ips, limit):
|
||||
@@ -375,7 +405,7 @@ class MinerFactory:
|
||||
|
||||
return results
|
||||
|
||||
async def get_miner_generator(self, ips: list, limit: int = 200):
|
||||
async def get_miner_generator(self, ips: list, limit: int = 200) -> AsyncGenerator:
|
||||
tasks = []
|
||||
semaphore = asyncio.Semaphore(limit)
|
||||
|
||||
@@ -383,13 +413,10 @@ class MinerFactory:
|
||||
tasks.append(asyncio.create_task(self.get_miner(ip)))
|
||||
|
||||
for task in tasks:
|
||||
await semaphore.acquire()
|
||||
try:
|
||||
async with semaphore:
|
||||
result = await task
|
||||
if result is not None:
|
||||
yield result
|
||||
finally:
|
||||
semaphore.release()
|
||||
|
||||
async def get_miner(self, ip: str):
|
||||
ip = str(ip)
|
||||
@@ -398,10 +425,12 @@ class MinerFactory:
|
||||
|
||||
miner_type = None
|
||||
|
||||
for _ in range(RETRIES):
|
||||
for _ in range(settings.get("factory_get_retries", 1)):
|
||||
task = asyncio.create_task(self._get_miner_type(ip))
|
||||
try:
|
||||
miner_type = await asyncio.wait_for(task, timeout=TIMEOUT)
|
||||
miner_type = await asyncio.wait_for(
|
||||
task, timeout=settings.get("factory_get_timeout", 3)
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
task.cancel()
|
||||
else:
|
||||
@@ -418,19 +447,30 @@ class MinerFactory:
|
||||
MinerTypes.GOLDSHELL: self.get_miner_model_goldshell,
|
||||
MinerTypes.BRAIINS_OS: self.get_miner_model_braiins_os,
|
||||
MinerTypes.VNISH: self.get_miner_model_vnish,
|
||||
MinerTypes.EPIC: self.get_miner_model_epic,
|
||||
MinerTypes.HIVEON: self.get_miner_model_hiveon,
|
||||
MinerTypes.LUX_OS: self.get_miner_model_luxos,
|
||||
}
|
||||
fn = miner_model_fns.get(miner_type)
|
||||
|
||||
if fn is not None:
|
||||
task = asyncio.create_task(fn(ip))
|
||||
try:
|
||||
miner_model = await asyncio.wait_for(task, timeout=30)
|
||||
miner_model = await asyncio.wait_for(
|
||||
task, timeout=settings.get("factory_get_timeout", 3)
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
task.cancel()
|
||||
|
||||
boser_enabled = None
|
||||
if miner_type == MinerTypes.BRAIINS_OS:
|
||||
boser_enabled = await self.get_boser_braiins_os(ip)
|
||||
|
||||
miner = self._select_miner_from_classes(
|
||||
ip, miner_type=miner_type, miner_model=miner_model
|
||||
ip,
|
||||
miner_type=miner_type,
|
||||
miner_model=miner_model,
|
||||
boser_enabled=boser_enabled,
|
||||
)
|
||||
|
||||
if miner is not None and not isinstance(miner, UnknownMiner):
|
||||
@@ -440,71 +480,87 @@ class MinerFactory:
|
||||
async def _get_miner_type(self, ip: str):
|
||||
tasks = [
|
||||
asyncio.create_task(self._get_miner_web(ip)),
|
||||
asyncio.create_task(self._get_miner_socket(ip)),
|
||||
# asyncio.create_task(self._get_miner_socket(ip)),
|
||||
]
|
||||
|
||||
return await concurrent_get_first_result(tasks, lambda x: x is not None)
|
||||
|
||||
async def _get_miner_web(self, ip: str):
|
||||
urls = [f"http://{ip}/", f"https://{ip}/"]
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with httpx.AsyncClient(
|
||||
transport=settings.transport(verify=False)
|
||||
) as session:
|
||||
tasks = [asyncio.create_task(self._web_ping(session, url)) for url in urls]
|
||||
|
||||
text, resp = await concurrent_get_first_result(
|
||||
tasks, lambda x: x[0] is not None
|
||||
tasks,
|
||||
lambda x: x[0] is not None
|
||||
and self._parse_web_type(x[0], x[1]) is not None,
|
||||
)
|
||||
|
||||
if text is not None:
|
||||
return self._parse_web_type(text, resp)
|
||||
|
||||
@staticmethod
|
||||
async def _web_ping(
|
||||
session: aiohttp.ClientSession, url: str
|
||||
) -> Tuple[Optional[str], Optional[aiohttp.ClientResponse]]:
|
||||
session: httpx.AsyncClient, url: str
|
||||
) -> Tuple[Optional[str], Optional[httpx.Response]]:
|
||||
try:
|
||||
resp = await session.get(url)
|
||||
return await resp.text(), resp
|
||||
except (aiohttp.ClientError, asyncio.TimeoutError):
|
||||
resp = await session.get(url, follow_redirects=True)
|
||||
return resp.text, resp
|
||||
except (
|
||||
httpx.HTTPError,
|
||||
asyncio.TimeoutError,
|
||||
anyio.EndOfStream,
|
||||
anyio.ClosedResourceError,
|
||||
):
|
||||
pass
|
||||
return None, None
|
||||
|
||||
@staticmethod
|
||||
def _parse_web_type(web_text: str, web_resp: aiohttp.ClientResponse) -> MinerTypes:
|
||||
if web_resp.status == 401 and 'realm="antMiner' in web_resp.headers.get(
|
||||
def _parse_web_type(web_text: str, web_resp: httpx.Response) -> MinerTypes:
|
||||
if web_resp.status_code == 401 and 'realm="antMiner' in web_resp.headers.get(
|
||||
"www-authenticate", ""
|
||||
):
|
||||
return MinerTypes.ANTMINER
|
||||
if web_resp.status == 307 and "https://" in web_resp.headers.get(
|
||||
"location", ""
|
||||
):
|
||||
return MinerTypes.WHATSMINER
|
||||
if "Braiins OS" in web_text or 'href="/cgi-bin/luci"' in web_text:
|
||||
if len(web_resp.history) > 0:
|
||||
history_resp = web_resp.history[0]
|
||||
if (
|
||||
"/cgi-bin/luci" in web_text
|
||||
and history_resp.status_code == 307
|
||||
and "https://" in history_resp.headers.get("location", "")
|
||||
):
|
||||
return MinerTypes.WHATSMINER
|
||||
if "Braiins OS" in web_text:
|
||||
return MinerTypes.BRAIINS_OS
|
||||
if "cloud-box" in web_text:
|
||||
return MinerTypes.GOLDSHELL
|
||||
if "AnthillOS" in web_text:
|
||||
return MinerTypes.VNISH
|
||||
if "Miner Web Dashboard" in web_text:
|
||||
return MinerTypes.EPIC
|
||||
if "Avalon" in web_text:
|
||||
return MinerTypes.AVALONMINER
|
||||
if "DragonMint" in web_text:
|
||||
return MinerTypes.INNOSILICON
|
||||
|
||||
async def _get_miner_socket(self, ip: str):
|
||||
commands = ["devdetails", "version"]
|
||||
commands = ["version", "devdetails"]
|
||||
tasks = [asyncio.create_task(self._socket_ping(ip, cmd)) for cmd in commands]
|
||||
|
||||
data = await concurrent_get_first_result(
|
||||
tasks, lambda x: x is not None and self._parse_socket_type(x) is not None
|
||||
)
|
||||
if data is not None:
|
||||
return self._parse_socket_type(data)
|
||||
d = self._parse_socket_type(data)
|
||||
return d
|
||||
|
||||
@staticmethod
|
||||
async def _socket_ping(ip: str, cmd: str) -> Optional[str]:
|
||||
data = b""
|
||||
try:
|
||||
reader, writer = await asyncio.wait_for(
|
||||
asyncio.open_connection(str(ip), 4028), timeout=30
|
||||
asyncio.open_connection(str(ip), 4028),
|
||||
timeout=settings.get("factory_get_timeout", 3),
|
||||
)
|
||||
except (ConnectionError, OSError, asyncio.TimeoutError):
|
||||
return
|
||||
@@ -555,7 +611,9 @@ class MinerFactory:
|
||||
return MinerTypes.VNISH
|
||||
if "HIVEON" in upper_data:
|
||||
return MinerTypes.HIVEON
|
||||
if "ANTMINER" in upper_data:
|
||||
if "LUXMINER" in upper_data:
|
||||
return MinerTypes.LUX_OS
|
||||
if "ANTMINER" in upper_data and not "DEVDETAILS" in upper_data:
|
||||
return MinerTypes.ANTMINER
|
||||
if "INTCHAINS_QOMO" in upper_data:
|
||||
return MinerTypes.GOLDSHELL
|
||||
@@ -566,26 +624,26 @@ class MinerFactory:
|
||||
self,
|
||||
ip: Union[ipaddress.ip_address, str],
|
||||
location: str,
|
||||
auth: Optional[aiohttp.BasicAuth] = None,
|
||||
auth: Optional[httpx.DigestAuth] = None,
|
||||
) -> Optional[dict]:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as session:
|
||||
try:
|
||||
data = await session.get(
|
||||
f"http://{str(ip)}{location}",
|
||||
auth=auth,
|
||||
timeout=30,
|
||||
timeout=settings.get("factory_get_timeout", 3),
|
||||
)
|
||||
except (aiohttp.ClientError, asyncio.TimeoutError):
|
||||
except (httpx.HTTPError, asyncio.TimeoutError):
|
||||
logger.info(f"{ip}: Web command timeout.")
|
||||
return
|
||||
if data is None:
|
||||
return
|
||||
try:
|
||||
json_data = await data.json()
|
||||
except (aiohttp.ContentTypeError, asyncio.TimeoutError):
|
||||
json_data = data.json()
|
||||
except (json.JSONDecodeError, asyncio.TimeoutError):
|
||||
try:
|
||||
return json.loads(await data.text())
|
||||
except (json.JSONDecodeError, aiohttp.ClientError):
|
||||
return json.loads(data.text)
|
||||
except (json.JSONDecodeError, httpx.HTTPError):
|
||||
return
|
||||
else:
|
||||
return json_data
|
||||
@@ -620,10 +678,15 @@ class MinerFactory:
|
||||
return
|
||||
except (ConnectionError, OSError):
|
||||
return
|
||||
if data == b"Socket connect failed: Connection refused\n":
|
||||
return
|
||||
|
||||
data = await self._fix_api_data(data)
|
||||
|
||||
data = json.loads(data)
|
||||
try:
|
||||
data = json.loads(data)
|
||||
except json.JSONDecodeError:
|
||||
return {}
|
||||
|
||||
return data
|
||||
|
||||
@@ -667,15 +730,41 @@ class MinerFactory:
|
||||
ip: ipaddress.ip_address,
|
||||
miner_model: Union[str, None],
|
||||
miner_type: Union[MinerTypes, None],
|
||||
boser_enabled: bool = None,
|
||||
) -> AnyMiner:
|
||||
kwargs = {}
|
||||
if boser_enabled is not None:
|
||||
kwargs["boser"] = boser_enabled
|
||||
try:
|
||||
return MINER_CLASSES[miner_type][str(miner_model).upper()](ip)
|
||||
return MINER_CLASSES[miner_type][str(miner_model).upper()](ip, **kwargs)
|
||||
except LookupError:
|
||||
if miner_type in MINER_CLASSES:
|
||||
return MINER_CLASSES[miner_type][None](ip)
|
||||
return UnknownMiner(str(ip))
|
||||
|
||||
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")
|
||||
try:
|
||||
miner_model = sock_json_data["VERSION"][0]["Type"]
|
||||
@@ -700,19 +789,6 @@ class MinerFactory:
|
||||
except (TypeError, LookupError):
|
||||
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):
|
||||
json_data = await self.send_web_command(ip, "/mcb/status")
|
||||
|
||||
@@ -726,7 +802,8 @@ class MinerFactory:
|
||||
async def get_miner_model_whatsminer(self, ip: str):
|
||||
sock_json_data = await self.send_api_command(ip, "devdetails")
|
||||
try:
|
||||
miner_model = sock_json_data["DEVDETAILS"][0]["Model"]
|
||||
miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace("_", "")
|
||||
miner_model = miner_model[:-1] + "0"
|
||||
|
||||
return miner_model
|
||||
except (TypeError, LookupError):
|
||||
@@ -745,14 +822,14 @@ class MinerFactory:
|
||||
|
||||
async def get_miner_model_innosilicon(self, ip: str) -> Optional[str]:
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as session:
|
||||
auth_req = await session.post(
|
||||
f"http://{ip}/api/auth",
|
||||
data={"username": "admin", "password": "admin"},
|
||||
)
|
||||
auth = (await auth_req.json())["jwt"]
|
||||
auth = auth_req.json()["jwt"]
|
||||
|
||||
web_data = await (
|
||||
web_data = (
|
||||
await session.post(
|
||||
f"http://{ip}/api/type",
|
||||
headers={"Authorization": "Bearer " + auth},
|
||||
@@ -760,7 +837,7 @@ class MinerFactory:
|
||||
)
|
||||
).json()
|
||||
return web_data["type"]
|
||||
except (aiohttp.ClientError, LookupError):
|
||||
except (httpx.HTTPError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_braiins_os(self, ip: str) -> Optional[str]:
|
||||
@@ -775,18 +852,27 @@ class MinerFactory:
|
||||
pass
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as session:
|
||||
d = await session.post(
|
||||
f"http://{ip}/graphql",
|
||||
json={"query": "{bosminer {info{modelName}}}"},
|
||||
)
|
||||
if d.status == 200:
|
||||
json_data = await d.json()
|
||||
if d.status_code == 200:
|
||||
json_data = d.json()
|
||||
miner_model = json_data["data"]["bosminer"]["info"]["modelName"]
|
||||
return miner_model
|
||||
except (aiohttp.ClientError, LookupError):
|
||||
except (httpx.HTTPError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_boser_braiins_os(self, ip: str):
|
||||
# TODO: refine this check
|
||||
try:
|
||||
sock_json_data = await self.send_api_command(ip, "version")
|
||||
return sock_json_data["STATUS"][0]["Msg"].split(" ")[0].upper() == "BOSER"
|
||||
except LookupError:
|
||||
# let the bosminer class decide
|
||||
return None
|
||||
|
||||
async def get_miner_model_vnish(self, ip: str) -> Optional[str]:
|
||||
sock_json_data = await self.send_api_command(ip, "stats")
|
||||
try:
|
||||
@@ -795,6 +881,20 @@ class MinerFactory:
|
||||
split_miner_model = miner_model.split(" (")
|
||||
miner_model = split_miner_model[0]
|
||||
|
||||
if "(88)" in miner_model:
|
||||
miner_model = miner_model.replace("(88)", "NOPIC")
|
||||
|
||||
if " AML" in miner_model:
|
||||
miner_model = miner_model.replace(" AML", "")
|
||||
|
||||
return miner_model
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_epic(self, ip: str) -> Optional[str]:
|
||||
sock_json_data = await self.send_web_command(ip, ":4028/capabilities")
|
||||
try:
|
||||
miner_model = sock_json_data["Model"]
|
||||
return miner_model
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
@@ -808,5 +908,17 @@ class MinerFactory:
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_luxos(self, ip: str):
|
||||
sock_json_data = await self.send_api_command(ip, "version")
|
||||
try:
|
||||
miner_model = sock_json_data["VERSION"][0]["Type"]
|
||||
|
||||
if " (" in miner_model:
|
||||
split_miner_model = miner_model.split(" (")
|
||||
miner_model = split_miner_model[0]
|
||||
return miner_model
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
|
||||
miner_factory = MinerFactory()
|
||||
|
||||
@@ -26,6 +26,15 @@ class S19(AntMiner): # noqa - ignore ABC method implementation
|
||||
self.fan_count = 4
|
||||
|
||||
|
||||
class S19NoPIC(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 No PIC"
|
||||
self.nominal_chips = 88
|
||||
self.fan_count = 4
|
||||
|
||||
|
||||
class S19Pro(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
@@ -35,6 +44,24 @@ class S19Pro(AntMiner): # noqa - ignore ABC method implementation
|
||||
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
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
|
||||
@@ -20,9 +20,12 @@ from .S19 import (
|
||||
S19XP,
|
||||
S19a,
|
||||
S19aPro,
|
||||
S19i,
|
||||
S19j,
|
||||
S19jNoPIC,
|
||||
S19jPro,
|
||||
S19NoPIC,
|
||||
S19Plus,
|
||||
S19Pro,
|
||||
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)
|
||||
self.ip = ip
|
||||
self.model = "M20S V30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M20SV30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 140
|
||||
self.fan_count = 2
|
||||
|
||||
@@ -24,8 +24,5 @@ class M21V10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M21 V10"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M21V10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 33
|
||||
self.fan_count = 2
|
||||
|
||||
@@ -42,8 +42,5 @@ class M21SV70(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M21S V70"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M21SV70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 111
|
||||
self.fan_count = 2
|
||||
|
||||
@@ -24,8 +24,5 @@ class M29V10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M29 V10"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M29V10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 50
|
||||
self.fan_count = 2
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .M20 import M20V10
|
||||
from .M20P import M20PV10, M20PV30
|
||||
from .M20S import M20SV10, M20SV20, M20SV30
|
||||
from .M20S_Plus import M20SPlusV30
|
||||
from .M21 import M21V10
|
||||
|
||||
@@ -24,10 +24,7 @@ class M30V10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30 V10"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30V10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 105
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -36,8 +33,5 @@ class M30V20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30 V20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30V20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 111
|
||||
self.fan_count = 2
|
||||
|
||||
29
pyasic/miners/types/whatsminer/M3X/M30K.py
Normal file
29
pyasic/miners/types/whatsminer/M3X/M30K.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import warnings
|
||||
|
||||
from pyasic.miners.makes import WhatsMiner
|
||||
|
||||
|
||||
class M30KV10(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 = "M30K V10"
|
||||
self.ideal_hashboards = 4
|
||||
self.nominal_chips = 240
|
||||
self.fan_count = 2
|
||||
29
pyasic/miners/types/whatsminer/M3X/M30L.py
Normal file
29
pyasic/miners/types/whatsminer/M3X/M30L.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import warnings
|
||||
|
||||
from pyasic.miners.makes import WhatsMiner
|
||||
|
||||
|
||||
class M30LV10(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 = "M30L V10"
|
||||
self.board_num = 4
|
||||
self.nominal_chips = 144
|
||||
self.fan_count = 2
|
||||
@@ -24,10 +24,7 @@ class M30SV10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S V10"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SV10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 148
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -36,10 +33,7 @@ class M30SV20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S V20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SV20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 156
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -48,10 +42,7 @@ class M30SV30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S V30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SV30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 164
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -60,10 +51,7 @@ class M30SV40(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S V40"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SV40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 172
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -81,10 +69,7 @@ class M30SV60(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S V60"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SV60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 164
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -105,10 +90,7 @@ class M30SV80(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S V80"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SV80, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 129
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -135,10 +117,7 @@ class M30SVE30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S VE30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SVE30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 117
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -147,10 +126,7 @@ class M30SVE40(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S VE40"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SVE40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 123
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -159,10 +135,7 @@ class M30SVE50(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S VE50"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SVE50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 129
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -195,10 +168,7 @@ class M30SVF10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S VF10"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SVF10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 70
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -207,10 +177,7 @@ class M30SVF20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S VF20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SVF20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 74
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -219,10 +186,7 @@ class M30SVF30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S VF30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SVF30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 78
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -249,10 +213,7 @@ class M30SVG30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S VG30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SVG30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 74
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -261,10 +222,7 @@ class M30SVG40(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S VG40"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SVG40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 78
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -273,10 +231,7 @@ class M30SVH10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S VH10"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SVH10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 64
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -285,10 +240,7 @@ class M30SVH20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S VH20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SVH20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 66
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -309,10 +261,7 @@ class M30SVH40(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S VH40"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SVH40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 64
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -321,10 +270,7 @@ class M30SVH50(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S VH50"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SVH50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 66
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -345,8 +291,5 @@ class M30SVI20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S VI20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30SVI20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 70
|
||||
self.fan_count = 2
|
||||
|
||||
@@ -24,10 +24,7 @@ class M30SPlusV10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ V10"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ V10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 215
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -36,10 +33,7 @@ class M30SPlusV20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ V20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ V20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 255
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -60,10 +54,7 @@ class M30SPlusV40(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ V40"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ V40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 235
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -72,10 +63,7 @@ class M30SPlusV50(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ V50"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ V50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 225
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -84,10 +72,7 @@ class M30SPlusV60(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ V60"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ V60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 245
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -96,10 +81,7 @@ class M30SPlusV70(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ V70"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ V70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 235
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -108,10 +90,7 @@ class M30SPlusV80(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ V80"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ V80, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 245
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -120,10 +99,7 @@ class M30SPlusV90(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ V90"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ V90, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 225
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -132,10 +108,7 @@ class M30SPlusV100(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ V100"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ V100, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 215
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -144,10 +117,7 @@ class M30SPlusVE30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ VE30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VE30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 148
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -165,10 +135,7 @@ class M30SPlusVE50(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ VE50"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VE50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 164
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -177,10 +144,7 @@ class M30SPlusVE60(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ VE60"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VE60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 172
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -246,34 +210,23 @@ class M30SPlusVF30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ VF30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VF30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 117
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M36SPlusVG30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
class M30SPlusVG20(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 = "M36S+ VG30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M36SPlusVG30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.model = "M30S+ VG20"
|
||||
self.nominal_chips = 82
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M30SPlusVG30(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 = "M30S+ VG30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VG30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 78
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -291,10 +244,7 @@ class M30SPlusVG50(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ VG50"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VG50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 111
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -312,10 +262,7 @@ class M30SPlusVH10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ VH10"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VH10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 64
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -324,10 +271,7 @@ class M30SPlusVH20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ VH20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VH20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 66
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -345,10 +289,7 @@ class M30SPlusVH40(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ VH40"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VH40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 74
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -357,10 +298,7 @@ class M30SPlusVH50(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S+ VH50"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VH50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 64
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
|
||||
@@ -24,10 +24,8 @@ class M30SPlusPlusV10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S++ V10"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S++ V10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.ideal_hashboards = 4
|
||||
self.nominal_chips = 255
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -36,10 +34,8 @@ class M30SPlusPlusV20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S++ V20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S++ V20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.ideal_hashboards = 4
|
||||
self.nominal_chips = 255
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -48,10 +44,7 @@ class M30SPlusPlusVE30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S++ VE30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S++ VE30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 215
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -60,10 +53,7 @@ class M30SPlusPlusVE40(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S++ VE40"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S++ VE40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 225
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -72,10 +62,7 @@ class M30SPlusPlusVE50(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S++ VE50"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S++ VE50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 235
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -84,10 +71,7 @@ class M30SPlusPlusVF40(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S++ VF40"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S++ VF40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 156
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -126,10 +110,7 @@ class M30SPlusPlusVH10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S++ VH10"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S++ VH10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 82
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -138,10 +119,7 @@ class M30SPlusPlusVH20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S++ VH20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S++ VH20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 86
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -150,10 +128,7 @@ class M30SPlusPlusVH30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S++ VH30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S++ VH30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 111
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -162,10 +137,7 @@ class M30SPlusPlusVH40(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S++ VH40"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S++ VH40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 70
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -201,10 +173,7 @@ class M30SPlusPlusVH80(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S++ VH80"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S++ VH80, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 74
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -213,10 +182,7 @@ class M30SPlusPlusVH90(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M30S++ VH90"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S++ VH90, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 78
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
|
||||
@@ -24,10 +24,7 @@ class M31V10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31 V10"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M31V10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 70
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -36,8 +33,5 @@ class M31V20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31 V20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M31V20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 74
|
||||
self.fan_count = 2
|
||||
|
||||
@@ -18,14 +18,19 @@ import warnings
|
||||
|
||||
from pyasic.miners.makes import WhatsMiner
|
||||
|
||||
class M31HV10(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 = "M31H V10"
|
||||
self.nominal_chips = 114
|
||||
self.fan_count = 0
|
||||
|
||||
class M31HV40(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 = "M31H V40"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M31HV40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.ideal_hashboards = 4
|
||||
self.nominal_chips = 136
|
||||
self.fan_count = 0
|
||||
|
||||
27
pyasic/miners/types/whatsminer/M3X/M31L.py
Normal file
27
pyasic/miners/types/whatsminer/M3X/M31L.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import warnings
|
||||
|
||||
from pyasic.miners.makes import WhatsMiner
|
||||
|
||||
class M31LV10(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 = "M31L V10"
|
||||
self.nominal_chips = 114
|
||||
self.fan_count = 2
|
||||
@@ -42,10 +42,7 @@ class M31SV30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31S V30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M31SV30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 117
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -54,10 +51,7 @@ class M31SV40(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31S V40"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M31SV40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 123
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -66,10 +60,7 @@ class M31SV50(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31S V50"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M31SV50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 78
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -108,10 +99,7 @@ class M31SV90(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31S V90"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M31SV90, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 117
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -129,10 +117,7 @@ class M31SVE20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31S VE20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M31SVE20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 74
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
|
||||
@@ -24,10 +24,7 @@ class M31SEV10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31SE V10"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M31SEV10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 82
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -36,10 +33,7 @@ class M31SEV20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31SE V20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M31SEV20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 78
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -48,8 +42,5 @@ class M31SEV30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31SE V30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M31SEV30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 78
|
||||
self.fan_count = 2
|
||||
|
||||
@@ -24,10 +24,7 @@ class M31SPlusV10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31S+ V10"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ V10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 105
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -36,10 +33,7 @@ class M31SPlusV20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31S+ V20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ V20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 111
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -66,10 +60,7 @@ class M31SPlusV50(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31S+ V50"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ V50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 148
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -114,10 +105,7 @@ class M31SPlusVE10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31S+ VE10"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VE10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 82
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -135,10 +123,7 @@ class M31SPlusVE30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31S+ VE30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VE30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 105
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -147,10 +132,7 @@ class M31SPlusVE40(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31S+ VE40"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VE40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 111
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -159,10 +141,7 @@ class M31SPlusVE50(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31S+ VE50"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VE50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 117
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -195,10 +174,7 @@ class M31SPlusVF20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31S+ VF20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VF20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 66
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -219,10 +195,7 @@ class M31SPlusVG20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31S+ VG20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VG20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 66
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -231,10 +204,7 @@ class M31SPlusVG30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M31S+ VG30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VG30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 70
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ class M32V10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M32 V10"
|
||||
self.nominal_chips = 74
|
||||
self.nominal_chips = 78
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
|
||||
@@ -24,10 +24,7 @@ class M33V10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M33 V10"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M33V10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 33
|
||||
self.fan_count = 0
|
||||
|
||||
|
||||
@@ -36,10 +33,7 @@ class M33V20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M33 V20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M33V20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 62
|
||||
self.fan_count = 0
|
||||
|
||||
|
||||
@@ -48,8 +42,5 @@ class M33V30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M33 V30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M33V30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 66
|
||||
self.fan_count = 0
|
||||
|
||||
@@ -24,8 +24,6 @@ class M33SVG30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M33S VG30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M33SVG30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.ideal_hashboards = 4
|
||||
self.nominal_chips = 116
|
||||
self.fan_count = 0
|
||||
|
||||
@@ -18,16 +18,23 @@ import warnings
|
||||
|
||||
from pyasic.miners.makes import WhatsMiner
|
||||
|
||||
class M33SPlusVG20(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 = "M33S+ VG20"
|
||||
self.ideal_hashboards = 4
|
||||
self.nominal_chips = 112
|
||||
self.fan_count = 0
|
||||
|
||||
|
||||
class M33SPlusVH20(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 = "M33S+ VH20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VH20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.ideal_hashboards = 4
|
||||
self.nominal_chips = 100
|
||||
self.fan_count = 0
|
||||
|
||||
|
||||
@@ -36,7 +43,8 @@ class M33SPlusVH30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M33S+ VH30"
|
||||
self.nominal_chips = 0
|
||||
self.ideal_hashboards = 4
|
||||
self.nominal_chips = 0 # slot1 116, slot2 106, slot3 116, slot4 106
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S+ VH30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
|
||||
@@ -48,8 +48,5 @@ class M33SPlusPlusVG40(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
self.ip = ip
|
||||
self.model = "M33S++ VG40"
|
||||
self.ideal_hashboards = 4
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M30S++ VG40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 174
|
||||
self.fan_count = 0
|
||||
|
||||
@@ -24,8 +24,6 @@ class M36SVE10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M36S VE10"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M36SVE10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.ideal_hashboards = 4
|
||||
self.nominal_chips = 114
|
||||
self.fan_count = 0
|
||||
|
||||
@@ -24,8 +24,6 @@ class M36SPlusVG30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M36S+ VG30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M36S+ VG30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.ideal_hashboards = 4
|
||||
self.nominal_chips = 108
|
||||
self.fan_count = 0
|
||||
|
||||
@@ -24,8 +24,6 @@ class M36SPlusPlusVH30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M36S++ VH30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M36S++ VH30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.ideal_hashboards = 4
|
||||
self.nominal_chips = 80
|
||||
self.fan_count = 0
|
||||
|
||||
@@ -18,14 +18,28 @@ import warnings
|
||||
|
||||
from pyasic.miners.makes import WhatsMiner
|
||||
|
||||
class M39V10(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 = "M39 V10"
|
||||
self.nominal_chips = 50
|
||||
self.fan_count = 0
|
||||
|
||||
|
||||
class M39V20(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 = "M39 V20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M39 V20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 54
|
||||
self.fan_count = 0
|
||||
|
||||
|
||||
class M39V30(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 = "M39 V30"
|
||||
self.nominal_chips = 68
|
||||
self.fan_count = 0
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .M30 import M30V10, M30V20
|
||||
from .M30K import M30KV10
|
||||
from .M30L import M30LV10
|
||||
from .M30S import (
|
||||
M30SV10,
|
||||
M30SV20,
|
||||
@@ -67,6 +69,7 @@ from .M30S_Plus import (
|
||||
M30SPlusVE100,
|
||||
M30SPlusVF20,
|
||||
M30SPlusVF30,
|
||||
M30SPlusVG20,
|
||||
M30SPlusVG30,
|
||||
M30SPlusVG40,
|
||||
M30SPlusVG50,
|
||||
@@ -77,7 +80,6 @@ from .M30S_Plus import (
|
||||
M30SPlusVH40,
|
||||
M30SPlusVH50,
|
||||
M30SPlusVH60,
|
||||
M36SPlusVG30,
|
||||
)
|
||||
from .M30S_Plus_Plus import (
|
||||
M30SPlusPlusV10,
|
||||
@@ -103,7 +105,8 @@ from .M30S_Plus_Plus import (
|
||||
M30SPlusPlusVJ30,
|
||||
)
|
||||
from .M31 import M31V10, M31V20
|
||||
from .M31H import M31HV40
|
||||
from .M31H import M31HV10, M31HV40
|
||||
from .M31L import M31LV10
|
||||
from .M31S import (
|
||||
M31SV10,
|
||||
M31SV20,
|
||||
@@ -145,10 +148,10 @@ from .M32 import M32V10, M32V20
|
||||
from .M32S import M32S
|
||||
from .M33 import M33V10, M33V20, M33V30
|
||||
from .M33S import M33SVG30
|
||||
from .M33S_Plus import M33SPlusVH20, M33SPlusVH30
|
||||
from .M33S_Plus import M33SPlusVG20, M33SPlusVH20, M33SPlusVH30
|
||||
from .M33S_Plus_Plus import M33SPlusPlusVG40, M33SPlusPlusVH20, M33SPlusPlusVH30
|
||||
from .M34S_Plus import M34SPlusVE10
|
||||
from .M36S import M36SVE10
|
||||
from .M36S_Plus import M36SPlusVG30
|
||||
from .M36S_Plus_Plus import M36SPlusPlusVH30
|
||||
from .M39 import M39V20
|
||||
from .M39 import M39V10, M39V20, M39V30
|
||||
|
||||
@@ -18,16 +18,21 @@ import warnings
|
||||
|
||||
from pyasic.miners.makes import WhatsMiner
|
||||
|
||||
class M50VE30(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 = "M50 VE30"
|
||||
self.ideal_hashboards = 4
|
||||
self.nominal_chips = 255
|
||||
self.fan_count = 2
|
||||
|
||||
class M50VG30(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 = "M50 VG30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M50 VG30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 156
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -36,10 +41,7 @@ class M50VH10(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M50 VH10"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M50 VH10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 86
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -57,10 +59,7 @@ class M50VH30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M50 VH30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M50 VH30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 117
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -87,10 +86,7 @@ class M50VH60(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M50 VH60"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M50 VH60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 84
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
|
||||
@@ -72,10 +72,7 @@ class M50SVH20(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M50S VH20"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M50S VH20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 135
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
@@ -84,10 +81,7 @@ class M50SVH30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M50S VH30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M50S VH30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.nominal_chips = 156
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@ class M53VH30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M53 VH30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M53 VH30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.ideal_hashboards = 4
|
||||
self.nominal_chips = 128
|
||||
self.fan_count = 0
|
||||
|
||||
@@ -24,8 +24,6 @@ class M56VH30(WhatsMiner): # noqa - ignore ABC method implementation
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "M56 VH30"
|
||||
self.nominal_chips = 0
|
||||
warnings.warn(
|
||||
"Unknown chip count for miner type M56 VH30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||
)
|
||||
self.ideal_hashboards = 4
|
||||
self.nominal_chips = 108
|
||||
self.fan_count = 0
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .M50 import (
|
||||
M50VE30,
|
||||
M50VG30,
|
||||
M50VH10,
|
||||
M50VH20,
|
||||
|
||||
@@ -148,6 +148,12 @@ class UnknownMiner(BaseMiner):
|
||||
async def get_nominal_hashrate(self) -> Optional[float]:
|
||||
return None
|
||||
|
||||
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
|
||||
return None
|
||||
|
||||
async def get_uptime(self, *args, **kwargs) -> Optional[int]:
|
||||
return None
|
||||
|
||||
async def get_data(
|
||||
self, allow_warning: bool = False, data_to_get: list = None
|
||||
) -> MinerData:
|
||||
|
||||
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 .M20P import BTMinerM20PV10, BTMinerM20PV30
|
||||
from .M20S import BTMinerM20SV10, BTMinerM20SV20, BTMinerM20SV30
|
||||
from .M20S_Plus import BTMinerM20SPlusV30
|
||||
from .M21 import BTMinerM21V10
|
||||
|
||||
22
pyasic/miners/whatsminer/btminer/M3X/M30K.py
Normal file
22
pyasic/miners/whatsminer/btminer/M3X/M30K.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 M3X
|
||||
from pyasic.miners.types import M30KV10
|
||||
|
||||
|
||||
class BTMinerM30KV10(M3X, M30KV10):
|
||||
pass
|
||||
22
pyasic/miners/whatsminer/btminer/M3X/M30L.py
Normal file
22
pyasic/miners/whatsminer/btminer/M3X/M30L.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 M3X
|
||||
from pyasic.miners.types import M30LV10
|
||||
|
||||
|
||||
class BTMinerM30LV10(M3X, M30LV10):
|
||||
pass
|
||||
@@ -36,6 +36,7 @@ from pyasic.miners.types import (
|
||||
M30SPlusVE100,
|
||||
M30SPlusVF20,
|
||||
M30SPlusVF30,
|
||||
M30SPlusVG20,
|
||||
M30SPlusVG30,
|
||||
M30SPlusVG40,
|
||||
M30SPlusVG50,
|
||||
@@ -46,7 +47,6 @@ from pyasic.miners.types import (
|
||||
M30SPlusVH40,
|
||||
M30SPlusVH50,
|
||||
M30SPlusVH60,
|
||||
M36SPlusVG30,
|
||||
)
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ class BTMinerM30SPlusVF30(M3X, M30SPlusVF30):
|
||||
pass
|
||||
|
||||
|
||||
class BTMinerM36SPlusVG30(M3X, M36SPlusVG30):
|
||||
class BTMinerM30SPlusVG20(M3X, M30SPlusVG20):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import M3X
|
||||
from pyasic.miners.types import M31HV40
|
||||
from pyasic.miners.types import M31HV10, M31HV40
|
||||
|
||||
|
||||
class BTMinerM31HV10(M3X, M31HV10):
|
||||
pass
|
||||
|
||||
|
||||
class BTMinerM31HV40(M3X, M31HV40):
|
||||
|
||||
22
pyasic/miners/whatsminer/btminer/M3X/M31L.py
Normal file
22
pyasic/miners/whatsminer/btminer/M3X/M31L.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 M3X
|
||||
from pyasic.miners.types import M30LV10
|
||||
|
||||
|
||||
class BTMinerM31LV10(M3X, M30LV10):
|
||||
pass
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user