Compare commits

..

54 Commits

Author SHA1 Message Date
UpstreamData
a32b61fe5d version: bump version number. 2023-07-07 12:37:34 -06:00
UpstreamData
597a178009 feature: Update MinerData to use None. 2023-07-07 12:37:20 -06:00
Michael Schmid
409b2527f0 use None instead of -1 for temps and wattages (#55)
* use `None` instead of `-1` for temps and wattages
this way it's easier for other tools like HomeAssistant to understand if the temperature is really negative or not available

* also handle cases where we look for `-1`
2023-07-07 12:06:24 -06:00
UpstreamData
58234fcf7f version: bump version number. 2023-07-07 12:03:28 -06:00
UpstreamData
1bf863cca8 bug: set miner_mode to int instead of str to fix some issues with some X19 models. 2023-07-07 12:03:00 -06:00
UpstreamData
6482d04185 version: bump version number. 2023-07-07 11:56:41 -06:00
UpstreamData
3b58b11501 bug: remove 0 frequency level when setting sleep mode on X19, as it seems to bug some types. 2023-07-07 11:56:06 -06:00
UpstreamData
7485b8ef77 version: bump version number. 2023-07-04 10:04:20 -06:00
UpstreamData
d2bea227db bug: fix an issue with a possible SSH command in BOS+. 2023-07-04 10:01:24 -06:00
UpstreamData
1b7afaaf7e version: bump version number. 2023-06-30 08:49:31 -06:00
UpstreamData
96898d639c bug: fix some handling errors with graphql. 2023-06-30 08:49:08 -06:00
UpstreamData
eb439f4dcf version: bump version number. 2023-06-30 08:44:01 -06:00
UpstreamData
69f4349393 feature: create pwd and username property in miner object that sets web and api passwords and usernames. 2023-06-30 08:43:30 -06:00
UpstreamData
e371bb577c version: bump version number. 2023-06-29 18:10:35 -06:00
UpstreamData
2500ec3869 bug: fix possible None return from some bosminer webcommands. 2023-06-29 18:10:10 -06:00
UpstreamData
5be3187eec version: bump version number. 2023-06-29 16:51:29 -06:00
UpstreamData
be1e9127b0 bug: fix weird pool info when multiple groups are defined. 2023-06-29 16:51:15 -06:00
UpstreamData
13572c4770 version: bump version number. 2023-06-29 16:48:09 -06:00
UpstreamData
08fa3961fe bug: fix bosminer on X19 not reporting pause mode correctly. 2023-06-29 16:47:27 -06:00
UpstreamData
b5d2809e9c format: remove random print statements. 2023-06-29 15:53:53 -06:00
UpstreamData
aa538d3079 docs: add miner docs generation file. 2023-06-28 11:21:19 -06:00
UpstreamData
e1500bb75c docs: update docs. 2023-06-28 11:20:22 -06:00
UpstreamData
7f00a65598 version: bump version number. 2023-06-27 16:23:46 -06:00
UpstreamData
64c473a7d4 bug: fix improper stats key when getting uptime. 2023-06-27 16:23:21 -06:00
UpstreamData
96d9fe8e6c version: bump version number. 2023-06-27 14:56:11 -06:00
UpstreamData
0b27400d27 feature: add set_static_ip and set_dhcp for bosminer. 2023-06-27 14:55:05 -06:00
UpstreamData
666b9dfe94 version: bump version number. 2023-06-27 09:36:06 -06:00
UpstreamData
df3a080c9d feature: add uptime check for some miners, and fix a bug with get_mac on BOS. 2023-06-27 09:35:07 -06:00
UpstreamData
bf3bd7c2b9 feature: add basic support for LuxOS 2023-06-26 15:35:52 -06:00
UpstreamData
37fd60b462 version: bump version number. 2023-06-26 09:53:21 -06:00
UpstreamData
2245904740 feature: remove ssh references when getting MAC on bosminer. 2023-06-26 09:52:30 -06:00
UpstreamData
7b1b23016e feature: add support for VNish S19 No PIC. 2023-06-26 08:20:45 -06:00
UpstreamData
b5fcd62e23 version: bump version number. 2023-06-23 14:50:14 -06:00
UpstreamData
9057cde274 docs: add is_mining to docs. 2023-06-23 14:49:38 -06:00
UpstreamData
f6d35888fe feature: add is_mining to MinerData and get_data. 2023-06-23 14:47:12 -06:00
UpstreamData
f2abe9fd9e feature: add is_mining to all miner types. 2023-06-23 14:38:38 -06:00
UpstreamData
7d1a702804 feature: make is_mining abstract method. 2023-06-23 14:34:44 -06:00
UpstreamData
65d1695ce4 bug: fix is_mining for some miners. 2023-06-23 14:33:32 -06:00
UpstreamData
65fd66b8bf feature: add is_mining for antminer. 2023-06-23 14:08:54 -06:00
UpstreamData
5db52c46f3 feature: add is_mining for btminer, and fix some minor bugs. 2023-06-23 13:38:45 -06:00
UpstreamData
d06cb19da3 feature: add is_mining for bosminers. 2023-06-23 12:39:00 -06:00
UpstreamData
4530d086da bug: fix some cases where web command data can fail. 2023-06-23 11:23:20 -06:00
UpstreamData
0bd679f259 version: bump version number. 2023-06-22 15:07:25 -06:00
UpstreamData
1ce5bd0566 Update miner data and fix various bugs related to the fix. (#47)
* feature: fix influxdb data.

* bug: fix an issue with some avalon stats parsing.

* bug: add chip count for 1166 Pro.

* bug: fix some issues with bosminer scanning and some data bugs.

* bug: remove print statement.

* bug: fix failed data gathering multicommand via graphql.

* feature: add partial support for M50S+VK20

* version: bump version number.

* bug: add chip count for M50S+VK20.

* version: bump version number.

* bug: attempt to fix offset check issue on BOS+.

* bug: fix NoneType subscription on BOS+.

* bug: add support for Vnish S17+.

* bug: remove web references for Avalons.

* bug: add support for VNish S17Pro.

* bug: Try secondary identification method for antminers.

* feature: fix a bunch of functionality for avalonminers.

* bug: fix avalonminer fan speed being set as str.

* bug: fix fans speeds being represented as strings.

* bug: fix some get_fan formatting.

* docs: update supported miners list, and fix A10X model name.

* docs: update MinerData docstrings.

* docs: update factory documentation.
2023-06-22 15:06:30 -06:00
UpstreamData
67c3d05ac3 version: bump version number. 2023-06-20 12:11:43 -06:00
UpstreamData
c691868e9b bug: add chip count for M50S+VK20. 2023-06-20 12:11:05 -06:00
UpstreamData
f5e15b4046 version: bump version number. 2023-06-19 16:10:34 -06:00
UpstreamData
e14df696ee feature: add partial support for M50S+VK20 2023-06-19 16:10:00 -06:00
UpstreamData
ce5dfad850 version: bump version number. 2023-06-12 13:04:11 -06:00
UpstreamData
5cb45390be bug: add chip count for avalon 1246. 2023-06-12 13:03:41 -06:00
UpstreamData
b5216a24a6 version: bump version number. 2023-06-12 12:47:58 -06:00
UpstreamData
dd175ff3a2 bug: fix an issue with 2020 versions of Braiins OS not identifying correctly. 2023-06-12 12:47:38 -06:00
UpstreamData
9b504a3157 version: bump version number. 2023-06-12 12:42:16 -06:00
UpstreamData
0bc3bf20ee bug: fix possible missing models. 2023-06-12 12:41:50 -06:00
71 changed files with 3542 additions and 1803 deletions

163
docs/generate_miners.py Normal file
View File

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

View File

@@ -42,8 +42,8 @@ if __name__ == "__main__":
<br> <br>
## Creating miners based on IP ## Creating miners based on IP
If you already know the IP address of your miner or miners, you can use the [`MinerFactory`][pyasic.miners.miner_factory.MinerFactory] to communicate and identify the miners, or an abstraction of its functionality, [`get_miner()`][pyasic.miners.miner_factory.MinerFactory.get_miner]. If you already know the IP address of your miner or miners, you can use the [`MinerFactory`][pyasic.miners.miner_factory.MinerFactory] to communicate and identify the miners, or an abstraction of its functionality, [`get_miner()`][pyasic.miners.get_miner].
The function [`get_miner()`][pyasic.miners.miner_factory.MinerFactory.get_miner] will return any miner it found at the IP address specified, or an `UnknownMiner` if it cannot identify the miner. The function [`get_miner()`][pyasic.miners.get_miner] will return any miner it found at the IP address specified, or an `UnknownMiner` if it cannot identify the miner.
```python ```python
import asyncio # asyncio for handling the async part import asyncio # asyncio for handling the async part
from pyasic import get_miner # handles miner creation from pyasic import get_miner # handles miner creation
@@ -126,6 +126,7 @@ These functions are
[`restart_backend`](#restart-backend), [`restart_backend`](#restart-backend),
[`stop_mining`](#stop-mining), [`stop_mining`](#stop-mining),
[`resume_mining`](#resume-mining), [`resume_mining`](#resume-mining),
[`is_mining`](#is-mining),
[`send_config`](#send-config), and [`send_config`](#send-config), and
[`set_power_limit`](#set-power-limit). [`set_power_limit`](#set-power-limit).
@@ -227,6 +228,14 @@ These functions are
<br> <br>
### Is Mining
::: pyasic.miners.BaseMiner.is_mining
handler: python
options:
heading_level: 4
<br>
### Send Config ### Send Config
::: pyasic.miners.BaseMiner.send_config ::: pyasic.miners.BaseMiner.send_config
handler: python handler: python

View File

@@ -1,11 +1,10 @@
# pyasic # pyasic
## X15 Models ## X15 Models
## Z15 ## Z15
::: pyasic.miners.antminer.cgminer.X15.Z15.CGMinerZ15
::: pyasic.miners.zec.antminer.cgminer.X15.Z15.CGMinerZ15
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4

View File

@@ -2,116 +2,114 @@
## X17 Models ## X17 Models
## S17 ## S17
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17
::: pyasic.miners.btc.antminer.bmminer.X17.S17.BMMinerS17
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S17+ ## S17+
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17Plus
::: pyasic.miners.btc.antminer.bmminer.X17.S17_Plus.BMMinerS17Plus
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S17 Pro ## S17 Pro
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17Pro
::: pyasic.miners.btc.antminer.bmminer.X17.S17_Pro.BMMinerS17Pro
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S17e ## S17e
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17e
::: pyasic.miners.btc.antminer.bmminer.X17.S17e.BMMinerS17e
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## T17 ## T17
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17
::: pyasic.miners.btc.antminer.bmminer.X17.T17.BMMinerT17
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## T17+ ## T17+
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17Plus
::: pyasic.miners.btc.antminer.bmminer.X17.T17_Plus.BMMinerT17Plus
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## T17e ## T17e
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17e
::: pyasic.miners.btc.antminer.bmminer.X17.T17e.BMMinerT17e
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S17 (BOS) ## S17 (BOS)
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17
::: pyasic.miners.btc.antminer.bosminer.X17.S17.BOSMinerS17
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S17+ (BOS) ## S17+ (BOS)
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17Plus
::: pyasic.miners.btc.antminer.bosminer.X17.S17_Plus.BOSMinerS17Plus
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S17 Pro (BOS) ## S17 Pro (BOS)
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17Pro
::: pyasic.miners.btc.antminer.bosminer.X17.S17_Pro.BOSMinerS17Pro
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S17e (BOS) ## S17e (BOS)
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17e
::: pyasic.miners.btc.antminer.bosminer.X17.S17e.BOSMinerS17e
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## T17 (BOS) ## T17 (BOS)
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17
::: pyasic.miners.btc.antminer.bosminer.X17.T17.BOSMinerT17
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## T17+ (BOS) ## T17+ (BOS)
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17Plus
::: pyasic.miners.btc.antminer.bosminer.X17.T17_Plus.BOSMinerT17Plus
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## T17e (BOS) ## T17e (BOS)
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17e
::: pyasic.miners.btc.antminer.bosminer.X17.T17e.BOSMinerT17e
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S17+ (VNish)
::: pyasic.miners.antminer.vnish.X17.S17.VNishS17Plus
handler: python
options:
show_root_heading: false
heading_level: 4
## S17 Pro (VNish)
::: pyasic.miners.antminer.vnish.X17.S17.VNishS17Pro
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -2,109 +2,177 @@
## X19 Models ## X19 Models
## S19 ## S19
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19
::: pyasic.miners.btc.antminer.bmminer.X19.S19.BMMinerS19
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S19L ## S19L
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19L
::: pyasic.miners.btc.antminer.bmminer.X19.S19L.BMMinerS19L
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S19 Pro ## S19 Pro
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19Pro
::: pyasic.miners.btc.antminer.bmminer.X19.S19_Pro.BMMinerS19Pro
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S19a
::: pyasic.miners.btc.antminer.bmminer.X19.S19a.BMMinerS19a
handler: python
options:
show_root_heading: false
heading_level: 4
## S19j ## S19j
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19j
handler: python
options:
show_root_heading: false
heading_level: 4
::: pyasic.miners.btc.antminer.bmminer.X19.S19j.BMMinerS19j ## S19j No PIC
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jNoPIC
handler: python
options:
show_root_heading: false
heading_level: 4
## S19 Pro+
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19ProPlus
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S19j Pro ## S19j Pro
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jPro
::: pyasic.miners.btc.antminer.bmminer.X19.S19j_Pro.BMMinerS19jPro
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S19 XP ## S19 XP
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19XP
handler: python
options:
show_root_heading: false
heading_level: 4
::: pyasic.miners.btc.antminer.bmminer.X19.S19_XP.BMMinerS19XP ## S19a
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19a
handler: python
options:
show_root_heading: false
heading_level: 4
## S19a Pro
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19aPro
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## T19 ## T19
::: pyasic.miners.antminer.bmminer.X19.T19.BMMinerT19
::: pyasic.miners.btc.antminer.bmminer.X19.T19.BMMinerT19
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S19 (BOS) ## S19 (BOS)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19
::: pyasic.miners.btc.antminer.bosminer.X19.S19.BOSMinerS19
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S19 Pro (BOS) ## S19 Pro (BOS)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19Pro
::: pyasic.miners.btc.antminer.bosminer.X19.S19_Pro.BOSMinerS19Pro
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S19j (BOS) ## S19j (BOS)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19j
handler: python
options:
show_root_heading: false
heading_level: 4
::: pyasic.miners.btc.antminer.bosminer.X19.S19j.BOSMinerS19j ## S19j No PIC (BOS)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jNoPIC
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S19j Pro (BOS) ## S19j Pro (BOS)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jPro
::: pyasic.miners.btc.antminer.bosminer.X19.S19j_Pro.BOSMinerS19jPro
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## T19 (BOS) ## T19 (BOS)
::: pyasic.miners.antminer.bosminer.X19.T19.BOSMinerT19
::: pyasic.miners.btc.antminer.bosminer.X19.T19.BOSMinerT19
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S19 (VNish)
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19
handler: python
options:
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
options:
show_root_heading: false
heading_level: 4
## S19j (VNish)
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19j
handler: python
options:
show_root_heading: false
heading_level: 4
## S19j Pro (VNish)
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19jPro
handler: python
options:
show_root_heading: false
heading_level: 4
## S19a (VNish)
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19a
handler: python
options:
show_root_heading: false
heading_level: 4
## S19a Pro (VNish)
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19aPro
handler: python
options:
show_root_heading: false
heading_level: 4
## T19 (VNish)
::: pyasic.miners.antminer.vnish.X19.T19.VNishT19
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -1,11 +1,31 @@
# pyasic # pyasic
## X3 Models ## X3 Models
## D3
## HS3 ::: pyasic.miners.antminer.cgminer.X3.D3.CGMinerD3
::: pyasic.miners.hns.antminer.cgminer.X3.HS3.CGMinerHS3
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## HS3
::: pyasic.miners.antminer.bmminer.X3.HS3.BMMinerHS3
handler: python
options:
show_root_heading: false
heading_level: 4
## L3+
::: pyasic.miners.antminer.bmminer.X3.L3.BMMinerL3Plus
handler: python
options:
show_root_heading: false
heading_level: 4
## L3+ (VNish)
::: pyasic.miners.antminer.vnish.X3.L3.VnishL3Plus
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -1,11 +1,10 @@
# pyasic # pyasic
## X5 Models ## X5 Models
## DR5 ## DR5
::: pyasic.miners.antminer.cgminer.X5.DR5.CGMinerDR5
::: pyasic.miners.dcr.antminer.cgminer.X5.DR5.CGMinerDR5
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4

View File

@@ -1,11 +1,10 @@
# pyasic # pyasic
## X7 Models ## X7 Models
## L7 ## L7
::: pyasic.miners.antminer.bmminer.X7.L7.BMMinerL7
::: pyasic.miners.ltc.antminer.bmminer.X7.L7.BMMinerL7
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4

View File

@@ -1,44 +1,59 @@
# pyasic # pyasic
## X9 Models ## X9 Models
## E9Pro
## X9 (BOS) ::: pyasic.miners.antminer.bmminer.X9.E9.BMMinerE9Pro
::: pyasic.miners.btc.antminer.bosminer.X9.S9.BOSMinerS9
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S9 ## S9
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9
::: pyasic.miners.btc.antminer.bmminer.X9.S9.BMMinerS9
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S9i ## S9i
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9i
handler: python
options:
show_root_heading: false
heading_level: 4
::: pyasic.miners.btc.antminer.bmminer.X9.S9i.BMMinerS9i ## S9j
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9j
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## T9 ## T9
::: pyasic.miners.antminer.bmminer.X9.T9.BMMinerT9
::: pyasic.miners.btc.antminer.bmminer.X9.T9.BMMinerT9
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## E9 Pro ## S9 (BOS)
::: pyasic.miners.antminer.bosminer.X9.S9.BOSMinerS9
::: pyasic.miners.etc.antminer.cgminer.X9.E9_Pro.CGMinerE9Pro
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## T9 (Hiveon)
::: pyasic.miners.antminer.hiveon.X9.T9.HiveonT9
handler: python
options:
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

View File

@@ -1,26 +1,24 @@
# pyasic # pyasic
## A10X Models ## A10X Models
## A1026 ## Avalon 1026
::: pyasic.miners.avalonminer.cgminer.A10X.A1026.CGMinerAvalon1026
::: pyasic.miners.btc.avalonminer.cgminer.A10X.A1026.CGMinerAvalon1026
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## A1047 ## Avalon 1047
::: pyasic.miners.avalonminer.cgminer.A10X.A1047.CGMinerAvalon1047
::: pyasic.miners.btc.avalonminer.cgminer.A10X.A1047.CGMinerAvalon1047
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## A1066 ## Avalon 1066
::: pyasic.miners.avalonminer.cgminer.A10X.A1066.CGMinerAvalon1066
::: pyasic.miners.btc.avalonminer.cgminer.A10X.A1066.CGMinerAvalon1066
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4

View File

@@ -0,0 +1,10 @@
# pyasic
## A11X Models
## Avalon 1166 Pro
::: pyasic.miners.avalonminer.cgminer.A11X.A1166.CGMinerAvalon1166Pro
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -1,10 +1,10 @@
# pyasic # pyasic
## HSX Models ## A12X Models
## HS5 ## Avalon 1246
::: pyasic.miners.avalonminer.cgminer.A12X.A1246.CGMinerAvalon1246
::: pyasic.miners.hns.goldshell.bfgminer.HSX.HS5.BFGMinerHS5
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4

View File

@@ -1,26 +1,24 @@
# pyasic # pyasic
## A7X Models ## A7X Models
## A721 ## Avalon 721
::: pyasic.miners.avalonminer.cgminer.A7X.A721.CGMinerAvalon721
::: pyasic.miners.btc.avalonminer.cgminer.A7X.A721.CGMinerAvalon721
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## A741 ## Avalon 741
::: pyasic.miners.avalonminer.cgminer.A7X.A741.CGMinerAvalon741
::: pyasic.miners.btc.avalonminer.cgminer.A7X.A741.CGMinerAvalon741
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## A761 ## Avalon 761
::: pyasic.miners.avalonminer.cgminer.A7X.A761.CGMinerAvalon761
::: pyasic.miners.btc.avalonminer.cgminer.A7X.A761.CGMinerAvalon761
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4

View File

@@ -1,26 +1,24 @@
# pyasic # pyasic
## A8X Models ## A8X Models
## A821 ## Avalon 821
::: pyasic.miners.avalonminer.cgminer.A8X.A821.CGMinerAvalon821
::: pyasic.miners.btc.avalonminer.cgminer.A8X.A821.CGMinerAvalon821
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## A841 ## Avalon 841
::: pyasic.miners.avalonminer.cgminer.A8X.A841.CGMinerAvalon841
::: pyasic.miners.btc.avalonminer.cgminer.A8X.A841.CGMinerAvalon841
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## A851 ## Avalon 851
::: pyasic.miners.avalonminer.cgminer.A8X.A851.CGMinerAvalon851
::: pyasic.miners.btc.avalonminer.cgminer.A8X.A851.CGMinerAvalon851
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4

View File

@@ -1,10 +1,10 @@
# pyasic # pyasic
## A9X Models ## A9X Models
## A921 ## Avalon 921
::: pyasic.miners.avalonminer.cgminer.A9X.A921.CGMinerAvalon921
::: pyasic.miners.btc.avalonminer.cgminer.A9X.A921.CGMinerAvalon921
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4

View File

@@ -1,19 +0,0 @@
# pyasic
## KDX Models
## KD5
::: pyasic.miners.kda.goldshell.bfgminer.KDX.KD5.BFGMinerKD5
handler: python
options:
show_root_heading: false
heading_level: 4
## KD Max
::: pyasic.miners.kda.goldshell.bfgminer.KDX.KDMax.BFGMinerKDMax
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -0,0 +1,24 @@
# pyasic
## X5 Models
## CK5
::: pyasic.miners.goldshell.bfgminer.X5.CK5.BFGMinerCK5
handler: python
options:
show_root_heading: false
heading_level: 4
## HS5
::: pyasic.miners.goldshell.bfgminer.X5.HS5.BFGMinerHS5
handler: python
options:
show_root_heading: false
heading_level: 4
## KD5
::: pyasic.miners.goldshell.bfgminer.X5.KD5.BFGMinerKD5
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -1,10 +1,10 @@
# pyasic # pyasic
## CKX Models ## XMax Models
## CK5 ## KD Max
::: pyasic.miners.goldshell.bfgminer.XMax.KDMax.BFGMinerKDMax
::: pyasic.miners.ckb.goldshell.bfgminer.CKX.CK5.BFGMinerCK5
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4

View File

@@ -2,9 +2,9 @@
## A10X Models ## A10X Models
## A10X ## A10X
::: pyasic.miners.innosilicon.cgminer.A10X.A10X.CGMinerA10X
::: pyasic.miners.etc.innosilicon.cgminer.A10X.A10X.CGMinerA10X
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4

View File

@@ -2,9 +2,9 @@
## T3X Models ## T3X Models
## T3H+ ## T3H+
::: pyasic.miners.innosilicon.cgminer.T3X.T3H.CGMinerT3HPlus
::: pyasic.miners.btc.innosilicon.cgminer.T3X.T3H_Plus.CGMinerInnosiliconT3HPlus
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4

View File

@@ -8,6 +8,14 @@
heading_level: 4 heading_level: 4
<br> <br>
## Get Miner
::: pyasic.miners.get_miner
handler: python
options:
show_root_heading: false
heading_level: 4
<br>
## AnyMiner ## AnyMiner
::: pyasic.miners.miner_factory.AnyMiner ::: pyasic.miners.miner_factory.AnyMiner
handler: python handler: python

View File

@@ -10,38 +10,76 @@ details {
padding-top:0px; padding-top:0px;
padding-bottom:0px; padding-bottom:0px;
} }
ul {
margin:0px;
}
</style> </style>
<details style="margin:0px; padding-top:0px; padding-bottom:0px;">
<summary>Braiins OS+ Devices:</summary> <details>
<summary>Stock Firmware Antminers:</summary>
<ul> <ul>
<details> <details>
<summary>X19 Series:</summary> <summary>X3 Series:</summary>
<ul> <ul>
<li><a href="../antminer/X19#s19-bos">S19</a></li> <li><a href="../antminer/X3#d3">D3</a></li>
<li><a href="../antminer/X19#s19-pro-bos">S19 Pro</a></li> <li><a href="../antminer/X3#hs3">HS3</a></li>
<li><a href="../antminer/X19#s19j-bos">S19j</a></li> <li><a href="../antminer/X3#l3_1">L3+</a></li>
<li><a href="../antminer/X19#s19j-pro-bos">S19j Pro</a></li>
<li><a href="../antminer/X19#t19-bos">T19</a></li>
</ul> </ul>
</details> </details>
<details> <details>
<summary>X17 Series:</summary> <summary>X5 Series:</summary>
<ul> <ul>
<li><a href="../antminer/X17#s17-bos">S17</a></li> <li><a href="../antminer/X5#dr5">DR5</a></li>
<li><a href="../antminer/X17#s17-plus-bos">S17+</a></li> </ul>
<li><a href="../antminer/X17#s17-pro-bos">S17 Pro</a></li> </details>
<li><a href="../antminer/X17#s17e-bos">S17e</a></li> <details>
<li><a href="../antminer/X17#t17-bos">T17</a></li> <summary>X7 Series:</summary>
<li><a href="../antminer/X17#t17-plus-bos">T17+</a></li> <ul>
<li><a href="../antminer/X17#t17e-bos">T17e</a></li> <li><a href="../antminer/X7#l7">L7</a></li>
</ul> </ul>
</details> </details>
<details> <details>
<summary>X9 Series:</summary> <summary>X9 Series:</summary>
<ul> <ul>
<li><a href="../antminer/X9#s9-bos">S9</a></li> <li><a href="../antminer/X9#e9pro">E9Pro</a></li>
<li><a href="../antminer/X9#s9-bos">S9i</a></li> <li><a href="../antminer/X9#s9">S9</a></li>
<li><a href="../antminer/X9#s9-bos">S9j</a></li> <li><a href="../antminer/X9#s9i">S9i</a></li>
<li><a href="../antminer/X9#s9j">S9j</a></li>
<li><a href="../antminer/X9#t9">T9</a></li>
</ul>
</details>
<details>
<summary>X15 Series:</summary>
<ul>
<li><a href="../antminer/X15#z15">Z15</a></li>
</ul>
</details>
<details>
<summary>X17 Series:</summary>
<ul>
<li><a href="../antminer/X17#s17">S17</a></li>
<li><a href="../antminer/X17#s17_1">S17+</a></li>
<li><a href="../antminer/X17#s17-pro">S17 Pro</a></li>
<li><a href="../antminer/X17#s17e">S17e</a></li>
<li><a href="../antminer/X17#t17">T17</a></li>
<li><a href="../antminer/X17#t17_1">T17+</a></li>
<li><a href="../antminer/X17#t17e">T17e</a></li>
</ul>
</details>
<details>
<summary>X19 Series:</summary>
<ul>
<li><a href="../antminer/X19#s19">S19</a></li>
<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#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>
<li><a href="../antminer/X19#s19-xp">S19 XP</a></li>
<li><a href="../antminer/X19#s19a">S19a</a></li>
<li><a href="../antminer/X19#s19a-pro">S19a Pro</a></li>
<li><a href="../antminer/X19#t19">T19</a></li>
</ul> </ul>
</details> </details>
</ul> </ul>
@@ -51,440 +89,199 @@ details {
<ul> <ul>
<details> <details>
<summary>M2X Series:</summary> <summary>M2X Series:</summary>
<ul> <ul>
<details> <li><a href="../whatsminer/M2X#m20-v10">M20 V10</a></li>
<summary><a href='../whatsminer/M2X/#M20'>M20</a></summary> <li><a href="../whatsminer/M2X#m20s-v10">M20S V10</a></li>
<ul> <li><a href="../whatsminer/M2X#m20s-v20">M20S V20</a></li>
<li><a href='../whatsminer/M2X/#M20V10'>M20V10</a></li> <li><a href="../whatsminer/M2X#m20s-v30">M20S V30</a></li>
</ul> <li><a href="../whatsminer/M2X#m20s_1-v30">M20S+ V30</a></li>
</details> <li><a href="../whatsminer/M2X#m21-v10">M21 V10</a></li>
<details> <li><a href="../whatsminer/M2X#m21s-v20">M21S V20</a></li>
<summary><a href='../whatsminer/M2X/#M20S'>M20S</a></summary> <li><a href="../whatsminer/M2X#m21s-v60">M21S V60</a></li>
<ul> <li><a href="../whatsminer/M2X#m21s-v70">M21S V70</a></li>
<li><a href='../whatsminer/M2X/#M20SV10'>M20SV10</a></li> <li><a href="../whatsminer/M2X#m21s_1-v20">M21S+ V20</a></li>
<li><a href='../whatsminer/M2X/#M20SV20'>M20SV20</a></li> <li><a href="../whatsminer/M2X#m29-v10">M29 V10</a></li>
<li><a href='../whatsminer/M2X/#M20SV30'>M20SV30</a></li> </ul>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M2X/#M20S_1'>M20S+</a></summary>
<ul>
<li><a href='../whatsminer/M2X/#M20S_1V30'>M20S+V30</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M2X/#M21'>M21</a></summary>
<ul>
<li><a href='../whatsminer/M2X/#M21V10'>M21V10</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M2X/#M21S'>M21S</a></summary>
<ul>
<li><a href='../whatsminer/M2X/#M21SV20'>M21SV20</a></li>
<li><a href='../whatsminer/M2X/#M21SV60'>M21SV60</a></li>
<li><a href='../whatsminer/M2X/#M21SV70'>M21SV70</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M2X/#M21S_1'>M21S+</a></summary>
<ul>
<li><a href='../whatsminer/M2X/#M21S_1V20'>M21S+V20</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M2X/#M29'>M29</a></summary>
<ul>
<li><a href='../whatsminer/M2X/#M29V10'>M29V10</a></li>
</ul>
</details>
</ul>
</details> </details>
<details> <details>
<summary>M3X Series:</summary> <summary>M3X Series:</summary>
<ul> <ul>
<details> <li><a href="../whatsminer/M3X#m30-v10">M30 V10</a></li>
<summary><a href='../whatsminer/M3X/#M30'>M30</a></summary> <li><a href="../whatsminer/M3X#m30-v20">M30 V20</a></li>
<ul> <li><a href="../whatsminer/M3X#m30s-v10">M30S V10</a></li>
<li><a href='../whatsminer/M3X/#M30V10'>M30V10</a></li> <li><a href="../whatsminer/M3X#m30s-v20">M30S V20</a></li>
<li><a href='../whatsminer/M3X/#M30V20'>M30V20</a></li> <li><a href="../whatsminer/M3X#m30s-v30">M30S V30</a></li>
</ul> <li><a href="../whatsminer/M3X#m30s-v40">M30S V40</a></li>
</details> <li><a href="../whatsminer/M3X#m30s-v50">M30S V50</a></li>
<details> <li><a href="../whatsminer/M3X#m30s-v60">M30S V60</a></li>
<summary><a href='../whatsminer/M3X/#M30S'>M30S</a></summary> <li><a href="../whatsminer/M3X#m30s-v70">M30S V70</a></li>
<ul> <li><a href="../whatsminer/M3X#m30s-v80">M30S V80</a></li>
<li><a href='../whatsminer/M3X/#M30SV10'>M30SV10</a></li> <li><a href="../whatsminer/M3X#m30s-ve10">M30S VE10</a></li>
<li><a href='../whatsminer/M3X/#M30SV20'>M30SV20</a></li> <li><a href="../whatsminer/M3X#m30s-ve20">M30S VE20</a></li>
<li><a href='../whatsminer/M3X/#M30SV30'>M30SV30</a></li> <li><a href="../whatsminer/M3X#m30s-ve30">M30S VE30</a></li>
<li><a href='../whatsminer/M3X/#M30SV40'>M30SV40</a></li> <li><a href="../whatsminer/M3X#m30s-ve40">M30S VE40</a></li>
<li><a href='../whatsminer/M3X/#M30SV50'>M30SV50</a></li> <li><a href="../whatsminer/M3X#m30s-ve50">M30S VE50</a></li>
<li><a href='../whatsminer/M3X/#M30SV60'>M30SV60</a></li> <li><a href="../whatsminer/M3X#m30s-ve60">M30S VE60</a></li>
<li><a href='../whatsminer/M3X/#M30SV70'>M30SV70</a></li> <li><a href="../whatsminer/M3X#m30s-ve70">M30S VE70</a></li>
<li><a href='../whatsminer/M3X/#M30SV80'>M30SV80</a></li> <li><a href="../whatsminer/M3X#m30s-vf10">M30S VF10</a></li>
<li><a href='../whatsminer/M3X/#M30SVE10'>M30SVE10</a></li> <li><a href="../whatsminer/M3X#m30s-vf20">M30S VF20</a></li>
<li><a href='../whatsminer/M3X/#M30SVE20'>M30SVE20</a></li> <li><a href="../whatsminer/M3X#m30s-vf30">M30S VF30</a></li>
<li><a href='../whatsminer/M3X/#M30SVE30'>M30SVE30</a></li> <li><a href="../whatsminer/M3X#m30s-vg10">M30S VG10</a></li>
<li><a href='../whatsminer/M3X/#M30SVE40'>M30SVE40</a></li> <li><a href="../whatsminer/M3X#m30s-vg20">M30S VG20</a></li>
<li><a href='../whatsminer/M3X/#M30SVE50'>M30SVE50</a></li> <li><a href="../whatsminer/M3X#m30s-vg30">M30S VG30</a></li>
<li><a href='../whatsminer/M3X/#M30SVE60'>M30SVE60</a></li> <li><a href="../whatsminer/M3X#m30s-vg40">M30S VG40</a></li>
<li><a href='../whatsminer/M3X/#M30SVE70'>M30SVE70</a></li> <li><a href="../whatsminer/M3X#m30s-vh10">M30S VH10</a></li>
<li><a href='../whatsminer/M3X/#M30SVF10'>M30SVF10</a></li> <li><a href="../whatsminer/M3X#m30s-vh20">M30S VH20</a></li>
<li><a href='../whatsminer/M3X/#M30SVF20'>M30SVF20</a></li> <li><a href="../whatsminer/M3X#m30s-vh30">M30S VH30</a></li>
<li><a href='../whatsminer/M3X/#M30SVF30'>M30SVF30</a></li> <li><a href="../whatsminer/M3X#m30s-vh40">M30S VH40</a></li>
<li><a href='../whatsminer/M3X/#M30SVG10'>M30SVG10</a></li> <li><a href="../whatsminer/M3X#m30s-vh50">M30S VH50</a></li>
<li><a href='../whatsminer/M3X/#M30SVG20'>M30SVG20</a></li> <li><a href="../whatsminer/M3X#m30s-vh60">M30S VH60</a></li>
<li><a href='../whatsminer/M3X/#M30SVG30'>M30SVG30</a></li> <li><a href="../whatsminer/M3X#m30s-vi20">M30S VI20</a></li>
<li><a href='../whatsminer/M3X/#M30SVG40'>M30SVG40</a></li> <li><a href="../whatsminer/M3X#m30s_1-v10">M30S+ V10</a></li>
<li><a href='../whatsminer/M3X/#M30SVH10'>M30SVH10</a></li> <li><a href="../whatsminer/M3X#m30s_1-v20">M30S+ V20</a></li>
<li><a href='../whatsminer/M3X/#M30SVH20'>M30SVH20</a></li> <li><a href="../whatsminer/M3X#m30s_1-v30">M30S+ V30</a></li>
<li><a href='../whatsminer/M3X/#M30SVH30'>M30SVH30</a></li> <li><a href="../whatsminer/M3X#m30s_1-v40">M30S+ V40</a></li>
<li><a href='../whatsminer/M3X/#M30SVH40'>M30SVH40</a></li> <li><a href="../whatsminer/M3X#m30s_1-v50">M30S+ V50</a></li>
<li><a href='../whatsminer/M3X/#M30SVH50'>M30SVH50</a></li> <li><a href="../whatsminer/M3X#m30s_1-v60">M30S+ V60</a></li>
<li><a href='../whatsminer/M3X/#M30SVH60'>M30SVH60</a></li> <li><a href="../whatsminer/M3X#m30s_1-v70">M30S+ V70</a></li>
<li><a href='../whatsminer/M3X/#M30SVI20'>M30SVI20</a></li> <li><a href="../whatsminer/M3X#m30s_1-v80">M30S+ V80</a></li>
</ul> <li><a href="../whatsminer/M3X#m30s_1-v90">M30S+ V90</a></li>
</details> <li><a href="../whatsminer/M3X#m30s_1-v100">M30S+ V100</a></li>
<details> <li><a href="../whatsminer/M3X#m30s_1-ve30">M30S+ VE30</a></li>
<summary><a href='../whatsminer/M3X/#M30S_1'>M30S+</a></summary> <li><a href="../whatsminer/M3X#m30s_1-ve40">M30S+ VE40</a></li>
<ul> <li><a href="../whatsminer/M3X#m30s_1-ve50">M30S+ VE50</a></li>
<li><a href='../whatsminer/M3X/#M30S_1V10'>M30S+V10</a></li> <li><a href="../whatsminer/M3X#m30s_1-ve60">M30S+ VE60</a></li>
<li><a href='../whatsminer/M3X/#M30S_1V20'>M30S+V20</a></li> <li><a href="../whatsminer/M3X#m30s_1-ve70">M30S+ VE70</a></li>
<li><a href='../whatsminer/M3X/#M30S_1V30'>M30S+V30</a></li> <li><a href="../whatsminer/M3X#m30s_1-ve80">M30S+ VE80</a></li>
<li><a href='../whatsminer/M3X/#M30S_1V40'>M30S+V40</a></li> <li><a href="../whatsminer/M3X#m30s_1-ve90">M30S+ VE90</a></li>
<li><a href='../whatsminer/M3X/#M30S_1V50'>M30S+V50</a></li> <li><a href="../whatsminer/M3X#m30s_1-ve100">M30S+ VE100</a></li>
<li><a href='../whatsminer/M3X/#M30S_1V60'>M30S+V60</a></li> <li><a href="../whatsminer/M3X#m30s_1-vf20">M30S+ VF20</a></li>
<li><a href='../whatsminer/M3X/#M30S_1V70'>M30S+V70</a></li> <li><a href="../whatsminer/M3X#m30s_1-vf30">M30S+ VF30</a></li>
<li><a href='../whatsminer/M3X/#M30S_1V80'>M30S+V80</a></li> <li><a href="../whatsminer/M3X#m30s_1-vg30">M30S+ VG30</a></li>
<li><a href='../whatsminer/M3X/#M30S_1V90'>M30S+V90</a></li> <li><a href="../whatsminer/M3X#m30s_1-vg40">M30S+ VG40</a></li>
<li><a href='../whatsminer/M3X/#M30S_1V100'>M30S+V100</a></li> <li><a href="../whatsminer/M3X#m30s_1-vg50">M30S+ VG50</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VE30'>M30S+VE30</a></li> <li><a href="../whatsminer/M3X#m30s_1-vg60">M30S+ VG60</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VE40'>M30S+VE40</a></li> <li><a href="../whatsminer/M3X#m30s_1-vh10">M30S+ VH10</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VE50'>M30S+VE50</a></li> <li><a href="../whatsminer/M3X#m30s_1-vh20">M30S+ VH20</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VE60'>M30S+VE60</a></li> <li><a href="../whatsminer/M3X#m30s_1-vh30">M30S+ VH30</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VE70'>M30S+VE70</a></li> <li><a href="../whatsminer/M3X#m30s_1-vh40">M30S+ VH40</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VE80'>M30S+VE80</a></li> <li><a href="../whatsminer/M3X#m30s_1-vh50">M30S+ VH50</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VE90'>M30S+VE90</a></li> <li><a href="../whatsminer/M3X#m30s_1-vh60">M30S+ VH60</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VE100'>M30S+VE100</a></li> <li><a href="../whatsminer/M3X#m30s_1_1-v10">M30S++ V10</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VF20'>M30S+VF20</a></li> <li><a href="../whatsminer/M3X#m30s_1_1-v20">M30S++ V20</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VF30'>M30S+VF30</a></li> <li><a href="../whatsminer/M3X#m30s_1_1-ve30">M30S++ VE30</a></li>
<li><a href='../whatsminer/M3X/#M36S_1VG30'>M36S+VG30</a></li> <li><a href="../whatsminer/M3X#m30s_1_1-ve40">M30S++ VE40</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VG30'>M30S+VG30</a></li> <li><a href="../whatsminer/M3X#m30s_1_1-ve50">M30S++ VE50</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VG40'>M30S+VG40</a></li> <li><a href="../whatsminer/M3X#m30s_1_1-vf40">M30S++ VF40</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VG50'>M30S+VG50</a></li> <li><a href="../whatsminer/M3X#m30s_1_1-vg30">M30S++ VG30</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VG60'>M30S+VG60</a></li> <li><a href="../whatsminer/M3X#m30s_1_1-vg40">M30S++ VG40</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VH10'>M30S+VH10</a></li> <li><a href="../whatsminer/M3X#m30s_1_1-vg50">M30S++ VG50</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VH20'>M30S+VH20</a></li> <li><a href="../whatsminer/M3X#m30s_1_1-vh10">M30S++ VH10</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VH30'>M30S+VH30</a></li> <li><a href="../whatsminer/M3X#m30s_1_1-vh20">M30S++ VH20</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VH40'>M30S+VH40</a></li> <li><a href="../whatsminer/M3X#m30s_1_1-vh30">M30S++ VH30</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VH50'>M30S+VH50</a></li> <li><a href="../whatsminer/M3X#m30s_1_1-vh40">M30S++ VH40</a></li>
<li><a href='../whatsminer/M3X/#M30S_1VH60'>M30S+VH60</a></li> <li><a href="../whatsminer/M3X#m30s_1_1-vh50">M30S++ VH50</a></li>
</ul> <li><a href="../whatsminer/M3X#m30s_1_1-vh60">M30S++ VH60</a></li>
</details> <li><a href="../whatsminer/M3X#m30s_1_1-vh70">M30S++ VH70</a></li>
<details> <li><a href="../whatsminer/M3X#m30s_1_1-vh80">M30S++ VH80</a></li>
<summary><a href='../whatsminer/M3X/#M30S_2'>M30S++</a></summary> <li><a href="../whatsminer/M3X#m30s_1_1-vh90">M30S++ VH90</a></li>
<ul> <li><a href="../whatsminer/M3X#m30s_1_1-vh100">M30S++ VH100</a></li>
<li><a href='../whatsminer/M3X/#M30S_2V10'>M30S++V10</a></li> <li><a href="../whatsminer/M3X#m30s_1_1-vj20">M30S++ VJ20</a></li>
<li><a href='../whatsminer/M3X/#M30S_2V20'>M30S++V20</a></li> <li><a href="../whatsminer/M3X#m30s_1_1-vj30">M30S++ VJ30</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VE30'>M30S++VE30</a></li> <li><a href="../whatsminer/M3X#m31-v10">M31 V10</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VE40'>M30S++VE40</a></li> <li><a href="../whatsminer/M3X#m31-v20">M31 V20</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VE50'>M30S++VE50</a></li> <li><a href="../whatsminer/M3X#m31s-v10">M31S V10</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VF40'>M30S++VF40</a></li> <li><a href="../whatsminer/M3X#m31s-v20">M31S V20</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VG30'>M30S++VG30</a></li> <li><a href="../whatsminer/M3X#m31s-v30">M31S V30</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VG40'>M30S++VG40</a></li> <li><a href="../whatsminer/M3X#m31s-v40">M31S V40</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VG50'>M30S++VG50</a></li> <li><a href="../whatsminer/M3X#m31s-v50">M31S V50</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VH10'>M30S++VH10</a></li> <li><a href="../whatsminer/M3X#m31s-v60">M31S V60</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VH20'>M30S++VH20</a></li> <li><a href="../whatsminer/M3X#m31s-v70">M31S V70</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VH30'>M30S++VH30</a></li> <li><a href="../whatsminer/M3X#m31s-v80">M31S V80</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VH40'>M30S++VH40</a></li> <li><a href="../whatsminer/M3X#m31s-v90">M31S V90</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VH50'>M30S++VH50</a></li> <li><a href="../whatsminer/M3X#m31s-ve10">M31S VE10</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VH60'>M30S++VH60</a></li> <li><a href="../whatsminer/M3X#m31s-ve20">M31S VE20</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VH70'>M30S++VH70</a></li> <li><a href="../whatsminer/M3X#m31s-ve30">M31S VE30</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VH80'>M30S++VH80</a></li> <li><a href="../whatsminer/M3X#m31se-v10">M31SE V10</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VH90'>M30S++VH90</a></li> <li><a href="../whatsminer/M3X#m31se-v20">M31SE V20</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VH100'>M30S++VH100</a></li> <li><a href="../whatsminer/M3X#m31se-v30">M31SE V30</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VJ20'>M30S++VJ20</a></li> <li><a href="../whatsminer/M3X#m31h-v40">M31H V40</a></li>
<li><a href='../whatsminer/M3X/#M30S_2VJ30'>M30S++VJ30</a></li> <li><a href="../whatsminer/M3X#m31s_1-v10">M31S+ V10</a></li>
</ul> <li><a href="../whatsminer/M3X#m31s_1-v20">M31S+ V20</a></li>
</details> <li><a href="../whatsminer/M3X#m31s_1-v30">M31S+ V30</a></li>
<details> <li><a href="../whatsminer/M3X#m31s_1-v40">M31S+ V40</a></li>
<summary><a href='../whatsminer/M3X/#M31'>M31</a></summary> <li><a href="../whatsminer/M3X#m31s_1-v50">M31S+ V50</a></li>
<ul> <li><a href="../whatsminer/M3X#m31s_1-v60">M31S+ V60</a></li>
<li><a href='../whatsminer/M3X/#M31V10'>M31V10</a></li> <li><a href="../whatsminer/M3X#m31s_1-v80">M31S+ V80</a></li>
<li><a href='../whatsminer/M3X/#M31V20'>M31V20</a></li> <li><a href="../whatsminer/M3X#m31s_1-v90">M31S+ V90</a></li>
</ul> <li><a href="../whatsminer/M3X#m31s_1-v100">M31S+ V100</a></li>
</details> <li><a href="../whatsminer/M3X#m31s_1-ve10">M31S+ VE10</a></li>
<details> <li><a href="../whatsminer/M3X#m31s_1-ve20">M31S+ VE20</a></li>
<summary><a href='../whatsminer/M3X/#M31S'>M31S</a></summary> <li><a href="../whatsminer/M3X#m31s_1-ve30">M31S+ VE30</a></li>
<ul> <li><a href="../whatsminer/M3X#m31s_1-ve40">M31S+ VE40</a></li>
<li><a href='../whatsminer/M3X/#M31SV10'>M31SV10</a></li> <li><a href="../whatsminer/M3X#m31s_1-ve50">M31S+ VE50</a></li>
<li><a href='../whatsminer/M3X/#M31SV20'>M31SV20</a></li> <li><a href="../whatsminer/M3X#m31s_1-ve60">M31S+ VE60</a></li>
<li><a href='../whatsminer/M3X/#M31SV30'>M31SV30</a></li> <li><a href="../whatsminer/M3X#m31s_1-ve80">M31S+ VE80</a></li>
<li><a href='../whatsminer/M3X/#M31SV40'>M31SV40</a></li> <li><a href="../whatsminer/M3X#m31s_1-vf20">M31S+ VF20</a></li>
<li><a href='../whatsminer/M3X/#M31SV50'>M31SV50</a></li> <li><a href="../whatsminer/M3X#m31s_1-vf30">M31S+ VF30</a></li>
<li><a href='../whatsminer/M3X/#M31SV60'>M31SV60</a></li> <li><a href="../whatsminer/M3X#m31s_1-vg20">M31S+ VG20</a></li>
<li><a href='../whatsminer/M3X/#M31SV70'>M31SV70</a></li> <li><a href="../whatsminer/M3X#m31s_1-vg30">M31S+ VG30</a></li>
<li><a href='../whatsminer/M3X/#M31SV80'>M31SV80</a></li> <li><a href="../whatsminer/M3X#m32-v10">M32 V10</a></li>
<li><a href='../whatsminer/M3X/#M31SV90'>M31SV90</a></li> <li><a href="../whatsminer/M3X#m32-v20">M32 V20</a></li>
<li><a href='../whatsminer/M3X/#M31SVE10'>M31SVE10</a></li> <li><a href="../whatsminer/M3X#m33-v10">M33 V10</a></li>
<li><a href='../whatsminer/M3X/#M31SVE20'>M31SVE20</a></li> <li><a href="../whatsminer/M3X#m33-v20">M33 V20</a></li>
<li><a href='../whatsminer/M3X/#M31SVE30'>M31SVE30</a></li> <li><a href="../whatsminer/M3X#m33-v30">M33 V30</a></li>
</ul> <li><a href="../whatsminer/M3X#m33s-vg30">M33S VG30</a></li>
</details> <li><a href="../whatsminer/M3X#m33s_1-vh20">M33S+ VH20</a></li>
<details> <li><a href="../whatsminer/M3X#m33s_1-vh30">M33S+ VH30</a></li>
<summary><a href='../whatsminer/M3X/#M31SE'>M31SE</a></summary> <li><a href="../whatsminer/M3X#m33s_1_1-vh20">M33S++ VH20</a></li>
<ul> <li><a href="../whatsminer/M3X#m33s_1_1-vh30">M33S++ VH30</a></li>
<li><a href='../whatsminer/M3X/#M31SEV10'>M31SEV10</a></li> <li><a href="../whatsminer/M3X#m33s_1_1-vg40">M33S++ VG40</a></li>
<li><a href='../whatsminer/M3X/#M31SEV20'>M31SEV20</a></li> <li><a href="../whatsminer/M3X#m34s_1-ve10">M34S+ VE10</a></li>
<li><a href='../whatsminer/M3X/#M31SEV30'>M31SEV30</a></li> <li><a href="../whatsminer/M3X#m36s-ve10">M36S VE10</a></li>
</ul> <li><a href="../whatsminer/M3X#m36s_1-vg30">M36S+ VG30</a></li>
</details> <li><a href="../whatsminer/M3X#m36s_1_1-vh30">M36S++ VH30</a></li>
<details> <li><a href="../whatsminer/M3X#m39-v20">M39 V20</a></li>
<summary><a href='../whatsminer/M3X/#M31H'>M31H</a></summary> </ul>
<ul>
<li><a href='../whatsminer/M3X/#M31HV40'>M31HV40</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M3X/#M31S_1'>M31S+</a></summary>
<ul>
<li><a href='../whatsminer/M3X/#M31S_1V10'>M31S+V10</a></li>
<li><a href='../whatsminer/M3X/#M31S_1V20'>M31S+V20</a></li>
<li><a href='../whatsminer/M3X/#M31S_1V30'>M31S+V30</a></li>
<li><a href='../whatsminer/M3X/#M31S_1V40'>M31S+V40</a></li>
<li><a href='../whatsminer/M3X/#M31S_1V50'>M31S+V50</a></li>
<li><a href='../whatsminer/M3X/#M31S_1V60'>M31S+V60</a></li>
<li><a href='../whatsminer/M3X/#M31S_1V80'>M31S+V80</a></li>
<li><a href='../whatsminer/M3X/#M31S_1V90'>M31S+V90</a></li>
<li><a href='../whatsminer/M3X/#M31S_1V100'>M31S+V100</a></li>
<li><a href='../whatsminer/M3X/#M31S_1VE10'>M31S+VE10</a></li>
<li><a href='../whatsminer/M3X/#M31S_1VE20'>M31S+VE20</a></li>
<li><a href='../whatsminer/M3X/#M31S_1VE30'>M31S+VE30</a></li>
<li><a href='../whatsminer/M3X/#M31S_1VE40'>M31S+VE40</a></li>
<li><a href='../whatsminer/M3X/#M31S_1VE50'>M31S+VE50</a></li>
<li><a href='../whatsminer/M3X/#M31S_1VE60'>M31S+VE60</a></li>
<li><a href='../whatsminer/M3X/#M31S_1VE80'>M31S+VE80</a></li>
<li><a href='../whatsminer/M3X/#M31S_1VF20'>M31S+VF20</a></li>
<li><a href='../whatsminer/M3X/#M31S_1VF30'>M31S+VF30</a></li>
<li><a href='../whatsminer/M3X/#M31S_1VG20'>M31S+VG20</a></li>
<li><a href='../whatsminer/M3X/#M31S_1VG30'>M31S+VG30</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M3X/#M32'>M32</a></summary>
<ul>
<li><a href='../whatsminer/M3X/#M32V10'>M32V10</a></li>
<li><a href='../whatsminer/M3X/#M32V20'>M32V20</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M3X/#M33'>M33</a></summary>
<ul>
<li><a href='../whatsminer/M3X/#M33V10'>M33V10</a></li>
<li><a href='../whatsminer/M3X/#M33V20'>M33V20</a></li>
<li><a href='../whatsminer/M3X/#M33V30'>M33V30</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M3X/#M33S'>M33S</a></summary>
<ul>
<li><a href='../whatsminer/M3X/#M33SVG30'>M33SVG30</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M3X/#M33S_1'>M33S+</a></summary>
<ul>
<li><a href='../whatsminer/M3X/#M33S_1VH20'>M33S+VH20</a></li>
<li><a href='../whatsminer/M3X/#M33S_1VH30'>M33S+VH30</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M3X/#M33S_2'>M33S++</a></summary>
<ul>
<li><a href='../whatsminer/M3X/#M33S_2VH20'>M33S++VH20</a></li>
<li><a href='../whatsminer/M3X/#M33S_2VH30'>M33S++VH30</a></li>
<li><a href='../whatsminer/M3X/#M33S_2VG40'>M33S++VG40</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M3X/#M34S_1'>M34S+</a></summary>
<ul>
<li><a href='../whatsminer/M3X/#M34S_1VE10'>M34S+VE10</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M3X/#M36S'>M36S</a></summary>
<ul>
<li><a href='../whatsminer/M3X/#M36SVE10'>M36SVE10</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M3X/#M36S_1'>M36S+</a></summary>
<ul>
<li><a href='../whatsminer/M3X/#M36S_1VG30'>M36S+VG30</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M3X/#M36S_2'>M36S++</a></summary>
<ul>
<li><a href='../whatsminer/M3X/#M36S_2VH30'>M36S++VH30</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M3X/#M39'>M39</a></summary>
<ul>
<li><a href='../whatsminer/M3X/#M39V20'>M39V20</a></li>
</ul>
</details>
</ul>
</details> </details>
<details> <details>
<summary>M5X Series:</summary> <summary>M5X Series:</summary>
<ul> <ul>
<details> <li><a href="../whatsminer/M5X#m50-vg30">M50 VG30</a></li>
<summary><a href='../whatsminer/M5X/#M50'>M50</a></summary> <li><a href="../whatsminer/M5X#m50-vh10">M50 VH10</a></li>
<ul> <li><a href="../whatsminer/M5X#m50-vh20">M50 VH20</a></li>
<li><a href='../whatsminer/M5X/#M50VG30'>M50VG30</a></li> <li><a href="../whatsminer/M5X#m50-vh30">M50 VH30</a></li>
<li><a href='../whatsminer/M5X/#M50VH10'>M50VH10</a></li> <li><a href="../whatsminer/M5X#m50-vh40">M50 VH40</a></li>
<li><a href='../whatsminer/M5X/#M50VH20'>M50VH20</a></li> <li><a href="../whatsminer/M5X#m50-vh50">M50 VH50</a></li>
<li><a href='../whatsminer/M5X/#M50VH30'>M50VH30</a></li> <li><a href="../whatsminer/M5X#m50-vh60">M50 VH60</a></li>
<li><a href='../whatsminer/M5X/#M50VH40'>M50VH40</a></li> <li><a href="../whatsminer/M5X#m50-vh70">M50 VH70</a></li>
<li><a href='../whatsminer/M5X/#M50VH50'>M50VH50</a></li> <li><a href="../whatsminer/M5X#m50-vh80">M50 VH80</a></li>
<li><a href='../whatsminer/M5X/#M50VH60'>M50VH60</a></li> <li><a href="../whatsminer/M5X#m50-vj10">M50 VJ10</a></li>
<li><a href='../whatsminer/M5X/#M50VH70'>M50VH70</a></li> <li><a href="../whatsminer/M5X#m50-vj20">M50 VJ20</a></li>
<li><a href='../whatsminer/M5X/#M50VH80'>M50VH80</a></li> <li><a href="../whatsminer/M5X#m50-vj30">M50 VJ30</a></li>
<li><a href='../whatsminer/M5X/#M50VJ10'>M50VJ10</a></li> <li><a href="../whatsminer/M5X#m50s-vj10">M50S VJ10</a></li>
<li><a href='../whatsminer/M5X/#M50VJ20'>M50VJ20</a></li> <li><a href="../whatsminer/M5X#m50s-vj20">M50S VJ20</a></li>
<li><a href='../whatsminer/M5X/#M50VJ30'>M50VJ30</a></li> <li><a href="../whatsminer/M5X#m50s-vj30">M50S VJ30</a></li>
</ul> <li><a href="../whatsminer/M5X#m50s-vh10">M50S VH10</a></li>
</details> <li><a href="../whatsminer/M5X#m50s-vh20">M50S VH20</a></li>
<details> <li><a href="../whatsminer/M5X#m50s-vh30">M50S VH30</a></li>
<summary><a href='../whatsminer/M5X/#M50S'>M50S</a></summary> <li><a href="../whatsminer/M5X#m50s-vh40">M50S VH40</a></li>
<ul> <li><a href="../whatsminer/M5X#m50s-vh50">M50S VH50</a></li>
<li><a href='../whatsminer/M5X/#M50SVJ10'>M50SVJ10</a></li> <li><a href="../whatsminer/M5X#m50s_1-vh30">M50S+ VH30</a></li>
<li><a href='../whatsminer/M5X/#M50SVJ20'>M50SVJ20</a></li> <li><a href="../whatsminer/M5X#m50s_1-vh40">M50S+ VH40</a></li>
<li><a href='../whatsminer/M5X/#M50SVJ30'>M50SVJ30</a></li> <li><a href="../whatsminer/M5X#m50s_1-vj30">M50S+ VJ30</a></li>
<li><a href='../whatsminer/M5X/#M50SVH10'>M50SVH10</a></li> <li><a href="../whatsminer/M5X#m50s_1-vk20">M50S+ VK20</a></li>
<li><a href='../whatsminer/M5X/#M50SVH20'>M50SVH20</a></li> <li><a href="../whatsminer/M5X#m50s_1_1-vk10">M50S++ VK10</a></li>
<li><a href='../whatsminer/M5X/#M50SVH30'>M50SVH30</a></li> <li><a href="../whatsminer/M5X#m50s_1_1-vk20">M50S++ VK20</a></li>
<li><a href='../whatsminer/M5X/#M50SVH40'>M50SVH40</a></li> <li><a href="../whatsminer/M5X#m50s_1_1-vk30">M50S++ VK30</a></li>
<li><a href='../whatsminer/M5X/#M50SVH50'>M50SVH50</a></li> <li><a href="../whatsminer/M5X#m53-vh30">M53 VH30</a></li>
</ul> <li><a href="../whatsminer/M5X#m53s-vh30">M53S VH30</a></li>
</details> <li><a href="../whatsminer/M5X#m53s_1-vj30">M53S+ VJ30</a></li>
<details> <li><a href="../whatsminer/M5X#m56-vh30">M56 VH30</a></li>
<summary><a href='../whatsminer/M5X/#M50S_1'>M50S+</a></summary> <li><a href="../whatsminer/M5X#m56s-vh30">M56S VH30</a></li>
<ul> <li><a href="../whatsminer/M5X#m56s_1-vj30">M56S+ VJ30</a></li>
<li><a href='../whatsminer/M5X/#M50S_1VH30'>M50S+VH30</a></li> <li><a href="../whatsminer/M5X#m59-vh30">M59 VH30</a></li>
<li><a href='../whatsminer/M5X/#M50S_1VH40'>M50S+VH40</a></li> </ul>
<li><a href='../whatsminer/M5X/#M50S_1VJ30'>M50S+VJ30</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M5X/#M53'>M53</a></summary>
<ul>
<li><a href='../whatsminer/M5X/#M53VH30'>M53VH30</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M5X/#M53S'>M53S</a></summary>
<ul>
<li><a href='../whatsminer/M5X/#M53SVH30'>M53SVH30</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M5X/#M53S_1'>M53S+</a></summary>
<ul>
<li><a href='../whatsminer/M5X/#M53S_1VJ30'>M53S+VJ30</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M5X/#M56'>M56</a></summary>
<ul>
<li><a href='../whatsminer/M5X/#M56VH30'>M56VH30</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M5X/#M56S'>M56S</a></summary>
<ul>
<li><a href='../whatsminer/M5X/#M56SVH30'>M56SVH30</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M5X/#M56S_1'>M56S+</a></summary>
<ul>
<li><a href='../whatsminer/M5X/#M56S_1VJ30'>M56S+VJ30</a></li>
</ul>
</details>
<details>
<summary><a href='../whatsminer/M5X/#M59'>M59</a></summary>
<ul>
<li><a href='../whatsminer/M5X/#M59VH30'>M59VH30</a></li>
</ul>
</details>
</ul>
</details>
</ul>
</details>
<details>
<summary>Stock Firmware Antminers:</summary>
<ul>
<details>
<summary>X19 Series:</summary>
<ul>
<li><a href="../antminer/X19/#s19">S19</a></li>
<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/#s19a">S19a</a></li>
<li><a href="../antminer/X19/#s19j">S19j</a></li>
<li><a href="../antminer/X19/#s19j-pro">S19j Pro</a></li>
<li><a href="../antminer/X19/#s19-xp">S19 XP</a></li>
<li><a href="../antminer/X19/#t19">T19</a></li>
</ul>
</details>
<details>
<summary>X17 Series:</summary>
<ul>
<li><a href="../antminer/X17/#s17">S17</a></li>
<li><a href="../antminer/X17/#s17_1">S17+</a></li>
<li><a href="../antminer/X17/#s17-pro">S17 Pro</a></li>
<li><a href="../antminer/X17/#s17e">S17e</a></li>
<li><a href="../antminer/X17/#t17">T17</a></li>
<li><a href="../antminer/X17/#t17_1">T17+</a></li>
<li><a href="../antminer/X17/#t17e">T17e</a></li>
</ul>
</details>
<details>
<summary>X15 Series:</summary>
<ul>
<li><a href="../antminer/X15/#z15">Z15</a></li>
</ul>
</details>
<details>
<summary>X9 Series:</summary>
<ul>
<li><a href="../antminer/X9/#s9">S9</a></li>
<li><a href="../antminer/X9/#s9i">S9i</a></li>
<li><a href="../antminer/X9/#t9">T9</a></li>
<li><a href="../antminer/X9/#e9-pro">E9 Pro</a></li>
</ul>
</details>
<details>
<summary>X7 Series:</summary>
<ul>
<li><a href="../antminer/X7/#l7">L7</a></li>
</ul>
</details>
<details>
<summary>X5 Series:</summary>
<ul>
<li><a href="../antminer/X5/#dr5">DR5</a></li>
</ul>
</details>
<details>
<summary>X3 Series:</summary>
<ul>
<li><a href="../antminer/X3/#hs3">HS3</a></li>
</ul>
</details> </details>
</ul> </ul>
</details> </details>
@@ -492,75 +289,169 @@ details {
<summary>Stock Firmware Avalonminers:</summary> <summary>Stock Firmware Avalonminers:</summary>
<ul> <ul>
<details> <details>
<summary>A7X Series:</summary> <summary>A7X Series:</summary>
<ul> <ul>
<li><a href="../avalonminer/A7X/#a721">A721</a></li> <li><a href="../avalonminer/A7X#avalon-721">Avalon 721</a></li>
<li><a href="../avalonminer/A7X/#a741">A741</a></li> <li><a href="../avalonminer/A7X#avalon-741">Avalon 741</a></li>
<li><a href="../avalonminer/A7X/#a761">A761</a></li> <li><a href="../avalonminer/A7X#avalon-761">Avalon 761</a></li>
</ul> </ul>
</details> </details>
<details> <details>
<summary>A8X Series:</summary> <summary>A8X Series:</summary>
<ul> <ul>
<li><a href="../avalonminer/A8X/#a821">A821</a></li> <li><a href="../avalonminer/A8X#avalon-821">Avalon 821</a></li>
<li><a href="../avalonminer/A8X/#a841">A841</a></li> <li><a href="../avalonminer/A8X#avalon-841">Avalon 841</a></li>
<li><a href="../avalonminer/A8X/#a851">A851</a></li> <li><a href="../avalonminer/A8X#avalon-851">Avalon 851</a></li>
</ul> </ul>
</details> </details>
<details> <details>
<summary>A9X Series:</summary> <summary>A9X Series:</summary>
<ul> <ul>
<li><a href="../avalonminer/A9X/#a921">A921</a></li> <li><a href="../avalonminer/A9X#avalon-921">Avalon 921</a></li>
</ul> </ul>
</details> </details>
<details> <details>
<summary>A10X Series:</summary> <summary>A10X Series:</summary>
<ul> <ul>
<li><a href="../avalonminer/A10X/#a1026">A1026</a></li> <li><a href="../avalonminer/A10X#avalon-1026">Avalon 1026</a></li>
<li><a href="../avalonminer/A10X/#a1047">A1047</a></li> <li><a href="../avalonminer/A10X#avalon-1047">Avalon 1047</a></li>
<li><a href="../avalonminer/A10X/#a1066">A1066</a></li> <li><a href="../avalonminer/A10X#avalon-1066">Avalon 1066</a></li>
</ul> </ul>
</details>
<details>
<summary>A11X Series:</summary>
<ul>
<li><a href="../avalonminer/A11X#avalon-1166-pro">Avalon 1166 Pro</a></li>
</ul>
</details>
<details>
<summary>A12X Series:</summary>
<ul>
<li><a href="../avalonminer/A12X#avalon-1246">Avalon 1246</a></li>
</ul>
</details> </details>
</ul> </ul>
</details> </details>
<details> <details>
<summary>Stock Firmware Innosilicon Miners:</summary> <summary>Stock Firmware Innosilicons:</summary>
<ul> <ul>
<details> <details>
<summary>T3X Series:</summary> <summary>T3X Series:</summary>
<ul> <ul>
<li><a href="../innosilicon/T3X/#t3h">T3H+</a></li> <li><a href="../innosilicon/T3X#t3h_1">T3H+</a></li>
</ul> </ul>
</details> </details>
<details> <details>
<summary>A10X Series:</summary> <summary>A10X Series:</summary>
<ul> <ul>
<li><a href="../innosilicon/A10X/#a10x">A10X</a></li> <li><a href="../innosilicon/A10X#a10x">A10X</a></li>
</ul> </ul>
</details> </details>
</ul> </ul>
</details> </details>
<details> <details>
<summary>Stock Firmware BFGMinerGoldshell Miners:</summary> <summary>Stock Firmware Goldshells:</summary>
<ul> <ul>
<details> <details>
<summary>CKX Series:</summary> <summary>X5 Series:</summary>
<ul> <ul>
<li><a href="../goldshell/CKX/#ck5">CK5</a></li> <li><a href="../goldshell/X5#ck5">CK5</a></li>
</ul> <li><a href="../goldshell/X5#hs5">HS5</a></li>
<li><a href="../goldshell/X5#kd5">KD5</a></li>
</ul>
</details> </details>
<details> <details>
<summary>HSX Series:</summary> <summary>XMax Series:</summary>
<ul> <ul>
<li><a href="../goldshell/HSX/#hs5">HS5</a></li> <li><a href="../goldshell/XMax#kd-max">KD Max</a></li>
</ul> </ul>
</details> </details>
<details> </ul>
<summary>KDX Series:</summary> </details>
<ul> <details>
<li><a href="../goldshell/KDX/#kd5">KD5</a></li> <summary>BOS+ Firmware Miners:</summary>
<li><a href="../goldshell/KDX/#kd-max">KD Max</a></li> <ul>
</ul> <details>
<summary>X9 Series:</summary>
<ul>
<li><a href="../antminer/X9#s9-bos">S9 (BOS)</a></li>
</ul>
</details>
<details>
<summary>X17 Series:</summary>
<ul>
<li><a href="../antminer/X17#s17-bos">S17 (BOS)</a></li>
<li><a href="../antminer/X17#s17_1-bos">S17+ (BOS)</a></li>
<li><a href="../antminer/X17#s17-pro-bos">S17 Pro (BOS)</a></li>
<li><a href="../antminer/X17#s17e-bos">S17e (BOS)</a></li>
<li><a href="../antminer/X17#t17-bos">T17 (BOS)</a></li>
<li><a href="../antminer/X17#t17_1-bos">T17+ (BOS)</a></li>
<li><a href="../antminer/X17#t17e-bos">T17e (BOS)</a></li>
</ul>
</details>
<details>
<summary>X19 Series:</summary>
<ul>
<li><a href="../antminer/X19#s19-bos">S19 (BOS)</a></li>
<li><a href="../antminer/X19#s19-pro-bos">S19 Pro (BOS)</a></li>
<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#t19-bos">T19 (BOS)</a></li>
</ul>
</details>
</ul>
</details>
<details>
<summary>Vnish Firmware Miners:</summary>
<ul>
<details>
<summary>X3 Series:</summary>
<ul>
<li><a href="../antminer/X3#l3_1-vnish">L3+ (VNish)</a></li>
</ul>
</details>
<details>
<summary>X17 Series:</summary>
<ul>
<li><a href="../antminer/X17#s17_1-vnish">S17+ (VNish)</a></li>
<li><a href="../antminer/X17#s17-pro-vnish">S17 Pro (VNish)</a></li>
</ul>
</details>
<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>
<li><a href="../antminer/X19#s19a-vnish">S19a (VNish)</a></li>
<li><a href="../antminer/X19#s19a-pro-vnish">S19a Pro (VNish)</a></li>
<li><a href="../antminer/X19#t19-vnish">T19 (VNish)</a></li>
</ul>
</details>
</ul>
</details>
<details>
<summary>HiveOS Firmware Miners:</summary>
<ul>
<details>
<summary>X9 Series:</summary>
<ul>
<li><a href="../antminer/X9#t9-hiveon">T9 (Hiveon)</a></li>
</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> </details>
</ul> </ul>
</details> </details>

View File

@@ -1,90 +1,80 @@
# pyasic # pyasic
## M2X Models ## M2X Models
## M20V10 ## M20 V10
::: pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20V10
::: pyasic.miners.btc.whatsminer.btminer.M2X.M20.BTMinerM20V10
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M20SV10 ## M20S V10
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV10
::: pyasic.miners.btc.whatsminer.btminer.M2X.M20S.BTMinerM20SV10
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M20SV20 ## M20S V20
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV20
::: pyasic.miners.btc.whatsminer.btminer.M2X.M20S.BTMinerM20SV20
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M20SV30 ## M20S V30
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV30
::: pyasic.miners.btc.whatsminer.btminer.M2X.M20S.BTMinerM20SV30
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M20S+V30 ## M20S+ V30
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlusV30
::: pyasic.miners.btc.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlusV30
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M21V10 ## M21 V10
::: pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21V10
::: pyasic.miners.btc.whatsminer.btminer.M2X.M21.BTMinerM21V10
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M21SV20 ## M21S V20
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV20
::: pyasic.miners.btc.whatsminer.btminer.M2X.M21S.BTMinerM21SV20
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M21SV60 ## M21S V60
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV60
::: pyasic.miners.btc.whatsminer.btminer.M2X.M21S.BTMinerM21SV60
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M21SV70 ## M21S V70
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV70
::: pyasic.miners.btc.whatsminer.btminer.M2X.M21S.BTMinerM21SV70
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M21S+V20 ## M21S+ V20
::: pyasic.miners.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlusV20
::: pyasic.miners.btc.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlusV20
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M29V10 ## M29 V10
::: pyasic.miners.whatsminer.btminer.M2X.M29.BTMinerM29V10
::: pyasic.miners.btc.whatsminer.btminer.M2X.M29.BTMinerM29V10
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4

File diff suppressed because it is too large Load Diff

View File

@@ -1,242 +1,241 @@
# pyasic # pyasic
## M5X Models ## M5X Models
## M50VG30 ## M50 VG30
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VG30
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VG30
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50VH10 ## M50 VH10
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH10
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VH10
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50VH20 ## M50 VH20
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH20
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VH20
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50VH30 ## M50 VH30
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH30
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VH30
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50VH40 ## M50 VH40
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH40
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VH40
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50VH50 ## M50 VH50
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH50
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VH50
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50VH60 ## M50 VH60
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH60
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VH60
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50VH70 ## M50 VH70
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH70
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VH70
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50VH80 ## M50 VH80
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH80
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VH80
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50VJ10 ## M50 VJ10
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ10
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VJ10
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50VJ20 ## M50 VJ20
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ20
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VJ20
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50VJ30 ## M50 VJ30
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ30
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VJ30
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50SVJ10 ## M50S VJ10
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ10
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ10
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50SVJ20 ## M50S VJ20
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ20
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ20
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50SVJ30 ## M50S VJ30
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ30
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ30
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50SVH10 ## M50S VH10
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH10
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S.BTMinerM50SVH10
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50SVH20 ## M50S VH20
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH20
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S.BTMinerM50SVH20
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50SVH30 ## M50S VH30
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH30
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S.BTMinerM50SVH30
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50SVH40 ## M50S VH40
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH40
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S.BTMinerM50SVH40
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50SVH50 ## M50S VH50
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH50
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S.BTMinerM50SVH50
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50S+VH30 ## M50S+ VH30
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH30
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH30
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50S+VH40 ## M50S+ VH40
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH40
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH40
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M50S+VJ30 ## M50S+ VJ30
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVJ30
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVJ30
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M53VH30 ## M50S+ VK20
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVK20
::: pyasic.miners.btc.whatsminer.btminer.M5X.M53.BTMinerM53VH30
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M53SVH30 ## M50S++ VK10
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK10
::: pyasic.miners.btc.whatsminer.btminer.M5X.M53S.BTMinerM53SVH30
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M53S+VJ30 ## M50S++ VK20
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK20
::: pyasic.miners.btc.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVJ30
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M56VH30 ## M50S++ VK30
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK30
::: pyasic.miners.btc.whatsminer.btminer.M5X.M56.BTMinerM56VH30
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M56SVH30 ## M53 VH30
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VH30
::: pyasic.miners.btc.whatsminer.btminer.M5X.M56S.BTMinerM56SVH30
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M56S+VJ30 ## M53S VH30
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVH30
::: pyasic.miners.btc.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVJ30
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M59VH30 ## M53S+ VJ30
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVJ30
::: pyasic.miners.btc.whatsminer.btminer.M5X.M59.BTMinerM59VH30
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M56 VH30
::: pyasic.miners.whatsminer.btminer.M5X.M56.BTMinerM56VH30
handler: python
options:
show_root_heading: false
heading_level: 4
## M56S VH30
::: pyasic.miners.whatsminer.btminer.M5X.M56S.BTMinerM56SVH30
handler: python
options:
show_root_heading: false
heading_level: 4
## M56S+ VJ30
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVJ30
handler: python
options:
show_root_heading: false
heading_level: 4
## M59 VH30
::: pyasic.miners.whatsminer.btminer.M5X.M59.BTMinerM59VH30
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -32,6 +32,8 @@ class BaseMinerAPI:
# ip address of the miner # ip address of the miner
self.ip = ipaddress.ip_address(ip) self.ip = ipaddress.ip_address(ip)
self.pwd = "admin"
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if cls is BaseMinerAPI: if cls is BaseMinerAPI:
raise TypeError(f"Only children of '{cls.__name__}' may be instantiated") raise TypeError(f"Only children of '{cls.__name__}' may be instantiated")
@@ -73,6 +75,11 @@ class BaseMinerAPI:
# send the command # send the command
data = await self._send_bytes(json.dumps(cmd).encode("utf-8")) 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) data = self._load_api_data(data)
# check for if the user wants to allow errors to return # check for if the user wants to allow errors to return

759
pyasic/API/luxminer.py Normal file
View 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)

View File

@@ -550,7 +550,7 @@ class MinerConfig:
"bitmain-fan-ctrl": False, "bitmain-fan-ctrl": False,
"bitmain-fan-pwn": "100", "bitmain-fan-pwn": "100",
"freq-level": "100", "freq-level": "100",
"miner-mode": str(self.miner_mode.value), "miner-mode": self.miner_mode.value,
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix), "pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
} }
@@ -560,9 +560,6 @@ class MinerConfig:
if self.fan_speed: if self.fan_speed:
cfg["bitmain-fan-pwn"] = str(self.fan_speed) cfg["bitmain-fan-pwn"] = str(self.fan_speed)
if self.miner_mode == X19PowerMode.Sleep:
cfg["freq-level"] = "0"
return cfg return cfg
def as_x17(self, user_suffix: str = None) -> dict: def as_x17(self, user_suffix: str = None) -> dict:

View File

@@ -20,7 +20,7 @@ import logging
import time import time
from dataclasses import asdict, dataclass, field, fields from dataclasses import asdict, dataclass, field, fields
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import List, Union from typing import List, Union, Any
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
@@ -40,13 +40,28 @@ class HashBoard:
""" """
slot: int = 0 slot: int = 0
hashrate: float = 0.0 hashrate: float = None
temp: int = -1 temp: int = None
chip_temp: int = -1 chip_temp: int = None
chips: int = 0 chips: int = None
expected_chips: int = 0 expected_chips: int = None
missing: bool = True missing: bool = True
def get(self, __key: str, default: Any = None):
try:
val = self.__getitem__(__key)
if val is None:
return default
return val
except KeyError:
return default
def __getitem__(self, item: str):
try:
return getattr(self, item)
except AttributeError:
raise KeyError(f"{item}")
@dataclass @dataclass
class Fan: class Fan:
@@ -56,7 +71,22 @@ class Fan:
speed: The speed of the fan. speed: The speed of the fan.
""" """
speed: int = -1 speed: int = None
def get(self, __key: str, default: Any = None):
try:
val = self.__getitem__(__key)
if val is None:
return default
return val
except KeyError:
return default
def __getitem__(self, item: str):
try:
return getattr(self, item)
except AttributeError:
raise KeyError(f"{item}")
@dataclass @dataclass
@@ -66,34 +96,23 @@ class MinerData:
Attributes: Attributes:
ip: The IP of the miner as a str. ip: The IP of the miner as a str.
datetime: The time and date this data was generated. 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. model: The model of the miner as a str.
make: The make of the miner as a str. make: The make of the miner as a str.
api_ver: The current api version on the miner as a str. api_ver: The current api version on the miner as a str.
fw_ver: The current firmware version on the miner as a str. fw_ver: The current firmware version on the miner as a str.
hostname: The network hostname of the miner as a str. hostname: The network hostname of the miner as a str.
hashrate: The hashrate of the miner in TH/s as a float. hashrate: The hashrate of the miner in TH/s as a float. Calculated automatically.
_hashrate: Backup for hashrate found via API instead of hashboards.
nominal_hashrate: The factory nominal hashrate of the miner in TH/s as a float. nominal_hashrate: The factory nominal hashrate of the miner in TH/s as a float.
left_board_hashrate: The hashrate of the left board of the miner in TH/s as a float. hashboards: A list of hashboards on the miner with their statistics.
center_board_hashrate: The hashrate of the center board of the miner in TH/s as a float.
right_board_hashrate: The hashrate of the right board of the miner in TH/s as a float.
temperature_avg: The average temperature across the boards. Calculated automatically. temperature_avg: The average temperature across the boards. Calculated automatically.
env_temp: The environment temps as a float. env_temp: The environment temps as a float.
left_board_temp: The temp of the left PCB as an int.
left_board_chip_temp: The temp of the left board chips as an int.
center_board_temp: The temp of the center PCB as an int.
center_board_chip_temp: The temp of the center board chips as an int.
right_board_temp: The temp of the right PCB as an int.
right_board_chip_temp: The temp of the right board chips as an int.
wattage: Current power draw of the miner as an int. wattage: Current power draw of the miner as an int.
wattage_limit: Power limit of the miner as an int. wattage_limit: Power limit of the miner as an int.
fan_1: The speed of the first fan as an int. fans: A list of fans on the miner with their speeds.
fan_2: The speed of the second fan as an int.
fan_3: The speed of the third fan as an int.
fan_4: The speed of the fourth fan as an int.
fan_psu: The speed of the PSU on the fan if the miner collects it. fan_psu: The speed of the PSU on the fan if the miner collects it.
left_chips: The number of chips online in the left board as an int.
center_chips: The number of chips online in the left board as an int.
right_chips: The number of chips online in the left board as an int.
total_chips: The total number of chips on all boards. Calculated automatically. total_chips: The total number of chips on all boards. Calculated automatically.
ideal_chips: The ideal number of chips in the miner as an int. ideal_chips: The ideal number of chips in the miner as an int.
percent_ideal_chips: The percent of total chips out of the ideal count. Calculated automatically. percent_ideal_chips: The percent of total chips out of the ideal count. Calculated automatically.
@@ -106,51 +125,37 @@ class MinerData:
pool_2_url: The second pool url on the miner as a str. pool_2_url: The second pool url on the miner as a str.
pool_2_user: The second pool user on the miner as a str. pool_2_user: The second pool user on the miner as a str.
errors: A list of errors on the miner. errors: A list of errors on the miner.
fault_light: Whether or not the fault light is on as a boolean. 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. efficiency: Efficiency of the miner in J/TH (Watts per TH/s). Calculated automatically.
is_mining: Whether the miner is mining.
""" """
ip: str ip: str
datetime: datetime = None datetime: datetime = None
mac: str = "00:00:00:00:00:00" uptime: int = None
model: str = "Unknown" mac: str = None
make: str = "Unknown" model: str = None
api_ver: str = "Unknown" make: str = None
fw_ver: str = "Unknown" api_ver: str = None
hostname: str = "Unknown" fw_ver: str = None
hostname: str = None
hashrate: float = field(init=False) hashrate: float = field(init=False)
_hashrate: float = 0 _hashrate: float = None
nominal_hashrate: float = 0 nominal_hashrate: float = None
hashboards: List[HashBoard] = field(default_factory=list) hashboards: List[HashBoard] = field(default_factory=list)
ideal_hashboards: int = 1 ideal_hashboards: int = None
left_board_hashrate: float = field(init=False)
center_board_hashrate: float = field(init=False)
right_board_hashrate: float = field(init=False)
temperature_avg: int = field(init=False) temperature_avg: int = field(init=False)
env_temp: float = -1.0 env_temp: float = None
left_board_temp: int = field(init=False) wattage: int = None
left_board_chip_temp: int = field(init=False) wattage_limit: int = None
center_board_temp: int = field(init=False)
center_board_chip_temp: int = field(init=False)
right_board_temp: int = field(init=False)
right_board_chip_temp: int = field(init=False)
wattage: int = -1
wattage_limit: int = -1
fans: List[Fan] = field(default_factory=list) fans: List[Fan] = field(default_factory=list)
fan_1: int = field(init=False) fan_psu: int = None
fan_2: int = field(init=False)
fan_3: int = field(init=False)
fan_4: int = field(init=False)
fan_psu: int = -1
left_chips: int = field(init=False)
center_chips: int = field(init=False)
right_chips: int = field(init=False)
total_chips: int = field(init=False) total_chips: int = field(init=False)
ideal_chips: int = 1 ideal_chips: int = None
percent_ideal_chips: float = field(init=False) percent_ideal_chips: float = field(init=False)
percent_ideal_hashrate: float = field(init=False) percent_ideal_hashrate: float = field(init=False)
percent_ideal_wattage: float = field(init=False) percent_ideal_wattage: float = field(init=False)
nominal: int = field(init=False) nominal: bool = field(init=False)
pool_split: str = "0" pool_split: str = "0"
pool_1_url: str = "Unknown" pool_1_url: str = "Unknown"
pool_1_user: str = "Unknown" pool_1_user: str = "Unknown"
@@ -161,6 +166,7 @@ class MinerData:
] = field(default_factory=list) ] = field(default_factory=list)
fault_light: Union[bool, None] = None fault_light: Union[bool, None] = None
efficiency: int = field(init=False) efficiency: int = field(init=False)
is_mining: bool = True
@classmethod @classmethod
def fields(cls): def fields(cls):
@@ -169,7 +175,16 @@ class MinerData:
def __post_init__(self): def __post_init__(self):
self.datetime = datetime.now(timezone.utc).astimezone() self.datetime = datetime.now(timezone.utc).astimezone()
def __getitem__(self, item): def get(self, __key: str, default: Any = None):
try:
val = self.__getitem__(__key)
if val is None:
return default
return val
except KeyError:
return default
def __getitem__(self, item: str):
try: try:
return getattr(self, item) return getattr(self, item)
except AttributeError: except AttributeError:
@@ -221,204 +236,37 @@ class MinerData:
@property @property
def hashrate(self): # noqa - Skip PyCharm inspection def hashrate(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) > 0: if len(self.hashboards) > 0:
return round(sum(map(lambda x: x.hashrate, self.hashboards)), 2) hr_data = []
for item in self.hashboards:
if item.hashrate is not None:
hr_data.append(item.hashrate)
if len(hr_data) > 0:
return round(sum(hr_data) / len(hr_data), 2)
return self._hashrate return self._hashrate
@hashrate.setter @hashrate.setter
def hashrate(self, val): def hashrate(self, val):
self._hashrate = val self._hashrate = val
@property
def fan_1(self): # noqa - Skip PyCharm inspection
if len(self.fans) > 0:
return self.fans[0].speed
@fan_1.setter
def fan_1(self, val):
pass
@property
def fan_2(self): # noqa - Skip PyCharm inspection
if len(self.fans) > 1:
return self.fans[1].speed
@fan_2.setter
def fan_2(self, val):
pass
@property
def fan_3(self): # noqa - Skip PyCharm inspection
if len(self.fans) > 2:
return self.fans[2].speed
@fan_3.setter
def fan_3(self, val):
pass
@property
def fan_4(self): # noqa - Skip PyCharm inspection
if len(self.fans) > 3:
return self.fans[3].speed
@fan_4.setter
def fan_4(self, val):
pass
@property @property
def total_chips(self): # noqa - Skip PyCharm inspection def total_chips(self): # noqa - Skip PyCharm inspection
return sum([hb.chips for hb in self.hashboards]) if len(self.hashboards) > 0:
chip_data = []
for item in self.hashboards:
if item.chips is not None:
chip_data.append(item.chips)
if len(chip_data) > 0:
return round(sum(chip_data) / len(chip_data), 2)
return None
@total_chips.setter @total_chips.setter
def total_chips(self, val): def total_chips(self, val):
pass pass
@property
def left_chips(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) in [2, 3, 4]:
return self.hashboards[0].chips
return 0
@left_chips.setter
def left_chips(self, val):
pass
@property
def center_chips(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) == 1:
return self.hashboards[0].chips
if len(self.hashboards) in [2, 3, 4]:
return self.hashboards[1].chips
return 0
@center_chips.setter
def center_chips(self, val):
pass
@property
def right_chips(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) == 2:
return self.hashboards[1].chips
if len(self.hashboards) == 3:
return self.hashboards[2].chips
if len(self.hashboards) > 3:
return self.hashboards[-1:][0].chips
return 0
@right_chips.setter
def right_chips(self, val):
pass
@property
def left_board_hashrate(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) in [2, 3, 4]:
return self.hashboards[0].hashrate
return 0
@left_board_hashrate.setter
def left_board_hashrate(self, val):
pass
@property
def center_board_hashrate(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) == 1:
return self.hashboards[0].hashrate
if len(self.hashboards) in [2, 3, 4]:
return self.hashboards[1].hashrate
return 0
@center_board_hashrate.setter
def center_board_hashrate(self, val):
pass
@property
def right_board_hashrate(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) == 2:
return self.hashboards[1].hashrate
if len(self.hashboards) == 3:
return self.hashboards[2].hashrate
if len(self.hashboards) > 3:
return self.hashboards[-1:][0].hashrate
return 0
@right_board_hashrate.setter
def right_board_hashrate(self, val):
pass
@property
def left_board_temp(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) in [2, 3, 4]:
return self.hashboards[0].temp
return 0
@left_board_temp.setter
def left_board_temp(self, val):
pass
@property
def center_board_temp(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) == 1:
return self.hashboards[0].temp
if len(self.hashboards) in [2, 3, 4]:
return self.hashboards[1].temp
return 0
@center_board_temp.setter
def center_board_temp(self, val):
pass
@property
def right_board_temp(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) == 2:
return self.hashboards[1].temp
if len(self.hashboards) == 3:
return self.hashboards[2].temp
if len(self.hashboards) > 3:
return self.hashboards[-1:][0].temp
return 0
@right_board_temp.setter
def right_board_temp(self, val):
pass
@property
def left_board_chip_temp(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) in [2, 3, 4]:
return self.hashboards[0].chip_temp
return 0
@left_board_chip_temp.setter
def left_board_chip_temp(self, val):
pass
@property
def center_board_chip_temp(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) == 1:
return self.hashboards[0].chip_temp
if len(self.hashboards) in [2, 3, 4]:
return self.hashboards[1].chip_temp
return 0
@center_board_chip_temp.setter
def center_board_chip_temp(self, val):
pass
@property
def right_board_chip_temp(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) == 2:
return self.hashboards[1].chip_temp
if len(self.hashboards) == 3:
return self.hashboards[2].chip_temp
if len(self.hashboards) > 3:
return self.hashboards[-1:][0].chip_temp
return 0
@right_board_chip_temp.setter
def right_board_chip_temp(self, val):
pass
@property @property
def nominal(self): # noqa - Skip PyCharm inspection def nominal(self): # noqa - Skip PyCharm inspection
if self.total_chips is None or self.ideal_chips is None:
return None
return self.ideal_chips == self.total_chips return self.ideal_chips == self.total_chips
@nominal.setter @nominal.setter
@@ -427,6 +275,8 @@ class MinerData:
@property @property
def percent_ideal_chips(self): # noqa - Skip PyCharm inspection def percent_ideal_chips(self): # noqa - Skip PyCharm inspection
if self.total_chips is None or self.ideal_chips is None:
return None
if self.total_chips == 0 or self.ideal_chips == 0: if self.total_chips == 0 or self.ideal_chips == 0:
return 0 return 0
return round((self.total_chips / self.ideal_chips) * 100) return round((self.total_chips / self.ideal_chips) * 100)
@@ -437,6 +287,8 @@ class MinerData:
@property @property
def percent_ideal_hashrate(self): # noqa - Skip PyCharm inspection def percent_ideal_hashrate(self): # noqa - Skip PyCharm inspection
if self.hashrate is None or self.nominal_hashrate is None:
return None
if self.hashrate == 0 or self.nominal_hashrate == 0: if self.hashrate == 0 or self.nominal_hashrate == 0:
return 0 return 0
return round((self.hashrate / self.nominal_hashrate) * 100) return round((self.hashrate / self.nominal_hashrate) * 100)
@@ -447,6 +299,8 @@ class MinerData:
@property @property
def percent_ideal_wattage(self): # noqa - Skip PyCharm inspection def percent_ideal_wattage(self): # noqa - Skip PyCharm inspection
if self.wattage_limit is None or self.wattage is None:
return None
if self.wattage_limit == 0 or self.wattage == 0: if self.wattage_limit == 0 or self.wattage == 0:
return 0 return 0
return round((self.wattage / self.wattage_limit) * 100) return round((self.wattage / self.wattage_limit) * 100)
@@ -460,11 +314,11 @@ class MinerData:
total_temp = 0 total_temp = 0
temp_count = 0 temp_count = 0
for hb in self.hashboards: for hb in self.hashboards:
if hb.temp and not hb.temp == -1: if hb.temp is not None:
total_temp += hb.temp total_temp += hb.temp
temp_count += 1 temp_count += 1
if not temp_count > 0: if not temp_count > 0:
return 0 return None
return round(total_temp / temp_count) return round(total_temp / temp_count)
@temperature_avg.setter @temperature_avg.setter
@@ -473,7 +327,9 @@ class MinerData:
@property @property
def efficiency(self): # noqa - Skip PyCharm inspection def efficiency(self): # noqa - Skip PyCharm inspection
if self.hashrate == 0 or self.wattage == -1: if self.hashrate is None or self.wattage is None:
return None
if self.hashrate == 0 or self.wattage == 0:
return 0 return 0
return round(self.wattage / self.hashrate) return round(self.wattage / self.hashrate)
@@ -533,27 +389,41 @@ class MinerData:
tags = ["ip", "mac", "model", "hostname"] tags = ["ip", "mac", "model", "hostname"]
for attribute in self: for attribute in self:
if attribute in tags: if attribute in tags:
escaped_data = self[attribute].replace(" ", "\\ ") escaped_data = self.get(attribute, "Unknown").replace(" ", "\\ ")
tag_data.append(f"{attribute}={escaped_data}") tag_data.append(f"{attribute}={escaped_data}")
continue continue
if isinstance(self[attribute], str): elif str(attribute).startswith("_"):
continue
elif isinstance(self[attribute], str):
field_data.append(f'{attribute}="{self[attribute]}"') field_data.append(f'{attribute}="{self[attribute]}"')
continue continue
if isinstance(self[attribute], bool): elif isinstance(self[attribute], bool):
field_data.append(f"{attribute}={str(self[attribute]).lower()}") field_data.append(f"{attribute}={str(self[attribute]).lower()}")
continue continue
if isinstance(self[attribute], int): elif isinstance(self[attribute], int):
field_data.append(f"{attribute}={self[attribute]}") field_data.append(f"{attribute}={self[attribute]}")
continue continue
if isinstance(self[attribute], float): elif isinstance(self[attribute], float):
field_data.append(f"{attribute}={self[attribute]}") field_data.append(f"{attribute}={self[attribute]}")
continue continue
if attribute == "fault_light" and not self[attribute]: elif attribute == "errors":
field_data.append(f"{attribute}=false")
continue
if attribute == "errors":
for idx, item in enumerate(self[attribute]): for idx, item in enumerate(self[attribute]):
field_data.append(f'error_{idx+1}="{item.error_message}"') field_data.append(f'error_{idx+1}="{item.error_message}"')
elif attribute == "hashboards":
for idx, item in enumerate(self[attribute]):
field_data.append(f"hashboard_{idx+1}_hashrate={item.get('hashrate', 0.0)}")
field_data.append(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]):
if item.speed is not None:
field_data.append(f"fan_{idx+1}={item.speed}")
tags_str = ",".join(tag_data) tags_str = ",".join(tag_data)
field_str = ",".join(field_data) field_str = ",".join(field_data)

View File

@@ -149,10 +149,10 @@ class _MinerPhaseBalancer:
not self.miners[data_point.ip]["shutdown"] not self.miners[data_point.ip]["shutdown"]
): ):
# cant do anything with it so need to find a semi-accurate power limit # cant do anything with it so need to find a semi-accurate power limit
if not data_point.wattage_limit == -1: if not data_point.wattage_limit == None:
self.miners[data_point.ip]["max"] = int(data_point.wattage_limit) self.miners[data_point.ip]["max"] = int(data_point.wattage_limit)
self.miners[data_point.ip]["min"] = int(data_point.wattage_limit) self.miners[data_point.ip]["min"] = int(data_point.wattage_limit)
elif not data_point.wattage == -1: elif not data_point.wattage == None:
self.miners[data_point.ip]["max"] = int(data_point.wattage) self.miners[data_point.ip]["max"] = int(data_point.wattage)
self.miners[data_point.ip]["min"] = int(data_point.wattage) self.miners[data_point.ip]["min"] = int(data_point.wattage)

View File

@@ -18,4 +18,5 @@ from .bmminer import *
from .bosminer import * from .bosminer import *
from .cgminer import * from .cgminer import *
from .hiveon import * from .hiveon import *
from .luxos import *
from .vnish import * from .vnish import *

View File

@@ -58,7 +58,7 @@ class HiveonT9(Hiveon, T9):
hashrate = 0 hashrate = 0
chips = 0 chips = 0
for chipset in board_map[board]: for chipset in board_map[board]:
if hashboard.chip_temp == -1: if hashboard.chip_temp == None:
try: try:
hashboard.board_temp = api_stats["STATS"][1][f"temp{chipset}"] hashboard.board_temp = api_stats["STATS"][1][f"temp{chipset}"]
hashboard.chip_temp = api_stats["STATS"][1][f"temp2_{chipset}"] hashboard.chip_temp = api_stats["STATS"][1][f"temp2_{chipset}"]

View 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

View 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

View 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 *

View File

@@ -0,0 +1,25 @@
# ------------------------------------------------------------------------------
# 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 VNish
from pyasic.miners.types import S17Plus, S17Pro
class VNishS17Plus(VNish, S17Plus):
pass
class VNishS17Pro(VNish, S17Pro):
pass

View File

@@ -0,0 +1,16 @@
# ------------------------------------------------------------------------------
# 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 .S17 import VNishS17Plus, VNishS17Pro

View File

@@ -15,13 +15,26 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import VNish 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): class VNishS19(VNish, S19):
pass pass
class VNishS19NoPIC(VNish, S19NoPIC):
pass
class VNishS19Pro(VNish, S19Pro): class VNishS19Pro(VNish, S19Pro):
pass pass

View File

@@ -20,6 +20,7 @@ from .S19 import (
VNishS19aPro, VNishS19aPro,
VNishS19j, VNishS19j,
VNishS19jPro, VNishS19jPro,
VNishS19NoPIC,
VNishS19Pro, VNishS19Pro,
VNishS19XP, VNishS19XP,
) )

View File

@@ -15,4 +15,5 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from .X3 import * from .X3 import *
from .X17 import *
from .X19 import * from .X19 import *

View File

@@ -22,5 +22,6 @@ from .btminer import BTMiner
from .cgminer import CGMiner from .cgminer import CGMiner
from .cgminer_avalon import CGMinerAvalon from .cgminer_avalon import CGMinerAvalon
from .hiveon import Hiveon from .hiveon import Hiveon
from .luxminer import LUXMiner
from .vnish import VNish from .vnish import VNish
from .whatsminer import M2X, M3X, M5X from .whatsminer import M2X, M3X, M5X

View File

@@ -45,6 +45,14 @@ ANTMINER_MODERN_DATA_LOC = {
"errors": {"cmd": "get_errors", "kwargs": {}}, "errors": {"cmd": "get_errors", "kwargs": {}},
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, "fault_light": {"cmd": "get_fault_light", "kwargs": {}},
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, "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"}},
},
} }
@@ -227,6 +235,32 @@ class AntminerModern(BMMiner):
protocol=protocol, 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:
return False if int(web_get_conf["bitmain-work-mode"]) == 1 else True
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 = { ANTMINER_OLD_DATA_LOC = {
"mac": {"cmd": "get_mac", "kwargs": {}}, "mac": {"cmd": "get_mac", "kwargs": {}},
@@ -257,6 +291,14 @@ ANTMINER_OLD_DATA_LOC = {
"kwargs": {"web_get_blink_status": {"web": "get_blink_status"}}, "kwargs": {"web_get_blink_status": {"web": "get_blink_status"}},
}, },
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, "pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
"is_mining": {
"cmd": "is_mining",
"kwargs": {"web_get_conf": {"web": "get_miner_conf"}},
},
"uptime": {
"cmd": "get_uptime",
"kwargs": {"api_stats": {"api": "stats"}},
},
} }
@@ -353,7 +395,7 @@ class AntminerOld(CGMiner):
except APIError: except APIError:
pass pass
fans_data = [Fan(), Fan(), Fan(), Fan()] fans_data = [Fan() for _ in range(self.fan_count)]
if api_stats: if api_stats:
try: try:
fan_offset = -1 fan_offset = -1
@@ -367,8 +409,8 @@ class AntminerOld(CGMiner):
fan_offset = 3 fan_offset = 3
for fan in range(self.fan_count): for fan in range(self.fan_count):
fans_data[fan] = Fan( fans_data[fan].speed = api_stats["STATS"][1].get(
api_stats["STATS"][1].get(f"fan{fan_offset+fan}") f"fan{fan_offset+fan}", 0
) )
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
@@ -425,3 +467,41 @@ class AntminerOld(CGMiner):
pass pass
return hashboards 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

View File

@@ -44,6 +44,8 @@ BFGMINER_DATA_LOC = {
"errors": {"cmd": "get_errors", "kwargs": {}}, "errors": {"cmd": "get_errors", "kwargs": {}},
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, "fault_light": {"cmd": "get_fault_light", "kwargs": {}},
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, "pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
"is_mining": {"cmd": "is_mining", "kwargs": {}},
"uptime": {"cmd": "get_uptime", "kwargs": {}},
} }
@@ -247,14 +249,16 @@ class BFGMiner(BaseMiner):
for fan_num in range(0, 8, 4): for fan_num in range(0, 8, 4):
for _f_num in range(4): for _f_num in range(4):
f = api_stats["STATS"][1].get(f"fan{fan_num + _f_num}") f = api_stats["STATS"][1].get(f"fan{fan_num + _f_num}", 0)
if f and not f == 0 and fan_offset == -1: if not f == 0 and fan_offset == -1:
fan_offset = fan_num fan_offset = fan_num
if fan_offset == -1: if fan_offset == -1:
fan_offset = 1 fan_offset = 1
for fan in range(self.fan_count): for fan in range(self.fan_count):
fans_data[fan] = api_stats["STATS"][1].get(f"fan{fan_offset+fan}") fans_data[fan] = api_stats["STATS"][1].get(
f"fan{fan_offset+fan}", 0
)
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
fans = [Fan(speed=d) if d else Fan() for d in fans_data] fans = [Fan(speed=d) if d else Fan() for d in fans_data]
@@ -316,3 +320,9 @@ class BFGMiner(BaseMiner):
return round(ideal_rate, 2) return round(ideal_rate, 2)
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
return None
async def get_uptime(self, *args, **kwargs) -> Optional[int]:
return None

View File

@@ -13,11 +13,12 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from typing import List from typing import List, Optional
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import HashBoard from pyasic.data import HashBoard
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.logger import logger
from pyasic.miners.backends import BFGMiner from pyasic.miners.backends import BFGMiner
from pyasic.web.goldshell import GoldshellWebAPI from pyasic.web.goldshell import GoldshellWebAPI
@@ -47,6 +48,8 @@ GOLDSHELL_DATA_LOC = {
"errors": {"cmd": "get_errors", "kwargs": {}}, "errors": {"cmd": "get_errors", "kwargs": {}},
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, "fault_light": {"cmd": "get_fault_light", "kwargs": {}},
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, "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: except KeyError:
pass pass
else: else:
print(self, api_devs) logger.error(self, api_devs)
if not api_devdetails: if not api_devdetails:
try: try:
@@ -154,6 +157,12 @@ class BFGMinerGoldshell(BFGMiner):
except KeyError: except KeyError:
pass pass
else: else:
print(self, api_devdetails) logger.error(self, api_devdetails)
return hashboards return hashboards
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
return None
async def get_uptime(self, *args, **kwargs) -> Optional[int]:
return None

View File

@@ -45,6 +45,11 @@ BMMINER_DATA_LOC = {
"errors": {"cmd": "get_errors", "kwargs": {}}, "errors": {"cmd": "get_errors", "kwargs": {}},
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, "fault_light": {"cmd": "get_fault_light", "kwargs": {}},
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, "pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
"is_mining": {"cmd": "is_mining", "kwargs": {}},
"uptime": {
"cmd": "get_uptime",
"kwargs": {"api_stats": {"api": "stats"}},
},
} }
@@ -275,24 +280,25 @@ class BMMiner(BaseMiner):
except APIError: except APIError:
pass pass
fans_data = [None, None, None, None] fans = [Fan() for _ in range(self.fan_count)]
if api_stats: if api_stats:
try: try:
fan_offset = -1 fan_offset = -1
for fan_num in range(1, 8, 4): for fan_num in range(1, 8, 4):
for _f_num in range(4): for _f_num in range(4):
f = api_stats["STATS"][1].get(f"fan{fan_num + _f_num}") f = api_stats["STATS"][1].get(f"fan{fan_num + _f_num}", 0)
if f and not f == 0 and fan_offset == -1: if f and not f == 0 and fan_offset == -1:
fan_offset = fan_num fan_offset = fan_num
if fan_offset == -1: if fan_offset == -1:
fan_offset = 1 fan_offset = 1
for fan in range(self.fan_count): for fan in range(self.fan_count):
fans_data[fan] = api_stats["STATS"][1].get(f"fan{fan_offset+fan}") fans[fan].speed = api_stats["STATS"][1].get(
f"fan{fan_offset+fan}", 0
)
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
fans = [Fan(speed=d) if d else Fan() for d in fans_data]
return fans return fans
@@ -351,3 +357,19 @@ class BMMiner(BaseMiner):
return round(ideal_rate, 2) return round(ideal_rate, 2)
except (KeyError, IndexError): except (KeyError, IndexError):
pass 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

View File

@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import asyncio
import logging import logging
from collections import namedtuple from collections import namedtuple
from typing import List, Optional, Tuple, Union from typing import List, Optional, Tuple, Union
@@ -29,7 +29,12 @@ from pyasic.miners.base import BaseMiner
from pyasic.web.bosminer import BOSMinerWebAPI from pyasic.web.bosminer import BOSMinerWebAPI
BOSMINER_DATA_LOC = { 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": {}}, "model": {"cmd": "get_model", "kwargs": {}},
"api_ver": { "api_ver": {
"cmd": "get_api_ver", "cmd": "get_api_ver",
@@ -155,7 +160,7 @@ BOSMINER_DATA_LOC = {
"config": { "config": {
"... on BosminerConfig": { "... on BosminerConfig": {
"groups": { "groups": {
"pools": {"urluser": None}, "pools": {"url": None, "user": None},
"strategy": { "strategy": {
"... on QuotaStrategy": {"quota": None} "... on QuotaStrategy": {"quota": None}
}, },
@@ -167,6 +172,14 @@ BOSMINER_DATA_LOC = {
}, },
}, },
}, },
"is_mining": {
"cmd": "is_mining",
"kwargs": {"api_devdetails": {"api": "devdetails"}},
},
"uptime": {
"cmd": "get_uptime",
"kwargs": {"api_summary": {"api": "summary"}},
},
} }
@@ -192,8 +205,8 @@ class BOSMiner(BaseMiner):
result = None result = None
try: try:
conn = await self._get_ssh_connection() conn = await asyncio.wait_for(self._get_ssh_connection(), timeout=10)
except ConnectionError: except (ConnectionError, asyncio.TimeoutError):
return None return None
# open an ssh connection # open an ssh connection
@@ -360,17 +373,85 @@ class BOSMiner(BaseMiner):
else: else:
return True return True
async def set_static_ip(
self,
ip: str,
dns: str,
gateway: str,
subnet_mask: str = "255.255.255.0",
):
cfg_data_lan = (
"config interface 'lan'\n\toption type 'bridge'\n\toption ifname 'eth0'\n\toption proto 'static'\n\toption ipaddr '"
+ ip
+ "'\n\toption netmask '"
+ subnet_mask
+ "'\n\toption gateway '"
+ gateway
+ "'\n\toption dns '"
+ dns
+ "'"
)
data = await self.send_ssh_command("cat /etc/config/network")
split_data = data.split("\n\n")
for idx in range(len(split_data)):
if "config interface 'lan'" in split_data[idx]:
split_data[idx] = cfg_data_lan
config = "\n\n".join(split_data)
conn = await self._get_ssh_connection()
async with conn:
await conn.run("echo '" + config + "' > /etc/config/network")
async def set_dhcp(self):
cfg_data_lan = "config interface 'lan'\n\toption type 'bridge'\n\toption ifname 'eth0'\n\toption proto 'dhcp'"
data = await self.send_ssh_command("cat /etc/config/network")
split_data = data.split("\n\n")
for idx in range(len(split_data)):
if "config interface 'lan'" in split_data[idx]:
split_data[idx] = cfg_data_lan
config = "\n\n".join(split_data)
conn = await self._get_ssh_connection()
async with conn:
await conn.run("echo '" + config + "' > /etc/config/network")
################################################## ##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
################################################## ##################################################
async def get_mac(self) -> Optional[str]: async def get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]:
result = await self.send_ssh_command("cat /sys/class/net/eth0/address") if not web_net_conf:
if result: try:
return result.upper().strip() 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]: async def get_model(self) -> Optional[str]:
return self.model + " (BOS)" if self.model is not None:
return self.model + " (BOS)"
return "? (BOS)"
async def get_version( async def get_version(
self, api_version: dict = None, graphql_version: dict = None self, api_version: dict = None, graphql_version: dict = None
@@ -412,8 +493,8 @@ class BOSMiner(BaseMiner):
if graphql_version: if graphql_version:
try: try:
fw_ver = graphql_version["bos"]["info"]["version"]["full"] fw_ver = graphql_version["data"]["bos"]["info"]["version"]["full"]
except KeyError: except (KeyError, TypeError):
pass pass
if not fw_ver: if not fw_ver:
@@ -440,9 +521,9 @@ class BOSMiner(BaseMiner):
if graphql_hostname: if graphql_hostname:
try: try:
hostname = graphql_hostname["bos"]["hostname"] hostname = graphql_hostname["data"]["bos"]["hostname"]
return hostname return hostname
except KeyError: except (TypeError, KeyError):
pass pass
try: try:
@@ -475,14 +556,14 @@ class BOSMiner(BaseMiner):
try: try:
return round( return round(
float( float(
graphql_hashrate["bosminer"]["info"]["workSolver"][ graphql_hashrate["data"]["bosminer"]["info"]["workSolver"][
"realHashrate" "realHashrate"
]["mhs1M"] ]["mhs1M"]
/ 1000000 / 1000000
), ),
2, 2,
) )
except (KeyError, IndexError, ValueError): except (LookupError, ValueError, TypeError):
pass pass
# get hr from API # get hr from API
@@ -533,14 +614,19 @@ class BOSMiner(BaseMiner):
if graphql_boards: if graphql_boards:
try: try:
boards = graphql_boards["bosminer"]["info"]["workSolver"][ boards = graphql_boards["data"]["bosminer"]["info"]["workSolver"][
"childSolvers" "childSolvers"
] ]
except (KeyError, IndexError): except (TypeError, LookupError):
boards = None boards = None
if boards: if boards:
offset = 6 if int(boards[0]["name"]) in [6, 7, 8] else 0 b_names = [int(b["name"]) for b in boards]
offset = 0
if 3 in b_names:
offset = 1
elif 6 in b_names:
offset = 6
for hb in boards: for hb in boards:
_id = int(hb["name"]) - offset _id = int(hb["name"]) - offset
board = hashboards[_id] board = hashboards[_id]
@@ -641,13 +727,12 @@ class BOSMiner(BaseMiner):
) )
except APIError: except APIError:
pass pass
if graphql_wattage is not None:
if graphql_wattage:
try: try:
return graphql_wattage["bosminer"]["info"]["workSolver"]["power"][ return graphql_wattage["data"]["bosminer"]["info"]["workSolver"][
"approxConsumptionW" "power"
] ]["approxConsumptionW"]
except KeyError: except (LookupError, TypeError):
pass pass
if not api_tunerstatus: if not api_tunerstatus:
@@ -677,10 +762,10 @@ class BOSMiner(BaseMiner):
if graphql_wattage_limit: if graphql_wattage_limit:
try: try:
return graphql_wattage_limit["bosminer"]["info"]["workSolver"]["power"][ return graphql_wattage_limit["data"]["bosminer"]["info"]["workSolver"][
"limitW" "power"
] ]["limitW"]
except KeyError: except (LookupError, TypeError):
pass pass
if not api_tunerstatus: if not api_tunerstatus:
@@ -705,17 +790,20 @@ class BOSMiner(BaseMiner):
) )
except APIError: except APIError:
pass pass
if graphql_fans: if graphql_fans:
fans = {"fan_1": Fan(), "fan_2": Fan(), "fan_3": Fan(), "fan_4": Fan()} fans = []
for n in range(self.fan_count): for n in range(self.fan_count):
try: try:
fans[f"fan_{n + 1}"].speed = graphql_fans["bosminer"]["info"][ fans.append(
"fans" Fan(
][n]["rpm"] speed=graphql_fans["data"]["bosminer"]["info"]["fans"][n][
except KeyError: "rpm"
]
)
)
except (LookupError, TypeError):
pass pass
return [fans["fan_1"], fans["fan_2"], fans["fan_3"], fans["fan_4"]] return fans
if not api_fans: if not api_fans:
try: try:
@@ -724,14 +812,14 @@ class BOSMiner(BaseMiner):
pass pass
if api_fans: if api_fans:
fans = {"fan_1": Fan(), "fan_2": Fan(), "fan_3": Fan(), "fan_4": Fan()} fans = []
for n in range(self.fan_count): for n in range(self.fan_count):
try: try:
fans[f"fan_{n + 1}"].speed = api_fans["FANS"][n]["RPM"] fans.append(Fan(api_fans["FANS"][n]["RPM"]))
except (IndexError, KeyError): except (IndexError, KeyError):
pass pass
return [fans["fan_1"], fans["fan_2"], fans["fan_3"], fans["fan_4"]] return fans
return [Fan(), Fan(), Fan(), Fan()] return [Fan() for _ in range(self.fan_count)]
async def get_fan_psu(self) -> Optional[int]: async def get_fan_psu(self) -> Optional[int]:
return None return None
@@ -761,7 +849,7 @@ class BOSMiner(BaseMiner):
if graphql_pools: if graphql_pools:
groups = [] groups = []
try: try:
g = graphql_pools["bosminer"]["config"]["groups"] g = graphql_pools["data"]["bosminer"]["config"]["groups"]
for group in g: for group in g:
pools = {"quota": group["strategy"]["quota"]} pools = {"quota": group["strategy"]["quota"]}
for i, pool in enumerate(group["pools"]): for i, pool in enumerate(group["pools"]):
@@ -773,7 +861,7 @@ class BOSMiner(BaseMiner):
pools[f"pool_{i + 1}_user"] = pool["user"] pools[f"pool_{i + 1}_user"] = pool["user"]
groups.append(pools) groups.append(pools)
return groups return groups
except KeyError: except (KeyError, TypeError):
pass pass
if not api_pools: if not api_pools:
@@ -850,10 +938,10 @@ class BOSMiner(BaseMiner):
if graphql_errors: if graphql_errors:
errors = [] errors = []
try: try:
boards = graphql_errors["bosminer"]["info"]["workSolver"][ boards = graphql_errors["data"]["bosminer"]["info"]["workSolver"][
"childSolvers" "childSolvers"
] ]
except (KeyError, IndexError): except (LookupError, TypeError):
boards = None boards = None
if boards: if boards:
@@ -944,19 +1032,22 @@ class BOSMiner(BaseMiner):
# get light through GraphQL # get light through GraphQL
if graphql_fault_light: if graphql_fault_light:
try: try:
self.light = graphql_fault_light["bos"]["faultLight"] self.light = graphql_fault_light["data"]["bos"]["faultLight"]
return self.light return self.light
except (TypeError, KeyError, ValueError, IndexError): except (TypeError, ValueError, LookupError):
pass pass
# get light via ssh if that fails (10x slower) # get light via ssh if that fails (10x slower)
data = ( try:
await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off") data = (
).strip() await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off")
self.light = False ).strip()
if data == "50": self.light = False
self.light = True if data == "50":
return self.light self.light = True
return self.light
except TypeError:
return self.light
async def get_nominal_hashrate(self, api_devs: dict = None) -> Optional[float]: async def get_nominal_hashrate(self, api_devs: dict = None) -> Optional[float]:
if not api_devs: if not api_devs:
@@ -983,3 +1074,29 @@ class BOSMiner(BaseMiner):
) )
except (IndexError, KeyError): except (IndexError, KeyError):
pass 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

View File

@@ -150,3 +150,9 @@ class BOSMinerOld(BOSMiner):
async def get_data(self, allow_warning: bool = False, **kwargs) -> MinerData: async def get_data(self, allow_warning: bool = False, **kwargs) -> MinerData:
return MinerData(ip=str(self.ip)) 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

View File

@@ -88,6 +88,11 @@ BTMINER_DATA_LOC = {
"kwargs": {"api_get_miner_info": {"api": "get_miner_info"}}, "kwargs": {"api_get_miner_info": {"api": "get_miner_info"}},
}, },
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, "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"}},
},
} }
@@ -463,15 +468,13 @@ class BTMiner(BaseMiner):
except APIError: except APIError:
pass pass
fans = [Fan(), Fan(), Fan(), Fan()] fans = [Fan() for _ in range(self.fan_count)]
if api_summary: if api_summary:
try: try:
if self.fan_count > 0: if self.fan_count > 0:
fans = [ fans = [
Fan(api_summary["SUMMARY"][0]["Fan Speed In"]), Fan(api_summary["SUMMARY"][0].get("Fan Speed In", 0)),
Fan(api_summary["SUMMARY"][0]["Fan Speed Out"]), Fan(api_summary["SUMMARY"][0].get("Fan Speed Out", 0)),
Fan(),
Fan(),
] ]
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
@@ -618,3 +621,35 @@ class BTMiner(BaseMiner):
async def set_hostname(self, hostname: str): async def set_hostname(self, hostname: str):
await self.api.set_hostname(hostname) 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

View File

@@ -45,6 +45,11 @@ CGMINER_DATA_LOC = {
"errors": {"cmd": "get_errors", "kwargs": {}}, "errors": {"cmd": "get_errors", "kwargs": {}},
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, "fault_light": {"cmd": "get_fault_light", "kwargs": {}},
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, "pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
"is_mining": {"cmd": "is_mining", "kwargs": {}},
"uptime": {
"cmd": "get_uptime",
"kwargs": {"api_stats": {"api": "stats"}},
},
} }
@@ -296,7 +301,7 @@ class CGMiner(BaseMiner):
except APIError: except APIError:
pass pass
fans_data = [Fan(), Fan(), Fan(), Fan()] fans = [Fan() for _ in range(self.fan_count)]
if api_stats: if api_stats:
try: try:
fan_offset = -1 fan_offset = -1
@@ -310,12 +315,12 @@ class CGMiner(BaseMiner):
fan_offset = 1 fan_offset = 1
for fan in range(self.fan_count): for fan in range(self.fan_count):
fans_data[fan] = Fan( fans[fan].speed = api_stats["STATS"][1].get(
api_stats["STATS"][1].get(f"fan{fan_offset+fan}") f"fan{fan_offset+fan}", 0
) )
except (KeyError, IndexError): except (KeyError, IndexError):
pass pass
return fans_data return fans
async def get_fan_psu(self) -> Optional[int]: async def get_fan_psu(self) -> Optional[int]:
return None return None
@@ -375,3 +380,19 @@ class CGMiner(BaseMiner):
return round(ideal_rate, 2) return round(ideal_rate, 2)
except (KeyError, IndexError): except (KeyError, IndexError):
pass 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

View File

@@ -29,21 +29,29 @@ AVALON_DATA_LOC = {
"model": {"cmd": "get_model", "kwargs": {}}, "model": {"cmd": "get_model", "kwargs": {}},
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}}, "api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}},
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}}, "fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
"hostname": {"cmd": "get_hostname", "kwargs": {"mac": {"web": "mac"}}}, "hostname": {"cmd": "get_hostname", "kwargs": {"mac": {"api": "version"}}},
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}}, "hashrate": {"cmd": "get_hashrate", "kwargs": {"api_devs": {"api": "devs"}}},
"nominal_hashrate": { "nominal_hashrate": {
"cmd": "get_nominal_hashrate", "cmd": "get_nominal_hashrate",
"kwargs": {"api_stats": {"api": "stats"}}, "kwargs": {"api_stats": {"api": "stats"}},
}, },
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}}, "hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
"env_temp": {"cmd": "get_env_temp", "kwargs": {}}, "env_temp": {"cmd": "get_env_temp", "kwargs": {"api_stats": {"api": "stats"}}},
"wattage": {"cmd": "get_wattage", "kwargs": {}}, "wattage": {"cmd": "get_wattage", "kwargs": {}},
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}}, "wattage_limit": {
"cmd": "get_wattage_limit",
"kwargs": {"api_stats": {"api": "stats"}},
},
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}}, "fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}},
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}}, "fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
"errors": {"cmd": "get_errors", "kwargs": {}}, "errors": {"cmd": "get_errors", "kwargs": {}},
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, "fault_light": {
"cmd": "get_fault_light",
"kwargs": {"api_stats": {"api": "stats"}},
},
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, "pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
"is_mining": {"cmd": "is_mining", "kwargs": {}},
"uptime": {"cmd": "get_uptime", "kwargs": {}},
} }
@@ -115,8 +123,17 @@ class CGMinerAvalon(CGMiner):
data = item.replace("]", "").split("[") data = item.replace("]", "").split("[")
data_list = [i.split(": ") for i in data[1].strip().split(", ")] data_list = [i.split(": ") for i in data[1].strip().split(", ")]
data_dict = {} data_dict = {}
for key, val in [tuple(item) for item in data_list]: try:
data_dict[key] = val for key, val in [tuple(item) for item in data_list]:
data_dict[key] = val
except ValueError:
# --avalon args
for arg_item in data_list:
item_data = arg_item[0].split(" ")
for idx in range(len(item_data)):
if idx % 2 == 0 or idx == 0:
data_dict[item_data[idx]] = item_data[idx + 1]
raw_data = [data[0].strip(), data_dict] raw_data = [data[0].strip(), data_dict]
else: else:
raw_data = [ raw_data = [
@@ -168,16 +185,16 @@ class CGMinerAvalon(CGMiner):
if mac: if mac:
return f"Avalon{mac.replace(':', '')[-6:]}" return f"Avalon{mac.replace(':', '')[-6:]}"
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]: async def get_hashrate(self, api_devs: dict = None) -> Optional[float]:
if not api_summary: if not api_devs:
try: try:
api_summary = await self.api.summary() api_devs = await self.api.devs()
except APIError: except APIError:
pass pass
if api_summary: if api_devs:
try: try:
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) return round(float(api_devs["DEVS"][0]["MHS 1m"] / 1000000), 2)
except (KeyError, IndexError, ValueError, TypeError): except (KeyError, IndexError, ValueError, TypeError):
pass pass
@@ -195,38 +212,87 @@ class CGMinerAvalon(CGMiner):
if api_stats: if api_stats:
try: try:
stats_data = api_stats[0].get("STATS") unparsed_stats = api_stats["STATS"][0]["MM ID0"]
if stats_data: parsed_stats = self.parse_stats(unparsed_stats)
for key in stats_data[0].keys():
if key.startswith("MM ID"):
raw_data = self.parse_stats(stats_data[0][key])
for board in range(self.ideal_hashboards):
chip_temp = raw_data.get("MTmax")
if chip_temp:
hashboards[board].chip_temp = chip_temp[board]
temp = raw_data.get("MTavg")
if temp:
hashboards[board].temp = temp[board]
chips = raw_data.get(f"PVT_T{board}")
if chips:
hashboards[board].chips = len(
[item for item in chips if not item == "0"]
)
except (IndexError, KeyError, ValueError, TypeError): except (IndexError, KeyError, ValueError, TypeError):
pass return hashboards
for board in range(self.ideal_hashboards):
try:
hashboards[board].chip_temp = int(parsed_stats["MTmax"][board])
except LookupError:
pass
try:
board_hr = parsed_stats["MGHS"][board]
hashboards[board].hashrate = round(float(board_hr) / 1000, 2)
except LookupError:
pass
try:
hashboards[board].temp = int(parsed_stats["MTavg"][board])
except LookupError:
pass
try:
chip_data = parsed_stats[f"PVT_T{board}"]
hashboards[board].missing = False
if chip_data:
hashboards[board].chips = len(
[item for item in chip_data if not item == "0"]
)
except LookupError:
pass
return hashboards return hashboards
async def get_env_temp(self) -> Optional[float]: async def get_nominal_hashrate(self, api_stats: dict = None) -> Optional[float]:
return None if not api_stats:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
return round(float(parsed_stats["GHSmm"]) / 1000, 2)
except (IndexError, KeyError, ValueError, TypeError):
pass
async def get_env_temp(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:
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
return float(parsed_stats["Temp"])
except (IndexError, KeyError, ValueError, TypeError):
pass
async def get_wattage(self) -> Optional[int]: async def get_wattage(self) -> Optional[int]:
return None return None
async def get_wattage_limit(self) -> Optional[int]: async def get_wattage_limit(self, api_stats: dict = None) -> Optional[int]:
return None if not api_stats:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
return int(parsed_stats["MPO"])
except (IndexError, KeyError, ValueError, TypeError):
pass
async def get_fans(self, api_stats: dict = None) -> List[Fan]: async def get_fans(self, api_stats: dict = None) -> List[Fan]:
if not api_stats: if not api_stats:
@@ -235,19 +301,19 @@ class CGMinerAvalon(CGMiner):
except APIError: except APIError:
pass pass
fans_data = [Fan(), Fan(), Fan(), Fan()] fans_data = [Fan() for _ in range(self.fan_count)]
if api_stats: if api_stats:
try: try:
stats_data = api_stats[0].get("STATS") unparsed_stats = api_stats["STATS"][0]["MM ID0"]
if stats_data: parsed_stats = self.parse_stats(unparsed_stats)
for key in stats_data[0].keys(): except LookupError:
if key.startswith("MM ID"): return fans_data
raw_data = self.parse_stats(stats_data[0][key])
for fan in range(self.fan_count):
fans_data[fan] = Fan(int(raw_data[f"Fan{fan + 1}"]))
except (KeyError, IndexError, ValueError, TypeError):
pass
for fan in range(self.fan_count):
try:
fans_data[fan].speed = int(parsed_stats[f"Fan{fan + 1}"])
except (IndexError, KeyError, ValueError, TypeError):
pass
return fans_data return fans_data
async def get_pools(self, api_pools: dict = None) -> List[dict]: async def get_pools(self, api_pools: dict = None) -> List[dict]:
@@ -279,13 +345,34 @@ class CGMinerAvalon(CGMiner):
async def get_errors(self) -> List[MinerErrorData]: async def get_errors(self) -> List[MinerErrorData]:
return [] return []
async def get_fault_light(self) -> bool: async def get_fault_light(self, api_stats: dict = None) -> bool: # noqa
if self.light: if self.light:
return self.light return self.light
if not api_stats:
try:
api_stats = await self.api.stats()
except APIError:
pass
if api_stats:
try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats)
led = int(parsed_stats["Led"])
return True if led == 1 else False
except (IndexError, KeyError, ValueError, TypeError):
pass
try: try:
data = await self.api.ascset(0, "led", "1-255") data = await self.api.ascset(0, "led", "1-255")
except APIError: except APIError:
return False return False
if data["STATUS"][0]["Msg"] == "ASC 0 set info: LED[1]": try:
return True if data["STATUS"][0]["Msg"] == "ASC 0 set info: LED[1]":
return True
except LookupError:
pass
return False return False
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
return None

View File

@@ -26,4 +26,6 @@ class Hiveon(BMMiner):
self.api_type = "Hiveon" self.api_type = "Hiveon"
async def get_model(self) -> Optional[str]: async def get_model(self) -> Optional[str]:
return self.model + " (Hiveon)" if self.model is not None:
return self.model + " (Hiveon)"
return "? (Hiveon)"

View 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

View File

@@ -17,6 +17,7 @@
from typing import Optional from typing import Optional
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.logger import logger
from pyasic.miners.backends.bmminer import BMMiner from pyasic.miners.backends.bmminer import BMMiner
from pyasic.web.vnish import VNishWebAPI from pyasic.web.vnish import VNishWebAPI
@@ -43,6 +44,8 @@ VNISH_DATA_LOC = {
"errors": {"cmd": "get_errors", "kwargs": {}}, "errors": {"cmd": "get_errors", "kwargs": {}},
"fault_light": {"cmd": "get_fault_light", "kwargs": {}}, "fault_light": {"cmd": "get_fault_light", "kwargs": {}},
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}}, "pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
"is_mining": {"cmd": "is_mining", "kwargs": {}},
"uptime": {"cmd": "get_uptime", "kwargs": {}},
} }
@@ -58,7 +61,9 @@ class VNish(BMMiner):
self.data_locations = VNISH_DATA_LOC self.data_locations = VNISH_DATA_LOC
async def get_model(self) -> Optional[str]: async def get_model(self) -> Optional[str]:
return self.model + " (VNISH)" if self.model is not None:
return self.model + " (VNish)"
return "? (VNish)"
async def restart_backend(self) -> bool: async def restart_backend(self) -> bool:
data = await self.web.restart_vnish() data = await self.web.restart_vnish()
@@ -140,7 +145,7 @@ class VNish(BMMiner):
float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2 float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
) )
except (IndexError, KeyError, ValueError, TypeError) as e: except (IndexError, KeyError, ValueError, TypeError) as e:
print(e) logger.error(e)
pass pass
async def get_wattage_limit(self, web_settings: dict = None) -> Optional[int]: async def get_wattage_limit(self, web_settings: dict = None) -> Optional[int]:
@@ -167,3 +172,9 @@ class VNish(BMMiner):
return fw_ver return fw_ver
except KeyError: except KeyError:
pass pass
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
return None
async def get_uptime(self, *args, **kwargs) -> Optional[int]:
return None

View File

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

View File

@@ -254,7 +254,7 @@ class CGMinerA10X(CGMiner, A10X):
else: else:
web_get_all = web_get_all["all"] web_get_all = web_get_all["all"]
fan_data = [Fan(), Fan(), Fan(), Fan()] fans = [Fan() for _ in range(self.fan_count)]
if web_get_all: if web_get_all:
try: try:
spd = web_get_all["fansSpeed"] spd = web_get_all["fansSpeed"]
@@ -263,9 +263,9 @@ class CGMinerA10X(CGMiner, A10X):
else: else:
round((int(spd) * 6000) / 100) round((int(spd) * 6000) / 100)
for i in range(self.fan_count): for i in range(self.fan_count):
fan_data[i] = Fan(spd) fans[i].speed = spd
return fan_data return fans
async def get_pools(self, api_pools: dict = None) -> List[dict]: async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = [] groups = []

View File

@@ -236,7 +236,7 @@ class CGMinerT3HPlus(CGMiner, T3HPlus):
else: else:
web_get_all = web_get_all["all"] web_get_all = web_get_all["all"]
fan_data = [Fan(), Fan(), Fan(), Fan()] fans = [Fan() for _ in range(self.fan_count)]
if web_get_all: if web_get_all:
try: try:
spd = web_get_all["fansSpeed"] spd = web_get_all["fansSpeed"]
@@ -245,9 +245,9 @@ class CGMinerT3HPlus(CGMiner, T3HPlus):
else: else:
round((int(spd) * 6000) / 100) round((int(spd) * 6000) / 100)
for i in range(self.fan_count): for i in range(self.fan_count):
fan_data[i] = Fan(spd) fans[i].speed = spd
return fan_data return fans
async def get_pools(self, api_pools: dict = None) -> List[dict]: async def get_pools(self, api_pools: dict = None) -> List[dict]:
groups = [] groups = []

View File

@@ -35,6 +35,7 @@ from pyasic.miners.backends import (
CGMiner, CGMiner,
CGMinerAvalon, CGMinerAvalon,
Hiveon, Hiveon,
LUXMiner,
VNish, VNish,
) )
from pyasic.miners.base import AnyMiner from pyasic.miners.base import AnyMiner
@@ -56,15 +57,16 @@ class MinerTypes(enum.Enum):
BRAIINS_OS = 5 BRAIINS_OS = 5
VNISH = 6 VNISH = 6
HIVEON = 7 HIVEON = 7
LUX_OS = 8
MINER_CLASSES = { MINER_CLASSES = {
MinerTypes.ANTMINER: { MinerTypes.ANTMINER: {
None: BMMiner, None: BMMiner,
"ANTMINER DR5": CGMinerDR5,
"ANTMINER D3": CGMinerD3, "ANTMINER D3": CGMinerD3,
"ANTMINER HS3": BMMinerHS3, "ANTMINER HS3": BMMinerHS3,
"ANTMINER L3+": BMMinerL3Plus, "ANTMINER L3+": BMMinerL3Plus,
"ANTMINER DR5": CGMinerDR5,
"ANTMINER L7": BMMinerL7, "ANTMINER L7": BMMinerL7,
"ANTMINER E9 PRO": BMMinerE9Pro, "ANTMINER E9 PRO": BMMinerE9Pro,
"ANTMINER S9": BMMinerS9, "ANTMINER S9": BMMinerS9,
@@ -263,6 +265,7 @@ MINER_CLASSES = {
"M50S+VH30": BTMinerM50SPlusVH30, "M50S+VH30": BTMinerM50SPlusVH30,
"M50S+VH40": BTMinerM50SPlusVH40, "M50S+VH40": BTMinerM50SPlusVH40,
"M50S+VJ30": BTMinerM50SPlusVJ30, "M50S+VJ30": BTMinerM50SPlusVJ30,
"M50S+VK20": BTMinerM50SPlusVK20,
"M50S++VK10": BTMinerM50SPlusPlusVK10, "M50S++VK10": BTMinerM50SPlusPlusVK10,
"M50S++VK20": BTMinerM50SPlusPlusVK20, "M50S++VK20": BTMinerM50SPlusPlusVK20,
"M50S++VK30": BTMinerM50SPlusPlusVK30, "M50S++VK30": BTMinerM50SPlusPlusVK30,
@@ -321,7 +324,10 @@ MINER_CLASSES = {
MinerTypes.VNISH: { MinerTypes.VNISH: {
None: VNish, None: VNish,
"ANTMINER L3+": VnishL3Plus, "ANTMINER L3+": VnishL3Plus,
"ANTMINER S17+": VNishS17Plus,
"ANTMINER S17 PRO": VNishS17Pro,
"ANTMINER S19": VNishS19, "ANTMINER S19": VNishS19,
"ANTMINER S19NOPIC": VNishS19NoPIC,
"ANTMINER S19 PRO": VNishS19Pro, "ANTMINER S19 PRO": VNishS19Pro,
"ANTMINER S19J": VNishS19j, "ANTMINER S19J": VNishS19j,
"ANTMINER S19J PRO": VNishS19jPro, "ANTMINER S19J PRO": VNishS19jPro,
@@ -333,6 +339,10 @@ MINER_CLASSES = {
None: Hiveon, None: Hiveon,
"ANTMINER T9": HiveonT9, "ANTMINER T9": HiveonT9,
}, },
MinerTypes.LUX_OS: {
None: LUXMiner,
"ANTMINER S9": LUXMinerS9,
},
} }
@@ -392,7 +402,9 @@ class MinerFactory:
ip = str(ip) ip = str(ip)
if ip in self.cache: if ip in self.cache:
return self.cache[ip] return self.cache[ip]
miner_type = None miner_type = None
for _ in range(RETRIES): for _ in range(RETRIES):
task = asyncio.create_task(self._get_miner_type(ip)) task = asyncio.create_task(self._get_miner_type(ip))
try: try:
@@ -405,23 +417,19 @@ class MinerFactory:
if miner_type is not None: if miner_type is not None:
miner_model = None miner_model = None
fn = None miner_model_fns = {
if miner_type == MinerTypes.ANTMINER: MinerTypes.ANTMINER: self.get_miner_model_antminer,
fn = self.get_miner_model_antminer MinerTypes.WHATSMINER: self.get_miner_model_whatsminer,
if miner_type == MinerTypes.WHATSMINER: MinerTypes.AVALONMINER: self.get_miner_model_avalonminer,
fn = self.get_miner_model_whatsminer MinerTypes.INNOSILICON: self.get_miner_model_innosilicon,
if miner_type == MinerTypes.AVALONMINER: MinerTypes.GOLDSHELL: self.get_miner_model_goldshell,
fn = self.get_miner_model_avalonminer MinerTypes.BRAIINS_OS: self.get_miner_model_braiins_os,
if miner_type == MinerTypes.INNOSILICON: MinerTypes.VNISH: self.get_miner_model_vnish,
fn = self.get_miner_model_innosilicon MinerTypes.HIVEON: self.get_miner_model_hiveon,
if miner_type == MinerTypes.GOLDSHELL: MinerTypes.LUX_OS: self.get_miner_model_luxos,
fn = self.get_miner_model_goldshell }
if miner_type == MinerTypes.BRAIINS_OS: fn = miner_model_fns.get(miner_type)
fn = self.get_miner_model_braiins_os
if miner_type == MinerTypes.VNISH:
fn = self.get_miner_model_vnish
if miner_type == MinerTypes.HIVEON:
fn = self.get_miner_model_hiveon
if fn is not None: if fn is not None:
task = asyncio.create_task(fn(ip)) task = asyncio.create_task(fn(ip))
try: try:
@@ -432,6 +440,7 @@ class MinerFactory:
miner = self._select_miner_from_classes( miner = self._select_miner_from_classes(
ip, miner_type=miner_type, miner_model=miner_model ip, miner_type=miner_type, miner_model=miner_model
) )
if miner is not None and not isinstance(miner, UnknownMiner): if miner is not None and not isinstance(miner, UnknownMiner):
self.cache[ip] = miner self.cache[ip] = miner
return miner return miner
@@ -452,7 +461,6 @@ class MinerFactory:
text, resp = await concurrent_get_first_result( text, resp = await concurrent_get_first_result(
tasks, lambda x: x[0] is not None tasks, lambda x: x[0] is not None
) )
if text is not None: if text is not None:
return self._parse_web_type(text, resp) return self._parse_web_type(text, resp)
@@ -461,9 +469,9 @@ class MinerFactory:
session: aiohttp.ClientSession, url: str session: aiohttp.ClientSession, url: str
) -> Tuple[Optional[str], Optional[aiohttp.ClientResponse]]: ) -> Tuple[Optional[str], Optional[aiohttp.ClientResponse]]:
try: try:
resp = await session.get(url) resp = await session.get(url, allow_redirects=False)
return await resp.text(), resp return await resp.text(), resp
except aiohttp.ClientError: except (aiohttp.ClientError, asyncio.TimeoutError):
pass pass
return None, None return None, None
@@ -489,14 +497,15 @@ class MinerFactory:
return MinerTypes.INNOSILICON return MinerTypes.INNOSILICON
async def _get_miner_socket(self, ip: str): 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] tasks = [asyncio.create_task(self._socket_ping(ip, cmd)) for cmd in commands]
data = await concurrent_get_first_result( data = await concurrent_get_first_result(
tasks, lambda x: x is not None and self._parse_socket_type(x) is not None tasks, lambda x: x is not None and self._parse_socket_type(x) is not None
) )
if data is not None: if data is not None:
return self._parse_socket_type(data) d = self._parse_socket_type(data)
return d
@staticmethod @staticmethod
async def _socket_ping(ip: str, cmd: str) -> Optional[str]: async def _socket_ping(ip: str, cmd: str) -> Optional[str]:
@@ -554,7 +563,9 @@ class MinerFactory:
return MinerTypes.VNISH return MinerTypes.VNISH
if "HIVEON" in upper_data: if "HIVEON" in upper_data:
return MinerTypes.HIVEON 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 return MinerTypes.ANTMINER
if "INTCHAINS_QOMO" in upper_data: if "INTCHAINS_QOMO" in upper_data:
return MinerTypes.GOLDSHELL return MinerTypes.GOLDSHELL
@@ -619,10 +630,15 @@ class MinerFactory:
return return
except (ConnectionError, OSError): except (ConnectionError, OSError):
return return
if data == b"Socket connect failed: Connection refused\n":
return
data = await self._fix_api_data(data) data = await self._fix_api_data(data)
data = json.loads(data) try:
data = json.loads(data)
except json.JSONDecodeError:
return {}
return data return data
@@ -679,6 +695,22 @@ class MinerFactory:
try: try:
miner_model = sock_json_data["VERSION"][0]["Type"] 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
sock_json_data = await self.send_api_command(ip, "stats")
try:
miner_model = sock_json_data["STATS"][0]["Type"]
if " (" in miner_model:
split_miner_model = miner_model.split(" (")
miner_model = split_miner_model[0]
return miner_model return miner_model
except (TypeError, LookupError): except (TypeError, LookupError):
pass pass
@@ -749,7 +781,9 @@ class MinerFactory:
async def get_miner_model_braiins_os(self, ip: str) -> Optional[str]: async def get_miner_model_braiins_os(self, ip: str) -> Optional[str]:
sock_json_data = await self.send_api_command(ip, "devdetails") sock_json_data = await self.send_api_command(ip, "devdetails")
try: try:
miner_model = sock_json_data["DEVDETAILS"][0]["Model"] miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace(
"Bitmain ", ""
)
return miner_model return miner_model
except (TypeError, LookupError): except (TypeError, LookupError):
@@ -758,7 +792,8 @@ class MinerFactory:
try: try:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
d = await session.post( d = await session.post(
url, json={"query": "{bosminer {info{modelName}}}"} f"http://{ip}/graphql",
json={"query": "{bosminer {info{modelName}}}"},
) )
if d.status == 200: if d.status == 200:
json_data = await d.json() json_data = await d.json()
@@ -770,11 +805,14 @@ class MinerFactory:
async def get_miner_model_vnish(self, ip: str) -> Optional[str]: async def get_miner_model_vnish(self, ip: str) -> Optional[str]:
sock_json_data = await self.send_api_command(ip, "stats") sock_json_data = await self.send_api_command(ip, "stats")
try: try:
miner_model = sock_json_data["STATS"][0]["Type"].upper() miner_model = sock_json_data["STATS"][0]["Type"]
if " (VNISH" in miner_model: if " (" in miner_model:
split_miner_model = miner_model.split(" (VNISH ") split_miner_model = miner_model.split(" (")
miner_model = split_miner_model[0] miner_model = split_miner_model[0]
if "(88)" in miner_model:
miner_model = miner_model.replace("(88)", "NOPIC")
return miner_model return miner_model
except (TypeError, LookupError): except (TypeError, LookupError):
pass pass
@@ -788,5 +826,17 @@ class MinerFactory:
except (TypeError, LookupError): except (TypeError, LookupError):
pass 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() miner_factory = MinerFactory()

View File

@@ -26,6 +26,15 @@ class S19(AntMiner): # noqa - ignore ABC method implementation
self.fan_count = 4 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 class S19Pro(AntMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"): def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver) super().__init__(ip, api_ver)

View File

@@ -23,6 +23,7 @@ from .S19 import (
S19j, S19j,
S19jNoPIC, S19jNoPIC,
S19jPro, S19jPro,
S19NoPIC,
S19Pro, S19Pro,
S19ProPlus, S19ProPlus,
) )

View File

@@ -22,8 +22,6 @@ class Avalon1166Pro(AvalonMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0"): def __init__(self, ip: str, api_ver: str = "0.0.0"):
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "Avalon 1166" self.model = "Avalon 1166 Pro"
warnings.warn( self.nominal_chips = 120
f"Unknown chip count for miner type {self.model}, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
)
self.fan_count = 4 self.fan_count = 4

View File

@@ -23,7 +23,5 @@ class Avalon1246(AvalonMiner): # noqa - ignore ABC method implementation
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "Avalon 1246" self.model = "Avalon 1246"
warnings.warn( self.nominal_chips = 120
f"Unknown chip count for miner type {self.model}, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
)
self.fan_count = 4 self.fan_count = 4

View File

@@ -20,3 +20,4 @@ class A10X(InnosiliconMiner): # noqa - ignore ABC method implementation
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
super().__init__(ip, api_ver) super().__init__(ip, api_ver)
self.ip = ip self.ip = ip
self.model = "A10X"

View File

@@ -53,3 +53,12 @@ class M50SPlusVJ30(WhatsMiner): # noqa - ignore ABC method implementation
"Unknown chip count for miner type M50S+ VJ30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." "Unknown chip count for miner type M50S+ VJ30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
) )
self.fan_count = 2 self.fan_count = 2
class M50SPlusVK20(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 = "M50S+ VK20"
self.nominal_chips = 117
self.fan_count = 2

View File

@@ -38,7 +38,7 @@ from .M50S import (
M50SVJ20, M50SVJ20,
M50SVJ30, M50SVJ30,
) )
from .M50S_Plus import M50SPlusVH30, M50SPlusVH40, M50SPlusVJ30 from .M50S_Plus import M50SPlusVH30, M50SPlusVH40, M50SPlusVJ30, M50SPlusVK20
from .M50S_Plus_Plus import M50SPlusPlusVK10, M50SPlusPlusVK20, M50SPlusPlusVK30 from .M50S_Plus_Plus import M50SPlusPlusVK10, M50SPlusPlusVK20, M50SPlusPlusVK30
from .M53 import M53VH30 from .M53 import M53VH30
from .M53S import M53SVH30 from .M53S import M53SVH30

View File

@@ -148,6 +148,12 @@ class UnknownMiner(BaseMiner):
async def get_nominal_hashrate(self) -> Optional[float]: async def get_nominal_hashrate(self) -> Optional[float]:
return None 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( async def get_data(
self, allow_warning: bool = False, data_to_get: list = None self, allow_warning: bool = False, data_to_get: list = None
) -> MinerData: ) -> MinerData:

View File

@@ -15,7 +15,7 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import M5X from pyasic.miners.backends import M5X
from pyasic.miners.types import M50SPlusVH30, M50SPlusVH40, M50SPlusVJ30 from pyasic.miners.types import M50SPlusVH30, M50SPlusVH40, M50SPlusVJ30, M50SPlusVK20
class BTMinerM50SPlusVH30(M5X, M50SPlusVH30): class BTMinerM50SPlusVH30(M5X, M50SPlusVH30):
@@ -28,3 +28,7 @@ class BTMinerM50SPlusVH40(M5X, M50SPlusVH40):
class BTMinerM50SPlusVJ30(M5X, M50SPlusVJ30): class BTMinerM50SPlusVJ30(M5X, M50SPlusVJ30):
pass pass
class BTMinerM50SPlusVK20(M5X, M50SPlusVK20):
pass

View File

@@ -38,7 +38,12 @@ from .M50S import (
BTMinerM50SVJ20, BTMinerM50SVJ20,
BTMinerM50SVJ30, BTMinerM50SVJ30,
) )
from .M50S_Plus import BTMinerM50SPlusVH30, BTMinerM50SPlusVH40, BTMinerM50SPlusVJ30 from .M50S_Plus import (
BTMinerM50SPlusVH30,
BTMinerM50SPlusVH40,
BTMinerM50SPlusVJ30,
BTMinerM50SPlusVK20,
)
from .M50S_Plus_Plus import ( from .M50S_Plus_Plus import (
BTMinerM50SPlusPlusVK10, BTMinerM50SPlusPlusVK10,
BTMinerM50SPlusPlusVK20, BTMinerM50SPlusPlusVK20,

View File

@@ -192,3 +192,12 @@ class AntminerOldWebAPI(BaseWebAPI):
async def set_miner_conf(self, conf: dict) -> dict: async def set_miner_conf(self, conf: dict) -> dict:
return await self.send_command("set_miner_conf", **conf) return await self.send_command("set_miner_conf", **conf)
async def stats(self) -> dict:
return await self.send_command("miner_stats")
async def summary(self) -> dict:
return await self.send_command("miner_summary")
async def pools(self) -> dict:
return await self.send_command("miner_pools")

View File

@@ -18,6 +18,7 @@ from typing import Union
import httpx import httpx
from pyasic import APIError
from pyasic.settings import PyasicSettings from pyasic.settings import PyasicSettings
from pyasic.web import BaseWebAPI from pyasic.web import BaseWebAPI
@@ -27,14 +28,24 @@ class BOSMinerWebAPI(BaseWebAPI):
super().__init__(ip) super().__init__(ip)
self.pwd = PyasicSettings().global_bosminer_password self.pwd = PyasicSettings().global_bosminer_password
async def send_command(
self,
command: Union[str, dict],
ignore_errors: bool = False,
allow_warning: bool = True,
**parameters: Union[str, int, bool],
) -> dict:
if isinstance(command, str):
return await self.send_luci_command(command)
else:
return await self.send_gql_command(command)
def parse_command(self, graphql_command: Union[dict, set]) -> str: def parse_command(self, graphql_command: Union[dict, set]) -> str:
if isinstance(graphql_command, dict): if isinstance(graphql_command, dict):
data = [] data = []
for key in graphql_command: for key in graphql_command:
if graphql_command[key]: if graphql_command[key] is not None:
parsed = self.parse_command(graphql_command[key]) parsed = self.parse_command(graphql_command[key])
if key.startswith("... on"):
parsed = parsed.replace(",", "")
data.append(key + parsed) data.append(key + parsed)
else: else:
data.append(key) data.append(key)
@@ -42,19 +53,18 @@ class BOSMinerWebAPI(BaseWebAPI):
data = graphql_command data = graphql_command
return "{" + ",".join(data) + "}" return "{" + ",".join(data) + "}"
async def send_command( async def send_gql_command(
self, self,
command: dict, command: dict,
ignore_errors: bool = False,
allow_warning: bool = True,
**parameters: Union[str, int, bool],
) -> dict: ) -> dict:
url = f"http://{self.ip}/graphql" url = f"http://{self.ip}/graphql"
query = self.parse_command(command) query = command
if command.get("query") is None:
query = {"query": self.parse_command(command)}
try: try:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
await self.auth(client) await self.auth(client)
data = await client.post(url, json={"query": query}) data = await client.post(url, json=query)
except httpx.HTTPError: except httpx.HTTPError:
pass pass
else: else:
@@ -65,8 +75,34 @@ class BOSMinerWebAPI(BaseWebAPI):
pass pass
async def multicommand( async def multicommand(
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True self, *commands: Union[dict, str], allow_warning: bool = True
) -> dict: ) -> dict:
luci_commands = []
gql_commands = []
for cmd in commands:
if isinstance(cmd, dict):
gql_commands.append(cmd)
if isinstance(cmd, str):
luci_commands.append(cmd)
luci_data = await self.luci_multicommand(*luci_commands)
gql_data = await self.gql_multicommand(*gql_commands)
if gql_data is None:
gql_data = {}
if luci_data is None:
luci_data = {}
data = dict(**luci_data, **gql_data)
return data
async def luci_multicommand(self, *commands: str) -> dict:
data = {}
for command in commands:
data[command] = await self.send_luci_command(command, ignore_errors=True)
return data
async def gql_multicommand(self, *commands: dict) -> dict:
def merge(*d: dict): def merge(*d: dict):
ret = {} ret = {}
for i in d: for i in d:
@@ -80,10 +116,20 @@ class BOSMinerWebAPI(BaseWebAPI):
command = merge(*commands) command = merge(*commands)
data = await self.send_command(command) data = await self.send_command(command)
if not data: if data is not None:
data = {} if data.get("data") is None:
data["multicommand"] = False try:
return data commands = list(commands)
# noinspection PyTypeChecker
commands.remove({"bos": {"faultLight": None}})
command = merge(*commands)
data = await self.send_gql_command(command)
except (LookupError, ValueError):
pass
if not data:
data = {}
data["multicommand"] = False
return data
async def auth(self, client: httpx.AsyncClient) -> None: async def auth(self, client: httpx.AsyncClient) -> None:
url = f"http://{self.ip}/graphql" url = f"http://{self.ip}/graphql"
@@ -97,3 +143,53 @@ class BOSMinerWebAPI(BaseWebAPI):
+ '"){__typename}}}' + '"){__typename}}}'
}, },
) )
async def send_luci_command(self, path: str, ignore_errors: bool = False) -> dict:
try:
async with httpx.AsyncClient() as client:
await self.luci_auth(client)
data = await client.get(
f"http://{self.ip}{path}", headers={"User-Agent": "BTC Tools v0.1"}
)
if data.status_code == 200:
return data.json()
if ignore_errors:
return {}
raise APIError(
f"Web command failed: path={path}, code={data.status_code}"
)
except (httpx.HTTPError, json.JSONDecodeError):
if ignore_errors:
return {}
raise APIError(f"Web command failed: path={path}")
async def luci_auth(self, session: httpx.AsyncClient):
login = {"luci_username": self.username, "luci_password": self.pwd}
url = f"http://{self.ip}/cgi-bin/luci"
headers = {
"User-Agent": "BTC Tools v0.1", # only seems to respond if this user-agent is set
"Content-Type": "application/x-www-form-urlencoded",
}
await session.post(url, headers=headers, data=login)
async def get_net_conf(self):
return await self.send_luci_command(
"/cgi-bin/luci/admin/network/iface_status/lan"
)
async def get_cfg_metadata(self):
return await self.send_luci_command("/cgi-bin/luci/admin/miner/cfg_metadata")
async def get_cfg_data(self):
return await self.send_luci_command("/cgi-bin/luci/admin/miner/cfg_data")
async def get_bos_info(self):
return await self.send_luci_command("/cgi-bin/luci/bos/info")
async def get_overview(self):
return await self.send_luci_command(
"/cgi-bin/luci/admin/status/overview?status=1"
) # needs status=1 or it fails
async def get_api_status(self):
return await self.send_luci_command("/cgi-bin/luci/admin/miner/api_status")

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "pyasic" name = "pyasic"
version = "0.33.23" version = "0.37.0"
description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH." description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH."
authors = ["UpstreamData <brett@upstreamdata.ca>"] authors = ["UpstreamData <brett@upstreamdata.ca>"]
repository = "https://github.com/UpstreamData/pyasic" repository = "https://github.com/UpstreamData/pyasic"

View File

@@ -23,157 +23,21 @@ from pyasic.miners.backends import CGMiner # noqa
from pyasic.miners.base import BaseMiner from pyasic.miners.base import BaseMiner
from pyasic.miners.miner_factory import MINER_CLASSES, MinerFactory from pyasic.miners.miner_factory import MINER_CLASSES, MinerFactory
# class MinersTest(unittest.TestCase):
# def test_miner_model_creation(self): class MinersTest(unittest.TestCase):
# warnings.filterwarnings("ignore") def test_miner_model_creation(self):
# for miner_model in MINER_CLASSES.keys(): warnings.filterwarnings("ignore")
# for miner_api in MINER_CLASSES[miner_model].keys(): for miner_model in MINER_CLASSES.keys():
# with self.subTest( for miner_api in MINER_CLASSES[miner_model].keys():
# msg=f"Creation of miner using model={miner_model}, api={miner_api}", with self.subTest(
# miner_model=miner_model, msg=f"Creation of miner using model={miner_model}, api={miner_api}",
# miner_api=miner_api, miner_model=miner_model,
# ): miner_api=miner_api,
# miner = MINER_CLASSES[miner_model][miner_api]("127.0.0.1") ):
# self.assertTrue( miner = MINER_CLASSES[miner_model][miner_api]("127.0.0.1")
# isinstance(miner, MINER_CLASSES[miner_model][miner_api]) self.assertTrue(
# ) isinstance(miner, MINER_CLASSES[miner_model][miner_api])
# )
# def test_miner_backend_backup_creation(self):
# warnings.filterwarnings("ignore")
#
# backends = [
# list(
# inspect.getmembers(
# sys.modules[f"pyasic.miners.backends"], inspect.isclass
# )
# )
# ]
# backends = [item for sublist in backends for item in sublist]
# for backend in backends:
# miner_class = backend[1]
# with self.subTest(miner_class=miner_class):
# miner = miner_class("127.0.0.1")
# self.assertTrue(isinstance(miner, miner_class))
#
# def test_miner_type_creation_failure(self):
# warnings.filterwarnings("ignore")
#
# backends = [
# list(
# inspect.getmembers(
# sys.modules[f"pyasic.miners.{algo}._types"], inspect.isclass
# )
# )
# for algo in ["btc", "zec", "ltc"]
# ]
# backends = [item for sublist in backends for item in sublist]
# for backend in backends:
# miner_class = backend[1]
# with self.subTest(miner_class=miner_class):
# with self.assertRaises(TypeError):
# miner_class("127.0.0.1")
# with self.assertRaises(TypeError):
# BaseMiner("127.0.0.1")
#
# def test_miner_comparisons(self):
# miner_1 = CGMiner("1.1.1.1")
# miner_2 = CGMiner("2.2.2.2")
# miner_3 = CGMiner("1.1.1.1")
# self.assertEqual(miner_1, miner_3)
# self.assertGreater(miner_2, miner_1)
# self.assertLess(miner_3, miner_2)
#
#
# class MinerFactoryTest(unittest.TestCase):
# def test_miner_factory_creation(self):
# warnings.filterwarnings("ignore")
#
# self.assertDictEqual(MinerFactory().miners, {})
# miner_factory = MinerFactory()
# self.assertIs(MinerFactory(), miner_factory)
#
# def test_get_miner_generator(self):
# async def _coro():
# gen = MinerFactory().get_miner_generator([])
# miners = []
# async for miner in gen:
# miners.append(miner)
# return miners
#
# _miners = asyncio.run(_coro())
# self.assertListEqual(_miners, [])
#
# def test_miner_selection(self):
# warnings.filterwarnings("ignore")
#
# for miner_model in MINER_CLASSES.keys():
# with self.subTest():
# miner = MinerFactory()._select_miner_from_classes(
# "127.0.0.1", miner_model, None, None
# )
# self.assertIsInstance(miner, BaseMiner)
# for api in ["BOSMiner+", "BOSMiner", "CGMiner", "BTMiner", "BMMiner"]:
# with self.subTest():
# miner = MinerFactory()._select_miner_from_classes(
# "127.0.0.1", None, api, None
# )
# self.assertIsInstance(miner, BaseMiner)
#
# with self.subTest():
# miner = MinerFactory()._select_miner_from_classes(
# "127.0.0.1", "ANTMINER S17+", "Fake API", None
# )
# self.assertIsInstance(miner, BaseMiner)
#
# with self.subTest():
# miner = MinerFactory()._select_miner_from_classes(
# "127.0.0.1", "M30S", "BTMiner", "G20"
# )
# self.assertIsInstance(miner, BaseMiner)
#
# def test_validate_command(self):
# bad_test_data_returns = [
# {},
# {
# "cmd": [
# {
# "STATUS": [
# {"STATUS": "E", "Msg": "Command failed for some reason."}
# ]
# }
# ]
# },
# {"STATUS": "E", "Msg": "Command failed for some reason."},
# {
# "STATUS": [{"STATUS": "E", "Msg": "Command failed for some reason."}],
# "id": 1,
# },
# ]
# for data in bad_test_data_returns:
# with self.subTest():
#
# async def _coro(miner_ret):
# _data = await MinerFactory()._validate_command(miner_ret)
# return _data
#
# ret = asyncio.run(_coro(data))
# self.assertFalse(ret[0])
#
# good_test_data_returns = [
# {
# "STATUS": [{"STATUS": "S", "Msg": "Yay! Command succeeded."}],
# "id": 1,
# },
# ]
# for data in good_test_data_returns:
# with self.subTest():
#
# async def _coro(miner_ret):
# _data = await MinerFactory()._validate_command(miner_ret)
# return _data
#
# ret = asyncio.run(_coro(data))
# self.assertTrue(ret[0])
if __name__ == "__main__": if __name__ == "__main__":