Compare commits
388 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a32b61fe5d | ||
|
|
597a178009 | ||
|
|
409b2527f0 | ||
|
|
58234fcf7f | ||
|
|
1bf863cca8 | ||
|
|
6482d04185 | ||
|
|
3b58b11501 | ||
|
|
7485b8ef77 | ||
|
|
d2bea227db | ||
|
|
1b7afaaf7e | ||
|
|
96898d639c | ||
|
|
eb439f4dcf | ||
|
|
69f4349393 | ||
|
|
e371bb577c | ||
|
|
2500ec3869 | ||
|
|
5be3187eec | ||
|
|
be1e9127b0 | ||
|
|
13572c4770 | ||
|
|
08fa3961fe | ||
|
|
b5d2809e9c | ||
|
|
aa538d3079 | ||
|
|
e1500bb75c | ||
|
|
7f00a65598 | ||
|
|
64c473a7d4 | ||
|
|
96d9fe8e6c | ||
|
|
0b27400d27 | ||
|
|
666b9dfe94 | ||
|
|
df3a080c9d | ||
|
|
bf3bd7c2b9 | ||
|
|
37fd60b462 | ||
|
|
2245904740 | ||
|
|
7b1b23016e | ||
|
|
b5fcd62e23 | ||
|
|
9057cde274 | ||
|
|
f6d35888fe | ||
|
|
f2abe9fd9e | ||
|
|
7d1a702804 | ||
|
|
65d1695ce4 | ||
|
|
65fd66b8bf | ||
|
|
5db52c46f3 | ||
|
|
d06cb19da3 | ||
|
|
4530d086da | ||
|
|
0bd679f259 | ||
|
|
1ce5bd0566 | ||
|
|
67c3d05ac3 | ||
|
|
c691868e9b | ||
|
|
f5e15b4046 | ||
|
|
e14df696ee | ||
|
|
ce5dfad850 | ||
|
|
5cb45390be | ||
|
|
b5216a24a6 | ||
|
|
dd175ff3a2 | ||
|
|
9b504a3157 | ||
|
|
0bc3bf20ee | ||
|
|
de5380715c | ||
|
|
00a108252d | ||
|
|
e446176922 | ||
|
|
134c44aedc | ||
|
|
a2bb8b5d9b | ||
|
|
62aaf36fd7 | ||
|
|
63023650a9 | ||
|
|
0025c613f0 | ||
|
|
e4ec3b2b28 | ||
|
|
08af02a394 | ||
|
|
afca497d8a | ||
|
|
d50896a896 | ||
|
|
117737afb5 | ||
|
|
eabae92da6 | ||
|
|
f816551d7a | ||
|
|
4e3ea4eabb | ||
|
|
74c13806fb | ||
|
|
934d469def | ||
|
|
0c563ef538 | ||
|
|
ecc76d09af | ||
|
|
caa66531b7 | ||
|
|
23ea90b56f | ||
|
|
9e2d3aeebd | ||
|
|
0cead26872 | ||
|
|
706844264b | ||
|
|
3257af975a | ||
|
|
83a4f86e15 | ||
|
|
f1c4cb400a | ||
|
|
d21d67a18b | ||
|
|
db1beceb2e | ||
|
|
827834a119 | ||
|
|
8e430b149b | ||
|
|
031dc534c7 | ||
|
|
ac9905717d | ||
|
|
ae835dbdb2 | ||
|
|
d59f29b582 | ||
|
|
c7d6f6cd9f | ||
|
|
bf19232ad9 | ||
|
|
b495f22f31 | ||
|
|
78213c682b | ||
|
|
3706c8fb75 | ||
|
|
ed9d386dc2 | ||
|
|
1183b5deb2 | ||
|
|
c4676438a8 | ||
|
|
5ea9126c77 | ||
|
|
853756ebcb | ||
|
|
05e82b85c5 | ||
|
|
9c4c8503d6 | ||
|
|
e25cc1d85e | ||
|
|
8e37d72337 | ||
|
|
f84a054ecc | ||
|
|
6b54607588 | ||
|
|
85ee8a479b | ||
|
|
9decbf2a4b | ||
|
|
15ce3a3140 | ||
|
|
d4d48f5582 | ||
|
|
a577f64d59 | ||
|
|
aaf48cc686 | ||
|
|
aa6dc74471 | ||
|
|
63c8fe6868 | ||
|
|
ee1502c6a0 | ||
|
|
2960295385 | ||
|
|
a9e09f7b1a | ||
|
|
fd17a20a1b | ||
|
|
1e03ec5fa3 | ||
|
|
a67e4ada8e | ||
|
|
2d08b10076 | ||
|
|
92e972aa57 | ||
|
|
05cfe8cc5d | ||
|
|
b4d9e60bff | ||
|
|
6bcf372be6 | ||
|
|
092a586329 | ||
|
|
e598d4b63c | ||
|
|
848acedd52 | ||
|
|
e3c4464556 | ||
|
|
9e28f7968a | ||
|
|
6159a72d46 | ||
|
|
932c034e0e | ||
|
|
645828f35b | ||
|
|
841a546505 | ||
|
|
e65b718699 | ||
|
|
15e4338046 | ||
|
|
720d4aec3d | ||
|
|
09f9028ab5 | ||
|
|
25d971b699 | ||
|
|
cd16ef3a25 | ||
|
|
b70010272f | ||
|
|
140a457445 | ||
|
|
f4775e6311 | ||
|
|
a4ecda93a2 | ||
|
|
ba90f2f082 | ||
|
|
44ac958bbb | ||
|
|
e9bcf2ec9f | ||
|
|
c73dfad01a | ||
|
|
d222912e30 | ||
|
|
6f1c1e0290 | ||
|
|
ba0bb73aa3 | ||
|
|
13fcf1d4aa | ||
|
|
6be1e94216 | ||
|
|
709b3efa81 | ||
|
|
5ac5770331 | ||
|
|
f131ebbdf5 | ||
|
|
5441e50f73 | ||
|
|
dc6a952de4 | ||
|
|
b781d215fb | ||
|
|
086b31ba23 | ||
|
|
46b7352769 | ||
|
|
e218c5039d | ||
|
|
3bb392980e | ||
|
|
92264619d2 | ||
|
|
3e21829fae | ||
|
|
d3619b0e48 | ||
|
|
0fbb05d62d | ||
|
|
01fc2591ad | ||
|
|
c08d7fa5cd | ||
|
|
91c0e1c125 | ||
|
|
3d137f95d9 | ||
|
|
d5e8062f09 | ||
|
|
2dc4b2cb8a | ||
|
|
5ff0074d33 | ||
|
|
bbc88e175c | ||
|
|
ff54bea0ed | ||
|
|
bcfa807e12 | ||
|
|
728e94f120 | ||
|
|
5f2832d632 | ||
|
|
59e5be280f | ||
|
|
e6424acf5f | ||
|
|
34389e9133 | ||
|
|
b0f7629607 | ||
|
|
6e50f1f769 | ||
|
|
708b506f6f | ||
|
|
673d63daf0 | ||
|
|
ea55fed8d1 | ||
|
|
f5fd539eba | ||
|
|
51a56f441c | ||
|
|
54310b1d79 | ||
|
|
cb30b761dc | ||
|
|
cb50a7cac8 | ||
|
|
738af245cb | ||
|
|
71e9af1b91 | ||
|
|
1cd2566d0a | ||
|
|
1f1e5f23a2 | ||
|
|
3394234e4f | ||
|
|
e9882124ff | ||
|
|
1e5b19c149 | ||
|
|
c9339fec2f | ||
|
|
018c09e84f | ||
|
|
46e7f9a569 | ||
|
|
996ab58252 | ||
|
|
04974d5287 | ||
|
|
1a8a5ccb0e | ||
|
|
4c61c4c345 | ||
|
|
a6d6bfe73d | ||
|
|
f159e9ccb4 | ||
|
|
8ae2932274 | ||
|
|
52786ba954 | ||
|
|
f5cc526e8a | ||
|
|
a616aaba04 | ||
|
|
15b4177ce4 | ||
|
|
5b382cdb0b | ||
|
|
16622099c8 | ||
|
|
a75434fe7b | ||
|
|
020558ed4d | ||
|
|
39a82d03bc | ||
|
|
41ecb5dbc6 | ||
|
|
2d057ca9f6 | ||
|
|
b71b23d2a0 | ||
|
|
b32649435d | ||
|
|
c0096126df | ||
|
|
d632360932 | ||
|
|
400001fa38 | ||
|
|
4ff32a8081 | ||
|
|
33b4ae2f2f | ||
|
|
62194bd627 | ||
|
|
83bb2950fa | ||
|
|
262dee3cfd | ||
|
|
7ccf6ed610 | ||
|
|
21b189f5a8 | ||
|
|
d8d8a050ce | ||
|
|
b9ca810903 | ||
|
|
6ad750d3e9 | ||
|
|
8423b64825 | ||
|
|
742ddef227 | ||
|
|
85d7f0abfb | ||
|
|
c4b4fa293d | ||
|
|
0204abfead | ||
|
|
3510f7b9d3 | ||
|
|
2d4c063dfa | ||
|
|
67b3e2f312 | ||
|
|
82006de30f | ||
|
|
5bde9d7fe6 | ||
|
|
f7cd428366 | ||
|
|
d5e797de9e | ||
|
|
b9d5e7b206 | ||
|
|
2d8c7eb4fd | ||
|
|
f69e07fe68 | ||
|
|
84aab38954 | ||
|
|
dcf37481bd | ||
|
|
1a9cca84d5 | ||
|
|
c5272d67de | ||
|
|
3bcfb14177 | ||
|
|
566280f280 | ||
|
|
a814f7eefb | ||
|
|
097b8ed534 | ||
|
|
da47d72749 | ||
|
|
abd4d18a01 | ||
|
|
2adbce3c21 | ||
|
|
c41324b324 | ||
|
|
151a4f6c2d | ||
|
|
d23777a83f | ||
|
|
bb0aee8337 | ||
|
|
2a23acb73a | ||
|
|
ad5eb0cef6 | ||
|
|
07dd8f55fe | ||
|
|
bafa91cc47 | ||
|
|
16399510fa | ||
|
|
e9f54eec0a | ||
|
|
fbbbc9f215 | ||
|
|
69e4f575c0 | ||
|
|
e95659d2e0 | ||
|
|
35f34310ec | ||
|
|
acc18e20fd | ||
|
|
72460eac08 | ||
|
|
3e5f9d4eca | ||
|
|
e873fa252c | ||
|
|
ff2c083a19 | ||
|
|
a30a84c34b | ||
|
|
97d2023298 | ||
|
|
1ce8430a14 | ||
|
|
1c0b638818 | ||
|
|
e852588eeb | ||
|
|
08b9bfd854 | ||
|
|
7ee2f3a29a | ||
|
|
5ee6a38f39 | ||
|
|
8f0bfd5f83 | ||
|
|
c54f39fc77 | ||
|
|
eb40769ccf | ||
|
|
418c62b40d | ||
|
|
198a480c35 | ||
|
|
2d4bf4e847 | ||
|
|
774e3d1a62 | ||
|
|
ade3cd6fee | ||
|
|
28dc3ccb84 | ||
|
|
6ca8582ec3 | ||
|
|
74b4aeb44a | ||
|
|
9c7ab5ac57 | ||
|
|
65ecf1fea2 | ||
|
|
44142c658b | ||
|
|
25a205ce6c | ||
|
|
25094084cf | ||
|
|
4eac601153 | ||
|
|
f0d8d66b9b | ||
|
|
cfa550f8c0 | ||
|
|
91f6a5bf41 | ||
|
|
464bd6be65 | ||
|
|
031d7e2186 | ||
|
|
126b0d124c | ||
|
|
81c84a3e8f | ||
|
|
406d5bd549 | ||
|
|
cd5fe09fd9 | ||
|
|
766fc4efed | ||
|
|
b70fed40c8 | ||
|
|
255d98fd08 | ||
|
|
78304631f7 | ||
|
|
ab230844fc | ||
|
|
8a58cb9fd3 | ||
|
|
70b6ed73dc | ||
|
|
d2400bf44e | ||
|
|
db780fe876 | ||
|
|
cd84ae828a | ||
|
|
970d5d1031 | ||
|
|
da0e327ec7 | ||
|
|
4c84a8d572 | ||
|
|
1cfd895deb | ||
|
|
d0da08eb10 | ||
|
|
ee4f2cd87d | ||
|
|
8a6577c8aa | ||
|
|
ed5eae4187 | ||
|
|
f3b25027ad | ||
|
|
228daecbbf | ||
|
|
81aa569d07 | ||
|
|
aba8d7b8b9 | ||
|
|
449403bc57 | ||
|
|
997a1bbe31 | ||
|
|
2c7e0e3efe | ||
|
|
6051a46654 | ||
|
|
c7847d2789 | ||
|
|
215af72a6b | ||
|
|
b33586f8eb | ||
|
|
8db81d9b3c | ||
|
|
cf3b2fedf4 | ||
|
|
81db6d8e28 | ||
|
|
d47e59d057 | ||
|
|
8388d2f4ac | ||
|
|
f5ec513ce0 | ||
|
|
8975842f0b | ||
|
|
c6ca1df112 | ||
|
|
92f58a9682 | ||
|
|
5dc19dbd78 | ||
|
|
b0fc11bcc1 | ||
|
|
23ebe460a4 | ||
|
|
92db8c8161 | ||
|
|
d9b522734a | ||
|
|
e02457f0b2 | ||
|
|
104b4c18c2 | ||
|
|
1970f8c924 | ||
|
|
e431a06ac5 | ||
|
|
6da9b58088 | ||
|
|
3108443041 | ||
|
|
37ebb9e6c3 | ||
|
|
6c2e0f59b1 | ||
|
|
6eb9cdce90 | ||
|
|
e96d9447d1 | ||
|
|
d3cca11322 | ||
|
|
86155db455 | ||
|
|
5b078b4b27 | ||
|
|
9672dd6873 | ||
|
|
1587f65196 | ||
|
|
5ff10c0cdd | ||
|
|
aa0a028564 | ||
|
|
82f6d2f274 | ||
|
|
833de3ab43 | ||
|
|
08d273c7c1 | ||
|
|
aac7598187 | ||
|
|
5c0ac4e665 | ||
|
|
7bd5e49412 | ||
|
|
53ff3c5f79 | ||
|
|
36ae6e5272 | ||
|
|
fb3dffb216 | ||
|
|
ff6a6d2ec6 | ||
|
|
f4bbc2c3e5 | ||
|
|
4dbfdbe29c | ||
|
|
5e2a18f91e | ||
|
|
3363bdc592 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ pyvenv.cfg
|
|||||||
.env/
|
.env/
|
||||||
bin/
|
bin/
|
||||||
lib/
|
lib/
|
||||||
|
.idea/
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ repos:
|
|||||||
rev: 22.6.0
|
rev: 22.6.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
|
- repo: https://github.com/pycqa/isort
|
||||||
|
rev: 5.10.1
|
||||||
|
hooks:
|
||||||
|
- id: isort
|
||||||
|
name: isort (python)
|
||||||
|
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: unittest
|
- id: unittest
|
||||||
|
|||||||
23
README.md
23
README.md
@@ -18,6 +18,10 @@ You can install pyasic directly from pip with the command `pip install pyasic`.
|
|||||||
For those of you who aren't comfortable with code and developer tools, there are windows builds of GUI applications that use this library [here](https://drive.google.com/drive/folders/1DjR8UOS_g0ehfiJcgmrV0FFoqFvE9akW?usp=sharing).
|
For those of you who aren't comfortable with code and developer tools, there are windows builds of GUI applications that use this library [here](https://drive.google.com/drive/folders/1DjR8UOS_g0ehfiJcgmrV0FFoqFvE9akW?usp=sharing).
|
||||||
|
|
||||||
## Developer Setup
|
## Developer Setup
|
||||||
|
It is highly reccommended that you contribute to this project through [`pyasic-super`](https://github.com/UpstreamData/pyasic-super) using its submodules. This allows testing in conjunction with other `pyasic` related programs.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
This repo uses poetry for dependencies, which can be installed by following the guide on their website [here](https://python-poetry.org/docs/#installation).
|
This repo uses poetry for dependencies, which can be installed by following the guide on their website [here](https://python-poetry.org/docs/#installation).
|
||||||
|
|
||||||
After you have poetry installed, run `poetry install --with dev`, or `poetry install --with dev,docs` if you want to include packages required for documentation.
|
After you have poetry installed, run `poetry install --with dev`, or `poetry install --with dev,docs` if you want to include packages required for documentation.
|
||||||
@@ -34,7 +38,6 @@ There are 2 main ways to get a miner (and the functions attached to it), via sca
|
|||||||
#### Scanning for miners
|
#### Scanning for miners
|
||||||
```python
|
```python
|
||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
|
||||||
|
|
||||||
from pyasic.network import MinerNetwork
|
from pyasic.network import MinerNetwork
|
||||||
|
|
||||||
@@ -43,7 +46,7 @@ from pyasic.network import MinerNetwork
|
|||||||
async def scan_and_get_data():
|
async def scan_and_get_data():
|
||||||
# Define network range to be used for scanning
|
# Define network range to be used for scanning
|
||||||
# This can take a list of IPs, a constructor string, or an IP and subnet mask
|
# This can take a list of IPs, a constructor string, or an IP and subnet mask
|
||||||
# The standard mask is /24, and you can pass any IP address in the subnet
|
# The standard mask is /24 (x.x.x.0-255), and you can pass any IP address in the subnet
|
||||||
net = MinerNetwork("192.168.1.69", mask=24)
|
net = MinerNetwork("192.168.1.69", mask=24)
|
||||||
# Scan the network for miners
|
# Scan the network for miners
|
||||||
# This function returns a list of miners of the correct type as a class
|
# This function returns a list of miners of the correct type as a class
|
||||||
@@ -68,9 +71,8 @@ if __name__ == "__main__":
|
|||||||
#### Getting a miner if you know the IP
|
#### Getting a miner if you know the IP
|
||||||
```python
|
```python
|
||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
|
||||||
|
|
||||||
from pyasic.miners import get_miner
|
from pyasic import get_miner
|
||||||
|
|
||||||
|
|
||||||
# define asynchronous function to get miner and data
|
# define asynchronous function to get miner and data
|
||||||
@@ -91,12 +93,15 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
If needed, this library exposes a wrapper for the miner API that can be used for advanced data gathering.
|
If needed, this library exposes a wrapper for the miner API that can be used for advanced data gathering.
|
||||||
|
|
||||||
|
You can see more information on basic usage of the APIs past this example in the docs [here](https://pyasic.readthedocs.io/en/latest/API/api/).
|
||||||
|
|
||||||
|
Please see the appropriate API documentation page (pyasic docs -> Advanced -> Miner APIs -> your API type) for a link to that specific miner's API documentation page and more information.
|
||||||
|
|
||||||
#### List available API commands
|
#### List available API commands
|
||||||
```python
|
```python
|
||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
|
||||||
|
|
||||||
from pyasic.miners import get_miner
|
from pyasic import get_miner
|
||||||
|
|
||||||
|
|
||||||
async def get_api_commands(miner_ip: str):
|
async def get_api_commands(miner_ip: str):
|
||||||
@@ -104,7 +109,8 @@ async def get_api_commands(miner_ip: str):
|
|||||||
miner = await get_miner(miner_ip)
|
miner = await get_miner(miner_ip)
|
||||||
|
|
||||||
# List all available commands
|
# List all available commands
|
||||||
print(miner.api.get_commands())
|
# Can also be called explicitly with the function miner.api.get_commands()
|
||||||
|
print(miner.api.commands)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -117,9 +123,8 @@ The miner API commands will raise an `APIError` if they fail with a bad status c
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
import asyncio
|
import asyncio
|
||||||
import sys
|
|
||||||
|
|
||||||
from pyasic.miners import get_miner
|
from pyasic import get_miner
|
||||||
|
|
||||||
|
|
||||||
async def get_api_commands(miner_ip: str):
|
async def get_api_commands(miner_ip: str):
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ All API implementations inherit from [`BaseMinerAPI`][pyasic.API.BaseMinerAPI],
|
|||||||
[`BaseMinerAPI`][pyasic.API.BaseMinerAPI] cannot be instantiated directly, it will raise a `TypeError`.
|
[`BaseMinerAPI`][pyasic.API.BaseMinerAPI] cannot be instantiated directly, it will raise a `TypeError`.
|
||||||
Use these instead -
|
Use these instead -
|
||||||
|
|
||||||
|
#### [BFGMiner API][pyasic.API.bfgminer.BFGMinerAPI]
|
||||||
#### [BMMiner API][pyasic.API.bmminer.BMMinerAPI]
|
#### [BMMiner API][pyasic.API.bmminer.BMMinerAPI]
|
||||||
#### [BOSMiner API][pyasic.API.bosminer.BOSMinerAPI]
|
#### [BOSMiner API][pyasic.API.bosminer.BOSMinerAPI]
|
||||||
#### [BTMiner API][pyasic.API.btminer.BTMinerAPI]
|
#### [BTMiner API][pyasic.API.btminer.BTMinerAPI]
|
||||||
|
|||||||
7
docs/API/bfgminer.md
Normal file
7
docs/API/bfgminer.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# pyasic
|
||||||
|
## BFGMinerAPI
|
||||||
|
::: pyasic.API.bfgminer.BFGMinerAPI
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
@@ -6,3 +6,10 @@
|
|||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## HashBoard Data
|
||||||
|
::: pyasic.data.HashBoard
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|||||||
163
docs/generate_miners.py
Normal file
163
docs/generate_miners.py
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import asyncio
|
||||||
|
import importlib
|
||||||
|
import os
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from pyasic.miners.miner_factory import MINER_CLASSES, MinerTypes
|
||||||
|
|
||||||
|
warnings.filterwarnings("ignore")
|
||||||
|
|
||||||
|
|
||||||
|
def path(cls):
|
||||||
|
module = importlib.import_module(cls.__module__)
|
||||||
|
return module.__name__ + "." + cls.__name__
|
||||||
|
|
||||||
|
|
||||||
|
def make(cls):
|
||||||
|
p = path(cls)
|
||||||
|
return p.split(".")[2]
|
||||||
|
|
||||||
|
|
||||||
|
def model_type(cls):
|
||||||
|
p = path(cls)
|
||||||
|
return p.split(".")[4]
|
||||||
|
|
||||||
|
|
||||||
|
def backend_str(backend: MinerTypes) -> str:
|
||||||
|
match backend:
|
||||||
|
case MinerTypes.ANTMINER:
|
||||||
|
return "Stock Firmware Antminers"
|
||||||
|
case MinerTypes.AVALONMINER:
|
||||||
|
return "Stock Firmware Avalonminers"
|
||||||
|
case MinerTypes.VNISH:
|
||||||
|
return "Vnish Firmware Miners"
|
||||||
|
case MinerTypes.BRAIINS_OS:
|
||||||
|
return "BOS+ Firmware Miners"
|
||||||
|
case MinerTypes.HIVEON:
|
||||||
|
return "HiveOS Firmware Miners"
|
||||||
|
case MinerTypes.INNOSILICON:
|
||||||
|
return "Stock Firmware Innosilicons"
|
||||||
|
case MinerTypes.WHATSMINER:
|
||||||
|
return "Stock Firmware Whatsminers"
|
||||||
|
case MinerTypes.GOLDSHELL:
|
||||||
|
return "Stock Firmware Goldshells"
|
||||||
|
case MinerTypes.LUX_OS:
|
||||||
|
return "LuxOS Firmware Miners"
|
||||||
|
|
||||||
|
|
||||||
|
def create_url_str(mtype: str):
|
||||||
|
return (
|
||||||
|
mtype.lower()
|
||||||
|
.replace(" ", "-")
|
||||||
|
.replace("(", "")
|
||||||
|
.replace(")", "")
|
||||||
|
.replace("+", "_1")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
HEADER_FORMAT = "# pyasic\n## {} Models\n\n"
|
||||||
|
MINER_HEADER_FORMAT = "## {}\n"
|
||||||
|
DATA_FORMAT = """::: {}
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
"""
|
||||||
|
SUPPORTED_TYPES_HEADER = """# pyasic
|
||||||
|
## Supported Miners
|
||||||
|
|
||||||
|
Supported miner types are here on this list. If your miner (or miner version) is not on this list, please feel free to [open an issue on GitHub](https://github.com/UpstreamData/pyasic/issues) to get it added.
|
||||||
|
|
||||||
|
##### pyasic currently supports the following miners and subtypes:
|
||||||
|
<style>
|
||||||
|
details {
|
||||||
|
margin:0px;
|
||||||
|
padding-top:0px;
|
||||||
|
padding-bottom:0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
"""
|
||||||
|
BACKEND_TYPE_HEADER = """
|
||||||
|
<details>
|
||||||
|
<summary>{}:</summary>
|
||||||
|
<ul>"""
|
||||||
|
|
||||||
|
MINER_TYPE_HEADER = """
|
||||||
|
<details>
|
||||||
|
<summary>{} Series:</summary>
|
||||||
|
<ul>"""
|
||||||
|
|
||||||
|
MINER_DETAILS = """
|
||||||
|
<li><a href="../{}/{}#{}">{}</a></li>"""
|
||||||
|
|
||||||
|
MINER_TYPE_CLOSER = """
|
||||||
|
</ul>
|
||||||
|
</details>"""
|
||||||
|
BACKEND_TYPE_CLOSER = """
|
||||||
|
</ul>
|
||||||
|
</details>"""
|
||||||
|
|
||||||
|
m_data = {}
|
||||||
|
|
||||||
|
|
||||||
|
for m in MINER_CLASSES:
|
||||||
|
for t in MINER_CLASSES[m]:
|
||||||
|
if t is not None:
|
||||||
|
miner = MINER_CLASSES[m][t]
|
||||||
|
if make(miner) not in m_data:
|
||||||
|
m_data[make(miner)] = {}
|
||||||
|
if model_type(miner) not in m_data[make(miner)]:
|
||||||
|
m_data[make(miner)][model_type(miner)] = []
|
||||||
|
m_data[make(miner)][model_type(miner)].append(miner)
|
||||||
|
|
||||||
|
|
||||||
|
async def create_directory_structure(directory, data):
|
||||||
|
if not os.path.exists(directory):
|
||||||
|
os.makedirs(directory)
|
||||||
|
|
||||||
|
for key, value in data.items():
|
||||||
|
subdirectory = os.path.join(directory, key)
|
||||||
|
if isinstance(value, dict):
|
||||||
|
await create_directory_structure(subdirectory, value)
|
||||||
|
elif isinstance(value, list):
|
||||||
|
file_path = os.path.join(subdirectory + ".md")
|
||||||
|
|
||||||
|
with open(file_path, "w") as file:
|
||||||
|
file.write(HEADER_FORMAT.format(key))
|
||||||
|
for item in value:
|
||||||
|
header = await item("1.1.1.1").get_model()
|
||||||
|
file.write(MINER_HEADER_FORMAT.format(header))
|
||||||
|
file.write(DATA_FORMAT.format(path(item)))
|
||||||
|
|
||||||
|
|
||||||
|
async def create_supported_types(directory):
|
||||||
|
with open(os.path.join(directory, "supported_types.md"), "w") as file:
|
||||||
|
file.write(SUPPORTED_TYPES_HEADER)
|
||||||
|
for mback in MINER_CLASSES:
|
||||||
|
backend_types = {}
|
||||||
|
file.write(BACKEND_TYPE_HEADER.format(backend_str(mback)))
|
||||||
|
for mtype in MINER_CLASSES[mback]:
|
||||||
|
if mtype is None:
|
||||||
|
continue
|
||||||
|
m = MINER_CLASSES[mback][mtype]
|
||||||
|
if model_type(m) not in backend_types:
|
||||||
|
backend_types[model_type(m)] = []
|
||||||
|
backend_types[model_type(m)].append(m)
|
||||||
|
|
||||||
|
for mtype in backend_types:
|
||||||
|
file.write(MINER_TYPE_HEADER.format(mtype))
|
||||||
|
for minstance in backend_types[mtype]:
|
||||||
|
model = await minstance("1.1.1.1").get_model()
|
||||||
|
file.write(
|
||||||
|
MINER_DETAILS.format(
|
||||||
|
make(minstance), mtype, create_url_str(model), model
|
||||||
|
)
|
||||||
|
)
|
||||||
|
file.write(MINER_TYPE_CLOSER)
|
||||||
|
file.write(BACKEND_TYPE_CLOSER)
|
||||||
|
|
||||||
|
|
||||||
|
root_directory = os.path.join(os.getcwd(), "miners")
|
||||||
|
asyncio.run(create_directory_structure(root_directory, m_data))
|
||||||
|
asyncio.run(create_supported_types(root_directory))
|
||||||
@@ -42,21 +42,27 @@ 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.
|
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 [`MinerFactory().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.miners.miner_factory import MinerFactory # miner factory handles miners creation
|
from pyasic import get_miner # handles miner creation
|
||||||
|
|
||||||
|
|
||||||
async def get_miners(): # define async scan function to allow awaiting
|
async def get_miners(): # define async scan function to allow awaiting
|
||||||
# get the miner with miner factory
|
# get the miner with the miner factory
|
||||||
# miner factory is a singleton, and will always use the same object and cache
|
# the miner factory is a singleton, and will always use the same object and cache
|
||||||
# this means you can always call it as MinerFactory().get_miner()
|
# this means you can always call it as MinerFactory().get_miner(), or just get_miner()
|
||||||
miner_1 = await MinerFactory().get_miner("192.168.1.75")
|
miner_1 = await get_miner("192.168.1.75")
|
||||||
miner_2 = await MinerFactory().get_miner("192.168.1.76")
|
miner_2 = await get_miner("192.168.1.76")
|
||||||
print(miner_1, miner_2)
|
print(miner_1, miner_2)
|
||||||
|
|
||||||
|
# can also gather these, since they are async
|
||||||
|
tasks = [get_miner("192.168.1.75"), get_miner("192.168.1.76")]
|
||||||
|
miners = await asyncio.gather(*tasks)
|
||||||
|
print(miners)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(get_miners()) # get the miners asynchronously with asyncio.run()
|
asyncio.run(get_miners()) # get the miners asynchronously with asyncio.run()
|
||||||
```
|
```
|
||||||
@@ -66,7 +72,7 @@ if __name__ == "__main__":
|
|||||||
## Getting data from miners
|
## Getting data from miners
|
||||||
|
|
||||||
Once you have your miner(s) identified, you will likely want to get data from the miner(s). You can do this using a built in function in each miner called `get_data()`.
|
Once you have your miner(s) identified, you will likely want to get data from the miner(s). You can do this using a built in function in each miner called `get_data()`.
|
||||||
This function will return a instance of the dataclass [`MinerData`][pyasic.data.MinerData] with all data it can gather from the miner.
|
This function will return an instance of the dataclass [`MinerData`][pyasic.data.MinerData] with all data it can gather from the miner.
|
||||||
Each piece of data in a [`MinerData`][pyasic.data.MinerData] instance can be referenced by getting it as an attribute, such as [`MinerData().hashrate`][pyasic.data.MinerData].
|
Each piece of data in a [`MinerData`][pyasic.data.MinerData] instance can be referenced by getting it as an attribute, such as [`MinerData().hashrate`][pyasic.data.MinerData].
|
||||||
```python
|
```python
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -117,10 +123,12 @@ These functions are
|
|||||||
[`get_hostname`](#get-hostname),
|
[`get_hostname`](#get-hostname),
|
||||||
[`get_model`](#get-model),
|
[`get_model`](#get-model),
|
||||||
[`reboot`](#reboot),
|
[`reboot`](#reboot),
|
||||||
[`restart_backend`](#restart-backend), and
|
[`restart_backend`](#restart-backend),
|
||||||
[`stop_mining`](#stop-mining), and
|
[`stop_mining`](#stop-mining),
|
||||||
[`resume_mining`](#resume-mining), and
|
[`resume_mining`](#resume-mining),
|
||||||
[`send_config`](#send-config).
|
[`is_mining`](#is-mining),
|
||||||
|
[`send_config`](#send-config), and
|
||||||
|
[`set_power_limit`](#set-power-limit).
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
@@ -220,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
|
||||||
@@ -228,9 +244,17 @@ These functions are
|
|||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
### Set Power Limit
|
||||||
|
::: pyasic.miners.BaseMiner.set_power_limit
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
## [`MinerConfig`][pyasic.config.MinerConfig] and [`MinerData`][pyasic.data.MinerData]
|
## [`MinerConfig`][pyasic.config.MinerConfig] and [`MinerData`][pyasic.data.MinerData]
|
||||||
|
|
||||||
Pyasic implements a few dataclasses as helpers to make data return types consistent across different miners and miner APIs.
|
Pyasic implements a few dataclasses as helpers to make data return types consistent across different miners and miner APIs. The different fields of these dataclasses can all be viewed with the classmethod `cls.fields()`.
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
|
|||||||
10
docs/miners/antminer/X15.md
Normal file
10
docs/miners/antminer/X15.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# pyasic
|
||||||
|
## X15 Models
|
||||||
|
|
||||||
|
## Z15
|
||||||
|
::: pyasic.miners.antminer.cgminer.X15.Z15.CGMinerZ15
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
## X17 Models
|
## X17 Models
|
||||||
|
|
||||||
## S17
|
## S17
|
||||||
|
|
||||||
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17
|
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
@@ -10,31 +9,27 @@
|
|||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
## S17+
|
## S17+
|
||||||
|
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17Plus
|
||||||
::: pyasic.miners.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.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.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.antminer.bmminer.X17.T17.BMMinerT17
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
@@ -42,25 +37,20 @@
|
|||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
## T17+
|
## T17+
|
||||||
|
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17Plus
|
||||||
::: pyasic.miners.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.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.antminer.bosminer.X17.S17.BOSMinerS17
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
@@ -68,31 +58,27 @@
|
|||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
## S17+ (BOS)
|
## S17+ (BOS)
|
||||||
|
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17Plus
|
||||||
::: pyasic.miners.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.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.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.antminer.bosminer.X17.T17.BOSMinerT17
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
@@ -100,18 +86,30 @@
|
|||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
## T17+ (BOS)
|
## T17+ (BOS)
|
||||||
|
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17Plus
|
||||||
::: pyasic.miners.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.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
|
||||||
|
|
||||||
|
|||||||
@@ -2,66 +2,83 @@
|
|||||||
## X19 Models
|
## X19 Models
|
||||||
|
|
||||||
## S19
|
## S19
|
||||||
|
|
||||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19
|
::: pyasic.miners.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
|
||||||
|
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19L
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
## S19 Pro
|
## S19 Pro
|
||||||
|
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19Pro
|
||||||
::: pyasic.miners.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.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.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.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.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.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.antminer.bosminer.X19.S19.BOSMinerS19
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
@@ -69,34 +86,93 @@
|
|||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
## S19 Pro (BOS)
|
## S19 Pro (BOS)
|
||||||
|
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19Pro
|
||||||
::: pyasic.miners.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.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.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.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
|
||||||
|
|
||||||
|
|||||||
31
docs/miners/antminer/X3.md
Normal file
31
docs/miners/antminer/X3.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# pyasic
|
||||||
|
## X3 Models
|
||||||
|
|
||||||
|
## D3
|
||||||
|
::: pyasic.miners.antminer.cgminer.X3.D3.CGMinerD3
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
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
|
||||||
|
|
||||||
10
docs/miners/antminer/X5.md
Normal file
10
docs/miners/antminer/X5.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# pyasic
|
||||||
|
## X5 Models
|
||||||
|
|
||||||
|
## DR5
|
||||||
|
::: pyasic.miners.antminer.cgminer.X5.DR5.CGMinerDR5
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
10
docs/miners/antminer/X7.md
Normal file
10
docs/miners/antminer/X7.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# pyasic
|
||||||
|
## X7 Models
|
||||||
|
|
||||||
|
## L7
|
||||||
|
::: pyasic.miners.antminer.bmminer.X7.L7.BMMinerL7
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
@@ -1,18 +1,14 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## X9 Models
|
## X9 Models
|
||||||
|
|
||||||
|
## E9Pro
|
||||||
## X9 (BOS)
|
::: pyasic.miners.antminer.bmminer.X9.E9.BMMinerE9Pro
|
||||||
|
|
||||||
::: pyasic.miners.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.antminer.bmminer.X9.S9.BMMinerS9
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
@@ -20,17 +16,44 @@
|
|||||||
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.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.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
|
||||||
|
|
||||||
|
## S9 (BOS)
|
||||||
|
::: pyasic.miners.antminer.bosminer.X9.S9.BOSMinerS9
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,24 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## A10X Models
|
## A10X Models
|
||||||
|
|
||||||
## A1026
|
## Avalon 1026
|
||||||
|
|
||||||
::: pyasic.miners.avalonminer.cgminer.A10X.A1026.CGMinerAvalon1026
|
::: pyasic.miners.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.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.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
|
||||||
|
|
||||||
|
|||||||
10
docs/miners/avalonminer/A11X.md
Normal file
10
docs/miners/avalonminer/A11X.md
Normal 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
|
||||||
|
|
||||||
10
docs/miners/avalonminer/A12X.md
Normal file
10
docs/miners/avalonminer/A12X.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# pyasic
|
||||||
|
## A12X Models
|
||||||
|
|
||||||
|
## Avalon 1246
|
||||||
|
::: pyasic.miners.avalonminer.cgminer.A12X.A1246.CGMinerAvalon1246
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
@@ -1,26 +1,24 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## A7X Models
|
## A7X Models
|
||||||
|
|
||||||
## A721
|
## Avalon 721
|
||||||
|
|
||||||
::: pyasic.miners.avalonminer.cgminer.A7X.A721.CGMinerAvalon721
|
::: pyasic.miners.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.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.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
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,24 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## A8X Models
|
## A8X Models
|
||||||
|
|
||||||
## A821
|
## Avalon 821
|
||||||
|
|
||||||
::: pyasic.miners.avalonminer.cgminer.A8X.A821.CGMinerAvalon821
|
::: pyasic.miners.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.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.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
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## A9X Models
|
## A9X Models
|
||||||
|
|
||||||
## A921
|
## Avalon 921
|
||||||
|
|
||||||
::: pyasic.miners.avalonminer.cgminer.A9X.A921.CGMinerAvalon921
|
::: pyasic.miners.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
|
||||||
|
|
||||||
|
|||||||
8
docs/miners/backends/bfgminer.md
Normal file
8
docs/miners/backends/bfgminer.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# pyasic
|
||||||
|
## BFGMiner Backend
|
||||||
|
|
||||||
|
::: pyasic.miners.backends.bfgminer.BFGMiner
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## BMMiner Backend
|
## BMMiner Backend
|
||||||
|
|
||||||
::: pyasic.miners._backends.bmminer.BMMiner
|
::: pyasic.miners.backends.bmminer.BMMiner
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## BOSMiner Backend
|
## BOSMiner Backend
|
||||||
|
|
||||||
::: pyasic.miners._backends.bosminer.BOSMiner
|
::: pyasic.miners.backends.bosminer.BOSMiner
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## BTMiner Backend
|
## BTMiner Backend
|
||||||
|
|
||||||
::: pyasic.miners._backends.btminer.BTMiner
|
::: pyasic.miners.backends.btminer.BTMiner
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## CGMiner Backend
|
## CGMiner Backend
|
||||||
|
|
||||||
::: pyasic.miners._backends.cgminer.CGMiner
|
::: pyasic.miners.backends.cgminer.CGMiner
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## Hiveon Backend
|
## Hiveon Backend
|
||||||
|
|
||||||
::: pyasic.miners._backends.hiveon.Hiveon
|
::: pyasic.miners.backends.hiveon.Hiveon
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
|
|||||||
24
docs/miners/goldshell/X5.md
Normal file
24
docs/miners/goldshell/X5.md
Normal 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
|
||||||
|
|
||||||
10
docs/miners/goldshell/XMax.md
Normal file
10
docs/miners/goldshell/XMax.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# pyasic
|
||||||
|
## XMax Models
|
||||||
|
|
||||||
|
## KD Max
|
||||||
|
::: pyasic.miners.goldshell.bfgminer.XMax.KDMax.BFGMinerKDMax
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
10
docs/miners/innosilicon/A10X.md
Normal file
10
docs/miners/innosilicon/A10X.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# pyasic
|
||||||
|
## A10X Models
|
||||||
|
|
||||||
|
## A10X
|
||||||
|
::: pyasic.miners.innosilicon.cgminer.A10X.A10X.CGMinerA10X
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
## T3X Models
|
## T3X Models
|
||||||
|
|
||||||
## T3H+
|
## T3H+
|
||||||
|
::: pyasic.miners.innosilicon.cgminer.T3X.T3H.CGMinerT3HPlus
|
||||||
::: pyasic.miners.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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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="/miners/antminer/X19#s19-bos">S19</a></li>
|
<li><a href="../antminer/X3#d3">D3</a></li>
|
||||||
<li><a href="/miners/antminer/X19#s19-pro-bos">S19 Pro</a></li>
|
<li><a href="../antminer/X3#hs3">HS3</a></li>
|
||||||
<li><a href="/miners/antminer/X19#s19j-bos">S19j</a></li>
|
<li><a href="../antminer/X3#l3_1">L3+</a></li>
|
||||||
<li><a href="/miners/antminer/X19#s19j-pro-bos">S19j Pro</a></li>
|
|
||||||
<li><a href="/miners/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="/miners/antminer/X17#s17-bos">S17</a></li>
|
<li><a href="../antminer/X5#dr5">DR5</a></li>
|
||||||
<li><a href="/miners/antminer/X17#s17-plus-bos">S17+</a></li>
|
</ul>
|
||||||
<li><a href="/miners/antminer/X17#s17-pro-bos">S17 Pro</a></li>
|
</details>
|
||||||
<li><a href="/miners/antminer/X17#s17e-bos">S17e</a></li>
|
<details>
|
||||||
<li><a href="/miners/antminer/X17#t17-bos">T17</a></li>
|
<summary>X7 Series:</summary>
|
||||||
<li><a href="/miners/antminer/X17#t17-plus-bos">T17+</a></li>
|
<ul>
|
||||||
<li><a href="/miners/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="/miners/antminer/X9#s9-bos">S9</a></li>
|
<li><a href="../antminer/X9#e9pro">E9Pro</a></li>
|
||||||
<li><a href="/miners/antminer/X9#s9-bos">S9i</a></li>
|
<li><a href="../antminer/X9#s9">S9</a></li>
|
||||||
<li><a href="/miners/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>
|
||||||
@@ -50,128 +88,200 @@ details {
|
|||||||
<summary>Stock Firmware Whatsminers:</summary>
|
<summary>Stock Firmware Whatsminers:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<details>
|
<details>
|
||||||
<summary>M3X Series:</summary>
|
<summary>M2X Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<details>
|
<li><a href="../whatsminer/M2X#m20-v10">M20 V10</a></li>
|
||||||
<summary><a href="/miners/whatsminer/M3X/#m30s">M30S</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="/miners/whatsminer/M3X/#m30sve10">VE10</a></li>
|
<li><a href="../whatsminer/M2X#m20s-v30">M20S V30</a></li>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30svg20">VG20</a></li>
|
<li><a href="../whatsminer/M2X#m20s_1-v30">M20S+ V30</a></li>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30sve20">VE20</a></li>
|
<li><a href="../whatsminer/M2X#m21-v10">M21 V10</a></li>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30sv50">V50</a></li>
|
<li><a href="../whatsminer/M2X#m21s-v20">M21S V20</a></li>
|
||||||
</ul>
|
<li><a href="../whatsminer/M2X#m21s-v60">M21S V60</a></li>
|
||||||
</details>
|
<li><a href="../whatsminer/M2X#m21s-v70">M21S V70</a></li>
|
||||||
<details>
|
<li><a href="../whatsminer/M2X#m21s_1-v20">M21S+ V20</a></li>
|
||||||
<summary><a href="/miners/whatsminer/M3X/#m30s_1">M30S+</a></summary>
|
<li><a href="../whatsminer/M2X#m29-v10">M29 V10</a></li>
|
||||||
<ul>
|
</ul>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30svf20">VF20</a></li>
|
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30sve40">VE40</a></li>
|
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30svg60">VG60</a></li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary><a href="/miners/whatsminer/M3X/#m30s_2">M30S++</a></summary>
|
|
||||||
<ul>
|
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30svg30">VG30</a></li>
|
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30svg40">VG40</a></li>
|
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30svh60">VH60</a></li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary><a href="/miners/whatsminer/M3X/#m31s">M31S</a></summary>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary><a href="/miners/whatsminer/M3X/#m31s_1">M31S+</a></summary>
|
|
||||||
<ul>
|
|
||||||
<li><a href="/miners/whatsminer/M3X/#m31sve20">VE20</a></li>
|
|
||||||
<li><a href="/miners/whatsminer/M3X/#m31sv30">V30</a></li>
|
|
||||||
<li><a href="/miners/whatsminer/M3X/#m31sv40">V40</a></li>
|
|
||||||
<li><a href="/miners/whatsminer/M3X/#m31sv60">V60</a></li>
|
|
||||||
<li><a href="/miners/whatsminer/M3X/#m31sv80">V80</a></li>
|
|
||||||
<li><a href="/miners/whatsminer/M3X/#m31sv90">V90</a></li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary><a href="/miners/whatsminer/M3X/#m32">M32</a></summary>
|
|
||||||
<ul>
|
|
||||||
<li><a href="/miners/whatsminer/M3X/#m32v20">V20</a></li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary><a href="/miners/whatsminer/M3X/#m32s">M32S</a></summary>
|
|
||||||
</details>
|
|
||||||
</ul>
|
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary>M2X Series:</summary>
|
<summary>M3X Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<details>
|
<li><a href="../whatsminer/M3X#m30-v10">M30 V10</a></li>
|
||||||
<summary><a href="/miners/whatsminer/M2X/#m20">M20</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="/miners/whatsminer/M2X/#m20v10">V10</a></li>
|
<li><a href="../whatsminer/M3X#m30s-v20">M30S V20</a></li>
|
||||||
</ul>
|
<li><a href="../whatsminer/M3X#m30s-v30">M30S V30</a></li>
|
||||||
</details>
|
<li><a href="../whatsminer/M3X#m30s-v40">M30S V40</a></li>
|
||||||
<details>
|
<li><a href="../whatsminer/M3X#m30s-v50">M30S V50</a></li>
|
||||||
<summary><a href="/miners/whatsminer/M2X/#m20s">M20S</a></summary>
|
<li><a href="../whatsminer/M3X#m30s-v60">M30S V60</a></li>
|
||||||
<ul>
|
<li><a href="../whatsminer/M3X#m30s-v70">M30S V70</a></li>
|
||||||
<li><a href="/miners/whatsminer/M2X/#m20sv10">V10</a></li>
|
<li><a href="../whatsminer/M3X#m30s-v80">M30S V80</a></li>
|
||||||
<li><a href="/miners/whatsminer/M2X/#m20sv20">V20</a></li>
|
<li><a href="../whatsminer/M3X#m30s-ve10">M30S VE10</a></li>
|
||||||
</ul>
|
<li><a href="../whatsminer/M3X#m30s-ve20">M30S VE20</a></li>
|
||||||
</details>
|
<li><a href="../whatsminer/M3X#m30s-ve30">M30S VE30</a></li>
|
||||||
<details>
|
<li><a href="../whatsminer/M3X#m30s-ve40">M30S VE40</a></li>
|
||||||
<summary><a href="/miners/whatsminer/M2X/#m20s_1">M20S+</a></summary>
|
<li><a href="../whatsminer/M3X#m30s-ve50">M30S VE50</a></li>
|
||||||
</details>
|
<li><a href="../whatsminer/M3X#m30s-ve60">M30S VE60</a></li>
|
||||||
<details>
|
<li><a href="../whatsminer/M3X#m30s-ve70">M30S VE70</a></li>
|
||||||
<summary><a href="/miners/whatsminer/M2X/#m21">M21</a></summary>
|
<li><a href="../whatsminer/M3X#m30s-vf10">M30S VF10</a></li>
|
||||||
</details>
|
<li><a href="../whatsminer/M3X#m30s-vf20">M30S VF20</a></li>
|
||||||
<details>
|
<li><a href="../whatsminer/M3X#m30s-vf30">M30S VF30</a></li>
|
||||||
<summary><a href="/miners/whatsminer/M2X/#m21s">M21S</a></summary>
|
<li><a href="../whatsminer/M3X#m30s-vg10">M30S VG10</a></li>
|
||||||
<ul>
|
<li><a href="../whatsminer/M3X#m30s-vg20">M30S VG20</a></li>
|
||||||
<li><a href="/miners/whatsminer/M2X/#m21sv20">V20</a></li>
|
<li><a href="../whatsminer/M3X#m30s-vg30">M30S VG30</a></li>
|
||||||
<li><a href="/miners/whatsminer/M2X/#m21sv60">V60</a></li>
|
<li><a href="../whatsminer/M3X#m30s-vg40">M30S VG40</a></li>
|
||||||
</ul>
|
<li><a href="../whatsminer/M3X#m30s-vh10">M30S VH10</a></li>
|
||||||
</details>
|
<li><a href="../whatsminer/M3X#m30s-vh20">M30S VH20</a></li>
|
||||||
<details>
|
<li><a href="../whatsminer/M3X#m30s-vh30">M30S VH30</a></li>
|
||||||
<summary><a href="/miners/whatsminer/M2X/#m21s_1">M21S+</a></summary>
|
<li><a href="../whatsminer/M3X#m30s-vh40">M30S VH40</a></li>
|
||||||
</details>
|
<li><a href="../whatsminer/M3X#m30s-vh50">M30S VH50</a></li>
|
||||||
</ul>
|
<li><a href="../whatsminer/M3X#m30s-vh60">M30S VH60</a></li>
|
||||||
</details>
|
<li><a href="../whatsminer/M3X#m30s-vi20">M30S VI20</a></li>
|
||||||
</ul>
|
<li><a href="../whatsminer/M3X#m30s_1-v10">M30S+ V10</a></li>
|
||||||
</details>
|
<li><a href="../whatsminer/M3X#m30s_1-v20">M30S+ V20</a></li>
|
||||||
<details>
|
<li><a href="../whatsminer/M3X#m30s_1-v30">M30S+ V30</a></li>
|
||||||
<summary>Stock Firmware Antminers:</summary>
|
<li><a href="../whatsminer/M3X#m30s_1-v40">M30S+ V40</a></li>
|
||||||
<ul>
|
<li><a href="../whatsminer/M3X#m30s_1-v50">M30S+ V50</a></li>
|
||||||
<details>
|
<li><a href="../whatsminer/M3X#m30s_1-v60">M30S+ V60</a></li>
|
||||||
<summary>X19 Series:</summary>
|
<li><a href="../whatsminer/M3X#m30s_1-v70">M30S+ V70</a></li>
|
||||||
<ul>
|
<li><a href="../whatsminer/M3X#m30s_1-v80">M30S+ V80</a></li>
|
||||||
<li><a href="/miners/antminer/X19/#s19">S19</a></li>
|
<li><a href="../whatsminer/M3X#m30s_1-v90">M30S+ V90</a></li>
|
||||||
<li><a href="/miners/antminer/X19/#s19-pro">S19 Pro</a></li>
|
<li><a href="../whatsminer/M3X#m30s_1-v100">M30S+ V100</a></li>
|
||||||
<li><a href="/miners/antminer/X19/#s19a">S19a</a></li>
|
<li><a href="../whatsminer/M3X#m30s_1-ve30">M30S+ VE30</a></li>
|
||||||
<li><a href="/miners/antminer/X19/#s19j">S19j</a></li>
|
<li><a href="../whatsminer/M3X#m30s_1-ve40">M30S+ VE40</a></li>
|
||||||
<li><a href="/miners/antminer/X19/#s19j-pro">S19j Pro</a></li>
|
<li><a href="../whatsminer/M3X#m30s_1-ve50">M30S+ VE50</a></li>
|
||||||
<li><a href="/miners/antminer/X19/#s19-xp">S19 XP</a></li>
|
<li><a href="../whatsminer/M3X#m30s_1-ve60">M30S+ VE60</a></li>
|
||||||
<li><a href="/miners/antminer/X19/#t19">T19</a></li>
|
<li><a href="../whatsminer/M3X#m30s_1-ve70">M30S+ VE70</a></li>
|
||||||
</ul>
|
<li><a href="../whatsminer/M3X#m30s_1-ve80">M30S+ VE80</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1-ve90">M30S+ VE90</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1-ve100">M30S+ VE100</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1-vf20">M30S+ VF20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1-vf30">M30S+ VF30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1-vg30">M30S+ VG30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1-vg40">M30S+ VG40</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1-vg50">M30S+ VG50</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1-vg60">M30S+ VG60</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1-vh10">M30S+ VH10</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1-vh20">M30S+ VH20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1-vh30">M30S+ VH30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1-vh40">M30S+ VH40</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1-vh50">M30S+ VH50</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1-vh60">M30S+ VH60</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-v10">M30S++ V10</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-v20">M30S++ V20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-ve30">M30S++ VE30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-ve40">M30S++ VE40</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-ve50">M30S++ VE50</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-vf40">M30S++ VF40</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-vg30">M30S++ VG30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-vg40">M30S++ VG40</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-vg50">M30S++ VG50</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-vh10">M30S++ VH10</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-vh20">M30S++ VH20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-vh30">M30S++ VH30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-vh40">M30S++ VH40</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-vh50">M30S++ VH50</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-vh60">M30S++ VH60</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-vh70">M30S++ VH70</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-vh80">M30S++ VH80</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-vh90">M30S++ VH90</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-vh100">M30S++ VH100</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-vj20">M30S++ VJ20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m30s_1_1-vj30">M30S++ VJ30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31-v10">M31 V10</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31-v20">M31 V20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s-v10">M31S V10</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s-v20">M31S V20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s-v30">M31S V30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s-v40">M31S V40</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s-v50">M31S V50</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s-v60">M31S V60</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s-v70">M31S V70</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s-v80">M31S V80</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s-v90">M31S V90</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s-ve10">M31S VE10</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s-ve20">M31S VE20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s-ve30">M31S VE30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31se-v10">M31SE V10</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31se-v20">M31SE V20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31se-v30">M31SE V30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31h-v40">M31H V40</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-v10">M31S+ V10</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-v20">M31S+ V20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-v30">M31S+ V30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-v40">M31S+ V40</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-v50">M31S+ V50</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-v60">M31S+ V60</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-v80">M31S+ V80</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-v90">M31S+ V90</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-v100">M31S+ V100</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-ve10">M31S+ VE10</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-ve20">M31S+ VE20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-ve30">M31S+ VE30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-ve40">M31S+ VE40</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-ve50">M31S+ VE50</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-ve60">M31S+ VE60</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-ve80">M31S+ VE80</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-vf20">M31S+ VF20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-vf30">M31S+ VF30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-vg20">M31S+ VG20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m31s_1-vg30">M31S+ VG30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m32-v10">M32 V10</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m32-v20">M32 V20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m33-v10">M33 V10</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m33-v20">M33 V20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m33-v30">M33 V30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m33s-vg30">M33S VG30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m33s_1-vh20">M33S+ VH20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m33s_1-vh30">M33S+ VH30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m33s_1_1-vh20">M33S++ VH20</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m33s_1_1-vh30">M33S++ VH30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m33s_1_1-vg40">M33S++ VG40</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m34s_1-ve10">M34S+ VE10</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m36s-ve10">M36S VE10</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m36s_1-vg30">M36S+ VG30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m36s_1_1-vh30">M36S++ VH30</a></li>
|
||||||
|
<li><a href="../whatsminer/M3X#m39-v20">M39 V20</a></li>
|
||||||
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary>X17 Series:</summary>
|
<summary>M5X Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/antminer/X17/#s17">S17</a></li>
|
<li><a href="../whatsminer/M5X#m50-vg30">M50 VG30</a></li>
|
||||||
<li><a href="/miners/antminer/X17/#s17_1">S17+</a></li>
|
<li><a href="../whatsminer/M5X#m50-vh10">M50 VH10</a></li>
|
||||||
<li><a href="/miners/antminer/X17/#s17-pro">S17 Pro</a></li>
|
<li><a href="../whatsminer/M5X#m50-vh20">M50 VH20</a></li>
|
||||||
<li><a href="/miners/antminer/X17/#s17e">S17e</a></li>
|
<li><a href="../whatsminer/M5X#m50-vh30">M50 VH30</a></li>
|
||||||
<li><a href="/miners/antminer/X17/#t17">T17</a></li>
|
<li><a href="../whatsminer/M5X#m50-vh40">M50 VH40</a></li>
|
||||||
<li><a href="/miners/antminer/X17/#t17_1">T17+</a></li>
|
<li><a href="../whatsminer/M5X#m50-vh50">M50 VH50</a></li>
|
||||||
<li><a href="/miners/antminer/X17/#t17e">T17e</a></li>
|
<li><a href="../whatsminer/M5X#m50-vh60">M50 VH60</a></li>
|
||||||
</ul>
|
<li><a href="../whatsminer/M5X#m50-vh70">M50 VH70</a></li>
|
||||||
</details>
|
<li><a href="../whatsminer/M5X#m50-vh80">M50 VH80</a></li>
|
||||||
<details>
|
<li><a href="../whatsminer/M5X#m50-vj10">M50 VJ10</a></li>
|
||||||
<summary>X9 Series:</summary>
|
<li><a href="../whatsminer/M5X#m50-vj20">M50 VJ20</a></li>
|
||||||
<ul>
|
<li><a href="../whatsminer/M5X#m50-vj30">M50 VJ30</a></li>
|
||||||
<li><a href="/miners/antminer/X9/#s9">S9</a></li>
|
<li><a href="../whatsminer/M5X#m50s-vj10">M50S VJ10</a></li>
|
||||||
<li><a href="/miners/antminer/X9/#s9i">S9i</a></li>
|
<li><a href="../whatsminer/M5X#m50s-vj20">M50S VJ20</a></li>
|
||||||
<li><a href="/miners/antminer/X9/#t9">T9</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>
|
||||||
|
<li><a href="../whatsminer/M5X#m50s-vh20">M50S VH20</a></li>
|
||||||
|
<li><a href="../whatsminer/M5X#m50s-vh30">M50S VH30</a></li>
|
||||||
|
<li><a href="../whatsminer/M5X#m50s-vh40">M50S VH40</a></li>
|
||||||
|
<li><a href="../whatsminer/M5X#m50s-vh50">M50S VH50</a></li>
|
||||||
|
<li><a href="../whatsminer/M5X#m50s_1-vh30">M50S+ VH30</a></li>
|
||||||
|
<li><a href="../whatsminer/M5X#m50s_1-vh40">M50S+ VH40</a></li>
|
||||||
|
<li><a href="../whatsminer/M5X#m50s_1-vj30">M50S+ VJ30</a></li>
|
||||||
|
<li><a href="../whatsminer/M5X#m50s_1-vk20">M50S+ VK20</a></li>
|
||||||
|
<li><a href="../whatsminer/M5X#m50s_1_1-vk10">M50S++ VK10</a></li>
|
||||||
|
<li><a href="../whatsminer/M5X#m50s_1_1-vk20">M50S++ VK20</a></li>
|
||||||
|
<li><a href="../whatsminer/M5X#m50s_1_1-vk30">M50S++ VK30</a></li>
|
||||||
|
<li><a href="../whatsminer/M5X#m53-vh30">M53 VH30</a></li>
|
||||||
|
<li><a href="../whatsminer/M5X#m53s-vh30">M53S VH30</a></li>
|
||||||
|
<li><a href="../whatsminer/M5X#m53s_1-vj30">M53S+ VJ30</a></li>
|
||||||
|
<li><a href="../whatsminer/M5X#m56-vh30">M56 VH30</a></li>
|
||||||
|
<li><a href="../whatsminer/M5X#m56s-vh30">M56S VH30</a></li>
|
||||||
|
<li><a href="../whatsminer/M5X#m56s_1-vj30">M56S+ VJ30</a></li>
|
||||||
|
<li><a href="../whatsminer/M5X#m59-vh30">M59 VH30</a></li>
|
||||||
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
@@ -179,45 +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="/miners/avalonminer/A7X/#a721">A721</a></li>
|
<li><a href="../avalonminer/A7X#avalon-721">Avalon 721</a></li>
|
||||||
<li><a href="/miners/avalonminer/A7X/#a741">A741</a></li>
|
<li><a href="../avalonminer/A7X#avalon-741">Avalon 741</a></li>
|
||||||
<li><a href="/miners/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="/miners/avalonminer/A8X/#a821">A821</a></li>
|
<li><a href="../avalonminer/A8X#avalon-821">Avalon 821</a></li>
|
||||||
<li><a href="/miners/avalonminer/A8X/#a841">A841</a></li>
|
<li><a href="../avalonminer/A8X#avalon-841">Avalon 841</a></li>
|
||||||
<li><a href="/miners/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="/miners/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="/miners/avalonminer/A10X/#a1026">A1026</a></li>
|
<li><a href="../avalonminer/A10X#avalon-1026">Avalon 1026</a></li>
|
||||||
<li><a href="/miners/avalonminer/A10X/#a1047">A1047</a></li>
|
<li><a href="../avalonminer/A10X#avalon-1047">Avalon 1047</a></li>
|
||||||
<li><a href="/miners/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="/miners/innosilicon/T3X/#t3h">T3H+</a></li>
|
<li><a href="../innosilicon/T3X#t3h_1">T3H+</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>A10X Series:</summary>
|
||||||
|
<ul>
|
||||||
|
<li><a href="../innosilicon/A10X#a10x">A10X</a></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>Stock Firmware Goldshells:</summary>
|
||||||
|
<ul>
|
||||||
|
<details>
|
||||||
|
<summary>X5 Series:</summary>
|
||||||
|
<ul>
|
||||||
|
<li><a href="../goldshell/X5#ck5">CK5</a></li>
|
||||||
|
<li><a href="../goldshell/X5#hs5">HS5</a></li>
|
||||||
|
<li><a href="../goldshell/X5#kd5">KD5</a></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>XMax Series:</summary>
|
||||||
|
<ul>
|
||||||
|
<li><a href="../goldshell/XMax#kd-max">KD Max</a></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>BOS+ Firmware Miners:</summary>
|
||||||
|
<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>
|
||||||
|
|||||||
@@ -1,91 +1,80 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## M2X Models
|
## M2X Models
|
||||||
|
|
||||||
## M20
|
## M20 V10
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
show_root_heading: false
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
## M20V10
|
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20V10
|
::: pyasic.miners.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
|
||||||
|
|
||||||
## M20S
|
## M20S V10
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20S
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
show_root_heading: false
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
## M20SV10
|
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV10
|
::: pyasic.miners.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.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
|
||||||
|
|
||||||
## M20S+
|
## M20S V30
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV30
|
||||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlus
|
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
## M21
|
## M20S+ V30
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlusV30
|
||||||
::: pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21
|
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## M21 V10
|
||||||
## M21S
|
::: pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21V10
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21S
|
|
||||||
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.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.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
|
||||||
|
|
||||||
## M21S+
|
## M21S V70
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV70
|
||||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlus
|
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
## M21S+ V20
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlusV20
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M29 V10
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M29.BTMinerM29V10
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
241
docs/miners/whatsminer/M5X.md
Normal file
241
docs/miners/whatsminer/M5X.md
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
# pyasic
|
||||||
|
## M5X Models
|
||||||
|
|
||||||
|
## M50 VG30
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VG30
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50 VH10
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH10
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50 VH20
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH20
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50 VH30
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH30
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50 VH40
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH40
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50 VH50
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH50
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50 VH60
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH60
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50 VH70
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH70
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50 VH80
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH80
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50 VJ10
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ10
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50 VJ20
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ20
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50 VJ30
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ30
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50S VJ10
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ10
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50S VJ20
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ20
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50S VJ30
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ30
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50S VH10
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH10
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50S VH20
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH20
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50S VH30
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH30
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50S VH40
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH40
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50S VH50
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH50
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50S+ VH30
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH30
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50S+ VH40
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH40
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50S+ VJ30
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVJ30
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50S+ VK20
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVK20
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50S++ VK10
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK10
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50S++ VK20
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK20
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50S++ VK30
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK30
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M53 VH30
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VH30
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M53S VH30
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVH30
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M53S+ VJ30
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVJ30
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
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
|
||||||
|
|
||||||
86
mkdocs.yml
86
mkdocs.yml
@@ -1,44 +1,54 @@
|
|||||||
site_name: pyasic
|
site_name: pyasic
|
||||||
repo_url: https://github.com/UpstreamData/pyasic
|
repo_url: https://github.com/UpstreamData/pyasic
|
||||||
nav:
|
nav:
|
||||||
- Introduction: "index.md"
|
- Introduction: "index.md"
|
||||||
- Miners:
|
- Miners:
|
||||||
- Supported Miners: "miners/supported_types.md"
|
- Supported Miners: "miners/supported_types.md"
|
||||||
- Miner Factory: "miners/miner_factory.md"
|
- Miner Factory: "miners/miner_factory.md"
|
||||||
- Backends:
|
- Network:
|
||||||
- BMMiner: "miners/backends/bmminer.md"
|
- Miner Network: "network/miner_network.md"
|
||||||
- BOSMiner: "miners/backends/bosminer.md"
|
- Miner Network Range: "network/miner_network_range.md"
|
||||||
- BTMiner: "miners/backends/btminer.md"
|
- Dataclasses:
|
||||||
- CGMiner: "miners/backends/cgminer.md"
|
- Miner Data: "data/miner_data.md"
|
||||||
- Hiveon: "miners/backends/hiveon.md"
|
- Error Codes: "data/error_codes.md"
|
||||||
- Classes:
|
- Miner Config: "config/miner_config.md"
|
||||||
- Antminer X9: "miners/antminer/X9.md"
|
- Advanced:
|
||||||
- Antminer X17: "miners/antminer/X17.md"
|
- Miner APIs:
|
||||||
- Antminer X19: "miners/antminer/X19.md"
|
- Intro: "API/api.md"
|
||||||
- Avalon 7X: "miners/avalonminer/A7X.md"
|
- BFGMiner: "API/bfgminer.md"
|
||||||
- Avalon 8X: "miners/avalonminer/A8X.md"
|
- BMMiner: "API/bmminer.md"
|
||||||
- Avalon 9X: "miners/avalonminer/A9X.md"
|
- BOSMiner: "API/bosminer.md"
|
||||||
- Avalon 10X: "miners/avalonminer/A10X.md"
|
- BTMiner: "API/btminer.md"
|
||||||
- Whatsminer M2X: "miners/whatsminer/M2X.md"
|
- CGMiner: "API/cgminer.md"
|
||||||
- Whatsminer M3X: "miners/whatsminer/M3X.md"
|
- Unknown: "API/unknown.md"
|
||||||
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
- Backends:
|
||||||
- Network:
|
- BMMiner: "miners/backends/bmminer.md"
|
||||||
- Miner Network: "network/miner_network.md"
|
- BOSMiner: "miners/backends/bosminer.md"
|
||||||
- Miner Network Range: "network/miner_network_range.md"
|
- BFGMiner: "miners/backends/bfgminer.md"
|
||||||
- Dataclasses:
|
- BTMiner: "miners/backends/btminer.md"
|
||||||
- Miner Data: "data/miner_data.md"
|
- CGMiner: "miners/backends/cgminer.md"
|
||||||
- Error Codes: "data/error_codes.md"
|
- Hiveon: "miners/backends/hiveon.md"
|
||||||
- Miner Config: "config/miner_config.md"
|
- Classes:
|
||||||
- Advanced:
|
- Antminer X3: "miners/antminer/X3.md"
|
||||||
- Miner APIs:
|
- Antminer X5: "miners/antminer/X5.md"
|
||||||
- Intro: "API/api.md"
|
- Antminer X7: "miners/antminer/X7.md"
|
||||||
- BMMiner: "API/bmminer.md"
|
- Antminer X9: "miners/antminer/X9.md"
|
||||||
- BOSMiner: "API/bosminer.md"
|
- Antminer X15: "miners/antminer/X15.md"
|
||||||
- BTMiner: "API/btminer.md"
|
- Antminer X17: "miners/antminer/X17.md"
|
||||||
- CGMiner: "API/cgminer.md"
|
- Antminer X19: "miners/antminer/X19.md"
|
||||||
- Unknown: "API/unknown.md"
|
- Avalon 7X: "miners/avalonminer/A7X.md"
|
||||||
|
- Avalon 8X: "miners/avalonminer/A8X.md"
|
||||||
- Base Miner: "miners/base_miner.md"
|
- Avalon 9X: "miners/avalonminer/A9X.md"
|
||||||
|
- Avalon 10X: "miners/avalonminer/A10X.md"
|
||||||
|
- Whatsminer M2X: "miners/whatsminer/M2X.md"
|
||||||
|
- Whatsminer M3X: "miners/whatsminer/M3X.md"
|
||||||
|
- Whatsminer M5X: "miners/whatsminer/M5X.md"
|
||||||
|
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
||||||
|
- Innosilicon A10X: "miners/innosilicon/A10X.md"
|
||||||
|
- Goldshell CKX: "miners/goldshell/CKX.md"
|
||||||
|
- Goldshell HSX: "miners/goldshell/HSX.md"
|
||||||
|
- Goldshell KDX: "miners/goldshell/KDX.md"
|
||||||
|
- Base Miner: "miners/base_miner.md"
|
||||||
|
|
||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
|
|||||||
955
poetry.lock
generated
955
poetry.lock
generated
@@ -1,955 +0,0 @@
|
|||||||
[[package]]
|
|
||||||
name = "anyio"
|
|
||||||
version = "3.6.1"
|
|
||||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6.2"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
idna = ">=2.8"
|
|
||||||
sniffio = ">=1.1"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
|
||||||
test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"]
|
|
||||||
trio = ["trio (>=0.16)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "asyncssh"
|
|
||||||
version = "2.12.0"
|
|
||||||
description = "AsyncSSH: Asynchronous SSHv2 client and server library"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">= 3.6"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
cryptography = ">=3.1"
|
|
||||||
typing-extensions = ">=3.6"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
bcrypt = ["bcrypt (>=3.1.3)"]
|
|
||||||
fido2 = ["fido2 (>=0.9.2)"]
|
|
||||||
gssapi = ["gssapi (>=1.2.0)"]
|
|
||||||
libnacl = ["libnacl (>=1.4.2)"]
|
|
||||||
pkcs11 = ["python-pkcs11 (>=0.7.0)"]
|
|
||||||
pyopenssl = ["pyOpenSSL (>=17.0.0)"]
|
|
||||||
pywin32 = ["pywin32 (>=227)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "certifi"
|
|
||||||
version = "2022.9.24"
|
|
||||||
description = "Python package for providing Mozilla's CA Bundle."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cffi"
|
|
||||||
version = "1.15.1"
|
|
||||||
description = "Foreign Function Interface for Python calling C code."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
pycparser = "*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfgv"
|
|
||||||
version = "3.3.1"
|
|
||||||
description = "Validate configuration and produce human readable error messages."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6.1"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "click"
|
|
||||||
version = "8.1.3"
|
|
||||||
description = "Composable command line interface toolkit"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "colorama"
|
|
||||||
version = "0.4.5"
|
|
||||||
description = "Cross-platform colored terminal text."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cryptography"
|
|
||||||
version = "38.0.1"
|
|
||||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
cffi = ">=1.12"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
|
|
||||||
docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"]
|
|
||||||
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
|
|
||||||
sdist = ["setuptools-rust (>=0.11.4)"]
|
|
||||||
ssh = ["bcrypt (>=3.1.5)"]
|
|
||||||
test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "distlib"
|
|
||||||
version = "0.3.6"
|
|
||||||
description = "Distribution utilities"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "filelock"
|
|
||||||
version = "3.8.0"
|
|
||||||
description = "A platform independent file lock."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"]
|
|
||||||
testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ghp-import"
|
|
||||||
version = "2.1.0"
|
|
||||||
description = "Copy your docs directly to the gh-pages branch."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
python-dateutil = ">=2.8.1"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
dev = ["flake8", "markdown", "twine", "wheel"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "griffe"
|
|
||||||
version = "0.22.2"
|
|
||||||
description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
async = ["aiofiles (>=0.7,<1.0)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "h11"
|
|
||||||
version = "0.12.0"
|
|
||||||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "httpcore"
|
|
||||||
version = "0.15.0"
|
|
||||||
description = "A minimal low-level HTTP client."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
anyio = ">=3.0.0,<4.0.0"
|
|
||||||
certifi = "*"
|
|
||||||
h11 = ">=0.11,<0.13"
|
|
||||||
sniffio = ">=1.0.0,<2.0.0"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
http2 = ["h2 (>=3,<5)"]
|
|
||||||
socks = ["socksio (>=1.0.0,<2.0.0)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "httpx"
|
|
||||||
version = "0.23.0"
|
|
||||||
description = "The next generation HTTP client."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
certifi = "*"
|
|
||||||
httpcore = ">=0.15.0,<0.16.0"
|
|
||||||
rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
|
|
||||||
sniffio = "*"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
brotli = ["brotli", "brotlicffi"]
|
|
||||||
cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"]
|
|
||||||
http2 = ["h2 (>=3,<5)"]
|
|
||||||
socks = ["socksio (>=1.0.0,<2.0.0)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "identify"
|
|
||||||
version = "2.5.5"
|
|
||||||
description = "File identification library for Python"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
license = ["ukkonen"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "idna"
|
|
||||||
version = "3.4"
|
|
||||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "importlib-metadata"
|
|
||||||
version = "4.12.0"
|
|
||||||
description = "Read metadata from Python packages"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
zipp = ">=0.5"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"]
|
|
||||||
perf = ["ipython"]
|
|
||||||
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "Jinja2"
|
|
||||||
version = "3.1.2"
|
|
||||||
description = "A very fast and expressive template engine."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
MarkupSafe = ">=2.0"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
i18n = ["Babel (>=2.7)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "Markdown"
|
|
||||||
version = "3.3.7"
|
|
||||||
description = "Python implementation of Markdown."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
testing = ["coverage", "pyyaml"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "MarkupSafe"
|
|
||||||
version = "2.1.1"
|
|
||||||
description = "Safely add untrusted strings to HTML/XML markup."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mergedeep"
|
|
||||||
version = "1.3.4"
|
|
||||||
description = "A deep merge function for 🐍."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mkdocs"
|
|
||||||
version = "1.4.0"
|
|
||||||
description = "Project documentation with Markdown."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
click = ">=7.0"
|
|
||||||
ghp-import = ">=1.0"
|
|
||||||
importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""}
|
|
||||||
Jinja2 = ">=2.11.1"
|
|
||||||
Markdown = ">=3.2.1,<3.4"
|
|
||||||
mergedeep = ">=1.3.4"
|
|
||||||
packaging = ">=20.5"
|
|
||||||
PyYAML = ">=5.1"
|
|
||||||
pyyaml-env-tag = ">=0.1"
|
|
||||||
watchdog = ">=2.0"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
i18n = ["babel (>=2.9.0)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mkdocs-autorefs"
|
|
||||||
version = "0.4.1"
|
|
||||||
description = "Automatically link across pages in MkDocs."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
Markdown = ">=3.3"
|
|
||||||
mkdocs = ">=1.1"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mkdocstrings"
|
|
||||||
version = "0.19.0"
|
|
||||||
description = "Automatic documentation from sources, for MkDocs."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
Jinja2 = ">=2.11.1"
|
|
||||||
Markdown = ">=3.3"
|
|
||||||
MarkupSafe = ">=1.1"
|
|
||||||
mkdocs = ">=1.2"
|
|
||||||
mkdocs-autorefs = ">=0.3.1"
|
|
||||||
mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""}
|
|
||||||
pymdown-extensions = ">=6.3"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
crystal = ["mkdocstrings-crystal (>=0.3.4)"]
|
|
||||||
python = ["mkdocstrings-python (>=0.5.2)"]
|
|
||||||
python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mkdocstrings-python"
|
|
||||||
version = "0.7.1"
|
|
||||||
description = "A Python handler for mkdocstrings."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
griffe = ">=0.11.1"
|
|
||||||
mkdocstrings = ">=0.19"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nodeenv"
|
|
||||||
version = "1.7.0"
|
|
||||||
description = "Node.js virtual environment builder"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
setuptools = "*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "packaging"
|
|
||||||
version = "21.3"
|
|
||||||
description = "Core utilities for Python packages"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "passlib"
|
|
||||||
version = "1.7.4"
|
|
||||||
description = "comprehensive password hashing framework supporting over 30 schemes"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
argon2 = ["argon2-cffi (>=18.2.0)"]
|
|
||||||
bcrypt = ["bcrypt (>=3.1.0)"]
|
|
||||||
build_docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"]
|
|
||||||
totp = ["cryptography"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "platformdirs"
|
|
||||||
version = "2.5.2"
|
|
||||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"]
|
|
||||||
test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pre-commit"
|
|
||||||
version = "2.20.0"
|
|
||||||
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
cfgv = ">=2.0.0"
|
|
||||||
identify = ">=1.0.0"
|
|
||||||
nodeenv = ">=0.11.1"
|
|
||||||
pyyaml = ">=5.1"
|
|
||||||
toml = "*"
|
|
||||||
virtualenv = ">=20.0.8"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pyaml"
|
|
||||||
version = "21.10.1"
|
|
||||||
description = "PyYAML-based module to produce pretty and readable YAML-serialized data"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
PyYAML = "*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pycparser"
|
|
||||||
version = "2.21"
|
|
||||||
description = "C parser in Python"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pymdown-extensions"
|
|
||||||
version = "9.6"
|
|
||||||
description = "Extension pack for Python Markdown."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
markdown = ">=3.2"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pyparsing"
|
|
||||||
version = "3.0.9"
|
|
||||||
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6.8"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
diagrams = ["jinja2", "railroad-diagrams"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "python-dateutil"
|
|
||||||
version = "2.8.2"
|
|
||||||
description = "Extensions to the standard Python datetime module"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
six = ">=1.5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "PyYAML"
|
|
||||||
version = "6.0"
|
|
||||||
description = "YAML parser and emitter for Python"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pyyaml_env_tag"
|
|
||||||
version = "0.1"
|
|
||||||
description = "A custom YAML tag for referencing environment variables in YAML files. "
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
pyyaml = "*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rfc3986"
|
|
||||||
version = "1.5.0"
|
|
||||||
description = "Validating URI References per RFC 3986"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
idna = {version = "*", optional = true, markers = "extra == \"idna2008\""}
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
idna2008 = ["idna"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "setuptools"
|
|
||||||
version = "65.4.1"
|
|
||||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
|
||||||
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
|
||||||
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "six"
|
|
||||||
version = "1.16.0"
|
|
||||||
description = "Python 2 and 3 compatibility utilities"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sniffio"
|
|
||||||
version = "1.3.0"
|
|
||||||
description = "Sniff out which async library your code is running under"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml"
|
|
||||||
version = "0.10.2"
|
|
||||||
description = "Python Library for Tom's Obvious, Minimal Language"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "typing-extensions"
|
|
||||||
version = "4.3.0"
|
|
||||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "virtualenv"
|
|
||||||
version = "20.16.5"
|
|
||||||
description = "Virtual Python Environment builder"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
distlib = ">=0.3.5,<1"
|
|
||||||
filelock = ">=3.4.1,<4"
|
|
||||||
platformdirs = ">=2.4,<3"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"]
|
|
||||||
testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "watchdog"
|
|
||||||
version = "2.1.9"
|
|
||||||
description = "Filesystem events monitoring"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
watchmedo = ["PyYAML (>=3.10)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zipp"
|
|
||||||
version = "3.8.1"
|
|
||||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"]
|
|
||||||
testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
|
|
||||||
|
|
||||||
[metadata]
|
|
||||||
lock-version = "1.1"
|
|
||||||
python-versions = "^3.9"
|
|
||||||
content-hash = "98c3026a6f27c29c0357bbfa07166d6d8b604a869f3a802adc3bb3610f86964c"
|
|
||||||
|
|
||||||
[metadata.files]
|
|
||||||
anyio = [
|
|
||||||
{file = "anyio-3.6.1-py3-none-any.whl", hash = "sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be"},
|
|
||||||
{file = "anyio-3.6.1.tar.gz", hash = "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b"},
|
|
||||||
]
|
|
||||||
asyncssh = [
|
|
||||||
{file = "asyncssh-2.12.0-py3-none-any.whl", hash = "sha256:6841c4242c606fd51188c974ec2f4887efeec67ecdfa5b84140711dacd985ab3"},
|
|
||||||
{file = "asyncssh-2.12.0.tar.gz", hash = "sha256:274101322c4b941823aeed8e1ab6e7be5191686c6db2d2bd35afeba30505e780"},
|
|
||||||
]
|
|
||||||
certifi = [
|
|
||||||
{file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
|
|
||||||
{file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
|
|
||||||
]
|
|
||||||
cffi = [
|
|
||||||
{file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
|
|
||||||
{file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
|
|
||||||
{file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
|
|
||||||
{file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"},
|
|
||||||
{file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
|
|
||||||
{file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"},
|
|
||||||
{file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"},
|
|
||||||
{file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"},
|
|
||||||
{file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"},
|
|
||||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"},
|
|
||||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"},
|
|
||||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"},
|
|
||||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"},
|
|
||||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"},
|
|
||||||
{file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
|
|
||||||
{file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
|
|
||||||
{file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"},
|
|
||||||
{file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
|
|
||||||
{file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
|
|
||||||
{file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"},
|
|
||||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
|
|
||||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"},
|
|
||||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"},
|
|
||||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
|
|
||||||
{file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"},
|
|
||||||
{file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"},
|
|
||||||
{file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"},
|
|
||||||
{file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
|
|
||||||
{file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"},
|
|
||||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"},
|
|
||||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"},
|
|
||||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"},
|
|
||||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
|
|
||||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
|
|
||||||
{file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"},
|
|
||||||
{file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"},
|
|
||||||
{file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"},
|
|
||||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"},
|
|
||||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"},
|
|
||||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"},
|
|
||||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
|
|
||||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"},
|
|
||||||
{file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"},
|
|
||||||
{file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
|
|
||||||
{file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"},
|
|
||||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
|
|
||||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"},
|
|
||||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"},
|
|
||||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"},
|
|
||||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"},
|
|
||||||
{file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"},
|
|
||||||
{file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"},
|
|
||||||
{file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
|
|
||||||
{file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
|
|
||||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"},
|
|
||||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"},
|
|
||||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"},
|
|
||||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"},
|
|
||||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
|
|
||||||
{file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"},
|
|
||||||
{file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"},
|
|
||||||
{file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"},
|
|
||||||
{file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
|
|
||||||
{file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
|
|
||||||
]
|
|
||||||
cfgv = [
|
|
||||||
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
|
|
||||||
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
|
|
||||||
]
|
|
||||||
click = [
|
|
||||||
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
|
||||||
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
|
||||||
]
|
|
||||||
colorama = [
|
|
||||||
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
|
|
||||||
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
|
|
||||||
]
|
|
||||||
cryptography = [
|
|
||||||
{file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f"},
|
|
||||||
{file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad"},
|
|
||||||
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153"},
|
|
||||||
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407"},
|
|
||||||
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e"},
|
|
||||||
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0"},
|
|
||||||
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd"},
|
|
||||||
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6"},
|
|
||||||
{file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a"},
|
|
||||||
{file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294"},
|
|
||||||
{file = "cryptography-38.0.1-cp36-abi3-win32.whl", hash = "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0"},
|
|
||||||
{file = "cryptography-38.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a"},
|
|
||||||
{file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d"},
|
|
||||||
{file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9"},
|
|
||||||
{file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d"},
|
|
||||||
{file = "cryptography-38.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818"},
|
|
||||||
{file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6"},
|
|
||||||
{file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750"},
|
|
||||||
{file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013"},
|
|
||||||
{file = "cryptography-38.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5"},
|
|
||||||
{file = "cryptography-38.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61"},
|
|
||||||
{file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac"},
|
|
||||||
{file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb"},
|
|
||||||
{file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a"},
|
|
||||||
{file = "cryptography-38.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b"},
|
|
||||||
{file = "cryptography-38.0.1.tar.gz", hash = "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7"},
|
|
||||||
]
|
|
||||||
distlib = [
|
|
||||||
{file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"},
|
|
||||||
{file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"},
|
|
||||||
]
|
|
||||||
filelock = [
|
|
||||||
{file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"},
|
|
||||||
{file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"},
|
|
||||||
]
|
|
||||||
ghp-import = [
|
|
||||||
{file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"},
|
|
||||||
{file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"},
|
|
||||||
]
|
|
||||||
griffe = [
|
|
||||||
{file = "griffe-0.22.2-py3-none-any.whl", hash = "sha256:cea5415ac6a92f4a22638e3f1f2e661402bac09fb8e8266936d67185a7e0d0fb"},
|
|
||||||
{file = "griffe-0.22.2.tar.gz", hash = "sha256:1408e336a4155392bbd81eed9f2f44bf144e71b9c664e905630affe83bbc088e"},
|
|
||||||
]
|
|
||||||
h11 = [
|
|
||||||
{file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
|
|
||||||
{file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"},
|
|
||||||
]
|
|
||||||
httpcore = [
|
|
||||||
{file = "httpcore-0.15.0-py3-none-any.whl", hash = "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6"},
|
|
||||||
{file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"},
|
|
||||||
]
|
|
||||||
httpx = [
|
|
||||||
{file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"},
|
|
||||||
{file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"},
|
|
||||||
]
|
|
||||||
identify = [
|
|
||||||
{file = "identify-2.5.5-py2.py3-none-any.whl", hash = "sha256:ef78c0d96098a3b5fe7720be4a97e73f439af7cf088ebf47b620aeaa10fadf97"},
|
|
||||||
{file = "identify-2.5.5.tar.gz", hash = "sha256:322a5699daecf7c6fd60e68852f36f2ecbb6a36ff6e6e973e0d2bb6fca203ee6"},
|
|
||||||
]
|
|
||||||
idna = [
|
|
||||||
{file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
|
|
||||||
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
|
|
||||||
]
|
|
||||||
importlib-metadata = [
|
|
||||||
{file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"},
|
|
||||||
{file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"},
|
|
||||||
]
|
|
||||||
Jinja2 = [
|
|
||||||
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
|
|
||||||
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
|
|
||||||
]
|
|
||||||
Markdown = [
|
|
||||||
{file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"},
|
|
||||||
{file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"},
|
|
||||||
]
|
|
||||||
MarkupSafe = [
|
|
||||||
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
|
|
||||||
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
|
|
||||||
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
|
|
||||||
]
|
|
||||||
mergedeep = [
|
|
||||||
{file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
|
|
||||||
{file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
|
|
||||||
]
|
|
||||||
mkdocs = [
|
|
||||||
{file = "mkdocs-1.4.0-py3-none-any.whl", hash = "sha256:ce057e9992f017b8e1496b591b6c242cbd34c2d406e2f9af6a19b97dd6248faa"},
|
|
||||||
{file = "mkdocs-1.4.0.tar.gz", hash = "sha256:e5549a22d59e7cb230d6a791edd2c3d06690908454c0af82edc31b35d57e3069"},
|
|
||||||
]
|
|
||||||
mkdocs-autorefs = [
|
|
||||||
{file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"},
|
|
||||||
{file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"},
|
|
||||||
]
|
|
||||||
mkdocstrings = [
|
|
||||||
{file = "mkdocstrings-0.19.0-py3-none-any.whl", hash = "sha256:3217d510d385c961f69385a670b2677e68e07b5fea4a504d86bf54c006c87c7d"},
|
|
||||||
{file = "mkdocstrings-0.19.0.tar.gz", hash = "sha256:efa34a67bad11229d532d89f6836a8a215937548623b64f3698a1df62e01cc3e"},
|
|
||||||
]
|
|
||||||
mkdocstrings-python = [
|
|
||||||
{file = "mkdocstrings-python-0.7.1.tar.gz", hash = "sha256:c334b382dca202dfa37071c182418a6df5818356a95d54362a2b24822ca3af71"},
|
|
||||||
{file = "mkdocstrings_python-0.7.1-py3-none-any.whl", hash = "sha256:a22060bfa374697678e9af4e62b020d990dad2711c98f7a9fac5c0345bef93c7"},
|
|
||||||
]
|
|
||||||
nodeenv = [
|
|
||||||
{file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"},
|
|
||||||
{file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"},
|
|
||||||
]
|
|
||||||
packaging = [
|
|
||||||
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
|
||||||
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
|
||||||
]
|
|
||||||
passlib = [
|
|
||||||
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
|
|
||||||
{file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
|
|
||||||
]
|
|
||||||
platformdirs = [
|
|
||||||
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
|
|
||||||
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
|
|
||||||
]
|
|
||||||
pre-commit = [
|
|
||||||
{file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"},
|
|
||||||
{file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"},
|
|
||||||
]
|
|
||||||
pyaml = [
|
|
||||||
{file = "pyaml-21.10.1-py2.py3-none-any.whl", hash = "sha256:19985ed303c3a985de4cf8fd329b6d0a5a5b5c9035ea240eccc709ebacbaf4a0"},
|
|
||||||
{file = "pyaml-21.10.1.tar.gz", hash = "sha256:c6519fee13bf06e3bb3f20cacdea8eba9140385a7c2546df5dbae4887f768383"},
|
|
||||||
]
|
|
||||||
pycparser = [
|
|
||||||
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
|
|
||||||
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
|
|
||||||
]
|
|
||||||
pymdown-extensions = [
|
|
||||||
{file = "pymdown_extensions-9.6-py3-none-any.whl", hash = "sha256:1e36490adc7bfcef1fdb21bb0306e93af99cff8ec2db199bd17e3bf009768c11"},
|
|
||||||
{file = "pymdown_extensions-9.6.tar.gz", hash = "sha256:b956b806439bbff10f726103a941266beb03fbe99f897c7d5e774d7170339ad9"},
|
|
||||||
]
|
|
||||||
pyparsing = [
|
|
||||||
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
|
|
||||||
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
|
|
||||||
]
|
|
||||||
python-dateutil = [
|
|
||||||
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
|
|
||||||
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
|
|
||||||
]
|
|
||||||
PyYAML = [
|
|
||||||
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
|
|
||||||
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
|
|
||||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
|
|
||||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
|
|
||||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
|
|
||||||
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
|
|
||||||
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
|
|
||||||
{file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"},
|
|
||||||
{file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"},
|
|
||||||
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"},
|
|
||||||
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"},
|
|
||||||
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"},
|
|
||||||
{file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"},
|
|
||||||
{file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"},
|
|
||||||
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
|
|
||||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
|
|
||||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
|
|
||||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
|
|
||||||
{file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
|
|
||||||
{file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
|
|
||||||
{file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
|
|
||||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
|
|
||||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
|
|
||||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
|
|
||||||
{file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
|
|
||||||
{file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
|
|
||||||
{file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
|
|
||||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
|
|
||||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
|
|
||||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
|
|
||||||
{file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
|
|
||||||
{file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
|
|
||||||
{file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
|
|
||||||
{file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
|
|
||||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
|
|
||||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
|
|
||||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
|
|
||||||
{file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
|
|
||||||
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
|
|
||||||
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
|
|
||||||
]
|
|
||||||
pyyaml_env_tag = [
|
|
||||||
{file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
|
|
||||||
{file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
|
|
||||||
]
|
|
||||||
rfc3986 = [
|
|
||||||
{file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
|
|
||||||
{file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
|
|
||||||
]
|
|
||||||
setuptools = [
|
|
||||||
{file = "setuptools-65.4.1-py3-none-any.whl", hash = "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012"},
|
|
||||||
{file = "setuptools-65.4.1.tar.gz", hash = "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"},
|
|
||||||
]
|
|
||||||
six = [
|
|
||||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
|
||||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
|
||||||
]
|
|
||||||
sniffio = [
|
|
||||||
{file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
|
|
||||||
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
|
|
||||||
]
|
|
||||||
toml = [
|
|
||||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
|
||||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
|
||||||
]
|
|
||||||
typing-extensions = [
|
|
||||||
{file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
|
|
||||||
{file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
|
|
||||||
]
|
|
||||||
virtualenv = [
|
|
||||||
{file = "virtualenv-20.16.5-py3-none-any.whl", hash = "sha256:d07dfc5df5e4e0dbc92862350ad87a36ed505b978f6c39609dc489eadd5b0d27"},
|
|
||||||
{file = "virtualenv-20.16.5.tar.gz", hash = "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da"},
|
|
||||||
]
|
|
||||||
watchdog = [
|
|
||||||
{file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"},
|
|
||||||
{file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d"},
|
|
||||||
{file = "watchdog-2.1.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee3e38a6cc050a8830089f79cbec8a3878ec2fe5160cdb2dc8ccb6def8552658"},
|
|
||||||
{file = "watchdog-2.1.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64a27aed691408a6abd83394b38503e8176f69031ca25d64131d8d640a307591"},
|
|
||||||
{file = "watchdog-2.1.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:195fc70c6e41237362ba720e9aaf394f8178bfc7fa68207f112d108edef1af33"},
|
|
||||||
{file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bfc4d351e6348d6ec51df007432e6fe80adb53fd41183716017026af03427846"},
|
|
||||||
{file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8250546a98388cbc00c3ee3cc5cf96799b5a595270dfcfa855491a64b86ef8c3"},
|
|
||||||
{file = "watchdog-2.1.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:117ffc6ec261639a0209a3252546b12800670d4bf5f84fbd355957a0595fe654"},
|
|
||||||
{file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:97f9752208f5154e9e7b76acc8c4f5a58801b338de2af14e7e181ee3b28a5d39"},
|
|
||||||
{file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:247dcf1df956daa24828bfea5a138d0e7a7c98b1a47cf1fa5b0c3c16241fcbb7"},
|
|
||||||
{file = "watchdog-2.1.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:226b3c6c468ce72051a4c15a4cc2ef317c32590d82ba0b330403cafd98a62cfd"},
|
|
||||||
{file = "watchdog-2.1.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d9820fe47c20c13e3c9dd544d3706a2a26c02b2b43c993b62fcd8011bcc0adb3"},
|
|
||||||
{file = "watchdog-2.1.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:70af927aa1613ded6a68089a9262a009fbdf819f46d09c1a908d4b36e1ba2b2d"},
|
|
||||||
{file = "watchdog-2.1.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed80a1628cee19f5cfc6bb74e173f1b4189eb532e705e2a13e3250312a62e0c9"},
|
|
||||||
{file = "watchdog-2.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9f05a5f7c12452f6a27203f76779ae3f46fa30f1dd833037ea8cbc2887c60213"},
|
|
||||||
{file = "watchdog-2.1.9-py3-none-manylinux2014_armv7l.whl", hash = "sha256:255bb5758f7e89b1a13c05a5bceccec2219f8995a3a4c4d6968fe1de6a3b2892"},
|
|
||||||
{file = "watchdog-2.1.9-py3-none-manylinux2014_i686.whl", hash = "sha256:d3dda00aca282b26194bdd0adec21e4c21e916956d972369359ba63ade616153"},
|
|
||||||
{file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64.whl", hash = "sha256:186f6c55abc5e03872ae14c2f294a153ec7292f807af99f57611acc8caa75306"},
|
|
||||||
{file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:083171652584e1b8829581f965b9b7723ca5f9a2cd7e20271edf264cfd7c1412"},
|
|
||||||
{file = "watchdog-2.1.9-py3-none-manylinux2014_s390x.whl", hash = "sha256:b530ae007a5f5d50b7fbba96634c7ee21abec70dc3e7f0233339c81943848dc1"},
|
|
||||||
{file = "watchdog-2.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:4f4e1c4aa54fb86316a62a87b3378c025e228178d55481d30d857c6c438897d6"},
|
|
||||||
{file = "watchdog-2.1.9-py3-none-win32.whl", hash = "sha256:5952135968519e2447a01875a6f5fc8c03190b24d14ee52b0f4b1682259520b1"},
|
|
||||||
{file = "watchdog-2.1.9-py3-none-win_amd64.whl", hash = "sha256:7a833211f49143c3d336729b0020ffd1274078e94b0ae42e22f596999f50279c"},
|
|
||||||
{file = "watchdog-2.1.9-py3-none-win_ia64.whl", hash = "sha256:ad576a565260d8f99d97f2e64b0f97a48228317095908568a9d5c786c829d428"},
|
|
||||||
{file = "watchdog-2.1.9.tar.gz", hash = "sha256:43ce20ebb36a51f21fa376f76d1d4692452b2527ccd601950d69ed36b9e21609"},
|
|
||||||
]
|
|
||||||
zipp = [
|
|
||||||
{file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"},
|
|
||||||
{file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"},
|
|
||||||
]
|
|
||||||
@@ -1,22 +1,25 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
# ------------------------------------------------------------------------------
|
||||||
#
|
# 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.
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# -
|
||||||
#
|
# 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,
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
# See the License for the specific language governing permissions and
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
# limitations under the License.
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import warnings
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
import warnings
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from pyasic.errors import APIError, APIWarning
|
from pyasic.errors import APIError, APIWarning
|
||||||
@@ -29,11 +32,106 @@ 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")
|
||||||
return object.__new__(cls)
|
return object.__new__(cls)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.__class__.__name__}: {str(self.ip)}"
|
||||||
|
|
||||||
|
async def send_command(
|
||||||
|
self,
|
||||||
|
command: Union[str, bytes],
|
||||||
|
parameters: Union[str, int, bool] = None,
|
||||||
|
ignore_errors: bool = False,
|
||||||
|
allow_warning: bool = True,
|
||||||
|
**kwargs,
|
||||||
|
) -> dict:
|
||||||
|
"""Send an API command to the miner and return the result.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
command: The command to sent to the miner.
|
||||||
|
parameters: Any additional parameters to be sent with the command.
|
||||||
|
ignore_errors: Whether to raise APIError when the command returns an error.
|
||||||
|
allow_warning: Whether to warn if the command fails.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The return data from the API command parsed from JSON into a dict.
|
||||||
|
"""
|
||||||
|
logging.debug(
|
||||||
|
f"{self} - (Send Privileged Command) - {command} "
|
||||||
|
+ f"with args {parameters}"
|
||||||
|
if parameters
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
# create the command
|
||||||
|
cmd = {"command": command, **kwargs}
|
||||||
|
if parameters:
|
||||||
|
cmd["parameter"] = parameters
|
||||||
|
|
||||||
|
# send the command
|
||||||
|
data = await self._send_bytes(json.dumps(cmd).encode("utf-8"))
|
||||||
|
|
||||||
|
if data == b"Socket connect failed: Connection refused\n":
|
||||||
|
if not ignore_errors:
|
||||||
|
raise APIError(data.decode("utf-8"))
|
||||||
|
return {}
|
||||||
|
|
||||||
|
data = self._load_api_data(data)
|
||||||
|
|
||||||
|
# check for if the user wants to allow errors to return
|
||||||
|
if not ignore_errors:
|
||||||
|
# validate the command succeeded
|
||||||
|
validation = self._validate_command_output(data)
|
||||||
|
if not validation[0]:
|
||||||
|
if allow_warning:
|
||||||
|
logging.warning(
|
||||||
|
f"{self.ip}: API Command Error: {command}: {validation[1]}"
|
||||||
|
)
|
||||||
|
raise APIError(validation[1])
|
||||||
|
|
||||||
|
logging.debug(f"{self} - (Send Command) - Received data.")
|
||||||
|
return data
|
||||||
|
|
||||||
|
# Privileged command handler, only used by whatsminers, defined here for consistency.
|
||||||
|
async def send_privileged_command(self, *args, **kwargs) -> dict:
|
||||||
|
return await self.send_command(*args, **kwargs)
|
||||||
|
|
||||||
|
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||||
|
"""Creates and sends multiple commands as one command to the miner.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
*commands: The commands to send as a multicommand to the miner.
|
||||||
|
allow_warning: A boolean to supress APIWarnings.
|
||||||
|
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
# make sure we can actually run each command, otherwise they will fail
|
||||||
|
commands = self._check_commands(*commands)
|
||||||
|
# standard multicommand format is "command1+command2"
|
||||||
|
# standard format doesn't work for X19
|
||||||
|
command = "+".join(commands)
|
||||||
|
try:
|
||||||
|
data = await self.send_command(command, allow_warning=allow_warning)
|
||||||
|
except APIError as e:
|
||||||
|
# try to identify the error
|
||||||
|
if ":" in e.message:
|
||||||
|
err_command = e.message.split(":")[0]
|
||||||
|
if err_command in commands:
|
||||||
|
commands.remove(err_command)
|
||||||
|
continue
|
||||||
|
return {command: [{}] for command in commands}
|
||||||
|
logging.debug(f"{self} - (Multicommand) - Received data")
|
||||||
|
data["multicommand"] = True
|
||||||
|
return data
|
||||||
|
|
||||||
|
@property
|
||||||
|
def commands(self) -> list:
|
||||||
|
return self.get_commands()
|
||||||
|
|
||||||
def get_commands(self) -> list:
|
def get_commands(self) -> list:
|
||||||
"""Get a list of command accessible to a specific type of API on the miner.
|
"""Get a list of command accessible to a specific type of API on the miner.
|
||||||
|
|
||||||
@@ -45,9 +143,10 @@ class BaseMinerAPI:
|
|||||||
for func in
|
for func in
|
||||||
# each function in self
|
# each function in self
|
||||||
dir(self)
|
dir(self)
|
||||||
|
if not func == "commands"
|
||||||
if callable(getattr(self, func)) and
|
if callable(getattr(self, func)) and
|
||||||
# no __ methods
|
# no __ or _ methods
|
||||||
not func.startswith("__") and
|
not func.startswith("__") and not func.startswith("_") and
|
||||||
# remove all functions that are in this base class
|
# remove all functions that are in this base class
|
||||||
func
|
func
|
||||||
not in [
|
not in [
|
||||||
@@ -58,9 +157,10 @@ class BaseMinerAPI:
|
|||||||
]
|
]
|
||||||
|
|
||||||
def _check_commands(self, *commands):
|
def _check_commands(self, *commands):
|
||||||
allowed_commands = self.get_commands()
|
allowed_commands = self.commands
|
||||||
return_commands = []
|
return_commands = []
|
||||||
for command in [*commands]:
|
|
||||||
|
for command in commands:
|
||||||
if command in allowed_commands:
|
if command in allowed_commands:
|
||||||
return_commands.append(command)
|
return_commands.append(command)
|
||||||
else:
|
else:
|
||||||
@@ -71,108 +171,58 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
)
|
)
|
||||||
return return_commands
|
return return_commands
|
||||||
|
|
||||||
async def multicommand(
|
async def _send_bytes(self, data: bytes, timeout: int = 100) -> bytes:
|
||||||
self, *commands: str, ignore_x19_error: bool = False
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Sending")
|
||||||
) -> dict:
|
|
||||||
"""Creates and sends multiple commands as one command to the miner.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
*commands: The commands to send as a multicommand to the miner.
|
|
||||||
ignore_x19_error: Whether or not to ignore errors raised by x19 miners when using the "+" delimited style.
|
|
||||||
"""
|
|
||||||
logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}")
|
|
||||||
# make sure we can actually run each command, otherwise they will fail
|
|
||||||
commands = self._check_commands(*commands)
|
|
||||||
# standard multicommand format is "command1+command2"
|
|
||||||
# doesnt work for S19 which uses the backup _x19_multicommand
|
|
||||||
command = "+".join(commands)
|
|
||||||
try:
|
|
||||||
data = await self.send_command(command, x19_command=ignore_x19_error)
|
|
||||||
except APIError:
|
|
||||||
logging.debug(f"{self.ip}: Handling X19 multicommand.")
|
|
||||||
data = await self._x19_multicommand(*command.split("+"))
|
|
||||||
logging.debug(f"{self.ip}: Received multicommand data.")
|
|
||||||
return data
|
|
||||||
|
|
||||||
async def _x19_multicommand(self, *commands):
|
|
||||||
data = None
|
|
||||||
try:
|
|
||||||
data = {}
|
|
||||||
# send all commands individually
|
|
||||||
for cmd in commands:
|
|
||||||
data[cmd] = []
|
|
||||||
data[cmd].append(await self.send_command(cmd, x19_command=True))
|
|
||||||
except APIError as e:
|
|
||||||
raise APIError(e)
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning(f"{self.ip}: API Multicommand Error: {e}")
|
|
||||||
return data
|
|
||||||
|
|
||||||
async def send_command(
|
|
||||||
self,
|
|
||||||
command: Union[str, bytes],
|
|
||||||
parameters: Union[str, int, bool] = None,
|
|
||||||
ignore_errors: bool = False,
|
|
||||||
x19_command: bool = False,
|
|
||||||
) -> dict:
|
|
||||||
"""Send an API command to the miner and return the result.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
command: The command to sent to the miner.
|
|
||||||
parameters: Any additional parameters to be sent with the command.
|
|
||||||
ignore_errors: Whether or not to raise APIError when the command returns an error.
|
|
||||||
x19_command: Whether this is a command for an x19 that may be an issue (such as a "+" delimited multicommand)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The return data from the API command parsed from JSON into a dict.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
# get reader and writer streams
|
# get reader and writer streams
|
||||||
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
|
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
|
||||||
# handle OSError 121
|
# handle OSError 121
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
if e.winerror == "121":
|
if e.errno == 121:
|
||||||
logging.warning("Semaphore Timeout has Expired.")
|
logging.warning(
|
||||||
return {}
|
f"{self} - ([Hidden] Send Bytes) - Semaphore timeout expired."
|
||||||
|
)
|
||||||
# create the command
|
return b"{}"
|
||||||
cmd = {"command": command}
|
|
||||||
if parameters:
|
|
||||||
cmd["parameter"] = parameters
|
|
||||||
|
|
||||||
# send the command
|
# send the command
|
||||||
writer.write(json.dumps(cmd).encode("utf-8"))
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Writing")
|
||||||
|
writer.write(data)
|
||||||
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Draining")
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
|
try:
|
||||||
# instantiate data
|
ret_data = await asyncio.wait_for(reader.read(4096), timeout=timeout)
|
||||||
data = b""
|
except ConnectionAbortedError:
|
||||||
|
return b"{}"
|
||||||
|
try:
|
||||||
|
# Fix for stupid whatsminer bug, reboot/restart seem to not load properly in the loop
|
||||||
|
# have to receive, save the data, check if there is more data by reading with a short timeout
|
||||||
|
# append that data if there is more, and then onto the main loop.
|
||||||
|
ret_data += await asyncio.wait_for(reader.read(1), timeout=1)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
return ret_data
|
||||||
|
|
||||||
# loop to receive all the data
|
# loop to receive all the data
|
||||||
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Receiving")
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
d = await reader.read(4096)
|
try:
|
||||||
if not d:
|
d = await asyncio.wait_for(reader.read(4096), timeout=timeout)
|
||||||
break
|
if not d:
|
||||||
data += d
|
break
|
||||||
|
ret_data += d
|
||||||
|
except (asyncio.CancelledError, asyncio.TimeoutError) as e:
|
||||||
|
raise e
|
||||||
|
except (asyncio.CancelledError, asyncio.TimeoutError) as e:
|
||||||
|
raise e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"{self.ip}: API Command Error: - {e}")
|
logging.warning(f"{self} - ([Hidden] Send Bytes) - API Command Error {e}")
|
||||||
|
|
||||||
data = self._load_api_data(data)
|
|
||||||
|
|
||||||
# close the connection
|
# close the connection
|
||||||
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Closing")
|
||||||
writer.close()
|
writer.close()
|
||||||
await writer.wait_closed()
|
await writer.wait_closed()
|
||||||
|
|
||||||
# check for if the user wants to allow errors to return
|
return ret_data
|
||||||
if not ignore_errors:
|
|
||||||
# validate the command succeeded
|
|
||||||
validation = self._validate_command_output(data)
|
|
||||||
if not validation[0]:
|
|
||||||
if not x19_command:
|
|
||||||
logging.warning(f"{self.ip}: API Command Error: {validation[1]}")
|
|
||||||
raise APIError(validation[1])
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _validate_command_output(data: dict) -> tuple:
|
def _validate_command_output(data: dict) -> tuple:
|
||||||
@@ -203,32 +253,43 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _load_api_data(data: bytes) -> dict:
|
def _load_api_data(data: bytes) -> dict:
|
||||||
str_data = None
|
# some json from the API returns with a null byte (\x00) on the end
|
||||||
|
if data.endswith(b"\x00"):
|
||||||
|
# handle the null byte
|
||||||
|
str_data = data.decode("utf-8")[:-1]
|
||||||
|
else:
|
||||||
|
# no null byte
|
||||||
|
str_data = data.decode("utf-8")
|
||||||
|
# fix an error with a btminer return having an extra comma that breaks json.loads()
|
||||||
|
str_data = str_data.replace(",}", "}")
|
||||||
|
# fix an error with a btminer return having a newline that breaks json.loads()
|
||||||
|
str_data = str_data.replace("\n", "")
|
||||||
|
# fix an error with a bmminer return not having a specific comma that breaks json.loads()
|
||||||
|
str_data = str_data.replace("}{", "},{")
|
||||||
|
# fix an error with a bmminer return having a specific comma that breaks json.loads()
|
||||||
|
str_data = str_data.replace("[,{", "[{")
|
||||||
|
# fix an error with a btminer return having a missing comma. (2023-01-06 version)
|
||||||
|
str_data = str_data.replace('""temp0', '","temp0')
|
||||||
|
# fix an error with Avalonminers returning inf and nan
|
||||||
|
str_data = str_data.replace("info", "1nfo")
|
||||||
|
str_data = str_data.replace("inf", "0")
|
||||||
|
str_data = str_data.replace("1nfo", "info")
|
||||||
|
str_data = str_data.replace("nan", "0")
|
||||||
|
# fix whatever this garbage from avalonminers is `,"id":1}`
|
||||||
|
if str_data.startswith(","):
|
||||||
|
str_data = f"{{{str_data[1:]}"
|
||||||
|
# try to fix an error with overflowing the receive buffer
|
||||||
|
# this can happen in cases such as bugged btminers returning arbitrary length error info with 100s of errors.
|
||||||
|
if not str_data.endswith("}"):
|
||||||
|
str_data = ",".join(str_data.split(",")[:-1]) + "}"
|
||||||
|
|
||||||
|
# fix a really nasty bug with whatsminer API v2.0.4 where they return a list structured like a dict
|
||||||
|
if re.search(r"\"error_code\":\[\".+\"\]", str_data):
|
||||||
|
str_data = str_data.replace("[", "{").replace("]", "}")
|
||||||
|
|
||||||
|
# parse the json
|
||||||
try:
|
try:
|
||||||
# some json from the API returns with a null byte (\x00) on the end
|
|
||||||
if data.endswith(b"\x00"):
|
|
||||||
# handle the null byte
|
|
||||||
str_data = data.decode("utf-8")[:-1]
|
|
||||||
else:
|
|
||||||
# no null byte
|
|
||||||
str_data = data.decode("utf-8")
|
|
||||||
# fix an error with a btminer return having an extra comma that breaks json.loads()
|
|
||||||
str_data = str_data.replace(",}", "}")
|
|
||||||
# fix an error with a btminer return having a newline that breaks json.loads()
|
|
||||||
str_data = str_data.replace("\n", "")
|
|
||||||
# fix an error with a bmminer return not having a specific comma that breaks json.loads()
|
|
||||||
str_data = str_data.replace("}{", "},{")
|
|
||||||
# fix an error with a bmminer return having a specific comma that breaks json.loads()
|
|
||||||
str_data = str_data.replace("[,{", "[{")
|
|
||||||
# fix an error with Avalonminers returning inf and nan
|
|
||||||
str_data = str_data.replace("inf", "0")
|
|
||||||
str_data = str_data.replace("nan", "0")
|
|
||||||
# fix whatever this garbage from avalonminers is `,"id":1}`
|
|
||||||
if str_data.startswith(","):
|
|
||||||
str_data = f"{{{str_data[1:]}"
|
|
||||||
# parse the json
|
|
||||||
parsed_data = json.loads(str_data)
|
parsed_data = json.loads(str_data)
|
||||||
# handle bad json
|
|
||||||
except json.decoder.JSONDecodeError as e:
|
except json.decoder.JSONDecodeError as e:
|
||||||
raise APIError(f"Decode Error {e}: {str_data}")
|
raise APIError(f"Decode Error {e}: {str_data}")
|
||||||
return parsed_data
|
return parsed_data
|
||||||
|
|||||||
674
pyasic/API/bfgminer.py
Normal file
674
pyasic/API/bfgminer.py
Normal file
@@ -0,0 +1,674 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 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 logging
|
||||||
|
|
||||||
|
from pyasic.API import APIError, BaseMinerAPI
|
||||||
|
|
||||||
|
|
||||||
|
class BFGMinerAPI(BaseMinerAPI):
|
||||||
|
"""An abstraction of the BFGMiner API.
|
||||||
|
|
||||||
|
Each method corresponds to an API command in BFGMiner.
|
||||||
|
|
||||||
|
[BFGMiner API documentation](https://github.com/luke-jr/bfgminer/blob/bfgminer/README.RPC)
|
||||||
|
|
||||||
|
This class abstracts use of the BFGMiner 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):
|
||||||
|
super().__init__(ip, port)
|
||||||
|
self.api_ver = api_ver
|
||||||
|
|
||||||
|
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||||
|
# make sure we can actually run each command, otherwise they will fail
|
||||||
|
commands = self._check_commands(*commands)
|
||||||
|
# standard multicommand format is "command1+command2"
|
||||||
|
# doesn't work for S19 which uses the backup _x19_multicommand
|
||||||
|
command = "+".join(commands)
|
||||||
|
try:
|
||||||
|
data = await self.send_command(command, allow_warning=allow_warning)
|
||||||
|
except APIError:
|
||||||
|
logging.debug(f"{self} - (Multicommand) - Handling X19 multicommand.")
|
||||||
|
data = await self._x19_multicommand(*command.split("+"))
|
||||||
|
data["multicommand"] = True
|
||||||
|
return data
|
||||||
|
|
||||||
|
async def _x19_multicommand(self, *commands) -> dict:
|
||||||
|
data = None
|
||||||
|
try:
|
||||||
|
data = {}
|
||||||
|
# send all commands individually
|
||||||
|
for cmd in commands:
|
||||||
|
data[cmd] = []
|
||||||
|
data[cmd].append(await self.send_command(cmd, allow_warning=True))
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(
|
||||||
|
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
|
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 config(self) -> dict:
|
||||||
|
"""Get some basic configuration info.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
## Some miner configuration information:
|
||||||
|
* ASC Count <- the number of ASCs
|
||||||
|
* PGA Count <- the number of PGAs
|
||||||
|
* Pool Count <- the number of Pools
|
||||||
|
* Strategy <- the current pool strategy
|
||||||
|
* Log Interval <- the interval of logging
|
||||||
|
* Device Code <- list of compiled device drivers
|
||||||
|
* OS <- the current operating system
|
||||||
|
* Failover-Only <- failover-only setting
|
||||||
|
* Scan Time <- scan-time setting
|
||||||
|
* Queue <- queue setting
|
||||||
|
* Expiry <- expiry setting
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("config")
|
||||||
|
|
||||||
|
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 pools(self) -> dict:
|
||||||
|
"""Get pool information.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Miner pool information.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("pools")
|
||||||
|
|
||||||
|
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 procs(self) -> dict:
|
||||||
|
"""Get data on each processor with their details.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data on each processor with their details.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("procs")
|
||||||
|
|
||||||
|
async def devscan(self, info: str = "") -> dict:
|
||||||
|
"""Get data on each processor with their details.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
info: Info to scan for device by.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data on each processor with their details.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("devscan", parameters=info)
|
||||||
|
|
||||||
|
async def pga(self, n: int) -> dict:
|
||||||
|
"""Get data from PGA n.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: The PGA number to get data from.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data on the PGA n.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("pga", parameters=n)
|
||||||
|
|
||||||
|
async def proc(self, n: int = 0) -> dict:
|
||||||
|
"""Get data processor n.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: The processor to get data on.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data on processor n.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("proc", parameters=n)
|
||||||
|
|
||||||
|
async def pgacount(self) -> dict:
|
||||||
|
"""Get data fon all PGAs.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data on the PGAs connected.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("pgacount")
|
||||||
|
|
||||||
|
async def proccount(self) -> dict:
|
||||||
|
"""Get data fon all processors.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data on the processors connected.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("proccount")
|
||||||
|
|
||||||
|
async def switchpool(self, n: int) -> dict:
|
||||||
|
"""Switch pools to pool n.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: The pool to switch to.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of switching to pool n.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("switchpool", parameters=n)
|
||||||
|
|
||||||
|
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 addpool(self, url: str, username: str, password: str) -> dict:
|
||||||
|
"""Add a pool to the miner.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
url: The URL of the new pool to add.
|
||||||
|
username: The users username on the new pool.
|
||||||
|
password: The worker password on the new pool.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of adding the pool.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command(
|
||||||
|
"addpool", parameters=f"{url},{username},{password}"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def poolpriority(self, *n: int) -> dict:
|
||||||
|
"""Set pool priority.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
*n: Pools in order of priority.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of setting pool priority.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
pools = f"{','.join([str(item) for item in n])}"
|
||||||
|
return await self.send_command("poolpriority", parameters=pools)
|
||||||
|
|
||||||
|
async def poolquota(self, n: int, q: int) -> dict:
|
||||||
|
"""Set pool quota.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: Pool number to set quota on.
|
||||||
|
q: Quota to set the pool to.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of setting pool quota.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("poolquota", parameters=f"{n},{q}")
|
||||||
|
|
||||||
|
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 removepool(self, n: int) -> dict:
|
||||||
|
"""Remove a pool.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: Pool to remove.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of removing the pool.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("removepool", parameters=n)
|
||||||
|
|
||||||
|
async def save(self, filename: str = None) -> dict:
|
||||||
|
"""Save the config.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
filename: Filename to save the config as.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of saving the config.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
if filename:
|
||||||
|
return await self.send_command("save", parameters=filename)
|
||||||
|
else:
|
||||||
|
return await self.send_command("save")
|
||||||
|
|
||||||
|
async def quit(self) -> dict:
|
||||||
|
"""Quit CGMiner.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A single "BYE" before CGMiner quits.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("quit")
|
||||||
|
|
||||||
|
async def notify(self) -> dict:
|
||||||
|
"""Notify the user of past errors.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The last status and count of each devices problem(s).
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("notify")
|
||||||
|
|
||||||
|
async def privileged(self) -> dict:
|
||||||
|
"""Check if you have privileged access.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The STATUS section with an error if you have no privileged access, or success if you have privileged access.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("privileged")
|
||||||
|
|
||||||
|
async def pgaenable(self, n: int) -> dict:
|
||||||
|
"""Enable PGA n.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: The PGA to enable.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of enabling PGA n.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("pgaenable", parameters=n)
|
||||||
|
|
||||||
|
async def pgadisable(self, n: int) -> dict:
|
||||||
|
"""Disable PGA n.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: The PGA to disable.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of disabling PGA n.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("pgadisable", parameters=n)
|
||||||
|
|
||||||
|
async def pgarestart(self, n: int) -> dict:
|
||||||
|
"""Restart PGA n.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: The PGA to restart.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of restarting PGA n.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("pgadisable", parameters=n)
|
||||||
|
|
||||||
|
async def pgaidentify(self, n: int) -> dict:
|
||||||
|
"""Identify PGA n.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: The PGA to identify.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of identifying PGA n.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("pgaidentify", parameters=n)
|
||||||
|
|
||||||
|
async def procenable(self, n: int) -> dict:
|
||||||
|
"""Enable processor n.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: The processor to enable.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of enabling processor n.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("procenable", parameters=n)
|
||||||
|
|
||||||
|
async def procdisable(self, n: int) -> dict:
|
||||||
|
"""Disable processor n.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: The processor to disable.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of disabling processor n.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("procdisable", parameters=n)
|
||||||
|
|
||||||
|
async def procrestart(self, n: int) -> dict:
|
||||||
|
"""Restart processor n.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: The processor to restart.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of restarting processor n.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("procdisable", parameters=n)
|
||||||
|
|
||||||
|
async def procidentify(self, n: int) -> dict:
|
||||||
|
"""Identify processor n.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: The processor to identify.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of identifying processor n.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("procidentify", parameters=n)
|
||||||
|
|
||||||
|
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 restart(self) -> dict:
|
||||||
|
"""Restart CGMiner using the API.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A reply informing of the restart.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("restart")
|
||||||
|
|
||||||
|
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 check(self, command: str) -> dict:
|
||||||
|
"""Check if the command command exists in CGMiner.
|
||||||
|
<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 failover_only(self, failover: bool) -> dict:
|
||||||
|
"""Set failover-only.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
failover: What to set failover-only to.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Confirmation of setting failover-only.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("failover-only", parameters=failover)
|
||||||
|
|
||||||
|
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 debug(self, setting: str) -> dict:
|
||||||
|
"""Set a debug setting.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
setting: Which setting to switch to.
|
||||||
|
## Options are:
|
||||||
|
* Silent
|
||||||
|
* Quiet
|
||||||
|
* Verbose
|
||||||
|
* Debug
|
||||||
|
* RPCProto
|
||||||
|
* PerDevice
|
||||||
|
* WorkTime
|
||||||
|
* Normal
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data on which debug setting was enabled or disabled.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("debug", parameters=setting)
|
||||||
|
|
||||||
|
async def setconfig(self, name: str, n: int) -> dict:
|
||||||
|
"""Set config of name to value n.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
name: The name of the config setting to set.
|
||||||
|
## Options are:
|
||||||
|
* queue
|
||||||
|
* scantime
|
||||||
|
* expiry
|
||||||
|
n: The value to set the 'name' setting to.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The results of setting config of name to n.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("setconfig", parameters=f"{name},{n}")
|
||||||
|
|
||||||
|
async def pgaset(self, n: int, opt: str, val: int = None) -> dict:
|
||||||
|
"""Set PGA option opt to val on PGA n.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Options:
|
||||||
|
```
|
||||||
|
MMQ -
|
||||||
|
opt: clock
|
||||||
|
val: 2 - 250 (multiple of 2)
|
||||||
|
XBS -
|
||||||
|
opt: clock
|
||||||
|
val: 2 - 250 (multiple of 2)
|
||||||
|
```
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: The PGA to set the options on.
|
||||||
|
opt: The option to set. Setting this to 'help' returns a help message.
|
||||||
|
val: The value to set the option to.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Confirmation of setting PGA n with opt[,val].
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
if val:
|
||||||
|
return await self.send_command("pgaset", parameters=f"{n},{opt},{val}")
|
||||||
|
else:
|
||||||
|
return await self.send_command("pgaset", parameters=f"{n},{opt}")
|
||||||
|
|
||||||
|
async def pprocset(self, n: int, opt: str, val: int = None) -> dict:
|
||||||
|
"""Set processor option opt to val on processor n.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Options:
|
||||||
|
```
|
||||||
|
MMQ -
|
||||||
|
opt: clock
|
||||||
|
val: 2 - 250 (multiple of 2)
|
||||||
|
XBS -
|
||||||
|
opt: clock
|
||||||
|
val: 2 - 250 (multiple of 2)
|
||||||
|
```
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: The PGA to set the options on.
|
||||||
|
opt: The option to set. Setting this to 'help' returns a help message.
|
||||||
|
val: The value to set the option to.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Confirmation of setting PGA n with opt[,val].
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
if val:
|
||||||
|
return await self.send_command("pgaset", parameters=f"{n},{opt},{val}")
|
||||||
|
else:
|
||||||
|
return await self.send_command("pgaset", parameters=f"{n},{opt}")
|
||||||
|
|
||||||
|
async def zero(self, which: str, summary: bool) -> dict:
|
||||||
|
"""Zero a device.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
which: Which device to zero. Setting this to 'all' zeros all devices. Setting this to 'bestshare' zeros only the bestshare values for each pool and global.
|
||||||
|
summary: Whether or not to show a full summary.
|
||||||
|
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
the STATUS section with info on the zero and optional summary.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("zero", parameters=f"{which},{summary}")
|
||||||
@@ -1,18 +1,21 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
# ------------------------------------------------------------------------------
|
||||||
#
|
# 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.
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# -
|
||||||
#
|
# 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,
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
# See the License for the specific language governing permissions and
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
# limitations under the License.
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
import logging
|
||||||
|
|
||||||
from pyasic.API import BaseMinerAPI
|
from pyasic.API import APIError, BaseMinerAPI
|
||||||
|
|
||||||
|
|
||||||
class BMMinerAPI(BaseMinerAPI):
|
class BMMinerAPI(BaseMinerAPI):
|
||||||
@@ -33,8 +36,43 @@ class BMMinerAPI(BaseMinerAPI):
|
|||||||
port: The port to reference the API on. Default is 4028.
|
port: The port to reference the API on. Default is 4028.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ip: str, port: int = 4028) -> None:
|
def __init__(self, ip: str, api_ver: str = "0.0.0", port: int = 4028) -> None:
|
||||||
super().__init__(ip, port)
|
super().__init__(ip, port=port)
|
||||||
|
self.api_ver = api_ver
|
||||||
|
|
||||||
|
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||||
|
# make sure we can actually run each command, otherwise they will fail
|
||||||
|
commands = self._check_commands(*commands)
|
||||||
|
# standard multicommand format is "command1+command2"
|
||||||
|
# doesn't work for S19 which uses the backup _x19_multicommand
|
||||||
|
command = "+".join(commands)
|
||||||
|
try:
|
||||||
|
data = await self.send_command(command, allow_warning=allow_warning)
|
||||||
|
except APIError:
|
||||||
|
logging.debug(f"{self} - (Multicommand) - Handling X19 multicommand.")
|
||||||
|
data = await self._x19_multicommand(
|
||||||
|
*command.split("+"), allow_warning=allow_warning
|
||||||
|
)
|
||||||
|
data["multicommand"] = True
|
||||||
|
return data
|
||||||
|
|
||||||
|
async def _x19_multicommand(self, *commands, allow_warning: bool = True) -> dict:
|
||||||
|
data = None
|
||||||
|
try:
|
||||||
|
data = {}
|
||||||
|
# send all commands individually
|
||||||
|
for cmd in commands:
|
||||||
|
data[cmd] = []
|
||||||
|
data[cmd].append(
|
||||||
|
await self.send_command(cmd, allow_warning=allow_warning)
|
||||||
|
)
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(
|
||||||
|
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
async def version(self) -> dict:
|
async def version(self) -> dict:
|
||||||
"""Get miner version info.
|
"""Get miner version info.
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
# ------------------------------------------------------------------------------
|
||||||
#
|
# 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.
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# -
|
||||||
#
|
# 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,
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
# See the License for the specific language governing permissions and
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
# limitations under the License.
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.API import BaseMinerAPI
|
from pyasic.API import BaseMinerAPI
|
||||||
|
|
||||||
@@ -33,8 +35,9 @@ class BOSMinerAPI(BaseMinerAPI):
|
|||||||
port: The port to reference the API on. Default is 4028.
|
port: The port to reference the API on. Default is 4028.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ip: str, port: int = 4028):
|
def __init__(self, ip: str, api_ver: str = "0.0.0", port: int = 4028) -> None:
|
||||||
super().__init__(ip, port)
|
super().__init__(ip, port=port)
|
||||||
|
self.api_ver = api_ver
|
||||||
|
|
||||||
async def asccount(self) -> dict:
|
async def asccount(self) -> dict:
|
||||||
"""Get data on the number of ASC devices and their info.
|
"""Get data on the number of ASC devices and their info.
|
||||||
@@ -166,7 +169,7 @@ class BOSMinerAPI(BaseMinerAPI):
|
|||||||
return await self.send_command("estats")
|
return await self.send_command("estats")
|
||||||
|
|
||||||
async def check(self, command: str) -> dict:
|
async def check(self, command: str) -> dict:
|
||||||
"""Check if the command command exists in BOSMiner.
|
"""Check if the command `command` exists in BOSMiner.
|
||||||
<details>
|
<details>
|
||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +1,43 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
# ------------------------------------------------------------------------------
|
||||||
#
|
# 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.
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# -
|
||||||
#
|
# 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,
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
# See the License for the specific language governing permissions and
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
# limitations under the License.
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import re
|
|
||||||
import json
|
|
||||||
import hashlib
|
|
||||||
import binascii
|
|
||||||
import base64
|
import base64
|
||||||
|
import binascii
|
||||||
|
import datetime
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from passlib.handlers.md5_crypt import md5_crypt
|
|
||||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
|
from passlib.handlers.md5_crypt import md5_crypt
|
||||||
|
|
||||||
from pyasic.errors import APIError
|
|
||||||
from pyasic.API import BaseMinerAPI
|
from pyasic.API import BaseMinerAPI
|
||||||
|
from pyasic.errors import APIError
|
||||||
|
from pyasic.misc import api_min_version
|
||||||
from pyasic.settings import PyasicSettings
|
from pyasic.settings import PyasicSettings
|
||||||
|
|
||||||
|
|
||||||
### IMPORTANT ###
|
### IMPORTANT ###
|
||||||
# you need to change the password of the miners using the Whatsminer
|
# you need to change the password of the miners using the Whatsminer
|
||||||
# tool, then you can set them back to admin with this tool, but they
|
# tool, then you can set them back to admin with this tool, but they
|
||||||
# must be changed to something else and set back to admin with this
|
# must be changed to something else and set back to admin with this
|
||||||
# or the privileged API will not work using admin as the password. If
|
# or the privileged API will not work using admin as the password. If
|
||||||
# you change the password, you can pass that to the this class as pwd,
|
# you change the password, you can pass that to this class as pwd,
|
||||||
# or add it as the Whatsminer_pwd in the settings.toml file.
|
# or add it as the Whatsminer_pwd in the settings.toml file.
|
||||||
|
|
||||||
|
|
||||||
@@ -83,7 +86,7 @@ def _add_to_16(string: str) -> bytes:
|
|||||||
def parse_btminer_priviledge_data(token_data: dict, data: dict):
|
def parse_btminer_priviledge_data(token_data: dict, data: dict):
|
||||||
"""Parses data returned from the BTMiner privileged API.
|
"""Parses data returned from the BTMiner privileged API.
|
||||||
|
|
||||||
Parses data from the BTMiner privileged API using the the token
|
Parses data from the BTMiner privileged API using the token
|
||||||
from the API in an AES format.
|
from the API in an AES format.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
@@ -125,6 +128,7 @@ def create_privileged_cmd(token_data: dict, command: dict) -> bytes:
|
|||||||
Returns:
|
Returns:
|
||||||
The encrypted privileged command to be sent to the miner.
|
The encrypted privileged command to be sent to the miner.
|
||||||
"""
|
"""
|
||||||
|
logging.debug(f"(Create Prilileged Command) - Creating Privileged Command")
|
||||||
# add token to command
|
# add token to command
|
||||||
command["token"] = token_data["host_sign"]
|
command["token"] = token_data["host_sign"]
|
||||||
# encode host_passwd data and get hexdigest
|
# encode host_passwd data and get hexdigest
|
||||||
@@ -180,64 +184,84 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
ip: str,
|
ip: str,
|
||||||
|
api_ver: str = "0.0.0",
|
||||||
port: int = 4028,
|
port: int = 4028,
|
||||||
pwd: str = PyasicSettings().global_whatsminer_password,
|
pwd: str = PyasicSettings().global_whatsminer_password,
|
||||||
):
|
):
|
||||||
super().__init__(ip, port)
|
super().__init__(ip, port)
|
||||||
self.pwd = pwd
|
self.pwd = pwd
|
||||||
self.current_token = None
|
self.current_token = None
|
||||||
|
self.api_ver = api_ver
|
||||||
|
|
||||||
async def send_command(
|
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||||
|
"""Creates and sends multiple commands as one command to the miner.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
*commands: The commands to send as a multicommand to the miner.
|
||||||
|
allow_warning: A boolean to supress APIWarnings.
|
||||||
|
"""
|
||||||
|
# make sure we can actually run each command, otherwise they will fail
|
||||||
|
commands = self._check_commands(*commands)
|
||||||
|
# standard multicommand format is "command1+command2"
|
||||||
|
# commands starting with "get_" aren't supported, but we can fake that
|
||||||
|
get_commands_data = {}
|
||||||
|
for command in list(commands):
|
||||||
|
if command.startswith("get_"):
|
||||||
|
commands.remove(command)
|
||||||
|
# send seperately and append later
|
||||||
|
try:
|
||||||
|
get_commands_data[command] = [
|
||||||
|
await self.send_command(command, allow_warning=allow_warning)
|
||||||
|
]
|
||||||
|
except APIError:
|
||||||
|
get_commands_data[command] = [{}]
|
||||||
|
|
||||||
|
command = "+".join(commands)
|
||||||
|
try:
|
||||||
|
main_data = await self.send_command(command, allow_warning=allow_warning)
|
||||||
|
except APIError:
|
||||||
|
main_data = {command: [{}] for command in commands}
|
||||||
|
logging.debug(f"{self} - (Multicommand) - Received data")
|
||||||
|
|
||||||
|
data = dict(**main_data, **get_commands_data)
|
||||||
|
data["multicommand"] = True
|
||||||
|
return data
|
||||||
|
|
||||||
|
async def send_privileged_command(
|
||||||
self,
|
self,
|
||||||
command: Union[str, bytes],
|
command: Union[str, bytes],
|
||||||
parameters: Union[str, int, bool] = None,
|
|
||||||
ignore_errors: bool = False,
|
ignore_errors: bool = False,
|
||||||
|
timeout: int = 10,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
# check if command is a string
|
logging.debug(
|
||||||
# if its bytes its encoded and needs to be sent raw
|
f"{self} - (Send Privileged Command) - {command} " + f"with args {kwargs}"
|
||||||
if isinstance(command, str):
|
if len(kwargs) > 0
|
||||||
# if it is a string, put it into the standard command format
|
else ""
|
||||||
command = json.dumps({"command": command}).encode("utf-8")
|
)
|
||||||
|
command = {"cmd": command, **kwargs}
|
||||||
|
|
||||||
|
token_data = await self.get_token()
|
||||||
|
enc_command = create_privileged_cmd(token_data, command)
|
||||||
|
|
||||||
|
logging.debug(f"{self} - (Send Privileged Command) - Sending")
|
||||||
try:
|
try:
|
||||||
# get reader and writer streams
|
data = await self._send_bytes(enc_command, timeout)
|
||||||
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
|
except (asyncio.CancelledError, asyncio.TimeoutError):
|
||||||
# handle OSError 121
|
if ignore_errors:
|
||||||
except OSError as e:
|
return {}
|
||||||
if e.winerror == "121":
|
raise APIError("No data was returned from the API.")
|
||||||
print("Semaphore Timeout has Expired.")
|
|
||||||
return {}
|
|
||||||
|
|
||||||
# send the command
|
|
||||||
writer.write(command)
|
|
||||||
await writer.drain()
|
|
||||||
|
|
||||||
# instantiate data
|
|
||||||
data = b""
|
|
||||||
|
|
||||||
# loop to receive all the data
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
d = await reader.read(4096)
|
|
||||||
if not d:
|
|
||||||
break
|
|
||||||
data += d
|
|
||||||
except Exception as e:
|
|
||||||
logging.info(f"{str(self.ip)}: {e}")
|
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
if ignore_errors:
|
||||||
|
return {}
|
||||||
|
raise APIError("No data was returned from the API.")
|
||||||
data = self._load_api_data(data)
|
data = self._load_api_data(data)
|
||||||
|
|
||||||
# close the connection
|
try:
|
||||||
writer.close()
|
data = parse_btminer_priviledge_data(self.current_token, data)
|
||||||
await writer.wait_closed()
|
except Exception as e:
|
||||||
|
logging.info(f"{str(self.ip)}: {e}")
|
||||||
# check if the returned data is encoded
|
|
||||||
if "enc" in data.keys():
|
|
||||||
# try to parse the encoded data
|
|
||||||
try:
|
|
||||||
data = parse_btminer_priviledge_data(self.current_token, data)
|
|
||||||
except Exception as e:
|
|
||||||
logging.info(f"{str(self.ip)}: {e}")
|
|
||||||
|
|
||||||
if not ignore_errors:
|
if not ignore_errors:
|
||||||
# if it fails to validate, it is likely an error
|
# if it fails to validate, it is likely an error
|
||||||
@@ -257,6 +281,13 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
An encoded token and md5 password, which are used for the privileged API.
|
An encoded token and md5 password, which are used for the privileged API.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
|
logging.debug(f"{self} - (Get Token) - Getting token")
|
||||||
|
if self.current_token:
|
||||||
|
if self.current_token[
|
||||||
|
"timestamp"
|
||||||
|
] > datetime.datetime.now() - datetime.timedelta(minutes=30):
|
||||||
|
return self.current_token
|
||||||
|
|
||||||
# get the token
|
# get the token
|
||||||
data = await self.send_command("get_token")
|
data = await self.send_command("get_token")
|
||||||
|
|
||||||
@@ -278,7 +309,11 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
self.current_token = {
|
self.current_token = {
|
||||||
"host_sign": host_sign,
|
"host_sign": host_sign,
|
||||||
"host_passwd_md5": host_passwd_md5,
|
"host_passwd_md5": host_passwd_md5,
|
||||||
|
"timestamp": datetime.datetime.now(),
|
||||||
}
|
}
|
||||||
|
logging.debug(
|
||||||
|
f"{self} - (Get Token) - Gathered token data: {self.current_token}"
|
||||||
|
)
|
||||||
return self.current_token
|
return self.current_token
|
||||||
|
|
||||||
#### PRIVILEGED COMMANDS ####
|
#### PRIVILEGED COMMANDS ####
|
||||||
@@ -320,46 +355,18 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
A dict from the API to confirm the pools were updated.
|
A dict from the API to confirm the pools were updated.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
# get the token and password from the miner
|
return await self.send_privileged_command(
|
||||||
token_data = await self.get_token()
|
"update_pools",
|
||||||
|
pool1=pool_1,
|
||||||
# parse pool data
|
worker1=worker_1,
|
||||||
if not pool_1:
|
passwd1=passwd_1,
|
||||||
raise APIError("No pools set.")
|
pool2=pool_2,
|
||||||
elif pool_2 and pool_3:
|
worker2=worker_2,
|
||||||
command = {
|
passwd2=passwd_2,
|
||||||
"cmd": "update_pools",
|
pool3=pool_3,
|
||||||
"pool1": pool_1,
|
worker3=worker_3,
|
||||||
"worker1": worker_1,
|
passwd3=passwd_3,
|
||||||
"passwd1": passwd_1,
|
)
|
||||||
"pool2": pool_2,
|
|
||||||
"worker2": worker_2,
|
|
||||||
"passwd2": passwd_2,
|
|
||||||
"pool3": pool_3,
|
|
||||||
"worker3": worker_3,
|
|
||||||
"passwd3": passwd_3,
|
|
||||||
}
|
|
||||||
elif pool_2:
|
|
||||||
command = {
|
|
||||||
"cmd": "update_pools",
|
|
||||||
"pool1": pool_1,
|
|
||||||
"worker1": worker_1,
|
|
||||||
"passwd1": passwd_1,
|
|
||||||
"pool2": pool_2,
|
|
||||||
"worker2": worker_2,
|
|
||||||
"passwd2": passwd_2,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
command = {
|
|
||||||
"cmd": "update_pools",
|
|
||||||
"pool1": pool_1,
|
|
||||||
"worker1": worker_1,
|
|
||||||
"passwd1": passwd_1,
|
|
||||||
}
|
|
||||||
# encode the command with the token data
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
# send the command
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def restart(self) -> dict:
|
async def restart(self) -> dict:
|
||||||
"""Restart BTMiner using the API.
|
"""Restart BTMiner using the API.
|
||||||
@@ -373,10 +380,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
A reply informing of the restart.
|
A reply informing of the restart.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "restart_btminer"}
|
return await self.send_privileged_command("restart_btminer")
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def power_off(self, respbefore: bool = True) -> dict:
|
async def power_off(self, respbefore: bool = True) -> dict:
|
||||||
"""Power off the miner using the API.
|
"""Power off the miner using the API.
|
||||||
@@ -393,12 +397,8 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
if respbefore:
|
if respbefore:
|
||||||
command = {"cmd": "power_off", "respbefore": "true"}
|
return await self.send_privileged_command("power_off", respbefore="true")
|
||||||
else:
|
return await self.send_privileged_command("power_off", respbefore="false")
|
||||||
command = {"cmd": "power_off", "respbefore": "false"}
|
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def power_on(self) -> dict:
|
async def power_on(self) -> dict:
|
||||||
"""Power on the miner using the API.
|
"""Power on the miner using the API.
|
||||||
@@ -409,14 +409,10 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
the password of the miner using the Whatsminer tool.
|
the password of the miner using the Whatsminer tool.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
A reply informing of the status of powering on.
|
A reply informing of the status of powering on.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "power_on"}
|
return await self.send_privileged_command("power_on")
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def reset_led(self) -> dict:
|
async def reset_led(self) -> dict:
|
||||||
"""Reset the LED on the miner using the API.
|
"""Reset the LED on the miner using the API.
|
||||||
@@ -427,14 +423,10 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
changing the password of the miner using the Whatsminer tool.
|
changing the password of the miner using the Whatsminer tool.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
A reply informing of the status of resetting the LED.
|
A reply informing of the status of resetting the LED.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "set_led", "param": "auto"}
|
return await self.set_led(auto=True)
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def set_led(
|
async def set_led(
|
||||||
self,
|
self,
|
||||||
@@ -458,23 +450,14 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
duration: LED on time in the cycle in ms.
|
duration: LED on time in the cycle in ms.
|
||||||
start: LED on time offset in the cycle in ms.
|
start: LED on time offset in the cycle in ms.
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
A reply informing of the status of setting the LED.
|
A reply informing of the status of setting the LED.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
if not auto:
|
if auto:
|
||||||
command = {
|
return await self.send_privileged_command("set_led", param="auto")
|
||||||
"cmd": "set_led",
|
return await self.send_privileged_command(
|
||||||
"color": color,
|
"set_led", color=color, period=period, duration=duration, start=start
|
||||||
"period": period,
|
)
|
||||||
"duration": duration,
|
|
||||||
"start": start,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
command = {"cmd": "set_led", "param": "auto"}
|
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command, ignore_errors=True)
|
|
||||||
|
|
||||||
async def set_low_power(self) -> dict:
|
async def set_low_power(self) -> dict:
|
||||||
"""Set low power mode on the miner using the API.
|
"""Set low power mode on the miner using the API.
|
||||||
@@ -485,14 +468,10 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
changing the password of the miner using the Whatsminer tool.
|
changing the password of the miner using the Whatsminer tool.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
A reply informing of the status of setting low power mode.
|
A reply informing of the status of setting low power mode.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "set_low_power"}
|
return await self.send_privileged_command("set_low_power")
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def update_firmware(self): # noqa - static
|
async def update_firmware(self): # noqa - static
|
||||||
"""Not implemented."""
|
"""Not implemented."""
|
||||||
@@ -500,20 +479,23 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
# requires a file stream in bytes
|
# requires a file stream in bytes
|
||||||
return NotImplementedError
|
return NotImplementedError
|
||||||
|
|
||||||
async def reboot(self) -> dict:
|
async def reboot(self, timeout: int = 10) -> dict:
|
||||||
"""Reboot the miner using the API.
|
"""Reboot the miner using the API.
|
||||||
<details>
|
<details>
|
||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
A reply informing of the status of the reboot.
|
A reply informing of the status of the reboot.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "reboot"}
|
try:
|
||||||
token_data = await self.get_token()
|
d = await asyncio.wait_for(
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
self.send_privileged_command("reboot"), timeout=timeout
|
||||||
return await self.send_command(enc_command)
|
)
|
||||||
|
except (asyncio.CancelledError, asyncio.TimeoutError):
|
||||||
|
return {}
|
||||||
|
else:
|
||||||
|
return d
|
||||||
|
|
||||||
async def factory_reset(self) -> dict:
|
async def factory_reset(self) -> dict:
|
||||||
"""Reset the miner to factory defaults.
|
"""Reset the miner to factory defaults.
|
||||||
@@ -521,14 +503,10 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
A reply informing of the status of the reset.
|
A reply informing of the status of the reset.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "factory_reset"}
|
return await self.send_privileged_command("factory_reset")
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def update_pwd(self, old_pwd: str, new_pwd: str) -> dict:
|
async def update_pwd(self, old_pwd: str, new_pwd: str) -> dict:
|
||||||
"""Update the admin user's password.
|
"""Update the admin user's password.
|
||||||
@@ -545,7 +523,6 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
old_pwd: The old admin password.
|
old_pwd: The old admin password.
|
||||||
new_pwd: The new password to set.
|
new_pwd: The new password to set.
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
A reply informing of the status of setting the password.
|
A reply informing of the status of setting the password.
|
||||||
"""
|
"""
|
||||||
self.pwd = old_pwd
|
self.pwd = old_pwd
|
||||||
@@ -555,16 +532,32 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
f"New password too long, the max length is 8. "
|
f"New password too long, the max length is 8. "
|
||||||
f"Password size: {len(new_pwd.encode('utf-8'))}"
|
f"Password size: {len(new_pwd.encode('utf-8'))}"
|
||||||
)
|
)
|
||||||
command = {"cmd": "update_pwd", "old": old_pwd, "new": new_pwd}
|
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
try:
|
try:
|
||||||
data = await self.send_command(enc_command)
|
data = await self.send_privileged_command(
|
||||||
|
"update_pwd", old=old_pwd, new=new_pwd
|
||||||
|
)
|
||||||
except APIError as e:
|
except APIError as e:
|
||||||
raise e
|
raise e
|
||||||
self.pwd = new_pwd
|
self.pwd = new_pwd
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
async def net_config(
|
||||||
|
self,
|
||||||
|
ip: str = None,
|
||||||
|
mask: str = None,
|
||||||
|
gate: str = None,
|
||||||
|
dns: str = None,
|
||||||
|
host: str = None,
|
||||||
|
dhcp: bool = True,
|
||||||
|
):
|
||||||
|
if dhcp:
|
||||||
|
return await self.send_privileged_command("net_config", param="dhcp")
|
||||||
|
if None in [ip, mask, gate, dns, host]:
|
||||||
|
raise APIError("Incorrect parameters.")
|
||||||
|
return await self.send_privileged_command(
|
||||||
|
"net_config", ip=ip, mask=mask, gate=gate, dns=dns, host=host
|
||||||
|
)
|
||||||
|
|
||||||
async def set_target_freq(self, percent: int) -> dict:
|
async def set_target_freq(self, percent: int) -> dict:
|
||||||
"""Update the target frequency.
|
"""Update the target frequency.
|
||||||
|
|
||||||
@@ -578,7 +571,6 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
Parameters:
|
Parameters:
|
||||||
percent: The frequency % to set.
|
percent: The frequency % to set.
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
A reply informing of the status of setting the frequency.
|
A reply informing of the status of setting the frequency.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
@@ -588,10 +580,9 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
f"range. Please set a % between -10 and "
|
f"range. Please set a % between -10 and "
|
||||||
f"100"
|
f"100"
|
||||||
)
|
)
|
||||||
command = {"cmd": "set_target_freq", "percent": str(percent)}
|
return await self.send_privileged_command(
|
||||||
token_data = await self.get_token()
|
"set_target_freq", percent=str(percent)
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
)
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def enable_fast_boot(self) -> dict:
|
async def enable_fast_boot(self) -> dict:
|
||||||
"""Turn on fast boot.
|
"""Turn on fast boot.
|
||||||
@@ -603,14 +594,10 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
the miner using the Whatsminer tool.
|
the miner using the Whatsminer tool.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
A reply informing of the status of enabling fast boot.
|
A reply informing of the status of enabling fast boot.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "enable_btminer_fast_boot"}
|
return await self.send_privileged_command("enable_btminer_fast_boot")
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def disable_fast_boot(self) -> dict:
|
async def disable_fast_boot(self) -> dict:
|
||||||
"""Turn off fast boot.
|
"""Turn off fast boot.
|
||||||
@@ -622,14 +609,10 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
the miner using the Whatsminer tool.
|
the miner using the Whatsminer tool.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
A reply informing of the status of disabling fast boot.
|
A reply informing of the status of disabling fast boot.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "disable_btminer_fast_boot"}
|
return await self.send_privileged_command("disable_btminer_fast_boot")
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def enable_web_pools(self) -> dict:
|
async def enable_web_pools(self) -> dict:
|
||||||
"""Turn on web pool updates.
|
"""Turn on web pool updates.
|
||||||
@@ -641,14 +624,10 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
password of the miner using the Whatsminer tool.
|
password of the miner using the Whatsminer tool.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
A reply informing of the status of enabling web pools.
|
A reply informing of the status of enabling web pools.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "enable_web_pools"}
|
return await self.send_privileged_command("enable_web_pools")
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def disable_web_pools(self) -> dict:
|
async def disable_web_pools(self) -> dict:
|
||||||
"""Turn off web pool updates.
|
"""Turn off web pool updates.
|
||||||
@@ -660,14 +639,10 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
password of the miner using the Whatsminer tool.
|
password of the miner using the Whatsminer tool.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
A reply informing of the status of disabling web pools.
|
A reply informing of the status of disabling web pools.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "disable_web_pools"}
|
return await self.send_privileged_command("disable_web_pools")
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def set_hostname(self, hostname: str) -> dict:
|
async def set_hostname(self, hostname: str) -> dict:
|
||||||
"""Set the hostname of the miner.
|
"""Set the hostname of the miner.
|
||||||
@@ -681,17 +656,13 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
Parameters:
|
Parameters:
|
||||||
hostname: The new hostname to use.
|
hostname: The new hostname to use.
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
A reply informing of the status of setting the hostname.
|
A reply informing of the status of setting the hostname.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "set_hostname", "hostname": hostname}
|
return await self.send_privileged_command("set_hostname", hostname=hostname)
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def set_power_pct(self, percent: int) -> dict:
|
async def set_power_pct(self, percent: int) -> dict:
|
||||||
"""Set the power percentage of the miner.
|
"""Set the power percentage of the miner based on current power. Used for temporary adjustment.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
@@ -702,7 +673,6 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
Parameters:
|
Parameters:
|
||||||
percent: The power percentage to set.
|
percent: The power percentage to set.
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
A reply informing of the status of setting the power percentage.
|
A reply informing of the status of setting the power percentage.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
@@ -713,10 +683,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
f"range. Please set a % between 0 and "
|
f"range. Please set a % between 0 and "
|
||||||
f"100"
|
f"100"
|
||||||
)
|
)
|
||||||
command = {"cmd": "set_power_pct", "percent": str(percent)}
|
return await self.send_privileged_command("set_power_pct", percent=str(percent))
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def pre_power_on(self, complete: bool, msg: str) -> dict:
|
async def pre_power_on(self, complete: bool, msg: str) -> dict:
|
||||||
"""Configure or check status of pre power on.
|
"""Configure or check status of pre power on.
|
||||||
@@ -734,7 +701,6 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
* `adjust complete`
|
* `adjust complete`
|
||||||
* `adjust continue`
|
* `adjust continue`
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
A reply informing of the status of pre power on.
|
A reply informing of the status of pre power on.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
@@ -747,13 +713,133 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
'"adjust continue"]'
|
'"adjust continue"]'
|
||||||
)
|
)
|
||||||
if complete:
|
if complete:
|
||||||
complete = "true"
|
return await self.send_privileged_command(
|
||||||
else:
|
"pre_power_on", complete="true", msg=msg
|
||||||
complete = "false"
|
)
|
||||||
command = {"cmd": "pre_power_on", "complete": complete, "msg": msg}
|
return await self.send_privileged_command(
|
||||||
token_data = await self.get_token()
|
"pre_power_on", complete="false", msg=msg
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
)
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
### ADDED IN V2.0.5 Whatsminer API ###
|
||||||
|
@api_min_version("2.0.5")
|
||||||
|
async def set_temp_offset(self, temp_offset: int) -> dict:
|
||||||
|
"""Set the offset of miner hash board target temperature.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Set the offset of miner hash board target temperature, only works after
|
||||||
|
changing the password of the miner using the Whatsminer tool.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
temp_offset: Target temperature offset.
|
||||||
|
Returns:
|
||||||
|
A reply informing of the status of setting temp offset.
|
||||||
|
</details>
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not -30 < temp_offset < 0:
|
||||||
|
raise APIError(
|
||||||
|
f"Temp offset is outside of the allowed "
|
||||||
|
f"range. Please set a number between -30 and "
|
||||||
|
f"0."
|
||||||
|
)
|
||||||
|
|
||||||
|
return await self.send_privileged_command(
|
||||||
|
"set_temp_offset", temp_offset=temp_offset
|
||||||
|
)
|
||||||
|
|
||||||
|
@api_min_version("2.0.5")
|
||||||
|
async def adjust_power_limit(self, power_limit: int) -> dict:
|
||||||
|
"""Set the upper limit of the miner's power. Cannot be higher than the ordinary power of the machine.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Set the upper limit of the miner's power, only works after
|
||||||
|
changing the password of the miner using the Whatsminer tool.
|
||||||
|
The miner will reboot after this is set.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
power_limit: New power limit.
|
||||||
|
Returns:
|
||||||
|
A reply informing of the status of setting power limit.
|
||||||
|
</details>
|
||||||
|
|
||||||
|
"""
|
||||||
|
return await self.send_privileged_command(
|
||||||
|
"adjust_power_limit", power_limit=str(power_limit)
|
||||||
|
)
|
||||||
|
|
||||||
|
@api_min_version("2.0.5")
|
||||||
|
async def adjust_upfreq_speed(self, upfreq_speed: int) -> dict:
|
||||||
|
"""Set the upfreq speed, 0 is the normal speed, 9 is the fastest speed.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Set the upfreq speed, 0 is the normal speed, 9 is the fastest speed, only works after
|
||||||
|
changing the password of the miner using the Whatsminer tool.
|
||||||
|
The faster the speed, the greater the final hash rate and power deviation, and the stability
|
||||||
|
may be impacted. Fast boot mode cannot be used at the same time.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
upfreq_speed: New upfreq speed.
|
||||||
|
Returns:
|
||||||
|
A reply informing of the status of setting upfreq speed.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
if not 0 < upfreq_speed < 9:
|
||||||
|
raise APIError(
|
||||||
|
f"Upfreq speed is outside of the allowed "
|
||||||
|
f"range. Please set a number between 0 (Normal) and "
|
||||||
|
f"9 (Fastest)."
|
||||||
|
)
|
||||||
|
return await self.send_privileged_command(
|
||||||
|
"adjust_upfreq_speed", upfreq_speed=upfreq_speed
|
||||||
|
)
|
||||||
|
|
||||||
|
@api_min_version("2.0.5")
|
||||||
|
async def set_poweroff_cool(self, poweroff_cool: bool) -> dict:
|
||||||
|
"""Set whether to cool the machine when mining is stopped.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Set whether to cool the machine when mining is stopped, only works after
|
||||||
|
changing the password of the miner using the Whatsminer tool.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
poweroff_cool: Whether to cool the miner during power off mode.
|
||||||
|
Returns:
|
||||||
|
A reply informing of the status of setting power off cooling mode.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|
||||||
|
return await self.send_privileged_command(
|
||||||
|
"set_poweroff_cool", poweroff_cool=int(poweroff_cool)
|
||||||
|
)
|
||||||
|
|
||||||
|
@api_min_version("2.0.5")
|
||||||
|
async def set_fan_zero_speed(self, fan_zero_speed: bool) -> dict:
|
||||||
|
"""Sets whether the fan speed supports the lowest 0 speed.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Sets whether the fan speed supports the lowest 0 speed, only works after
|
||||||
|
changing the password of the miner using the Whatsminer tool.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
fan_zero_speed: Whether the fan is allowed to support 0 speed.
|
||||||
|
Returns:
|
||||||
|
A reply informing of the status of setting fan minimum speed.
|
||||||
|
</details>
|
||||||
|
|
||||||
|
"""
|
||||||
|
return await self.send_privileged_command(
|
||||||
|
"set_fan_zero_speed", fan_zero_speed=int(fan_zero_speed)
|
||||||
|
)
|
||||||
|
|
||||||
#### END privileged COMMANDS ####
|
#### END privileged COMMANDS ####
|
||||||
|
|
||||||
@@ -763,7 +849,6 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
Summary status of the miner.
|
Summary status of the miner.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
@@ -775,7 +860,6 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
Pool status of the miner.
|
Pool status of the miner.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
@@ -787,7 +871,6 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
Data on each PGA/ASC with their details.
|
Data on each PGA/ASC with their details.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
@@ -799,7 +882,6 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
Data on each PGA/ASC with their details.
|
Data on each PGA/ASC with their details.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
@@ -811,7 +893,6 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
Data on all devices with their static details.
|
Data on all devices with their static details.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
@@ -823,7 +904,6 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
Data on the PSU and power information.
|
Data on the PSU and power information.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
@@ -839,7 +919,6 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
with the other miner APIs.
|
with the other miner APIs.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
Version data for the miner.
|
Version data for the miner.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
@@ -851,7 +930,6 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
Version data for the miner.
|
Version data for the miner.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
@@ -863,7 +941,6 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
BTMiner status and firmware version.
|
BTMiner status and firmware version.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
@@ -875,8 +952,21 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
General miner info.
|
General miner info.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
return await self.send_command("get_miner_info")
|
return await self.send_command("get_miner_info", allow_warning=False)
|
||||||
|
|
||||||
|
@api_min_version("2.0.1")
|
||||||
|
async def get_error_code(self) -> dict:
|
||||||
|
"""Get a list of error codes from the miner.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
Get a list of error codes from the miner. Replaced `summary` as the location of error codes with API version 2.0.4.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A list of error codes on the miner.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("get_error_code", allow_warning=False)
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
# ------------------------------------------------------------------------------
|
||||||
#
|
# 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.
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# -
|
||||||
#
|
# 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,
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
# See the License for the specific language governing permissions and
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
# limitations under the License.
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.API import BaseMinerAPI
|
import logging
|
||||||
|
|
||||||
|
from pyasic.API import APIError, BaseMinerAPI
|
||||||
|
|
||||||
|
|
||||||
class CGMinerAPI(BaseMinerAPI):
|
class CGMinerAPI(BaseMinerAPI):
|
||||||
@@ -33,8 +37,39 @@ class CGMinerAPI(BaseMinerAPI):
|
|||||||
port: The port to reference the API on. Default is 4028.
|
port: The port to reference the API on. Default is 4028.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ip: str, port: int = 4028):
|
def __init__(self, ip: str, api_ver: str = "0.0.0", port: int = 4028):
|
||||||
super().__init__(ip, port)
|
super().__init__(ip, port)
|
||||||
|
self.api_ver = api_ver
|
||||||
|
|
||||||
|
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||||
|
# make sure we can actually run each command, otherwise they will fail
|
||||||
|
commands = self._check_commands(*commands)
|
||||||
|
# standard multicommand format is "command1+command2"
|
||||||
|
# doesn't work for S19 which uses the backup _x19_multicommand
|
||||||
|
command = "+".join(commands)
|
||||||
|
try:
|
||||||
|
data = await self.send_command(command, allow_warning=allow_warning)
|
||||||
|
except APIError:
|
||||||
|
logging.debug(f"{self} - (Multicommand) - Handling X19 multicommand.")
|
||||||
|
data = await self._x19_multicommand(*command.split("+"))
|
||||||
|
data["multicommand"] = True
|
||||||
|
return data
|
||||||
|
|
||||||
|
async def _x19_multicommand(self, *commands) -> dict:
|
||||||
|
data = None
|
||||||
|
try:
|
||||||
|
data = {}
|
||||||
|
# send all commands individually
|
||||||
|
for cmd in commands:
|
||||||
|
data[cmd] = []
|
||||||
|
data[cmd].append(await self.send_command(cmd, allow_warning=True))
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(
|
||||||
|
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
|
||||||
async def version(self) -> dict:
|
async def version(self) -> dict:
|
||||||
"""Get miner version info.
|
"""Get miner version info.
|
||||||
|
|||||||
759
pyasic/API/luxminer.py
Normal file
759
pyasic/API/luxminer.py
Normal file
@@ -0,0 +1,759 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
from pyasic.API import BaseMinerAPI
|
||||||
|
|
||||||
|
|
||||||
|
class LUXMinerAPI(BaseMinerAPI):
|
||||||
|
"""An abstraction of the LUXMiner API.
|
||||||
|
|
||||||
|
Each method corresponds to an API command in LUXMiner.
|
||||||
|
|
||||||
|
[LUXMiner API documentation](https://docs.firmware.luxor.tech/API/intro)
|
||||||
|
|
||||||
|
This class abstracts use of the LUXMiner API, as well as the
|
||||||
|
methods for sending commands to it. The `self.send_command()`
|
||||||
|
function handles sending a command to the miner asynchronously, and
|
||||||
|
as such is the base for many of the functions in this class, which
|
||||||
|
rely on it to send the command for them.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
ip: The IP of the miner to reference the API on.
|
||||||
|
port: The port to reference the API on. Default is 4028.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, ip: str, api_ver: str = "0.0.0", port: int = 4028) -> None:
|
||||||
|
super().__init__(ip, port=port)
|
||||||
|
self.api_ver = api_ver
|
||||||
|
|
||||||
|
async def addgroup(self, name: str, quota: int) -> dict:
|
||||||
|
"""Add a pool group.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
name: The group name.
|
||||||
|
quota: The group quota.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Confirmation of adding a pool group.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("addgroup", parameters=f"{name},{quota}")
|
||||||
|
|
||||||
|
async def addpool(
|
||||||
|
self, url: str, user: str, pwd: str = "", group_id: str = None
|
||||||
|
) -> dict:
|
||||||
|
"""Add a pool.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
url: The pool url.
|
||||||
|
user: The pool username.
|
||||||
|
pwd: The pool password.
|
||||||
|
group_id: The group ID to use.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Confirmation of adding a pool.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
pool_data = [url, user, pwd]
|
||||||
|
if group_id is not None:
|
||||||
|
pool_data.append(group_id)
|
||||||
|
return await self.send_command("addpool", parameters=",".join(pool_data))
|
||||||
|
|
||||||
|
async def asc(self, n: int) -> dict:
|
||||||
|
"""Get data for ASC device n.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: The device to get data for.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The data for ASC device n.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("asc", parameters=n)
|
||||||
|
|
||||||
|
async def asccount(self) -> dict:
|
||||||
|
"""Get data on the number of ASC devices and their info.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data on all ASC devices.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("asccount")
|
||||||
|
|
||||||
|
async def check(self, command: str) -> dict:
|
||||||
|
"""Check if the command `command` exists in LUXMiner.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
command: The command to check.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
## Information about a command:
|
||||||
|
* Exists (Y/N) <- the command exists in this version
|
||||||
|
* Access (Y/N) <- you have access to use the command
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("check", parameters=command)
|
||||||
|
|
||||||
|
async def coin(self) -> dict:
|
||||||
|
"""Get information on the current coin.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
## Information about the current coin being mined:
|
||||||
|
* Hash Method <- the hashing algorithm
|
||||||
|
* Current Block Time <- blocktime as a float, 0 means none
|
||||||
|
* Current Block Hash <- the hash of the current block, blank means none
|
||||||
|
* LP <- whether LP is in use on at least 1 pool
|
||||||
|
* Network Difficulty: the current network difficulty
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("coin")
|
||||||
|
|
||||||
|
async def config(self) -> dict:
|
||||||
|
"""Get some basic configuration info.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Miner configuration information.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("config")
|
||||||
|
|
||||||
|
async def curtail(self, session_id: str) -> dict:
|
||||||
|
"""Put the miner into sleep mode. Requires a session_id from logon.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
session_id: Session id from the logon command.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of putting the miner to sleep.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("curtail", parameters=session_id)
|
||||||
|
|
||||||
|
async def devdetails(self) -> dict:
|
||||||
|
"""Get data on all devices with their static details.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data on all devices with their static details.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("devdetails")
|
||||||
|
|
||||||
|
async def devs(self) -> dict:
|
||||||
|
"""Get data on each PGA/ASC with their details.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data on each PGA/ASC with their details.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("devs")
|
||||||
|
|
||||||
|
async def disablepool(self, n: int) -> dict:
|
||||||
|
"""Disable a pool.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: Pool to disable.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of diabling the pool.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("disablepool", parameters=n)
|
||||||
|
|
||||||
|
async def edevs(self) -> dict:
|
||||||
|
"""Alias for devs"""
|
||||||
|
return await self.devs()
|
||||||
|
|
||||||
|
async def enablepool(self, n: int) -> dict:
|
||||||
|
"""Enable pool n.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
n: The pool to enable.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of enabling pool n.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("enablepool", parameters=n)
|
||||||
|
|
||||||
|
async def estats(self) -> dict:
|
||||||
|
"""Alias for stats"""
|
||||||
|
return await self.stats()
|
||||||
|
|
||||||
|
async def fans(self) -> dict:
|
||||||
|
"""Get fan data.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data on the fans of the miner.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("fans")
|
||||||
|
|
||||||
|
async def fanset(self, session_id: str, speed: int, min_fans: int = None) -> dict:
|
||||||
|
"""Set fan control. Requires a session_id from logon.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
session_id: Session id from the logon command.
|
||||||
|
speed: The fan speed to set. Use -1 to set automatically.
|
||||||
|
min_fans: The minimum number of fans to use. Optional.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of setting fan control values.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
fanset_data = [str(session_id), str(speed)]
|
||||||
|
if min_fans is not None:
|
||||||
|
fanset_data.append(str(min_fans))
|
||||||
|
return await self.send_command("fanset", parameters=",".join(fanset_data))
|
||||||
|
|
||||||
|
async def frequencyget(self, board_n: int, chip_n: int = None) -> dict:
|
||||||
|
"""Get frequency data for a board and chips.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
board_n: The board number to get frequency info from.
|
||||||
|
chip_n: The chip number to get frequency info from. Optional.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Board and/or chip frequency values.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
frequencyget_data = [str(board_n)]
|
||||||
|
if chip_n is not None:
|
||||||
|
frequencyget_data.append(str(chip_n))
|
||||||
|
return await self.send_command(
|
||||||
|
"frequencyget", parameters=",".join(frequencyget_data)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def frequencyset(self, session_id: str, board_n: int, freq: int) -> dict:
|
||||||
|
"""Set frequency. Requires a session_id from logon.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
session_id: Session id from the logon command.
|
||||||
|
board_n: The board number to set frequency on.
|
||||||
|
freq: The frequency to set.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of setting frequency values.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command(
|
||||||
|
"frequencyset", parameters=f"{session_id},{board_n},{freq}"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def frequencystop(self, session_id: str, board_n: int) -> dict:
|
||||||
|
"""Stop set frequency. Requires a session_id from logon.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
session_id: Session id from the logon command.
|
||||||
|
board_n: The board number to set frequency on.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of stopping frequencyset value.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command(
|
||||||
|
"frequencystop", parameters=f"{session_id},{board_n}"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def groupquota(self, group_n: int, quota: int) -> dict:
|
||||||
|
"""Set a group's quota.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
group_n: The group number to set quota on.
|
||||||
|
quota: The quota to use.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of setting quota value.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("groupquota", parameters=f"{group_n},{quota}")
|
||||||
|
|
||||||
|
async def groups(self) -> dict:
|
||||||
|
"""Get pool group data.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data on the pool groups on the miner.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("groups")
|
||||||
|
|
||||||
|
async def healthchipget(self, board_n: int, chip_n: int = None) -> dict:
|
||||||
|
"""Get chip health.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
board_n: The board number to get chip health of.
|
||||||
|
chip_n: The chip number to get chip health of. Optional.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Chip health data.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
healthchipget_data = [str(board_n)]
|
||||||
|
if chip_n is not None:
|
||||||
|
healthchipget_data.append(str(chip_n))
|
||||||
|
return await self.send_command(
|
||||||
|
"healthchipget", parameters=",".join(healthchipget_data)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def healthchipset(
|
||||||
|
self, session_id: str, board_n: int, chip_n: int = None
|
||||||
|
) -> dict:
|
||||||
|
"""Select the next chip to have its health checked. Requires a session_id from logon.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
session_id: Session id from the logon command.
|
||||||
|
board_n: The board number to next get chip health of.
|
||||||
|
chip_n: The chip number to next get chip health of. Optional.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Confirmation of selecting the next health check chip.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
healthchipset_data = [session_id, str(board_n)]
|
||||||
|
if chip_n is not None:
|
||||||
|
healthchipset_data.append(str(chip_n))
|
||||||
|
return await self.send_command(
|
||||||
|
"healthchipset", parameters=",".join(healthchipset_data)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def healthctrl(self) -> dict:
|
||||||
|
"""Get health check config.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Health check config.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("healthctrl")
|
||||||
|
|
||||||
|
async def healthctrlset(
|
||||||
|
self, session_id: str, num_readings: int, amplified_factor: float
|
||||||
|
) -> dict:
|
||||||
|
"""Set health control config. Requires a session_id from logon.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
session_id: Session id from the logon command.
|
||||||
|
num_readings: The minimum number of readings for evaluation.
|
||||||
|
amplified_factor: Performance factor of the evaluation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of setting health control config.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command(
|
||||||
|
"healthctrlset",
|
||||||
|
parameters=f"{session_id},{num_readings},{amplified_factor}",
|
||||||
|
)
|
||||||
|
|
||||||
|
async def kill(self) -> dict:
|
||||||
|
"""Forced session kill. Use logoff instead.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of killing the active session.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("kill")
|
||||||
|
|
||||||
|
async def lcd(self) -> dict:
|
||||||
|
"""Get a general all-in-one status summary of the miner. Always zeros on LUXMiner.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An all-in-one status summary of the miner.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("lcd")
|
||||||
|
|
||||||
|
async def ledset(
|
||||||
|
self,
|
||||||
|
session_id: str,
|
||||||
|
color: Literal["red"],
|
||||||
|
state: Literal["on", "off", "blink"],
|
||||||
|
) -> dict:
|
||||||
|
"""Set led. Requires a session_id from logon.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
session_id: Session id from the logon command.
|
||||||
|
color: The color LED to set. Can be "red".
|
||||||
|
state: The state to set the LED to. Can be "on", "off", or "blink".
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of setting LED.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command(
|
||||||
|
"ledset", parameters=f"{session_id},{color},{state}"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def limits(self) -> dict:
|
||||||
|
"""Get max and min values of config parameters.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data on max and min values of config parameters.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("limits")
|
||||||
|
|
||||||
|
async def logoff(self, session_id: str) -> dict:
|
||||||
|
"""Log off of a session. Requires a session id from an active session.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
session_id: Session id from the logon command.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Confirmation of logging off a session.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("logoff", parameters=session_id)
|
||||||
|
|
||||||
|
async def logon(self) -> dict:
|
||||||
|
"""Get or create a session.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The Session ID to be used.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("logon")
|
||||||
|
|
||||||
|
async def pools(self) -> dict:
|
||||||
|
"""Get pool information.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Miner pool information.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("pools")
|
||||||
|
|
||||||
|
async def power(self) -> dict:
|
||||||
|
"""Get the estimated power usage in watts.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Estimated power usage in watts.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("power")
|
||||||
|
|
||||||
|
async def profiles(self) -> dict:
|
||||||
|
"""Get the available profiles.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data on available profiles.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("profiles")
|
||||||
|
|
||||||
|
async def profileset(self, session_id: str, board_n: int, profile: str) -> dict:
|
||||||
|
"""Set active profile for a board. Requires a session_id from logon.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
session_id: Session id from the logon command.
|
||||||
|
board_n: The board to set the profile on.
|
||||||
|
profile: The profile name to use.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of setting the profile on board_n.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command(
|
||||||
|
"profileset", parameters=f"{session_id},{board_n},{profile}"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def reboot(self, session_id: str, board_n: int, delay_s: int = None) -> dict:
|
||||||
|
"""Reboot a board. Requires a session_id from logon.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
session_id: Session id from the logon command.
|
||||||
|
board_n: The board to reboot.
|
||||||
|
delay_s: The number of seconds to delay until startup. If it is 0, the board will just stop. Optional.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of rebooting board_n.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
reboot_data = [session_id, str(board_n)]
|
||||||
|
if delay_s is not None:
|
||||||
|
reboot_data.append(str(delay_s))
|
||||||
|
return await self.send_command("reboot", parameters=",".join(reboot_data))
|
||||||
|
|
||||||
|
async def rebootdevice(self, session_id: str) -> dict:
|
||||||
|
"""Reboot the miner. Requires a session_id from logon.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
session_id: Session id from the logon command.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of rebooting the miner.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("rebootdevice", parameters=session_id)
|
||||||
|
|
||||||
|
async def removegroup(self, group_id: str) -> dict:
|
||||||
|
"""Remove a pool group.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
group_id: Group id to remove.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of removing the pool group.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("removegroup", parameters=group_id)
|
||||||
|
|
||||||
|
async def resetminer(self, session_id: str) -> dict:
|
||||||
|
"""Restart the mining process. Requires a session_id from logon.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
session_id: Session id from the logon command.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of restarting the mining process.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("resetminer", parameters=session_id)
|
||||||
|
|
||||||
|
async def removepool(self, pool_id: int) -> dict:
|
||||||
|
"""Remove a pool.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
pool_id: Pool to remove.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of removing the pool.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("removepool", parameters=str(pool_id))
|
||||||
|
|
||||||
|
async def session(self) -> dict:
|
||||||
|
"""Get the current session.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data on the current session.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("session")
|
||||||
|
|
||||||
|
async def tempctrlset(self, target: int, hot: int, dangerous: int) -> dict:
|
||||||
|
"""Set temp control values.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
target: Target temp.
|
||||||
|
hot: Hot temp.
|
||||||
|
dangerous: Dangerous temp.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of setting the temp control config.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command(
|
||||||
|
"tempctrlset", parameters=f"{target},{hot},{dangerous}"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def stats(self) -> dict:
|
||||||
|
"""Get stats of each device/pool with more than 1 getwork.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Stats of each device/pool with more than 1 getwork.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("stats")
|
||||||
|
|
||||||
|
async def summary(self) -> dict:
|
||||||
|
"""Get the status summary of the miner.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The status summary of the miner.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("summary")
|
||||||
|
|
||||||
|
async def switchpool(self, pool_id: int) -> dict:
|
||||||
|
"""Switch to a pool.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
pool_id: Pool to switch to.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of switching to the pool.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("switchpool", parameters=str(pool_id))
|
||||||
|
|
||||||
|
async def tempctrl(self) -> dict:
|
||||||
|
"""Get temperature control data.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data about the temp control settings of the miner.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("tempctrl")
|
||||||
|
|
||||||
|
async def temps(self) -> dict:
|
||||||
|
"""Get temperature data.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Data on the temps of the miner.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("temps")
|
||||||
|
|
||||||
|
async def version(self) -> dict:
|
||||||
|
"""Get miner version info.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Miner version information.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("version")
|
||||||
|
|
||||||
|
async def voltageget(self, board_n: int) -> dict:
|
||||||
|
"""Get voltage data for a board.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
board_n: The board number to get voltage info from.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Board voltage values.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("frequencyget", parameters=str(board_n))
|
||||||
|
|
||||||
|
async def voltageset(self, session_id: str, board_n: int, voltage: float) -> dict:
|
||||||
|
"""Set voltage values.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
session_id: Session id from the logon command.
|
||||||
|
board_n: The board to set the voltage on.
|
||||||
|
voltage: The voltage to use.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of setting the voltage.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command(
|
||||||
|
"voltageset", parameters=f"{session_id},{board_n},{voltage}"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def wakeup(self, session_id: str) -> dict:
|
||||||
|
"""Take the miner out of sleep mode. Requires a session_id from logon.
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
session_id: Session id from the logon command.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A confirmation of resuming mining.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("wakeup", parameters=session_id)
|
||||||
@@ -1,16 +1,18 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
# ------------------------------------------------------------------------------
|
||||||
#
|
# 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.
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# -
|
||||||
#
|
# 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,
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
# See the License for the specific language governing permissions and
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
# limitations under the License.
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.API import BaseMinerAPI
|
from pyasic.API import BaseMinerAPI
|
||||||
|
|
||||||
@@ -18,13 +20,14 @@ from pyasic.API import BaseMinerAPI
|
|||||||
class UnknownAPI(BaseMinerAPI):
|
class UnknownAPI(BaseMinerAPI):
|
||||||
"""An abstraction of an API for a miner which is unknown.
|
"""An abstraction of an API for a miner which is unknown.
|
||||||
|
|
||||||
This class is designed to try to be a intersection of as many miner APIs
|
This class is designed to try to be an intersection of as many miner APIs
|
||||||
and API commands as possible (API ⋂ API), to ensure that it can be used
|
and API commands as possible (API ⋂ API), to ensure that it can be used
|
||||||
with as many APIs as possible.
|
with as many APIs as possible.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ip, port=4028):
|
def __init__(self, ip, api_ver: str = "0.0.0", port: int = 4028):
|
||||||
super().__init__(ip, port)
|
super().__init__(ip, port)
|
||||||
|
self.api_ver = api_ver
|
||||||
|
|
||||||
async def asccount(self) -> dict:
|
async def asccount(self) -> dict:
|
||||||
return await self.send_command("asccount")
|
return await self.send_command("asccount")
|
||||||
|
|||||||
@@ -1,41 +1,37 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
# ------------------------------------------------------------------------------
|
||||||
#
|
# 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.
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# -
|
||||||
#
|
# 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,
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
# See the License for the specific language governing permissions and
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
# limitations under the License.
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
from pyasic.API.bmminer import BMMinerAPI
|
from pyasic.API.bmminer import BMMinerAPI
|
||||||
from pyasic.API.bosminer import BOSMinerAPI
|
from pyasic.API.bosminer import BOSMinerAPI
|
||||||
from pyasic.API.btminer import BTMinerAPI
|
from pyasic.API.btminer import BTMinerAPI
|
||||||
from pyasic.API.cgminer import CGMinerAPI
|
from pyasic.API.cgminer import CGMinerAPI
|
||||||
from pyasic.API.unknown import UnknownAPI
|
from pyasic.API.unknown import UnknownAPI
|
||||||
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
|
|
||||||
from pyasic.data import (
|
from pyasic.data import (
|
||||||
MinerData,
|
|
||||||
BraiinsOSError,
|
BraiinsOSError,
|
||||||
InnosiliconError,
|
InnosiliconError,
|
||||||
|
MinerData,
|
||||||
WhatsminerError,
|
WhatsminerError,
|
||||||
X19Error,
|
X19Error,
|
||||||
)
|
)
|
||||||
|
|
||||||
from pyasic.errors import APIError, APIWarning
|
from pyasic.errors import APIError, APIWarning
|
||||||
|
|
||||||
from pyasic.miners import get_miner
|
from pyasic.miners import get_miner
|
||||||
from pyasic.miners.base import AnyMiner
|
from pyasic.miners.base import AnyMiner
|
||||||
from pyasic.miners.miner_factory import MinerFactory
|
from pyasic.miners.miner_factory import MinerFactory
|
||||||
from pyasic.miners.miner_listener import MinerListener
|
from pyasic.miners.miner_listener import MinerListener
|
||||||
|
|
||||||
from pyasic.network import MinerNetwork
|
from pyasic.network import MinerNetwork
|
||||||
|
|
||||||
from pyasic.settings import PyasicSettings
|
from pyasic.settings import PyasicSettings
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|||||||
@@ -1,26 +1,35 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
# ------------------------------------------------------------------------------
|
||||||
#
|
# 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.
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# -
|
||||||
#
|
# 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,
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
# See the License for the specific language governing permissions and
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
# limitations under the License.
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from dataclasses import dataclass, asdict
|
import logging
|
||||||
from typing import Literal, List
|
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
import time
|
||||||
|
from dataclasses import asdict, dataclass, fields
|
||||||
|
from enum import IntEnum
|
||||||
|
from typing import List, Literal
|
||||||
|
|
||||||
import toml
|
import toml
|
||||||
import yaml
|
import yaml
|
||||||
import json
|
|
||||||
import time
|
|
||||||
|
class X19PowerMode(IntEnum):
|
||||||
|
Normal = 0
|
||||||
|
Sleep = 1
|
||||||
|
LPM = 3
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -37,6 +46,10 @@ class _Pool:
|
|||||||
username: str = ""
|
username: str = ""
|
||||||
password: str = ""
|
password: str = ""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fields(cls):
|
||||||
|
return fields(cls)
|
||||||
|
|
||||||
def from_dict(self, data: dict):
|
def from_dict(self, data: dict):
|
||||||
"""Convert raw pool data as a dict to usable data and save it to this class.
|
"""Convert raw pool data as a dict to usable data and save it to this class.
|
||||||
|
|
||||||
@@ -78,6 +91,32 @@ class _Pool:
|
|||||||
pool = {"url": self.url, "user": username, "pass": self.password}
|
pool = {"url": self.url, "user": username, "pass": self.password}
|
||||||
return pool
|
return pool
|
||||||
|
|
||||||
|
def as_x17(self, user_suffix: str = None) -> dict:
|
||||||
|
"""Convert the data in this class to a dict usable by an X5 device.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
user_suffix: The suffix to append to username.
|
||||||
|
"""
|
||||||
|
username = self.username
|
||||||
|
if user_suffix:
|
||||||
|
username = f"{username}{user_suffix}"
|
||||||
|
|
||||||
|
pool = {"url": self.url, "user": username, "pass": self.password}
|
||||||
|
return pool
|
||||||
|
|
||||||
|
def as_goldshell(self, user_suffix: str = None) -> dict:
|
||||||
|
"""Convert the data in this class to a dict usable by a goldshell device.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
user_suffix: The suffix to append to username.
|
||||||
|
"""
|
||||||
|
username = self.username
|
||||||
|
if user_suffix:
|
||||||
|
username = f"{username}{user_suffix}"
|
||||||
|
|
||||||
|
pool = {"url": self.url, "user": username, "pass": self.password}
|
||||||
|
return pool
|
||||||
|
|
||||||
def as_inno(self, user_suffix: str = None) -> dict:
|
def as_inno(self, user_suffix: str = None) -> dict:
|
||||||
"""Convert the data in this class to a dict usable by an Innosilicon device.
|
"""Convert the data in this class to a dict usable by an Innosilicon device.
|
||||||
|
|
||||||
@@ -136,6 +175,10 @@ class _PoolGroup:
|
|||||||
group_name: str = None
|
group_name: str = None
|
||||||
pools: List[_Pool] = None
|
pools: List[_Pool] = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fields(cls):
|
||||||
|
return fields(cls)
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
if not self.group_name:
|
if not self.group_name:
|
||||||
self.group_name = "".join(
|
self.group_name = "".join(
|
||||||
@@ -171,30 +214,80 @@ class _PoolGroup:
|
|||||||
pools.append(pool.as_x19(user_suffix=user_suffix))
|
pools.append(pool.as_x19(user_suffix=user_suffix))
|
||||||
return pools
|
return pools
|
||||||
|
|
||||||
|
def as_x17(self, user_suffix: str = None) -> dict:
|
||||||
|
"""Convert the data in this class to a list usable by an X17 device.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
user_suffix: The suffix to append to username.
|
||||||
|
"""
|
||||||
|
pools = {
|
||||||
|
"_ant_pool1url": "",
|
||||||
|
"_ant_pool1user": "",
|
||||||
|
"_ant_pool1pw": "",
|
||||||
|
"_ant_pool2url": "",
|
||||||
|
"_ant_pool2user": "",
|
||||||
|
"_ant_pool2pw": "",
|
||||||
|
"_ant_pool3url": "",
|
||||||
|
"_ant_pool3user": "",
|
||||||
|
"_ant_pool3pw": "",
|
||||||
|
}
|
||||||
|
for idx, pool in enumerate(self.pools[:3]):
|
||||||
|
pools[f"_ant_pool{idx+1}url"] = pool.as_x17(user_suffix=user_suffix)["url"]
|
||||||
|
pools[f"_ant_pool{idx+1}user"] = pool.as_x17(user_suffix=user_suffix)[
|
||||||
|
"user"
|
||||||
|
]
|
||||||
|
pools[f"_ant_pool{idx+1}pw"] = pool.as_x17(user_suffix=user_suffix)["pass"]
|
||||||
|
|
||||||
|
return pools
|
||||||
|
|
||||||
|
def as_goldshell(self, user_suffix: str = None) -> list:
|
||||||
|
"""Convert the data in this class to a list usable by a goldshell device.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
user_suffix: The suffix to append to username.
|
||||||
|
"""
|
||||||
|
return [pool.as_goldshell(user_suffix=user_suffix) for pool in self.pools[:3]]
|
||||||
|
|
||||||
def as_inno(self, user_suffix: str = None) -> dict:
|
def as_inno(self, user_suffix: str = None) -> dict:
|
||||||
"""Convert the data in this class to a list usable by an Innosilicon device.
|
"""Convert the data in this class to a list usable by an Innosilicon device.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
user_suffix: The suffix to append to username.
|
user_suffix: The suffix to append to username.
|
||||||
"""
|
"""
|
||||||
pools = {}
|
pools = {
|
||||||
|
"Pool1": None,
|
||||||
|
"UserName1": None,
|
||||||
|
"Password1": None,
|
||||||
|
"Pool2": None,
|
||||||
|
"UserName2": None,
|
||||||
|
"Password2": None,
|
||||||
|
"Pool3": None,
|
||||||
|
"UserName3": None,
|
||||||
|
"Password3": None,
|
||||||
|
}
|
||||||
for idx, pool in enumerate(self.pools[:3]):
|
for idx, pool in enumerate(self.pools[:3]):
|
||||||
pool_data = pool.as_inno(user_suffix=user_suffix)
|
pool_data = pool.as_inno(user_suffix=user_suffix)
|
||||||
for key in pool_data:
|
for key in pool_data:
|
||||||
pools[f"{key}{idx+1}"] = pool_data[key]
|
pools[f"{key}{idx+1}"] = pool_data[key]
|
||||||
return pools
|
return pools
|
||||||
|
|
||||||
def as_wm(self, user_suffix: str = None) -> List[dict]:
|
def as_wm(self, user_suffix: str = None) -> dict:
|
||||||
"""Convert the data in this class to a list usable by an Whatsminer device.
|
"""Convert the data in this class to a list usable by a Whatsminer device.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
user_suffix: The suffix to append to username.
|
user_suffix: The suffix to append to username.
|
||||||
"""
|
"""
|
||||||
pools = []
|
pools = {}
|
||||||
for pool in self.pools[:3]:
|
for i in range(1, 4):
|
||||||
pools.append(pool.as_wm(user_suffix=user_suffix))
|
if i <= len(self.pools):
|
||||||
while len(pools) < 3:
|
pool_wm = self.pools[i - 1].as_wm(user_suffix)
|
||||||
pools.append({"url": None, "user": None, "pass": None})
|
pools[f"pool_{i}"] = pool_wm["url"]
|
||||||
|
pools[f"worker_{i}"] = pool_wm["user"]
|
||||||
|
pools[f"passwd_{i}"] = pool_wm["pass"]
|
||||||
|
else:
|
||||||
|
pools[f"pool_{i}"] = ""
|
||||||
|
pools[f"worker_{i}"] = ""
|
||||||
|
pools[f"passwd_{i}"] = ""
|
||||||
return pools
|
return pools
|
||||||
|
|
||||||
def as_avalon(self, user_suffix: str = None) -> str:
|
def as_avalon(self, user_suffix: str = None) -> str:
|
||||||
@@ -234,7 +327,9 @@ class MinerConfig:
|
|||||||
fan_speed: Manual fan speed to run the fan at (only if temp_mode == "manual").
|
fan_speed: Manual fan speed to run the fan at (only if temp_mode == "manual").
|
||||||
asicboost: Whether or not to enable asicboost.
|
asicboost: Whether or not to enable asicboost.
|
||||||
autotuning_enabled: Whether or not to enable autotuning.
|
autotuning_enabled: Whether or not to enable autotuning.
|
||||||
|
autotuning_mode: Autotuning mode, either "wattage" or "hashrate".
|
||||||
autotuning_wattage: The wattage to use when autotuning.
|
autotuning_wattage: The wattage to use when autotuning.
|
||||||
|
autotuning_hashrate: The hashrate to use when autotuning.
|
||||||
dps_enabled: Whether or not to enable dynamic power scaling.
|
dps_enabled: Whether or not to enable dynamic power scaling.
|
||||||
dps_power_step: The amount of power to reduce autotuning by when the miner reaches dangerous temp.
|
dps_power_step: The amount of power to reduce autotuning by when the miner reaches dangerous temp.
|
||||||
dps_min_power: The minimum power to reduce autotuning to.
|
dps_min_power: The minimum power to reduce autotuning to.
|
||||||
@@ -254,8 +349,11 @@ class MinerConfig:
|
|||||||
|
|
||||||
asicboost: bool = None
|
asicboost: bool = None
|
||||||
|
|
||||||
|
miner_mode: IntEnum = X19PowerMode.Normal
|
||||||
autotuning_enabled: bool = True
|
autotuning_enabled: bool = True
|
||||||
autotuning_wattage: int = 900
|
autotuning_mode: Literal["power", "hashrate"] = None
|
||||||
|
autotuning_wattage: int = None
|
||||||
|
autotuning_hashrate: int = None
|
||||||
|
|
||||||
dps_enabled: bool = None
|
dps_enabled: bool = None
|
||||||
dps_power_step: int = None
|
dps_power_step: int = None
|
||||||
@@ -263,20 +361,29 @@ class MinerConfig:
|
|||||||
dps_shutdown_enabled: bool = None
|
dps_shutdown_enabled: bool = None
|
||||||
dps_shutdown_duration: float = None
|
dps_shutdown_duration: float = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fields(cls):
|
||||||
|
return fields(cls)
|
||||||
|
|
||||||
def as_dict(self) -> dict:
|
def as_dict(self) -> dict:
|
||||||
"""Convert the data in this class to a dict."""
|
"""Convert the data in this class to a dict."""
|
||||||
|
logging.debug(f"MinerConfig - (To Dict) - Dumping Dict config")
|
||||||
data_dict = asdict(self)
|
data_dict = asdict(self)
|
||||||
for key in asdict(self).keys():
|
for key in asdict(self).keys():
|
||||||
|
if isinstance(data_dict[key], IntEnum):
|
||||||
|
data_dict[key] = data_dict[key].value
|
||||||
if data_dict[key] is None:
|
if data_dict[key] is None:
|
||||||
del data_dict[key]
|
del data_dict[key]
|
||||||
return data_dict
|
return data_dict
|
||||||
|
|
||||||
def as_toml(self) -> str:
|
def as_toml(self) -> str:
|
||||||
"""Convert the data in this class to toml."""
|
"""Convert the data in this class to toml."""
|
||||||
|
logging.debug(f"MinerConfig - (To TOML) - Dumping TOML config")
|
||||||
return toml.dumps(self.as_dict())
|
return toml.dumps(self.as_dict())
|
||||||
|
|
||||||
def as_yaml(self) -> str:
|
def as_yaml(self) -> str:
|
||||||
"""Convert the data in this class to yaml."""
|
"""Convert the data in this class to yaml."""
|
||||||
|
logging.debug(f"MinerConfig - (To YAML) - Dumping YAML config")
|
||||||
return yaml.dump(self.as_dict(), sort_keys=False)
|
return yaml.dump(self.as_dict(), sort_keys=False)
|
||||||
|
|
||||||
def from_raw(self, data: dict):
|
def from_raw(self, data: dict):
|
||||||
@@ -286,7 +393,11 @@ class MinerConfig:
|
|||||||
Parameters:
|
Parameters:
|
||||||
data: The raw config data to convert.
|
data: The raw config data to convert.
|
||||||
"""
|
"""
|
||||||
|
logging.debug(f"MinerConfig - (From Raw) - Loading raw config")
|
||||||
pool_groups = []
|
pool_groups = []
|
||||||
|
if isinstance(data, list):
|
||||||
|
# goldshell config list
|
||||||
|
data = {"pools": data}
|
||||||
for key in data.keys():
|
for key in data.keys():
|
||||||
if key == "pools":
|
if key == "pools":
|
||||||
pool_groups.append(_PoolGroup().from_dict({"pools": data[key]}))
|
pool_groups.append(_PoolGroup().from_dict({"pools": data[key]}))
|
||||||
@@ -301,16 +412,15 @@ class MinerConfig:
|
|||||||
self.fan_speed = int(data["bitmain-fan-pwm"])
|
self.fan_speed = int(data["bitmain-fan-pwm"])
|
||||||
elif key == "bitmain-work-mode":
|
elif key == "bitmain-work-mode":
|
||||||
if data[key]:
|
if data[key]:
|
||||||
if data[key] == 1:
|
self.miner_mode = X19PowerMode(int(data[key]))
|
||||||
self.autotuning_wattage = 0
|
|
||||||
elif key == "fan_control":
|
elif key == "fan_control":
|
||||||
for _key in data[key].keys():
|
for _key in data[key]:
|
||||||
if _key == "min_fans":
|
if _key == "min_fans":
|
||||||
self.minimum_fans = data[key][_key]
|
self.minimum_fans = data[key][_key]
|
||||||
elif _key == "speed":
|
elif _key == "speed":
|
||||||
self.fan_speed = data[key][_key]
|
self.fan_speed = data[key][_key]
|
||||||
elif key == "temp_control":
|
elif key == "temp_control":
|
||||||
for _key in data[key].keys():
|
for _key in data[key]:
|
||||||
if _key == "mode":
|
if _key == "mode":
|
||||||
self.temp_mode = data[key][_key]
|
self.temp_mode = data[key][_key]
|
||||||
elif _key == "target_temp":
|
elif _key == "target_temp":
|
||||||
@@ -325,19 +435,25 @@ class MinerConfig:
|
|||||||
self.asicboost = data[key]["asic_boost"]
|
self.asicboost = data[key]["asic_boost"]
|
||||||
|
|
||||||
if key == "autotuning":
|
if key == "autotuning":
|
||||||
for _key in data[key].keys():
|
for _key in data[key]:
|
||||||
if _key == "enabled":
|
if _key == "enabled":
|
||||||
self.autotuning_enabled = data[key][_key]
|
self.autotuning_enabled = data[key][_key]
|
||||||
elif _key == "psu_power_limit":
|
elif _key == "psu_power_limit":
|
||||||
self.autotuning_wattage = data[key][_key]
|
self.autotuning_wattage = data[key][_key]
|
||||||
|
elif _key == "power_target":
|
||||||
|
self.autotuning_wattage = data[key][_key]
|
||||||
|
elif _key == "hashrate_target":
|
||||||
|
self.autotuning_hashrate = data[key][_key]
|
||||||
|
elif _key == "mode":
|
||||||
|
self.autotuning_mode = data[key][_key].replace("_target", "")
|
||||||
|
|
||||||
if key == "power_scaling":
|
if key in ["power_scaling", "performance_scaling"]:
|
||||||
for _key in data[key].keys():
|
for _key in data[key]:
|
||||||
if _key == "enabled":
|
if _key == "enabled":
|
||||||
self.dps_enabled = data[key][_key]
|
self.dps_enabled = data[key][_key]
|
||||||
elif _key == "power_step":
|
elif _key == "power_step":
|
||||||
self.dps_power_step = data[key][_key]
|
self.dps_power_step = data[key][_key]
|
||||||
elif _key == "min_psu_power_limit":
|
elif _key in ["min_psu_power_limit", "min_power_target"]:
|
||||||
self.dps_min_power = data[key][_key]
|
self.dps_min_power = data[key][_key]
|
||||||
elif _key == "shutdown_enabled":
|
elif _key == "shutdown_enabled":
|
||||||
self.dps_shutdown_enabled = data[key][_key]
|
self.dps_shutdown_enabled = data[key][_key]
|
||||||
@@ -348,6 +464,12 @@ class MinerConfig:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def from_api(self, pools: list):
|
def from_api(self, pools: list):
|
||||||
|
"""Convert list output from the `AnyMiner.api.pools()` command into a usable data and save it to this class.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
pools: The list of pool data to convert.
|
||||||
|
"""
|
||||||
|
logging.debug(f"MinerConfig - (From API) - Loading API config")
|
||||||
_pools = []
|
_pools = []
|
||||||
for pool in pools:
|
for pool in pools:
|
||||||
url = pool.get("URL")
|
url = pool.get("URL")
|
||||||
@@ -362,12 +484,19 @@ class MinerConfig:
|
|||||||
Parameters:
|
Parameters:
|
||||||
data: The dict config data to convert.
|
data: The dict config data to convert.
|
||||||
"""
|
"""
|
||||||
|
logging.debug(f"MinerConfig - (From Dict) - Loading Dict config")
|
||||||
pool_groups = []
|
pool_groups = []
|
||||||
for group in data["pool_groups"]:
|
for group in data["pool_groups"]:
|
||||||
pool_groups.append(_PoolGroup().from_dict(group))
|
pool_groups.append(_PoolGroup().from_dict(group))
|
||||||
for key in data.keys():
|
for key in data:
|
||||||
if getattr(self, key) and not key == "pool_groups":
|
if (
|
||||||
|
hasattr(self, key)
|
||||||
|
and not key == "pool_groups"
|
||||||
|
and not key == "miner_mode"
|
||||||
|
):
|
||||||
setattr(self, key, data[key])
|
setattr(self, key, data[key])
|
||||||
|
if key == "miner_mode":
|
||||||
|
self.miner_mode = X19PowerMode(data[key])
|
||||||
self.pool_groups = pool_groups
|
self.pool_groups = pool_groups
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@@ -377,6 +506,7 @@ class MinerConfig:
|
|||||||
Parameters:
|
Parameters:
|
||||||
data: The toml config data to convert.
|
data: The toml config data to convert.
|
||||||
"""
|
"""
|
||||||
|
logging.debug(f"MinerConfig - (From TOML) - Loading TOML config")
|
||||||
return self.from_dict(toml.loads(data))
|
return self.from_dict(toml.loads(data))
|
||||||
|
|
||||||
def from_yaml(self, data: str):
|
def from_yaml(self, data: str):
|
||||||
@@ -385,15 +515,20 @@ class MinerConfig:
|
|||||||
Parameters:
|
Parameters:
|
||||||
data: The yaml config data to convert.
|
data: The yaml config data to convert.
|
||||||
"""
|
"""
|
||||||
|
logging.debug(f"MinerConfig - (From YAML) - Loading YAML config")
|
||||||
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
|
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
|
||||||
|
|
||||||
def as_wm(self, user_suffix: str = None) -> List[dict]:
|
def as_wm(self, user_suffix: str = None) -> dict:
|
||||||
"""Convert the data in this class to a config usable by an Whatsminer device.
|
"""Convert the data in this class to a config usable by a Whatsminer device.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
user_suffix: The suffix to append to username.
|
user_suffix: The suffix to append to username.
|
||||||
"""
|
"""
|
||||||
return self.pool_groups[0].as_wm(user_suffix=user_suffix)
|
logging.debug(f"MinerConfig - (As Whatsminer) - Generating Whatsminer config")
|
||||||
|
return {
|
||||||
|
"pools": self.pool_groups[0].as_wm(user_suffix=user_suffix),
|
||||||
|
"wattage": self.autotuning_wattage,
|
||||||
|
}
|
||||||
|
|
||||||
def as_inno(self, user_suffix: str = None) -> dict:
|
def as_inno(self, user_suffix: str = None) -> dict:
|
||||||
"""Convert the data in this class to a config usable by an Innosilicon device.
|
"""Convert the data in this class to a config usable by an Innosilicon device.
|
||||||
@@ -401,30 +536,51 @@ class MinerConfig:
|
|||||||
Parameters:
|
Parameters:
|
||||||
user_suffix: The suffix to append to username.
|
user_suffix: The suffix to append to username.
|
||||||
"""
|
"""
|
||||||
|
logging.debug(f"MinerConfig - (As Inno) - Generating Innosilicon config")
|
||||||
return self.pool_groups[0].as_inno(user_suffix=user_suffix)
|
return self.pool_groups[0].as_inno(user_suffix=user_suffix)
|
||||||
|
|
||||||
def as_x19(self, user_suffix: str = None) -> str:
|
def as_x19(self, user_suffix: str = None) -> dict:
|
||||||
"""Convert the data in this class to a config usable by an X19 device.
|
"""Convert the data in this class to a config usable by an X19 device.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
user_suffix: The suffix to append to username.
|
user_suffix: The suffix to append to username.
|
||||||
"""
|
"""
|
||||||
|
logging.debug(f"MinerConfig - (As X19) - Generating X19 config")
|
||||||
cfg = {
|
cfg = {
|
||||||
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
|
|
||||||
"bitmain-fan-ctrl": False,
|
"bitmain-fan-ctrl": False,
|
||||||
"bitmain-fan-pwn": 100,
|
"bitmain-fan-pwn": "100",
|
||||||
"miner-mode": 0, # Normal Mode
|
"freq-level": "100",
|
||||||
|
"miner-mode": self.miner_mode.value,
|
||||||
|
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
|
||||||
}
|
}
|
||||||
if self.autotuning_wattage == 0:
|
|
||||||
cfg["miner-mode"] = 1 # Sleep Mode
|
|
||||||
|
|
||||||
if not self.temp_mode == "auto":
|
if not self.temp_mode == "auto":
|
||||||
cfg["bitmain-fan-ctrl"] = True
|
cfg["bitmain-fan-ctrl"] = True
|
||||||
|
|
||||||
if self.fan_speed:
|
if self.fan_speed:
|
||||||
cfg["bitmain-fan-ctrl"] = str(self.fan_speed)
|
cfg["bitmain-fan-pwn"] = str(self.fan_speed)
|
||||||
|
|
||||||
return json.dumps(cfg)
|
return cfg
|
||||||
|
|
||||||
|
def as_x17(self, user_suffix: str = None) -> dict:
|
||||||
|
"""Convert the data in this class to a config usable by an X5 device.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
user_suffix: The suffix to append to username.
|
||||||
|
"""
|
||||||
|
cfg = self.pool_groups[0].as_x17(user_suffix=user_suffix)
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
|
||||||
|
def as_goldshell(self, user_suffix: str = None) -> list:
|
||||||
|
"""Convert the data in this class to a config usable by a goldshell device.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
user_suffix: The suffix to append to username.
|
||||||
|
"""
|
||||||
|
cfg = self.pool_groups[0].as_goldshell(user_suffix=user_suffix)
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
|
||||||
def as_avalon(self, user_suffix: str = None) -> str:
|
def as_avalon(self, user_suffix: str = None) -> str:
|
||||||
"""Convert the data in this class to a config usable by an Avalonminer device.
|
"""Convert the data in this class to a config usable by an Avalonminer device.
|
||||||
@@ -432,6 +588,7 @@ class MinerConfig:
|
|||||||
Parameters:
|
Parameters:
|
||||||
user_suffix: The suffix to append to username.
|
user_suffix: The suffix to append to username.
|
||||||
"""
|
"""
|
||||||
|
logging.debug(f"MinerConfig - (As Avalon) - Generating AvalonMiner config")
|
||||||
cfg = self.pool_groups[0].as_avalon(user_suffix=user_suffix)
|
cfg = self.pool_groups[0].as_avalon(user_suffix=user_suffix)
|
||||||
return cfg
|
return cfg
|
||||||
|
|
||||||
@@ -442,11 +599,12 @@ class MinerConfig:
|
|||||||
model: The model of the miner to be used in the format portion of the config.
|
model: The model of the miner to be used in the format portion of the config.
|
||||||
user_suffix: The suffix to append to username.
|
user_suffix: The suffix to append to username.
|
||||||
"""
|
"""
|
||||||
|
logging.debug(f"MinerConfig - (As BOS) - Generating BOSMiner config")
|
||||||
cfg = {
|
cfg = {
|
||||||
"format": {
|
"format": {
|
||||||
"version": "1.2+",
|
"version": "1.2+",
|
||||||
"model": f"Antminer {model}",
|
"model": f"Antminer {model.replace('j', 'J')}",
|
||||||
"generator": "Upstream Config Utility",
|
"generator": "pyasic",
|
||||||
"timestamp": int(time.time()),
|
"timestamp": int(time.time()),
|
||||||
},
|
},
|
||||||
"group": [
|
"group": [
|
||||||
@@ -463,14 +621,31 @@ class MinerConfig:
|
|||||||
if self.autotuning_enabled or self.autotuning_wattage:
|
if self.autotuning_enabled or self.autotuning_wattage:
|
||||||
cfg["autotuning"] = {}
|
cfg["autotuning"] = {}
|
||||||
if self.autotuning_enabled:
|
if self.autotuning_enabled:
|
||||||
cfg["autotuning"]["enabled"] = self.autotuning_enabled
|
cfg["autotuning"]["enabled"] = True
|
||||||
if self.autotuning_wattage:
|
else:
|
||||||
cfg["autotuning"]["psu_power_limit"] = self.autotuning_wattage
|
cfg["autotuning"]["enabled"] = False
|
||||||
|
if self.autotuning_mode:
|
||||||
|
cfg["format"]["version"] = "2.0"
|
||||||
|
cfg["autotuning"]["mode"] = self.autotuning_mode + "_target"
|
||||||
|
if self.autotuning_wattage:
|
||||||
|
cfg["autotuning"]["power_target"] = self.autotuning_wattage
|
||||||
|
elif self.autotuning_hashrate:
|
||||||
|
cfg["autotuning"]["hashrate_target"] = self.autotuning_hashrate
|
||||||
|
else:
|
||||||
|
if self.autotuning_wattage:
|
||||||
|
cfg["autotuning"]["psu_power_limit"] = self.autotuning_wattage
|
||||||
|
|
||||||
if self.asicboost:
|
if self.asicboost:
|
||||||
cfg["hash_chain_global"] = {}
|
cfg["hash_chain_global"] = {}
|
||||||
cfg["hash_chain_global"]["asic_boost"] = self.asicboost
|
cfg["hash_chain_global"]["asic_boost"] = self.asicboost
|
||||||
|
|
||||||
|
if self.minimum_fans is not None or self.fan_speed is not None:
|
||||||
|
cfg["fan_control"] = {}
|
||||||
|
if self.minimum_fans is not None:
|
||||||
|
cfg["fan_control"]["min_fans"] = self.minimum_fans
|
||||||
|
if self.fan_speed is not None:
|
||||||
|
cfg["fan_control"]["speed"] = self.fan_speed
|
||||||
|
|
||||||
if any(
|
if any(
|
||||||
[
|
[
|
||||||
getattr(self, item)
|
getattr(self, item)
|
||||||
@@ -489,7 +664,10 @@ class MinerConfig:
|
|||||||
if self.dps_power_step:
|
if self.dps_power_step:
|
||||||
cfg["power_scaling"]["power_step"] = self.dps_power_step
|
cfg["power_scaling"]["power_step"] = self.dps_power_step
|
||||||
if self.dps_min_power:
|
if self.dps_min_power:
|
||||||
cfg["power_scaling"]["min_psu_power_limit"] = self.dps_min_power
|
if cfg["format"]["version"] == "2.0":
|
||||||
|
cfg["power_scaling"]["min_power_target"] = self.dps_min_power
|
||||||
|
else:
|
||||||
|
cfg["power_scaling"]["min_psu_power_limit"] = self.dps_min_power
|
||||||
if self.dps_shutdown_enabled:
|
if self.dps_shutdown_enabled:
|
||||||
cfg["power_scaling"]["shutdown_enabled"] = self.dps_shutdown_enabled
|
cfg["power_scaling"]["shutdown_enabled"] = self.dps_shutdown_enabled
|
||||||
if self.dps_shutdown_duration:
|
if self.dps_shutdown_duration:
|
||||||
|
|||||||
@@ -1,25 +1,92 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
# ------------------------------------------------------------------------------
|
||||||
#
|
# 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.
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# -
|
||||||
#
|
# 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,
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
# See the License for the specific language governing permissions and
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
# limitations under the License.
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from typing import Union, List
|
|
||||||
from dataclasses import dataclass, field, asdict
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
import time
|
|
||||||
import json
|
|
||||||
import copy
|
import copy
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from dataclasses import asdict, dataclass, field, fields
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from typing import List, Union, Any
|
||||||
|
|
||||||
from .error_codes import X19Error, WhatsminerError, BraiinsOSError, InnosiliconError
|
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class HashBoard:
|
||||||
|
"""A Dataclass to standardize hashboard data.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
slot: The slot of the board as an int.
|
||||||
|
hashrate: The hashrate of the board in TH/s as a float.
|
||||||
|
temp: The temperature of the PCB as an int.
|
||||||
|
chip_temp: The temperature of the chips as an int.
|
||||||
|
chips: The chip count of the board as an int.
|
||||||
|
expected_chips: The ideal chip count of the board as an int.
|
||||||
|
missing: Whether the board is returned from the miners data as a bool.
|
||||||
|
"""
|
||||||
|
|
||||||
|
slot: int = 0
|
||||||
|
hashrate: float = None
|
||||||
|
temp: int = None
|
||||||
|
chip_temp: int = None
|
||||||
|
chips: int = None
|
||||||
|
expected_chips: int = None
|
||||||
|
missing: bool = True
|
||||||
|
|
||||||
|
def get(self, __key: str, default: Any = None):
|
||||||
|
try:
|
||||||
|
val = self.__getitem__(__key)
|
||||||
|
if val is None:
|
||||||
|
return default
|
||||||
|
return val
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def __getitem__(self, item: str):
|
||||||
|
try:
|
||||||
|
return getattr(self, item)
|
||||||
|
except AttributeError:
|
||||||
|
raise KeyError(f"{item}")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Fan:
|
||||||
|
"""A Dataclass to standardize fan data.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
speed: The speed of the fan.
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
||||||
@@ -29,32 +96,28 @@ 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.
|
||||||
|
api_ver: The current api 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.
|
||||||
left_board_hashrate: The hashrate of the left board of the miner in TH/s as a float.
|
_hashrate: Backup for hashrate found via API instead of hashboards.
|
||||||
center_board_hashrate: The hashrate of the center board of the miner in TH/s as a float.
|
nominal_hashrate: The factory nominal hashrate 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.
|
hashboards: A list of hashboards on the miner with their statistics.
|
||||||
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_psu: The speed of the PSU on the fan if the miner collects it.
|
||||||
fan_3: The speed of the third fan as an int.
|
|
||||||
fan_4: The speed of the fourth fan as an int.
|
|
||||||
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: 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.
|
||||||
|
percent_ideal_hashrate: The percent of total hashrate out of the ideal hashrate. Calculated automatically.
|
||||||
|
percent_ideal_wattage: The percent of total wattage out of the ideal wattage. Calculated automatically.
|
||||||
nominal: Whether the number of chips in the miner is nominal. Calculated automatically.
|
nominal: Whether the number of chips in the miner is nominal. Calculated automatically.
|
||||||
pool_split: The pool split as a str.
|
pool_split: The pool split as a str.
|
||||||
pool_1_url: The first pool url on the miner as a str.
|
pool_1_url: The first pool url on the miner as a str.
|
||||||
@@ -62,40 +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
|
||||||
hostname: str = "Unknown"
|
model: str = None
|
||||||
hashrate: float = 0
|
make: str = None
|
||||||
left_board_hashrate: float = 0.0
|
api_ver: str = None
|
||||||
center_board_hashrate: float = 0.0
|
fw_ver: str = None
|
||||||
right_board_hashrate: float = 0.0
|
hostname: str = None
|
||||||
|
hashrate: float = field(init=False)
|
||||||
|
_hashrate: float = None
|
||||||
|
nominal_hashrate: float = None
|
||||||
|
hashboards: List[HashBoard] = field(default_factory=list)
|
||||||
|
ideal_hashboards: int = None
|
||||||
temperature_avg: int = field(init=False)
|
temperature_avg: int = field(init=False)
|
||||||
env_temp: float = 0.0
|
env_temp: float = None
|
||||||
left_board_temp: int = 0
|
wattage: int = None
|
||||||
left_board_chip_temp: int = 0
|
wattage_limit: int = None
|
||||||
center_board_temp: int = 0
|
fans: List[Fan] = field(default_factory=list)
|
||||||
center_board_chip_temp: int = 0
|
fan_psu: int = None
|
||||||
right_board_temp: int = 0
|
|
||||||
right_board_chip_temp: int = 0
|
|
||||||
wattage: int = 0
|
|
||||||
wattage_limit: int = 0
|
|
||||||
fan_1: int = -1
|
|
||||||
fan_2: int = -1
|
|
||||||
fan_3: int = -1
|
|
||||||
fan_4: int = -1
|
|
||||||
left_chips: int = 0
|
|
||||||
center_chips: int = 0
|
|
||||||
right_chips: int = 0
|
|
||||||
total_chips: int = field(init=False)
|
total_chips: int = field(init=False)
|
||||||
ideal_chips: int = 1
|
ideal_chips: int = None
|
||||||
percent_ideal: float = field(init=False)
|
percent_ideal_chips: float = field(init=False)
|
||||||
nominal: int = field(init=False)
|
percent_ideal_hashrate: float = field(init=False)
|
||||||
|
percent_ideal_wattage: float = 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"
|
||||||
@@ -106,11 +166,25 @@ 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
|
||||||
|
def fields(cls):
|
||||||
|
return [f.name for f in fields(cls)]
|
||||||
|
|
||||||
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:
|
||||||
@@ -159,9 +233,31 @@ class MinerData:
|
|||||||
setattr(cp, key, item & other_item)
|
setattr(cp, key, item & other_item)
|
||||||
return cp
|
return cp
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hashrate(self): # noqa - Skip PyCharm inspection
|
||||||
|
if len(self.hashboards) > 0:
|
||||||
|
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
|
||||||
|
|
||||||
|
@hashrate.setter
|
||||||
|
def hashrate(self, val):
|
||||||
|
self._hashrate = val
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_chips(self): # noqa - Skip PyCharm inspection
|
def total_chips(self): # noqa - Skip PyCharm inspection
|
||||||
return self.right_chips + self.center_chips + self.left_chips
|
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):
|
||||||
@@ -169,6 +265,8 @@ class MinerData:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def nominal(self): # noqa - Skip PyCharm inspection
|
def nominal(self): # noqa - Skip PyCharm inspection
|
||||||
|
if self.total_chips is None or self.ideal_chips is None:
|
||||||
|
return None
|
||||||
return self.ideal_chips == self.total_chips
|
return self.ideal_chips == self.total_chips
|
||||||
|
|
||||||
@nominal.setter
|
@nominal.setter
|
||||||
@@ -176,27 +274,51 @@ class MinerData:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def percent_ideal(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:
|
||||||
|
return 0
|
||||||
return round((self.total_chips / self.ideal_chips) * 100)
|
return round((self.total_chips / self.ideal_chips) * 100)
|
||||||
|
|
||||||
@percent_ideal.setter
|
@percent_ideal_chips.setter
|
||||||
def percent_ideal(self, val):
|
def percent_ideal_chips(self, val):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def percent_ideal_hashrate(self): # noqa - Skip PyCharm inspection
|
||||||
|
if self.hashrate is None or self.nominal_hashrate is None:
|
||||||
|
return None
|
||||||
|
if self.hashrate == 0 or self.nominal_hashrate == 0:
|
||||||
|
return 0
|
||||||
|
return round((self.hashrate / self.nominal_hashrate) * 100)
|
||||||
|
|
||||||
|
@percent_ideal_hashrate.setter
|
||||||
|
def percent_ideal_hashrate(self, val):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def percent_ideal_wattage(self): # noqa - Skip PyCharm inspection
|
||||||
|
if self.wattage_limit is None or self.wattage is None:
|
||||||
|
return None
|
||||||
|
if self.wattage_limit == 0 or self.wattage == 0:
|
||||||
|
return 0
|
||||||
|
return round((self.wattage / self.wattage_limit) * 100)
|
||||||
|
|
||||||
|
@percent_ideal_wattage.setter
|
||||||
|
def percent_ideal_wattage(self, val):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def temperature_avg(self): # noqa - Skip PyCharm inspection
|
def temperature_avg(self): # noqa - Skip PyCharm inspection
|
||||||
total_temp = 0
|
total_temp = 0
|
||||||
temp_count = 0
|
temp_count = 0
|
||||||
for temp in [
|
for hb in self.hashboards:
|
||||||
self.left_board_chip_temp,
|
if hb.temp is not None:
|
||||||
self.center_board_chip_temp,
|
total_temp += hb.temp
|
||||||
self.right_board_chip_temp,
|
|
||||||
]:
|
|
||||||
if temp and not temp == 0:
|
|
||||||
total_temp += 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
|
||||||
@@ -205,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:
|
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)
|
||||||
|
|
||||||
@@ -219,6 +343,7 @@ class MinerData:
|
|||||||
Returns:
|
Returns:
|
||||||
A dictionary version of this class.
|
A dictionary version of this class.
|
||||||
"""
|
"""
|
||||||
|
logging.debug(f"MinerData - (To Dict) - Dumping Dict data")
|
||||||
return asdict(self)
|
return asdict(self)
|
||||||
|
|
||||||
def as_json(self) -> str:
|
def as_json(self) -> str:
|
||||||
@@ -227,11 +352,18 @@ class MinerData:
|
|||||||
Returns:
|
Returns:
|
||||||
A JSON version of this class.
|
A JSON version of this class.
|
||||||
"""
|
"""
|
||||||
|
logging.debug(f"MinerData - (To JSON) - Dumping JSON data")
|
||||||
data = self.asdict()
|
data = self.asdict()
|
||||||
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
|
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
|
||||||
return json.dumps(data)
|
return json.dumps(data)
|
||||||
|
|
||||||
def as_csv(self) -> str:
|
def as_csv(self) -> str:
|
||||||
|
"""Get this dataclass as CSV.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A CSV version of this class with no headers.
|
||||||
|
"""
|
||||||
|
logging.debug(f"MinerData - (To CSV) - Dumping CSV data")
|
||||||
data = self.asdict()
|
data = self.asdict()
|
||||||
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
|
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
|
||||||
errs = []
|
errs = []
|
||||||
@@ -250,33 +382,48 @@ class MinerData:
|
|||||||
Returns:
|
Returns:
|
||||||
A influxdb line protocol version of this class.
|
A influxdb line protocol version of this class.
|
||||||
"""
|
"""
|
||||||
|
logging.debug(f"MinerData - (To InfluxDB) - Dumping InfluxDB data")
|
||||||
tag_data = [measurement_name]
|
tag_data = [measurement_name]
|
||||||
field_data = []
|
field_data = []
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
# ------------------------------------------------------------------------------
|
||||||
#
|
# 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.
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# -
|
||||||
#
|
# 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,
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
# See the License for the specific language governing permissions and
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
# limitations under the License.
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from dataclasses import dataclass, asdict
|
from dataclasses import asdict, dataclass, fields
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -21,9 +23,15 @@ class X19Error:
|
|||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
error_message: The error message as a string.
|
error_message: The error message as a string.
|
||||||
|
error_code: The error code as an int. 0 if the message is not assigned a code.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
error_message: str
|
error_message: str
|
||||||
|
error_code: int = 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fields(cls):
|
||||||
|
return fields(cls)
|
||||||
|
|
||||||
def asdict(self):
|
def asdict(self):
|
||||||
return asdict(self)
|
return asdict(self)
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
# ------------------------------------------------------------------------------
|
||||||
#
|
# 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.
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# -
|
||||||
#
|
# 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,
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
# See the License for the specific language governing permissions and
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
# limitations under the License.
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
from .whatsminer import WhatsminerError
|
# ------------------------------------------------------------------------------
|
||||||
from .bos import BraiinsOSError
|
|
||||||
from .X19 import X19Error
|
|
||||||
from .innosilicon import InnosiliconError
|
|
||||||
|
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
|
|
||||||
|
from .bos import BraiinsOSError
|
||||||
|
from .innosilicon import InnosiliconError
|
||||||
|
from .whatsminer import WhatsminerError
|
||||||
|
from .X19 import X19Error
|
||||||
|
|
||||||
MinerErrorData = TypeVar(
|
MinerErrorData = TypeVar(
|
||||||
"MinerErrorData", WhatsminerError, BraiinsOSError, X19Error, InnosiliconError
|
"MinerErrorData", WhatsminerError, BraiinsOSError, X19Error, InnosiliconError
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
# ------------------------------------------------------------------------------
|
||||||
#
|
# 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.
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# -
|
||||||
#
|
# 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,
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
# See the License for the specific language governing permissions and
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
# limitations under the License.
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from dataclasses import dataclass, asdict
|
from dataclasses import asdict, dataclass, fields
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -21,9 +23,15 @@ class BraiinsOSError:
|
|||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
error_message: The error message as a string.
|
error_message: The error message as a string.
|
||||||
|
error_code: The error code as an int. 0 if the message is not assigned a code.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
error_message: str
|
error_message: str
|
||||||
|
error_code: int = 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fields(cls):
|
||||||
|
return fields(cls)
|
||||||
|
|
||||||
def asdict(self):
|
def asdict(self):
|
||||||
return asdict(self)
|
return asdict(self)
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
# ------------------------------------------------------------------------------
|
||||||
#
|
# 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.
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# -
|
||||||
#
|
# 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,
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
# See the License for the specific language governing permissions and
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
# limitations under the License.
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from dataclasses import dataclass, field, asdict
|
from dataclasses import asdict, dataclass, field, fields
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -27,6 +29,10 @@ class InnosiliconError:
|
|||||||
error_code: int
|
error_code: int
|
||||||
error_message: str = field(init=False)
|
error_message: str = field(init=False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fields(cls):
|
||||||
|
return fields(cls)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def error_message(self): # noqa - Skip PyCharm inspection
|
def error_message(self): # noqa - Skip PyCharm inspection
|
||||||
if self.error_code in ERROR_CODES:
|
if self.error_code in ERROR_CODES:
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
# ------------------------------------------------------------------------------
|
||||||
#
|
# 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.
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# -
|
||||||
#
|
# 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,
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
# See the License for the specific language governing permissions and
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
# limitations under the License.
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from dataclasses import dataclass, field, asdict
|
from dataclasses import asdict, dataclass, field, fields
|
||||||
|
|
||||||
|
C_N_CODES = ["52", "53", "54", "55", "56"]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -27,11 +31,52 @@ class WhatsminerError:
|
|||||||
error_code: int
|
error_code: int
|
||||||
error_message: str = field(init=False)
|
error_message: str = field(init=False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def fields(cls):
|
||||||
|
return fields(cls)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def error_message(self): # noqa - Skip PyCharm inspection
|
def error_message(self): # noqa - Skip PyCharm inspection
|
||||||
if self.error_code in ERROR_CODES:
|
if len(str(self.error_code)) > 3 and str(self.error_code)[:2] in C_N_CODES:
|
||||||
return ERROR_CODES[self.error_code]
|
# 55 error code base has chip numbers, so the format is
|
||||||
return "Unknown error type."
|
# 55 -> board num len 1 -> chip num len 3
|
||||||
|
err_type = 55
|
||||||
|
err_subtype = int(str(self.error_code)[2:3])
|
||||||
|
err_value = int(str(self.error_code)[3:])
|
||||||
|
else:
|
||||||
|
err_type = int(str(self.error_code)[:-2])
|
||||||
|
err_subtype = int(str(self.error_code)[-2:-1])
|
||||||
|
err_value = int(str(self.error_code)[-1:])
|
||||||
|
try:
|
||||||
|
select_err_type = ERROR_CODES[err_type]
|
||||||
|
if err_subtype in select_err_type:
|
||||||
|
select_err_subtype = select_err_type[err_subtype]
|
||||||
|
if err_value in select_err_subtype:
|
||||||
|
return select_err_subtype[err_value]
|
||||||
|
elif "n" in select_err_subtype:
|
||||||
|
return select_err_subtype[
|
||||||
|
"n" # noqa: picks up `select_err_subtype["n"]` as not being numeric?
|
||||||
|
].replace("{n}", str(err_value))
|
||||||
|
else:
|
||||||
|
return "Unknown error type."
|
||||||
|
elif "n" in select_err_type:
|
||||||
|
select_err_subtype = select_err_type[
|
||||||
|
"n" # noqa: picks up `select_err_subtype["n"]` as not being numeric?
|
||||||
|
]
|
||||||
|
if err_value in select_err_subtype:
|
||||||
|
return select_err_subtype[err_value]
|
||||||
|
elif "c" in select_err_subtype:
|
||||||
|
return (
|
||||||
|
select_err_subtype["c"]
|
||||||
|
.replace( # noqa: picks up `select_err_subtype["n"]` as not being numeric?
|
||||||
|
"{n}", str(err_subtype)
|
||||||
|
)
|
||||||
|
.replace("{c}", str(err_value))
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return "Unknown error type."
|
||||||
|
except KeyError:
|
||||||
|
return "Unknown error type."
|
||||||
|
|
||||||
@error_message.setter
|
@error_message.setter
|
||||||
def error_message(self, val):
|
def error_message(self, val):
|
||||||
@@ -42,131 +87,252 @@ class WhatsminerError:
|
|||||||
|
|
||||||
|
|
||||||
ERROR_CODES = {
|
ERROR_CODES = {
|
||||||
110: "Intake fan speed error.",
|
1: { # Fan error
|
||||||
111: "Exhaust fan speed error.",
|
0: {0: "Fan unknown."},
|
||||||
120: "Intake fan speed error. Fan speed deviates by more than 2000.",
|
1: { # Fan speed error of 1000+
|
||||||
121: "Exhaust fan speed error. Fan speed deviates by more than 2000.",
|
0: "Intake fan speed error.",
|
||||||
130: "Intake fan speed error. Fan speed deviates by more than 3000.",
|
1: "Exhaust fan speed error.",
|
||||||
131: "Exhaust fan speed error. Fan speed deviates by more than 3000.",
|
},
|
||||||
140: "Fan speed too high.",
|
2: { # Fan speed error of 2000+
|
||||||
200: "Power probing error. No power found.",
|
0: "Intake fan speed error. Fan speed deviates by more than 2000.",
|
||||||
201: "Power supply and configuration file don't match.",
|
1: "Exhaust fan speed error. Fan speed deviates by more than 2000.",
|
||||||
202: "Power output voltage error.",
|
},
|
||||||
203: "Power protecting due to high environment temperature.",
|
3: { # Fan speed error of 3000+
|
||||||
204: "Power current protecting due to high environment temperature.",
|
0: "Intake fan speed error. Fan speed deviates by more than 3000.",
|
||||||
205: "Power current error.",
|
1: "Exhaust fan speed error. Fan speed deviates by more than 3000.",
|
||||||
206: "Power input low voltage error.",
|
},
|
||||||
207: "Power input current protecting due to bad power input.",
|
4: {0: "Fan speed too high."}, # High speed
|
||||||
210: "Power error.",
|
},
|
||||||
213: "Power input voltage and current do not match power output.",
|
2: { # Power error
|
||||||
216: "Power remained unchanged for a long time.",
|
0: {
|
||||||
217: "Power set enable error.",
|
0: "Power probing error. No power found.",
|
||||||
218: "Power input voltage is lower than 230V for high power mode.",
|
1: "Power supply and configuration file don't match.",
|
||||||
233: "Power output high temperature protection error.",
|
2: "Power output voltage error.",
|
||||||
234: "Power output high temperature protection error.",
|
3: "Power protecting due to high environment temperature.",
|
||||||
235: "Power output high temperature protection error.",
|
4: "Power current protecting due to high environment temperature.",
|
||||||
236: "Power output high current protection error.",
|
5: "Power current error.",
|
||||||
237: "Power output high current protection error.",
|
6: "Power input low voltage error.",
|
||||||
238: "Power output high current protection error.",
|
7: "Power input current protecting due to bad power input.",
|
||||||
239: "Power output high voltage protection error.",
|
8: "Power power error.",
|
||||||
240: "Power output low voltage protection error.",
|
9: "Power voltage offset error.",
|
||||||
241: "Power output current imbalance error.",
|
},
|
||||||
243: "Power input high temperature protection error.",
|
1: {
|
||||||
244: "Power input high temperature protection error.",
|
0: "Power error.",
|
||||||
245: "Power input high temperature protection error.",
|
1: "Power iout error, please reboot.",
|
||||||
246: "Power input high voltage protection error.",
|
2: "Power vout error, reach vout border. Border: [1150, 1500]",
|
||||||
247: "Power input high voltage protection error.",
|
3: "Power input voltage and current do not match power output.",
|
||||||
248: "Power input high current protection error.",
|
4: "Power pin did not change.",
|
||||||
249: "Power input high current protection error.",
|
5: "Power vout set error.",
|
||||||
250: "Power input low voltage protection error.",
|
6: "Power remained unchanged for a long time.",
|
||||||
251: "Power input low voltage protection error.",
|
7: "Power set enable error.",
|
||||||
253: "Power supply fan error.",
|
8: "Power input voltage is lower than 230V for high power mode.",
|
||||||
254: "Power supply fan error.",
|
},
|
||||||
255: "Power output high power protection error.",
|
3: {
|
||||||
256: "Power output high power protection error.",
|
3: "Power output high temperature protection error.",
|
||||||
257: "Input over current protection of power supply on primary side.",
|
4: "Power output high temperature protection error.",
|
||||||
263: "Power communication warning.",
|
5: "Power output high temperature protection error.",
|
||||||
264: "Power communication error.",
|
6: "Power output high current protection error.",
|
||||||
267: "Power watchdog protection.",
|
7: "Power output high current protection error.",
|
||||||
268: "Power output high current protection.",
|
8: "Power output high current protection error.",
|
||||||
269: "Power input high current protection.",
|
9: "Power output high voltage protection error.",
|
||||||
270: "Power input high voltage protection.",
|
},
|
||||||
271: "Power input low voltage protection.",
|
4: {
|
||||||
272: "Excessive power supply output warning.",
|
0: "Power output low voltage protection error.",
|
||||||
273: "Power input too high warning.",
|
1: "Power output current imbalance error.",
|
||||||
274: "Power fan warning.",
|
3: "Power input high temperature protection error.",
|
||||||
275: "Power high temperature warning.",
|
4: "Power input high temperature protection error.",
|
||||||
300: "Right board temperature sensor detection error.",
|
5: "Power input high temperature protection error.",
|
||||||
301: "Center board temperature sensor detection error.",
|
6: "Power input high voltage protection error.",
|
||||||
302: "Left board temperature sensor detection error.",
|
7: "Power input high voltage protection error.",
|
||||||
320: "Right board temperature reading error.",
|
8: "Power input high current protection error.",
|
||||||
321: "Center board temperature reading error.",
|
9: "Power input high current protection error.",
|
||||||
322: "Left board temperature reading error.",
|
},
|
||||||
329: "Control board temperature sensor communication error.",
|
5: {
|
||||||
350: "Right board temperature protecting.",
|
0: "Power input low voltage protection error.",
|
||||||
351: "Center board temperature protecting.",
|
1: "Power input low voltage protection error.",
|
||||||
352: "Left board temperature protecting.",
|
3: "Power supply fan error.",
|
||||||
360: "Hashboard high temperature error.",
|
4: "Power supply fan error.",
|
||||||
410: "Right board eeprom detection error.",
|
5: "Power output high power protection error.",
|
||||||
411: "Center board eeprom detection error.",
|
6: "Power output high power protection error.",
|
||||||
412: "Left board eeprom detection error.",
|
7: "Input over current protection of power supply on primary side.",
|
||||||
420: "Right board eeprom parsing error.",
|
},
|
||||||
421: "Center board eeprom parsing error.",
|
6: {
|
||||||
422: "Left board eeprom parsing error.",
|
3: "Power communication warning.",
|
||||||
430: "Right board chip bin type error.",
|
4: "Power communication error.",
|
||||||
431: "Center board chip bin type error.",
|
7: "Power watchdog protection.",
|
||||||
432: "Left board chip bin type error.",
|
8: "Power output high current protection.",
|
||||||
440: "Right board eeprom chip number X error.",
|
9: "Power input high current protection.",
|
||||||
441: "Center board eeprom chip number X error.",
|
},
|
||||||
442: "Left board eeprom chip number X error.",
|
7: {
|
||||||
450: "Right board eeprom xfer error.",
|
0: "Power input high voltage protection.",
|
||||||
451: "Center board eeprom xfer error.",
|
1: "Power input low voltage protection.",
|
||||||
452: "Left board eeprom xfer error.",
|
2: "Excessive power supply output warning.",
|
||||||
510: "Right board miner type error.",
|
3: "Power input too high warning.",
|
||||||
511: "Center board miner type error.",
|
4: "Power fan warning.",
|
||||||
512: "Left board miner type error.",
|
5: "Power high temperature warning.",
|
||||||
520: "Right board bin type error.",
|
},
|
||||||
521: "Center board bin type error.",
|
},
|
||||||
522: "Left board bin type error.",
|
3: { # temperature error
|
||||||
530: "Right board not found.",
|
0: { # sensor detection error
|
||||||
531: "Center board not found.",
|
"n": "Slot {n} temperature sensor detection error."
|
||||||
532: "Left board not found.",
|
},
|
||||||
540: "Right board error reading chip id.",
|
2: { # temperature reading error
|
||||||
541: "Center board error reading chip id.",
|
"n": "Slot {n} temperature reading error.",
|
||||||
542: "Left board error reading chip id.",
|
9: "Control board temperature sensor communication error.",
|
||||||
550: "Right board has bad chips.",
|
},
|
||||||
551: "Center board has bad chips.",
|
5: {"n": "Slot {n} temperature protecting."}, # temperature protection
|
||||||
552: "Left board has bad chips.",
|
6: {0: "Hashboard high temperature error."}, # high temp
|
||||||
560: "Right board loss of balance error.",
|
8: {
|
||||||
561: "Center board loss of balance error.",
|
0: "Humidity sensor not found.",
|
||||||
562: "Left board loss of balance error.",
|
1: "Humidity sensor read error.",
|
||||||
600: "Environment temperature is too high.",
|
2: "Humidity sensor read error.",
|
||||||
610: "Environment temperature is too high for high performance mode.",
|
3: "Humidity sensor protecting.",
|
||||||
701: "Control board no support chip.",
|
},
|
||||||
710: "Control board rebooted as an exception.",
|
},
|
||||||
712: "Control board rebooted as an exception.",
|
4: { # EEPROM error
|
||||||
800: "CGMiner checksum error.",
|
0: {0: "Eeprom unknown error."},
|
||||||
801: "System monitor checksum error.",
|
1: {"n": "Slot {n} eeprom detection error."}, # EEPROM detection error
|
||||||
802: "Remote daemon checksum error.",
|
2: {"n": "Slot {n} eeprom parsing error."}, # EEPROM parsing error
|
||||||
2010: "All pools are disabled.",
|
3: {"n": "Slot {n} chip bin type error."}, # chip bin error
|
||||||
2020: "Pool 0 connection failed.",
|
4: {"n": "Slot {n} eeprom chip number X error."}, # EEPROM chip number error
|
||||||
2021: "Pool 1 connection failed.",
|
5: {"n": "Slot {n} eeprom xfer error."}, # EEPROM xfer error
|
||||||
2022: "Pool 2 connection failed.",
|
},
|
||||||
2023: "Pool 3 connection failed.",
|
5: { # hashboard error
|
||||||
2030: "High rejection rate on pool.",
|
0: {0: "Board unknown error."},
|
||||||
2040: "The pool does not support asicboost mode.",
|
1: {"n": "Slot {n} miner type error."}, # board miner type error
|
||||||
2310: "Hashrate is too low.",
|
2: {"n": "Slot {n} bin type error."}, # chip bin type error
|
||||||
2320: "Hashrate is too low.",
|
3: {"n": "Slot {n} not found."}, # board not found error
|
||||||
2340: "Hashrate loss is too high.",
|
4: {"n": "Slot {n} error reading chip id."}, # reading chip id error
|
||||||
2350: "Hashrate loss is too high.",
|
5: {"n": "Slot {n} has bad chips."}, # board has bad chips error
|
||||||
5070: "Right hashboard water velocity is abnormal.",
|
6: {"n": "Slot {n} loss of balance error."}, # loss of balance error
|
||||||
5071: "Center hashboard water velocity is abnormal.",
|
7: {"n": "Slot {n} xfer error chip."}, # xfer error
|
||||||
5072: "Left hashboard water velocity is abnormal.",
|
8: {"n": "Slot {n} reset error."}, # reset error
|
||||||
5110: "Right hashboard frequency up timeout.",
|
9: {"n": "Slot {n} frequency too low."}, # freq error
|
||||||
5111: "Center hashboard frequency up timeout.",
|
},
|
||||||
5112: "Left hashboard frequency up timeout.",
|
6: { # env temp error
|
||||||
8410: "Software version error.",
|
0: {0: "Environment temperature is too high."}, # normal env temp error
|
||||||
100001: "/antiv/signature illegal.",
|
1: { # high power env temp error
|
||||||
100002: "/antiv/dig/init.d illegal.",
|
0: "Environment temperature is too high for high performance mode."
|
||||||
100003: "/antiv/dig/pf_partial.dig illegal.",
|
},
|
||||||
|
},
|
||||||
|
7: { # control board error
|
||||||
|
0: {0: "MAC address invalid", 1: "Control board no support chip."},
|
||||||
|
1: {
|
||||||
|
0: "Control board rebooted as an exception.",
|
||||||
|
1: "Control board rebooted as exception and cpufreq reduced, please upgrade the firmware",
|
||||||
|
2: "Control board rebooted as an exception.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
8: { # checksum error
|
||||||
|
0: {
|
||||||
|
0: "CGMiner checksum error.",
|
||||||
|
1: "System monitor checksum error.",
|
||||||
|
2: "Remote daemon checksum error.",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
9: {0: {1: "Power rate error."}}, # power rate error
|
||||||
|
20: { # pool error
|
||||||
|
1: {0: "All pools are disabled."}, # all disabled error
|
||||||
|
2: {"n": "Pool {n} connection failed."}, # pool connection failed error
|
||||||
|
3: {0: "High rejection rate on pool."}, # rejection rate error
|
||||||
|
4: { # asicboost not supported error
|
||||||
|
0: "The pool does not support asicboost mode."
|
||||||
|
},
|
||||||
|
},
|
||||||
|
21: {1: {"n": "Slot {n} factory test step failed."}},
|
||||||
|
23: { # hashrate error
|
||||||
|
1: {0: "Hashrate is too low."},
|
||||||
|
2: {0: "Hashrate is too low."},
|
||||||
|
3: {0: "Hashrate loss is too high."},
|
||||||
|
4: {0: "Hashrate loss is too high."},
|
||||||
|
5: {0: "Hashrate loss."},
|
||||||
|
},
|
||||||
|
50: { # water velocity error/voltage error
|
||||||
|
1: {"n": "Slot {n} chip voltage too low."},
|
||||||
|
2: {"n": "Slot {n} chip voltage changed."},
|
||||||
|
3: {"n": "Slot {n} chip temperature difference is too large."},
|
||||||
|
4: {"n": "Slot {n} chip hottest temperature difference is too large."},
|
||||||
|
7: {"n": "Slot {n} water velocity is abnormal."}, # abnormal water velocity
|
||||||
|
8: {0: "Chip temp calibration failed, please restore factory settings."},
|
||||||
|
9: {"n": "Slot {n} chip temp calibration check no balance."},
|
||||||
|
},
|
||||||
|
51: { # frequency error
|
||||||
|
1: {"n": "Slot {n} frequency up timeout."}, # frequency up timeout
|
||||||
|
7: {"n": "Slot {n} frequency up timeout."}, # frequency up timeout
|
||||||
|
},
|
||||||
|
52: {"n": {"c": "Slot {n} chip {c} error nonce."}},
|
||||||
|
53: {"n": {"c": "Slot {n} chip {c} too few nonce."}},
|
||||||
|
54: {"n": {"c": "Slot {n} chip {c} temp protected."}},
|
||||||
|
55: {"n": {"c": "Slot {n} chip {c} has been reset."}},
|
||||||
|
56: {"n": {"c": "Slot {n} chip {c} does not return to the nonce."}},
|
||||||
|
80: {
|
||||||
|
0: {0: "The tool version is too low, please update."},
|
||||||
|
1: {0: "Low freq."},
|
||||||
|
2: {0: "Low hashrate."},
|
||||||
|
3: {5: "High env temp."},
|
||||||
|
},
|
||||||
|
81: {
|
||||||
|
0: {0: "Chip data error."},
|
||||||
|
},
|
||||||
|
82: {
|
||||||
|
0: {0: "Power version error."},
|
||||||
|
1: {0: "Miner type error."},
|
||||||
|
2: {0: "Version info error."},
|
||||||
|
},
|
||||||
|
83: {
|
||||||
|
0: {0: "Empty level error."},
|
||||||
|
},
|
||||||
|
84: {
|
||||||
|
0: {0: "Old firmware."},
|
||||||
|
1: {0: "Software version error."},
|
||||||
|
},
|
||||||
|
85: {
|
||||||
|
"n": {
|
||||||
|
0: "Hashrate substandard L{n}.",
|
||||||
|
1: "Power consumption substandard L{n}.",
|
||||||
|
2: "Fan speed substandard L{n}.",
|
||||||
|
3: "Fan speed substandard L{n}.",
|
||||||
|
4: "Voltage substandard L{n}.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
86: {
|
||||||
|
0: {0: "Missing product serial #."},
|
||||||
|
1: {0: "Missing product type."},
|
||||||
|
2: {
|
||||||
|
0: "Missing miner serial #.",
|
||||||
|
1: "Wrong miner serial # length.",
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
0: "Missing power serial #.",
|
||||||
|
1: "Wrong power serial #.",
|
||||||
|
2: "Fault miner serial #.",
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
0: "Missing power model.",
|
||||||
|
1: "Wrong power model name.",
|
||||||
|
2: "Wrong power model vout.",
|
||||||
|
3: "Wrong power model rate.",
|
||||||
|
4: "Wrong power model format.",
|
||||||
|
},
|
||||||
|
5: {0: "Wrong hash board struct."},
|
||||||
|
6: {0: "Wrong miner cooling type."},
|
||||||
|
7: {0: "Missing PCB serial #."},
|
||||||
|
},
|
||||||
|
87: {0: {0: "Miner power mismatch."}},
|
||||||
|
99: {9: {9: "Miner unknown error."}},
|
||||||
|
1000: {
|
||||||
|
0: {
|
||||||
|
0: "Security library error, please upgrade firmware",
|
||||||
|
1: "/antiv/signature illegal.",
|
||||||
|
2: "/antiv/dig/init.d illegal.",
|
||||||
|
3: "/antiv/dig/pf_partial.dig illegal.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
1001: {0: {0: "Security BTMiner removed, please upgrade firmware."}},
|
||||||
|
1100: {
|
||||||
|
0: {
|
||||||
|
0: "Security illegal file, please upgrade firmware.",
|
||||||
|
1: "Security virus 0001 is removed, please upgrade firmware.",
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
# ------------------------------------------------------------------------------
|
||||||
#
|
# 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.
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# -
|
||||||
#
|
# 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,
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
# See the License for the specific language governing permissions and
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
# limitations under the License.
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class APIError(Exception):
|
class APIError(Exception):
|
||||||
@@ -22,11 +24,27 @@ class APIError(Exception):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.message:
|
if self.message:
|
||||||
|
if self.message == "can't access write cmd":
|
||||||
|
return f"{self.message}, please make sure your miner has been unlocked."
|
||||||
return f"{self.message}"
|
return f"{self.message}"
|
||||||
else:
|
else:
|
||||||
return "Incorrect API parameters."
|
return "Incorrect API parameters."
|
||||||
|
|
||||||
|
|
||||||
|
class PhaseBalancingError(Exception):
|
||||||
|
def __init__(self, *args):
|
||||||
|
if args:
|
||||||
|
self.message = args[0]
|
||||||
|
else:
|
||||||
|
self.message = None
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.message:
|
||||||
|
return f"{self.message}"
|
||||||
|
else:
|
||||||
|
return "Failed to balance phase."
|
||||||
|
|
||||||
|
|
||||||
class APIWarning(Warning):
|
class APIWarning(Warning):
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
if args:
|
if args:
|
||||||
|
|||||||
337
pyasic/load/__init__.py
Normal file
337
pyasic/load/__init__.py
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 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
|
||||||
|
from typing import List, Union
|
||||||
|
|
||||||
|
from pyasic.errors import APIError
|
||||||
|
from pyasic.miners import AnyMiner
|
||||||
|
from pyasic.miners.backends import AntminerModern, BOSMiner, BTMiner
|
||||||
|
from pyasic.miners.types import S9, S17, T17, S17e, S17Plus, S17Pro, T17e, T17Plus
|
||||||
|
|
||||||
|
FAN_USAGE = 50 # 50 W per fan
|
||||||
|
|
||||||
|
|
||||||
|
class MinerLoadBalancer:
|
||||||
|
"""A load balancer for miners. Can be passed a list of `AnyMiner`, or a list of phases (lists of `AnyMiner`)."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
phases: Union[List[List[AnyMiner]], None] = None,
|
||||||
|
):
|
||||||
|
self.phases = [_MinerPhaseBalancer(phase) for phase in phases]
|
||||||
|
|
||||||
|
async def balance(self, wattage: int) -> int:
|
||||||
|
phase_wattage = wattage // len(self.phases)
|
||||||
|
setpoints = await asyncio.gather(
|
||||||
|
*[phase.get_balance_setpoints(phase_wattage) for phase in self.phases]
|
||||||
|
)
|
||||||
|
tasks = []
|
||||||
|
total_wattage = 0
|
||||||
|
for setpoint in setpoints:
|
||||||
|
wattage_set = 0
|
||||||
|
for miner in setpoint:
|
||||||
|
if setpoint[miner]["set"] == "on":
|
||||||
|
wattage_set += setpoint[miner]["max"]
|
||||||
|
tasks.append(setpoint[miner]["miner"].resume_mining())
|
||||||
|
elif setpoint[miner]["set"] == "off":
|
||||||
|
wattage_set += setpoint[miner]["min"]
|
||||||
|
tasks.append(setpoint[miner]["miner"].stop_mining())
|
||||||
|
else:
|
||||||
|
wattage_set += setpoint[miner]["set"]
|
||||||
|
tasks.append(
|
||||||
|
setpoint[miner]["miner"].set_power_limit(setpoint[miner]["set"])
|
||||||
|
)
|
||||||
|
total_wattage += wattage_set
|
||||||
|
await asyncio.gather(*tasks)
|
||||||
|
return total_wattage
|
||||||
|
|
||||||
|
|
||||||
|
class _MinerPhaseBalancer:
|
||||||
|
def __init__(self, miners: List[AnyMiner]):
|
||||||
|
self.miners = {
|
||||||
|
str(miner.ip): {
|
||||||
|
"miner": miner,
|
||||||
|
"set": 0,
|
||||||
|
"min": miner.fan_count * FAN_USAGE,
|
||||||
|
}
|
||||||
|
for miner in miners
|
||||||
|
}
|
||||||
|
for miner in miners:
|
||||||
|
if (
|
||||||
|
isinstance(miner, BTMiner)
|
||||||
|
and not (miner.model.startswith("M2") if miner.model else True)
|
||||||
|
) or isinstance(miner, BOSMiner):
|
||||||
|
if isinstance(miner, S9):
|
||||||
|
self.miners[str(miner.ip)]["tune"] = True
|
||||||
|
self.miners[str(miner.ip)]["shutdown"] = True
|
||||||
|
self.miners[str(miner.ip)]["max"] = 1400
|
||||||
|
elif True in [
|
||||||
|
isinstance(miner, x)
|
||||||
|
for x in [S17, S17Plus, S17Pro, S17e, T17, T17Plus, T17e]
|
||||||
|
]:
|
||||||
|
self.miners[str(miner.ip)]["tune"] = True
|
||||||
|
self.miners[str(miner.ip)]["shutdown"] = True
|
||||||
|
self.miners[str(miner.ip)]["max"] = 2400
|
||||||
|
else:
|
||||||
|
self.miners[str(miner.ip)]["tune"] = True
|
||||||
|
self.miners[str(miner.ip)]["shutdown"] = True
|
||||||
|
self.miners[str(miner.ip)]["max"] = 3600
|
||||||
|
elif isinstance(miner, AntminerModern):
|
||||||
|
self.miners[str(miner.ip)]["tune"] = False
|
||||||
|
self.miners[str(miner.ip)]["shutdown"] = True
|
||||||
|
self.miners[str(miner.ip)]["max"] = 3600
|
||||||
|
elif isinstance(miner, BTMiner):
|
||||||
|
self.miners[str(miner.ip)]["tune"] = False
|
||||||
|
self.miners[str(miner.ip)]["shutdown"] = True
|
||||||
|
self.miners[str(miner.ip)]["max"] = 3600
|
||||||
|
if miner.model:
|
||||||
|
if miner.model.startswith("M2"):
|
||||||
|
self.miners[str(miner.ip)]["tune"] = False
|
||||||
|
self.miners[str(miner.ip)]["shutdown"] = True
|
||||||
|
self.miners[str(miner.ip)]["max"] = 2400
|
||||||
|
else:
|
||||||
|
self.miners[str(miner.ip)]["tune"] = False
|
||||||
|
self.miners[str(miner.ip)]["shutdown"] = False
|
||||||
|
self.miners[str(miner.ip)]["max"] = 3600
|
||||||
|
self.miners[str(miner.ip)]["min"] = 3600
|
||||||
|
|
||||||
|
async def balance(self, wattage: int) -> int:
|
||||||
|
setpoint = await self.get_balance_setpoints(wattage)
|
||||||
|
wattage_set = 0
|
||||||
|
tasks = []
|
||||||
|
for miner in setpoint:
|
||||||
|
if setpoint[miner]["set"] == "on":
|
||||||
|
wattage_set += setpoint[miner]["max"]
|
||||||
|
tasks.append(setpoint[miner]["miner"].resume_mining())
|
||||||
|
elif setpoint[miner]["set"] == "off":
|
||||||
|
wattage_set += setpoint[miner]["min"]
|
||||||
|
tasks.append(setpoint[miner]["miner"].stop_mining())
|
||||||
|
else:
|
||||||
|
wattage_set += setpoint[miner]["set"]
|
||||||
|
tasks.append(
|
||||||
|
setpoint[miner]["miner"].set_power_limit(setpoint[miner]["set"])
|
||||||
|
)
|
||||||
|
await asyncio.gather(*tasks)
|
||||||
|
return wattage_set
|
||||||
|
|
||||||
|
async def get_balance_setpoints(self, wattage: int) -> dict:
|
||||||
|
# gather data needed to optimize shutdown only miners
|
||||||
|
dp = ["hashrate", "wattage", "wattage_limit", "hashboards"]
|
||||||
|
data = await asyncio.gather(
|
||||||
|
*[
|
||||||
|
self.miners[miner]["miner"].get_data(data_to_get=dp)
|
||||||
|
for miner in self.miners
|
||||||
|
]
|
||||||
|
)
|
||||||
|
pct_ideal_list = [d.percent_ideal for d in data]
|
||||||
|
pct_ideal = 0
|
||||||
|
if len(pct_ideal_list) > 0:
|
||||||
|
pct_ideal = sum(pct_ideal_list) / len(pct_ideal_list)
|
||||||
|
|
||||||
|
wattage = round(wattage * 1 / (pct_ideal / 100))
|
||||||
|
|
||||||
|
for data_point in data:
|
||||||
|
if (not self.miners[data_point.ip]["tune"]) and (
|
||||||
|
not self.miners[data_point.ip]["shutdown"]
|
||||||
|
):
|
||||||
|
# cant do anything with it so need to find a semi-accurate power limit
|
||||||
|
if not data_point.wattage_limit == None:
|
||||||
|
self.miners[data_point.ip]["max"] = int(data_point.wattage_limit)
|
||||||
|
self.miners[data_point.ip]["min"] = int(data_point.wattage_limit)
|
||||||
|
elif not data_point.wattage == None:
|
||||||
|
self.miners[data_point.ip]["max"] = int(data_point.wattage)
|
||||||
|
self.miners[data_point.ip]["min"] = int(data_point.wattage)
|
||||||
|
|
||||||
|
max_tune_wattage = sum(
|
||||||
|
[miner["max"] for miner in self.miners.values() if miner["tune"]]
|
||||||
|
)
|
||||||
|
max_shutdown_wattage = sum(
|
||||||
|
[
|
||||||
|
miner["max"]
|
||||||
|
for miner in self.miners.values()
|
||||||
|
if (not miner["tune"]) and (miner["shutdown"])
|
||||||
|
]
|
||||||
|
)
|
||||||
|
max_other_wattage = sum(
|
||||||
|
[
|
||||||
|
miner["max"]
|
||||||
|
for miner in self.miners.values()
|
||||||
|
if (not miner["tune"]) and (not miner["shutdown"])
|
||||||
|
]
|
||||||
|
)
|
||||||
|
min_tune_wattage = sum(
|
||||||
|
[miner["min"] for miner in self.miners.values() if miner["tune"]]
|
||||||
|
)
|
||||||
|
min_shutdown_wattage = sum(
|
||||||
|
[
|
||||||
|
miner["min"]
|
||||||
|
for miner in self.miners.values()
|
||||||
|
if (not miner["tune"]) and (miner["shutdown"])
|
||||||
|
]
|
||||||
|
)
|
||||||
|
# min_other_wattage = sum([miner["min"] for miner in self.miners.values() if (not miner["tune"]) and (not miner["shutdown"])])
|
||||||
|
|
||||||
|
# make sure wattage isnt set too high
|
||||||
|
if wattage > (max_tune_wattage + max_shutdown_wattage + max_other_wattage):
|
||||||
|
raise APIError(
|
||||||
|
f"Wattage setpoint is too high, setpoint: {wattage}W, max: {max_tune_wattage + max_shutdown_wattage + max_other_wattage}W"
|
||||||
|
) # PhaseBalancingError(f"Wattage setpoint is too high, setpoint: {wattage}W, max: {max_tune_wattage + max_shutdown_wattage + max_other_wattage}W")
|
||||||
|
|
||||||
|
# should now know wattage limits and which can be tuned/shutdown
|
||||||
|
# check if 1/2 max of the miners which can be tuned is low enough
|
||||||
|
if (max_tune_wattage / 2) + max_shutdown_wattage + max_other_wattage < wattage:
|
||||||
|
useable_wattage = wattage - (max_other_wattage + max_shutdown_wattage)
|
||||||
|
useable_miners = len(
|
||||||
|
[m for m in self.miners.values() if (m["set"] == 0) and (m["tune"])]
|
||||||
|
)
|
||||||
|
if not useable_miners == 0:
|
||||||
|
watts_per_miner = useable_wattage // useable_miners
|
||||||
|
# loop through and set useable miners to wattage
|
||||||
|
for miner in self.miners:
|
||||||
|
if (self.miners[miner]["set"] == 0) and (
|
||||||
|
self.miners[miner]["tune"]
|
||||||
|
):
|
||||||
|
self.miners[miner]["set"] = watts_per_miner
|
||||||
|
elif self.miners[miner]["set"] == 0 and (
|
||||||
|
self.miners[miner]["shutdown"]
|
||||||
|
):
|
||||||
|
self.miners[miner]["set"] = "on"
|
||||||
|
|
||||||
|
# check if shutting down miners will help
|
||||||
|
elif (
|
||||||
|
max_tune_wattage / 2
|
||||||
|
) + min_shutdown_wattage + max_other_wattage < wattage:
|
||||||
|
# tuneable inclusive since could be S9 BOS+ and S19 Stock, would rather shut down the S9, tuneable should always support shutdown
|
||||||
|
useable_wattage = wattage - (
|
||||||
|
min_tune_wattage + max_other_wattage + min_shutdown_wattage
|
||||||
|
)
|
||||||
|
for miner in sorted(
|
||||||
|
[miner for miner in self.miners.values() if miner["shutdown"]],
|
||||||
|
key=lambda x: x["max"],
|
||||||
|
reverse=True,
|
||||||
|
):
|
||||||
|
if miner["tune"]:
|
||||||
|
miner_min_watt_use = miner["max"] / 2
|
||||||
|
useable_wattage -= miner_min_watt_use - miner["min"]
|
||||||
|
if useable_wattage < 0:
|
||||||
|
useable_wattage += miner_min_watt_use - miner["min"]
|
||||||
|
self.miners[str(miner["miner"].ip)]["set"] = "off"
|
||||||
|
else:
|
||||||
|
miner_min_watt_use = miner["max"]
|
||||||
|
useable_wattage -= miner_min_watt_use - miner["min"]
|
||||||
|
if useable_wattage < 0:
|
||||||
|
useable_wattage += miner_min_watt_use - miner["min"]
|
||||||
|
self.miners[str(miner["miner"].ip)]["set"] = "off"
|
||||||
|
|
||||||
|
new_shutdown_wattage = sum(
|
||||||
|
[
|
||||||
|
miner["max"] if miner["set"] == 0 else miner["min"]
|
||||||
|
for miner in self.miners.values()
|
||||||
|
if miner["shutdown"] and not miner["tune"]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
new_tune_wattage = sum(
|
||||||
|
[
|
||||||
|
miner["min"]
|
||||||
|
for miner in self.miners.values()
|
||||||
|
if miner["tune"] and miner["set"] == "off"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
useable_wattage = wattage - (
|
||||||
|
new_tune_wattage + max_other_wattage + new_shutdown_wattage
|
||||||
|
)
|
||||||
|
useable_miners = len(
|
||||||
|
[m for m in self.miners.values() if (m["set"] == 0) and (m["tune"])]
|
||||||
|
)
|
||||||
|
|
||||||
|
if not useable_miners == 0:
|
||||||
|
watts_per_miner = useable_wattage // useable_miners
|
||||||
|
# loop through and set useable miners to wattage
|
||||||
|
for miner in self.miners:
|
||||||
|
if (self.miners[miner]["set"] == 0) and (
|
||||||
|
self.miners[miner]["tune"]
|
||||||
|
):
|
||||||
|
self.miners[miner]["set"] = watts_per_miner
|
||||||
|
elif self.miners[miner]["set"] == 0 and (
|
||||||
|
self.miners[miner]["shutdown"]
|
||||||
|
):
|
||||||
|
self.miners[miner]["set"] = "on"
|
||||||
|
|
||||||
|
# check if shutting down tuneable miners will do it
|
||||||
|
elif min_tune_wattage + min_shutdown_wattage + max_other_wattage < wattage:
|
||||||
|
# all miners that can be shutdown need to be
|
||||||
|
for miner in self.miners:
|
||||||
|
if (not self.miners[miner]["tune"]) and (
|
||||||
|
self.miners[miner]["shutdown"]
|
||||||
|
):
|
||||||
|
self.miners[miner]["set"] = "off"
|
||||||
|
# calculate wattage usable by tuneable miners
|
||||||
|
useable_wattage = wattage - (
|
||||||
|
min_tune_wattage + max_other_wattage + min_shutdown_wattage
|
||||||
|
)
|
||||||
|
|
||||||
|
# loop through miners to see how much is actually useable
|
||||||
|
# sort the largest first
|
||||||
|
for miner in sorted(
|
||||||
|
[
|
||||||
|
miner
|
||||||
|
for miner in self.miners.values()
|
||||||
|
if miner["tune"] and miner["shutdown"]
|
||||||
|
],
|
||||||
|
key=lambda x: x["max"],
|
||||||
|
reverse=True,
|
||||||
|
):
|
||||||
|
# add min to useable wattage since it was removed earlier, and remove 1/2 tuner max
|
||||||
|
useable_wattage -= (miner["max"] / 2) - miner["min"]
|
||||||
|
if useable_wattage < 0:
|
||||||
|
useable_wattage += (miner["max"] / 2) - miner["min"]
|
||||||
|
self.miners[str(miner["miner"].ip)]["set"] = "off"
|
||||||
|
|
||||||
|
new_tune_wattage = sum(
|
||||||
|
[
|
||||||
|
miner["min"]
|
||||||
|
for miner in self.miners.values()
|
||||||
|
if miner["tune"] and miner["set"] == "off"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
useable_wattage = wattage - (
|
||||||
|
new_tune_wattage + max_other_wattage + min_shutdown_wattage
|
||||||
|
)
|
||||||
|
useable_miners = len(
|
||||||
|
[m for m in self.miners.values() if (m["set"] == 0) and (m["tune"])]
|
||||||
|
)
|
||||||
|
|
||||||
|
if not useable_miners == 0:
|
||||||
|
watts_per_miner = useable_wattage // useable_miners
|
||||||
|
# loop through and set useable miners to wattage
|
||||||
|
for miner in self.miners:
|
||||||
|
if (self.miners[miner]["set"] == 0) and (
|
||||||
|
self.miners[miner]["tune"]
|
||||||
|
):
|
||||||
|
self.miners[miner]["set"] = watts_per_miner
|
||||||
|
elif self.miners[miner]["set"] == 0 and (
|
||||||
|
self.miners[miner]["shutdown"]
|
||||||
|
):
|
||||||
|
self.miners[miner]["set"] = "on"
|
||||||
|
else:
|
||||||
|
raise APIError(
|
||||||
|
f"Wattage setpoint is too low, setpoint: {wattage}W, min: {min_tune_wattage + min_shutdown_wattage + max_other_wattage}W"
|
||||||
|
) # PhaseBalancingError(f"Wattage setpoint is too low, setpoint: {wattage}W, min: {min_tune_wattage + min_shutdown_wattage + max_other_wattage}W")
|
||||||
|
|
||||||
|
return self.miners
|
||||||
@@ -1,18 +1,21 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
# ------------------------------------------------------------------------------
|
||||||
#
|
# 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.
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# -
|
||||||
#
|
# 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,
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
# See the License for the specific language governing permissions and
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
# limitations under the License.
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pyasic.settings import PyasicSettings
|
from pyasic.settings import PyasicSettings
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
# ------------------------------------------------------------------------------
|
||||||
#
|
# 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.
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# You may obtain a copy of the License at
|
# 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
|
# -
|
||||||
#
|
# 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,
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
# See the License for the specific language governing permissions and
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
# limitations under the License.
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from pyasic.miners.base import BaseMiner, AnyMiner
|
from pyasic.miners.base import AnyMiner, BaseMiner
|
||||||
from pyasic.miners.miner_factory import MinerFactory
|
from pyasic.miners.miner_factory import miner_factory
|
||||||
|
|
||||||
|
|
||||||
# abstracted version of get miner that is easier to access
|
# abstracted version of get miner that is easier to access
|
||||||
async def get_miner(ip: Union[ipaddress.ip_address, str]) -> AnyMiner:
|
async def get_miner(ip: Union[ipaddress.ip_address, str]) -> AnyMiner:
|
||||||
return await MinerFactory().get_miner(ip)
|
return await miner_factory.get_miner(ip)
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
# 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 .bmminer import BMMiner
|
|
||||||
from .bosminer import BOSMiner
|
|
||||||
from .btminer import BTMiner
|
|
||||||
from .cgminer import CGMiner
|
|
||||||
from .hiveon import Hiveon
|
|
||||||
@@ -1,356 +0,0 @@
|
|||||||
# 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 ipaddress
|
|
||||||
import logging
|
|
||||||
from typing import Union, List
|
|
||||||
|
|
||||||
|
|
||||||
from pyasic.API.bmminer import BMMinerAPI
|
|
||||||
from pyasic.miners.base import BaseMiner
|
|
||||||
|
|
||||||
from pyasic.data import MinerData
|
|
||||||
from pyasic.config import MinerConfig
|
|
||||||
from pyasic.data.error_codes import MinerErrorData
|
|
||||||
|
|
||||||
from pyasic.settings import PyasicSettings
|
|
||||||
|
|
||||||
|
|
||||||
class BMMiner(BaseMiner):
|
|
||||||
"""Base handler for BMMiner based miners."""
|
|
||||||
|
|
||||||
def __init__(self, ip: str) -> None:
|
|
||||||
super().__init__(ip)
|
|
||||||
self.ip = ipaddress.ip_address(ip)
|
|
||||||
self.api = BMMinerAPI(ip)
|
|
||||||
self.api_type = "BMMiner"
|
|
||||||
self.uname = "root"
|
|
||||||
self.pwd = "admin"
|
|
||||||
|
|
||||||
async def get_model(self) -> Union[str, None]:
|
|
||||||
"""Get miner model.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Miner model or None.
|
|
||||||
"""
|
|
||||||
# check if model is cached
|
|
||||||
if self.model:
|
|
||||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
|
||||||
return self.model
|
|
||||||
|
|
||||||
# get devdetails data
|
|
||||||
version_data = await self.api.devdetails()
|
|
||||||
|
|
||||||
# if we get data back, parse it for model
|
|
||||||
if version_data:
|
|
||||||
# handle Antminer BMMiner as a base
|
|
||||||
self.model = version_data["DEVDETAILS"][0]["Model"].replace("Antminer ", "")
|
|
||||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
|
||||||
return self.model
|
|
||||||
|
|
||||||
# if we don't get devdetails, log a failed attempt
|
|
||||||
logging.warning(f"Failed to get model for miner: {self}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_hostname(self) -> str:
|
|
||||||
"""Get miner hostname.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The hostname of the miner as a string or "?"
|
|
||||||
"""
|
|
||||||
if self.hostname:
|
|
||||||
return self.hostname
|
|
||||||
try:
|
|
||||||
# open an ssh connection
|
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
|
||||||
# if we get the connection, check hostname
|
|
||||||
if conn is not None:
|
|
||||||
# get output of the hostname file
|
|
||||||
data = await conn.run("cat /proc/sys/kernel/hostname")
|
|
||||||
host = data.stdout.strip()
|
|
||||||
|
|
||||||
# return hostname data
|
|
||||||
logging.debug(f"Found hostname for {self.ip}: {host}")
|
|
||||||
self.hostname = host
|
|
||||||
return self.hostname
|
|
||||||
else:
|
|
||||||
# return ? if we fail to get hostname with no ssh connection
|
|
||||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
|
||||||
return "?"
|
|
||||||
except Exception:
|
|
||||||
# return ? if we fail to get hostname with an exception
|
|
||||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
|
||||||
return "?"
|
|
||||||
|
|
||||||
async def send_ssh_command(self, cmd: str) -> Union[str, None]:
|
|
||||||
"""Send a command to the miner over ssh.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
cmd: The command to run.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Result of the command or None.
|
|
||||||
"""
|
|
||||||
result = None
|
|
||||||
|
|
||||||
# open an ssh connection
|
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
|
||||||
# 3 retries
|
|
||||||
for i in range(3):
|
|
||||||
try:
|
|
||||||
# run the command and get the result
|
|
||||||
result = await conn.run(cmd)
|
|
||||||
result = result.stdout
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# if the command fails, log it
|
|
||||||
logging.warning(f"{self} command {cmd} error: {e}")
|
|
||||||
|
|
||||||
# on the 3rd retry, return None
|
|
||||||
if i == 3:
|
|
||||||
return
|
|
||||||
continue
|
|
||||||
# return the result, either command output or None
|
|
||||||
return result
|
|
||||||
|
|
||||||
async def get_config(self) -> Union[list, None]:
|
|
||||||
"""Get the pool configuration of the miner.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Pool config data or None.
|
|
||||||
"""
|
|
||||||
# get pool data
|
|
||||||
pools = await self.api.pools()
|
|
||||||
pool_data = []
|
|
||||||
|
|
||||||
# ensure we got pool data
|
|
||||||
if not pools:
|
|
||||||
return
|
|
||||||
|
|
||||||
# parse all the pools
|
|
||||||
for pool in pools["POOLS"]:
|
|
||||||
pool_data.append({"url": pool["URL"], "user": pool["User"], "pwd": "123"})
|
|
||||||
return pool_data
|
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
|
||||||
"""Reboot the miner.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The result of rebooting the miner.
|
|
||||||
"""
|
|
||||||
logging.debug(f"{self}: Sending reboot command.")
|
|
||||||
_ret = await self.send_ssh_command("reboot")
|
|
||||||
logging.debug(f"{self}: Reboot command completed.")
|
|
||||||
if isinstance(_ret, str):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def check_light(self) -> bool:
|
|
||||||
if not self.light:
|
|
||||||
self.light = False
|
|
||||||
return self.light
|
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def get_errors(self) -> List[MinerErrorData]:
|
|
||||||
return []
|
|
||||||
|
|
||||||
async def get_mac(self) -> str:
|
|
||||||
return "00:00:00:00:00:00"
|
|
||||||
|
|
||||||
async def restart_backend(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def stop_mining(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def resume_mining(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def get_data(self) -> MinerData:
|
|
||||||
"""Get data from the miner.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
|
||||||
"""
|
|
||||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
|
||||||
|
|
||||||
board_offset = -1
|
|
||||||
fan_offset = -1
|
|
||||||
|
|
||||||
model = await self.get_model()
|
|
||||||
hostname = await self.get_hostname()
|
|
||||||
mac = await self.get_mac()
|
|
||||||
errors = await self.get_errors()
|
|
||||||
|
|
||||||
if model:
|
|
||||||
data.model = model
|
|
||||||
|
|
||||||
if hostname:
|
|
||||||
data.hostname = hostname
|
|
||||||
|
|
||||||
if mac:
|
|
||||||
data.mac = mac
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
for error in errors:
|
|
||||||
data.errors.append(error)
|
|
||||||
|
|
||||||
data.fault_light = await self.check_light()
|
|
||||||
|
|
||||||
miner_data = None
|
|
||||||
for i in range(PyasicSettings().miner_get_data_retries):
|
|
||||||
miner_data = await self.api.multicommand(
|
|
||||||
"summary", "pools", "stats", ignore_x19_error=True
|
|
||||||
)
|
|
||||||
if miner_data:
|
|
||||||
break
|
|
||||||
|
|
||||||
if not miner_data:
|
|
||||||
return data
|
|
||||||
|
|
||||||
summary = miner_data.get("summary")[0]
|
|
||||||
pools = miner_data.get("pools")[0]
|
|
||||||
stats = miner_data.get("stats")[0]
|
|
||||||
|
|
||||||
if summary:
|
|
||||||
hr = summary.get("SUMMARY")
|
|
||||||
if hr:
|
|
||||||
if len(hr) > 0:
|
|
||||||
hr = hr[0].get("GHS av")
|
|
||||||
if hr:
|
|
||||||
data.hashrate = round(hr / 1000, 2)
|
|
||||||
|
|
||||||
if stats:
|
|
||||||
boards = stats.get("STATS")
|
|
||||||
if boards:
|
|
||||||
if len(boards) > 0:
|
|
||||||
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
|
|
||||||
|
|
||||||
data.left_chips = boards[1].get(f"chain_acn{board_offset}")
|
|
||||||
data.center_chips = boards[1].get(f"chain_acn{board_offset+1}")
|
|
||||||
data.right_chips = boards[1].get(f"chain_acn{board_offset+2}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
data.left_board_hashrate = round(
|
|
||||||
float(boards[1].get(f"chain_rate{board_offset}")) / 1000, 2
|
|
||||||
)
|
|
||||||
except ValueError as e:
|
|
||||||
data.left_board_hashrate = round(0.00, 2)
|
|
||||||
try:
|
|
||||||
data.center_board_hashrate = round(
|
|
||||||
float(boards[1].get(f"chain_rate{board_offset+1}")) / 1000,
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
except ValueError as e:
|
|
||||||
data.center_board_hashrate = round(0.00, 2)
|
|
||||||
try:
|
|
||||||
data.right_board_hashrate = round(
|
|
||||||
float(boards[1].get(f"chain_rate{board_offset+2}")) / 1000,
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
except ValueError as e:
|
|
||||||
data.right_board_hashrate = round(0.00, 2)
|
|
||||||
|
|
||||||
if stats:
|
|
||||||
temp = stats.get("STATS")
|
|
||||||
if temp:
|
|
||||||
if len(temp) > 1:
|
|
||||||
for fan_num in range(1, 8, 4):
|
|
||||||
for _f_num in range(4):
|
|
||||||
f = temp[1].get(f"fan{fan_num + _f_num}")
|
|
||||||
if f and not f == 0 and fan_offset == -1:
|
|
||||||
fan_offset = fan_num
|
|
||||||
if fan_offset == -1:
|
|
||||||
fan_offset = 1
|
|
||||||
for fan in range(self.fan_count):
|
|
||||||
setattr(
|
|
||||||
data, f"fan_{fan + 1}", temp[1].get(f"fan{fan_offset+fan}")
|
|
||||||
)
|
|
||||||
|
|
||||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
|
||||||
env_temp_list = []
|
|
||||||
for item in range(3):
|
|
||||||
board_temp = temp[1].get(f"temp{item + board_offset}")
|
|
||||||
chip_temp = temp[1].get(f"temp2_{item + board_offset}")
|
|
||||||
setattr(data, f"{board_map[item]}_chip_temp", chip_temp)
|
|
||||||
setattr(data, f"{board_map[item]}_temp", board_temp)
|
|
||||||
if f"temp_pcb{item}" in temp[1].keys():
|
|
||||||
env_temp = temp[1][f"temp_pcb{item}"].split("-")[0]
|
|
||||||
if not env_temp == 0:
|
|
||||||
env_temp_list.append(int(env_temp))
|
|
||||||
if not env_temp_list == []:
|
|
||||||
data.env_temp = sum(env_temp_list) / len(env_temp_list)
|
|
||||||
|
|
||||||
if pools:
|
|
||||||
pool_1 = None
|
|
||||||
pool_2 = None
|
|
||||||
pool_1_user = None
|
|
||||||
pool_2_user = None
|
|
||||||
pool_1_quota = 1
|
|
||||||
pool_2_quota = 1
|
|
||||||
quota = 0
|
|
||||||
for pool in pools.get("POOLS"):
|
|
||||||
if not pool_1_user:
|
|
||||||
pool_1_user = pool.get("User")
|
|
||||||
pool_1 = pool["URL"]
|
|
||||||
pool_1_quota = pool["Quota"]
|
|
||||||
elif not pool_2_user:
|
|
||||||
pool_2_user = pool.get("User")
|
|
||||||
pool_2 = pool["URL"]
|
|
||||||
pool_2_quota = pool["Quota"]
|
|
||||||
if not pool.get("User") == pool_1_user:
|
|
||||||
if not pool_2_user == pool.get("User"):
|
|
||||||
pool_2_user = pool.get("User")
|
|
||||||
pool_2 = pool["URL"]
|
|
||||||
pool_2_quota = pool["Quota"]
|
|
||||||
if pool_2_user and not pool_2_user == pool_1_user:
|
|
||||||
quota = f"{pool_1_quota}/{pool_2_quota}"
|
|
||||||
|
|
||||||
if pool_1:
|
|
||||||
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
|
|
||||||
"stratum2+tcp://", ""
|
|
||||||
)
|
|
||||||
data.pool_1_url = pool_1
|
|
||||||
|
|
||||||
if pool_1_user:
|
|
||||||
data.pool_1_user = pool_1_user
|
|
||||||
|
|
||||||
if pool_2:
|
|
||||||
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
|
|
||||||
"stratum2+tcp://", ""
|
|
||||||
)
|
|
||||||
data.pool_2_url = pool_2
|
|
||||||
|
|
||||||
if pool_2_user:
|
|
||||||
data.pool_2_user = pool_2_user
|
|
||||||
|
|
||||||
if quota:
|
|
||||||
data.pool_split = str(quota)
|
|
||||||
|
|
||||||
return data
|
|
||||||
@@ -1,489 +0,0 @@
|
|||||||
# 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 ipaddress
|
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
from typing import Union, List
|
|
||||||
|
|
||||||
import toml
|
|
||||||
|
|
||||||
|
|
||||||
from pyasic.miners.base import BaseMiner
|
|
||||||
from pyasic.API.bosminer import BOSMinerAPI
|
|
||||||
from pyasic.errors import APIError
|
|
||||||
|
|
||||||
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
|
|
||||||
from pyasic.data import MinerData
|
|
||||||
|
|
||||||
from pyasic.config import MinerConfig
|
|
||||||
|
|
||||||
from pyasic.settings import PyasicSettings
|
|
||||||
|
|
||||||
|
|
||||||
class BOSMiner(BaseMiner):
|
|
||||||
def __init__(self, ip: str) -> None:
|
|
||||||
super().__init__(ip)
|
|
||||||
self.ip = ipaddress.ip_address(ip)
|
|
||||||
self.api = BOSMinerAPI(ip)
|
|
||||||
self.api_type = "BOSMiner"
|
|
||||||
self.uname = "root"
|
|
||||||
self.pwd = "admin"
|
|
||||||
self.config = None
|
|
||||||
|
|
||||||
async def send_ssh_command(self, cmd: str) -> Union[str, None]:
|
|
||||||
"""Send a command to the miner over ssh.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Result of the command or None.
|
|
||||||
"""
|
|
||||||
result = None
|
|
||||||
|
|
||||||
# open an ssh connection
|
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
|
||||||
# 3 retries
|
|
||||||
for i in range(3):
|
|
||||||
try:
|
|
||||||
# run the command and get the result
|
|
||||||
result = await conn.run(cmd)
|
|
||||||
result = result.stdout
|
|
||||||
except Exception as e:
|
|
||||||
# if the command fails, log it
|
|
||||||
logging.warning(f"{self} command {cmd} error: {e}")
|
|
||||||
|
|
||||||
# on the 3rd retry, return None
|
|
||||||
if i == 3:
|
|
||||||
return
|
|
||||||
continue
|
|
||||||
# return the result, either command output or None
|
|
||||||
return str(result)
|
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
|
||||||
"""Sends command to turn on fault light on the miner."""
|
|
||||||
logging.debug(f"{self}: Sending fault_light on command.")
|
|
||||||
_ret = await self.send_ssh_command("miner fault_light on")
|
|
||||||
logging.debug(f"{self}: fault_light on command completed.")
|
|
||||||
if isinstance(_ret, str):
|
|
||||||
self.light = True
|
|
||||||
return self.light
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
|
||||||
"""Sends command to turn off fault light on the miner."""
|
|
||||||
logging.debug(f"{self}: Sending fault_light off command.")
|
|
||||||
self.light = False
|
|
||||||
_ret = await self.send_ssh_command("miner fault_light off")
|
|
||||||
logging.debug(f"{self}: fault_light off command completed.")
|
|
||||||
if isinstance(_ret, str):
|
|
||||||
self.light = False
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def restart_backend(self) -> bool:
|
|
||||||
"""Restart bosminer hashing process. Wraps [`restart_bosminer`][pyasic.miners._backends.bosminer.BOSMiner.restart_bosminer] to standardize."""
|
|
||||||
return await self.restart_bosminer()
|
|
||||||
|
|
||||||
async def restart_bosminer(self) -> bool:
|
|
||||||
"""Restart bosminer hashing process."""
|
|
||||||
logging.debug(f"{self}: Sending bosminer restart command.")
|
|
||||||
_ret = await self.send_ssh_command("/etc/init.d/bosminer restart")
|
|
||||||
logging.debug(f"{self}: bosminer restart command completed.")
|
|
||||||
if isinstance(_ret, str):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def stop_mining(self) -> bool:
|
|
||||||
data = await self.api.pause()
|
|
||||||
if data.get("PAUSE"):
|
|
||||||
if data["PAUSE"][0]:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def resume_mining(self) -> bool:
|
|
||||||
data = await self.api.resume()
|
|
||||||
if data.get("RESUME"):
|
|
||||||
if data["RESUME"][0]:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
|
||||||
"""Reboots power to the physical miner."""
|
|
||||||
logging.debug(f"{self}: Sending reboot command.")
|
|
||||||
_ret = await self.send_ssh_command("/sbin/reboot")
|
|
||||||
logging.debug(f"{self}: Reboot command completed.")
|
|
||||||
if isinstance(_ret, str):
|
|
||||||
return True
|
|
||||||
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`.
|
|
||||||
"""
|
|
||||||
logging.debug(f"{self}: Getting config.")
|
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
|
||||||
logging.debug(f"{self}: Opening SFTP connection.")
|
|
||||||
async with conn.start_sftp_client() as sftp:
|
|
||||||
logging.debug(f"{self}: Reading config file.")
|
|
||||||
async with sftp.open("/etc/bosminer.toml") as file:
|
|
||||||
toml_data = toml.loads(await file.read())
|
|
||||||
logging.debug(f"{self}: Converting config file.")
|
|
||||||
cfg = MinerConfig().from_raw(toml_data)
|
|
||||||
self.config = cfg
|
|
||||||
return self.config
|
|
||||||
|
|
||||||
async def get_hostname(self) -> str:
|
|
||||||
"""Get miner hostname.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The hostname of the miner as a string or "?"
|
|
||||||
"""
|
|
||||||
if self.hostname:
|
|
||||||
return self.hostname
|
|
||||||
try:
|
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
|
||||||
if conn is not None:
|
|
||||||
data = await conn.run("cat /proc/sys/kernel/hostname")
|
|
||||||
host = data.stdout.strip()
|
|
||||||
logging.debug(f"Found hostname for {self.ip}: {host}")
|
|
||||||
self.hostname = host
|
|
||||||
return self.hostname
|
|
||||||
else:
|
|
||||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
|
||||||
return "?"
|
|
||||||
except Exception:
|
|
||||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
|
||||||
return "?"
|
|
||||||
|
|
||||||
async def get_model(self) -> Union[str, None]:
|
|
||||||
"""Get miner model.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Miner model or None.
|
|
||||||
"""
|
|
||||||
# check if model is cached
|
|
||||||
if self.model:
|
|
||||||
logging.debug(f"Found model for {self.ip}: {self.model} (BOS)")
|
|
||||||
return self.model + " (BOS)"
|
|
||||||
|
|
||||||
# get devdetails data
|
|
||||||
try:
|
|
||||||
version_data = await self.api.devdetails()
|
|
||||||
except APIError as e:
|
|
||||||
version_data = None
|
|
||||||
if e.message == "Not ready":
|
|
||||||
cfg = json.loads(await self.send_ssh_command("bosminer config --data"))
|
|
||||||
model = cfg.get("data").get("format").get("model")
|
|
||||||
if model:
|
|
||||||
model = model.replace("Antminer ", "")
|
|
||||||
self.model = model
|
|
||||||
return self.model + " (BOS)"
|
|
||||||
|
|
||||||
# if we get data back, parse it for model
|
|
||||||
if version_data:
|
|
||||||
if not version_data["DEVDETAILS"] == []:
|
|
||||||
# handle Antminer BOSMiner as a base
|
|
||||||
self.model = version_data["DEVDETAILS"][0]["Model"].replace(
|
|
||||||
"Antminer ", ""
|
|
||||||
)
|
|
||||||
logging.debug(f"Found model for {self.ip}: {self.model} (BOS)")
|
|
||||||
return self.model + " (BOS)"
|
|
||||||
|
|
||||||
# if we don't get devdetails, log a failed attempt
|
|
||||||
logging.warning(f"Failed to get model for miner: {self}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_version(self) -> Union[str, None]:
|
|
||||||
"""Get miner firmware version.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Miner firmware version or None.
|
|
||||||
"""
|
|
||||||
# check if version is cached
|
|
||||||
if self.version:
|
|
||||||
logging.debug(f"Found version for {self.ip}: {self.version}")
|
|
||||||
return self.version
|
|
||||||
|
|
||||||
# get output of bos version file
|
|
||||||
version_data = await self.send_ssh_command("cat /etc/bos_version")
|
|
||||||
|
|
||||||
# if we get the version data, parse it
|
|
||||||
if version_data:
|
|
||||||
self.version = version_data.split("-")[5]
|
|
||||||
logging.debug(f"Found version for {self.ip}: {self.version}")
|
|
||||||
return self.version
|
|
||||||
|
|
||||||
# if we fail to get version, log a failed attempt
|
|
||||||
logging.warning(f"Failed to get model for miner: {self}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
|
||||||
"""Configures miner with yaml config."""
|
|
||||||
logging.debug(f"{self}: Sending config.")
|
|
||||||
toml_conf = config.as_bos(
|
|
||||||
model=self.model.replace(" (BOS)", ""), user_suffix=user_suffix
|
|
||||||
)
|
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
|
||||||
await conn.run("/etc/init.d/bosminer stop")
|
|
||||||
logging.debug(f"{self}: Opening SFTP connection.")
|
|
||||||
async with conn.start_sftp_client() as sftp:
|
|
||||||
logging.debug(f"{self}: Opening config file.")
|
|
||||||
async with sftp.open("/etc/bosminer.toml", "w+") as file:
|
|
||||||
await file.write(toml_conf)
|
|
||||||
logging.debug(f"{self}: Restarting BOSMiner")
|
|
||||||
await conn.run("/etc/init.d/bosminer start")
|
|
||||||
|
|
||||||
async def check_light(self) -> bool:
|
|
||||||
if self.light:
|
|
||||||
return self.light
|
|
||||||
data = (
|
|
||||||
await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off")
|
|
||||||
).strip()
|
|
||||||
self.light = False
|
|
||||||
if data == "50":
|
|
||||||
self.light = True
|
|
||||||
return self.light
|
|
||||||
|
|
||||||
async def get_errors(self) -> List[MinerErrorData]:
|
|
||||||
tunerstatus = None
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
try:
|
|
||||||
tunerstatus = await self.api.tunerstatus()
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning(e)
|
|
||||||
|
|
||||||
if tunerstatus:
|
|
||||||
tuner = tunerstatus[0].get("TUNERSTATUS")
|
|
||||||
if tuner:
|
|
||||||
if len(tuner) > 0:
|
|
||||||
chain_status = tuner[0].get("TunerChainStatus")
|
|
||||||
if chain_status and len(chain_status) > 0:
|
|
||||||
board_map = {
|
|
||||||
0: "Left board",
|
|
||||||
1: "Center board",
|
|
||||||
2: "Right board",
|
|
||||||
}
|
|
||||||
offset = (
|
|
||||||
6
|
|
||||||
if chain_status[0]["HashchainIndex"] in [6, 7, 8]
|
|
||||||
else chain_status[0]["HashchainIndex"]
|
|
||||||
)
|
|
||||||
for board in chain_status:
|
|
||||||
_id = board["HashchainIndex"] - offset
|
|
||||||
if board["Status"] not in [
|
|
||||||
"Stable",
|
|
||||||
"Testing performance profile",
|
|
||||||
]:
|
|
||||||
_error = board["Status"].split(" {")[0]
|
|
||||||
_error = _error[0].lower() + _error[1:]
|
|
||||||
errors.append(
|
|
||||||
BraiinsOSError(f"{board_map[_id]} {_error}")
|
|
||||||
)
|
|
||||||
return errors
|
|
||||||
|
|
||||||
async def get_data(self) -> MinerData:
|
|
||||||
"""Get data from the miner.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
|
||||||
"""
|
|
||||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
|
||||||
|
|
||||||
board_offset = -1
|
|
||||||
fan_offset = -1
|
|
||||||
|
|
||||||
model = await self.get_model()
|
|
||||||
hostname = await self.get_hostname()
|
|
||||||
mac = await self.get_mac()
|
|
||||||
|
|
||||||
if model:
|
|
||||||
data.model = model
|
|
||||||
|
|
||||||
if hostname:
|
|
||||||
data.hostname = hostname
|
|
||||||
|
|
||||||
if mac:
|
|
||||||
data.mac = mac
|
|
||||||
|
|
||||||
data.fault_light = await self.check_light()
|
|
||||||
|
|
||||||
miner_data = None
|
|
||||||
for i in range(PyasicSettings().miner_get_data_retries):
|
|
||||||
try:
|
|
||||||
miner_data = await self.api.multicommand(
|
|
||||||
"summary",
|
|
||||||
"temps",
|
|
||||||
"tunerstatus",
|
|
||||||
"pools",
|
|
||||||
"devdetails",
|
|
||||||
"fans",
|
|
||||||
"devs",
|
|
||||||
)
|
|
||||||
except APIError as e:
|
|
||||||
if str(e.message) == "Not ready":
|
|
||||||
miner_data = await self.api.multicommand(
|
|
||||||
"summary", "tunerstatus", "pools", "devs"
|
|
||||||
)
|
|
||||||
if miner_data:
|
|
||||||
break
|
|
||||||
if not miner_data:
|
|
||||||
return data
|
|
||||||
summary = miner_data.get("summary")
|
|
||||||
temps = miner_data.get("temps")
|
|
||||||
tunerstatus = miner_data.get("tunerstatus")
|
|
||||||
pools = miner_data.get("pools")
|
|
||||||
devdetails = miner_data.get("devdetails")
|
|
||||||
devs = miner_data.get("devs")
|
|
||||||
fans = miner_data.get("fans")
|
|
||||||
|
|
||||||
if summary:
|
|
||||||
hr = summary[0].get("SUMMARY")
|
|
||||||
if hr:
|
|
||||||
if len(hr) > 0:
|
|
||||||
hr = hr[0].get("MHS 1m")
|
|
||||||
if hr:
|
|
||||||
data.hashrate = round(hr / 1000000, 2)
|
|
||||||
|
|
||||||
if temps:
|
|
||||||
temp = temps[0].get("TEMPS")
|
|
||||||
if temp:
|
|
||||||
if len(temp) > 0:
|
|
||||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
|
||||||
offset = 6 if temp[0]["ID"] in [6, 7, 8] else temp[0]["ID"]
|
|
||||||
for board in temp:
|
|
||||||
_id = board["ID"] - offset
|
|
||||||
chip_temp = round(board["Chip"])
|
|
||||||
board_temp = round(board["Board"])
|
|
||||||
setattr(data, f"{board_map[_id]}_chip_temp", chip_temp)
|
|
||||||
setattr(data, f"{board_map[_id]}_temp", board_temp)
|
|
||||||
|
|
||||||
if fans:
|
|
||||||
fan_data = fans[0].get("FANS")
|
|
||||||
if fan_data:
|
|
||||||
for fan in range(self.fan_count):
|
|
||||||
setattr(data, f"fan_{fan+1}", fan_data[fan]["RPM"])
|
|
||||||
|
|
||||||
if pools:
|
|
||||||
pool_1 = None
|
|
||||||
pool_2 = None
|
|
||||||
pool_1_user = None
|
|
||||||
pool_2_user = None
|
|
||||||
pool_1_quota = 1
|
|
||||||
pool_2_quota = 1
|
|
||||||
quota = 0
|
|
||||||
for pool in pools[0].get("POOLS"):
|
|
||||||
if not pool_1_user:
|
|
||||||
pool_1_user = pool.get("User")
|
|
||||||
pool_1 = pool["URL"]
|
|
||||||
pool_1_quota = pool["Quota"]
|
|
||||||
elif not pool_2_user:
|
|
||||||
pool_2_user = pool.get("User")
|
|
||||||
pool_2 = pool["URL"]
|
|
||||||
pool_2_quota = pool["Quota"]
|
|
||||||
if not pool.get("User") == pool_1_user:
|
|
||||||
if not pool_2_user == pool.get("User"):
|
|
||||||
pool_2_user = pool.get("User")
|
|
||||||
pool_2 = pool["URL"]
|
|
||||||
pool_2_quota = pool["Quota"]
|
|
||||||
if pool_2_user and not pool_2_user == pool_1_user:
|
|
||||||
quota = f"{pool_1_quota}/{pool_2_quota}"
|
|
||||||
|
|
||||||
if pool_1:
|
|
||||||
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
|
|
||||||
"stratum2+tcp://", ""
|
|
||||||
)
|
|
||||||
data.pool_1_url = pool_1
|
|
||||||
|
|
||||||
if pool_1_user:
|
|
||||||
data.pool_1_user = pool_1_user
|
|
||||||
|
|
||||||
if pool_2:
|
|
||||||
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
|
|
||||||
"stratum2+tcp://", ""
|
|
||||||
)
|
|
||||||
data.pool_2_url = pool_2
|
|
||||||
|
|
||||||
if pool_2_user:
|
|
||||||
data.pool_2_user = pool_2_user
|
|
||||||
|
|
||||||
if quota:
|
|
||||||
data.pool_split = str(quota)
|
|
||||||
|
|
||||||
if tunerstatus:
|
|
||||||
tuner = tunerstatus[0].get("TUNERSTATUS")
|
|
||||||
if tuner:
|
|
||||||
if len(tuner) > 0:
|
|
||||||
wattage = tuner[0].get("ApproximateMinerPowerConsumption")
|
|
||||||
wattage_limit = tuner[0].get("PowerLimit")
|
|
||||||
if wattage_limit:
|
|
||||||
data.wattage_limit = wattage_limit
|
|
||||||
if wattage:
|
|
||||||
data.wattage = wattage
|
|
||||||
|
|
||||||
chain_status = tuner[0].get("TunerChainStatus")
|
|
||||||
if chain_status and len(chain_status) > 0:
|
|
||||||
board_map = {
|
|
||||||
0: "Left board",
|
|
||||||
1: "Center board",
|
|
||||||
2: "Right board",
|
|
||||||
}
|
|
||||||
offset = (
|
|
||||||
6
|
|
||||||
if chain_status[0]["HashchainIndex"] in [6, 7, 8]
|
|
||||||
else chain_status[0]["HashchainIndex"]
|
|
||||||
)
|
|
||||||
for board in chain_status:
|
|
||||||
_id = board["HashchainIndex"] - offset
|
|
||||||
if board["Status"] not in [
|
|
||||||
"Stable",
|
|
||||||
"Testing performance profile",
|
|
||||||
]:
|
|
||||||
_error = board["Status"].split(" {")[0]
|
|
||||||
_error = _error[0].lower() + _error[1:]
|
|
||||||
data.errors.append(
|
|
||||||
BraiinsOSError(f"{board_map[_id]} {_error}")
|
|
||||||
)
|
|
||||||
|
|
||||||
if devdetails:
|
|
||||||
boards = devdetails[0].get("DEVDETAILS")
|
|
||||||
if boards:
|
|
||||||
if len(boards) > 0:
|
|
||||||
board_map = {0: "left_chips", 1: "center_chips", 2: "right_chips"}
|
|
||||||
offset = 6 if boards[0]["ID"] in [6, 7, 8] else boards[0]["ID"]
|
|
||||||
for board in boards:
|
|
||||||
_id = board["ID"] - offset
|
|
||||||
chips = board["Chips"]
|
|
||||||
setattr(data, board_map[_id], chips)
|
|
||||||
|
|
||||||
if devs:
|
|
||||||
boards = devs[0].get("DEVS")
|
|
||||||
if boards:
|
|
||||||
if len(boards) > 0:
|
|
||||||
board_map = {
|
|
||||||
0: "left_board_hashrate",
|
|
||||||
1: "center_board_hashrate",
|
|
||||||
2: "right_board_hashrate",
|
|
||||||
}
|
|
||||||
offset = 6 if boards[0]["ID"] in [6, 7, 8] else boards[0]["ID"]
|
|
||||||
for board in boards:
|
|
||||||
_id = board["ID"] - offset
|
|
||||||
hashrate = round(board["MHS 1m"] / 1000000, 2)
|
|
||||||
setattr(data, board_map[_id], hashrate)
|
|
||||||
return data
|
|
||||||
|
|
||||||
async def get_mac(self):
|
|
||||||
result = await self.send_ssh_command("cat /sys/class/net/eth0/address")
|
|
||||||
return result.upper().strip()
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
# 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 logging
|
|
||||||
|
|
||||||
import ipaddress
|
|
||||||
from typing import Union, List
|
|
||||||
|
|
||||||
from pyasic.API.bosminer import BOSMinerAPI
|
|
||||||
from pyasic.miners.base import BaseMiner
|
|
||||||
from pyasic.config import MinerConfig
|
|
||||||
from pyasic.data import MinerData
|
|
||||||
from pyasic.data.error_codes import MinerErrorData
|
|
||||||
|
|
||||||
|
|
||||||
class BOSMinerOld(BaseMiner):
|
|
||||||
def __init__(self, ip: str) -> None:
|
|
||||||
super().__init__(ip)
|
|
||||||
self.ip = ipaddress.ip_address(ip)
|
|
||||||
self.api = BOSMinerAPI(ip)
|
|
||||||
self.api_type = "BOSMiner"
|
|
||||||
self.uname = "root"
|
|
||||||
self.pwd = "admin"
|
|
||||||
|
|
||||||
async def send_ssh_command(self, cmd: str) -> Union[str, None]:
|
|
||||||
"""Send a command to the miner over ssh.
|
|
||||||
|
|
||||||
:return: Result of the command or None.
|
|
||||||
"""
|
|
||||||
result = None
|
|
||||||
|
|
||||||
# open an ssh connection
|
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
|
||||||
# 3 retries
|
|
||||||
for i in range(3):
|
|
||||||
try:
|
|
||||||
# run the command and get the result
|
|
||||||
result = await conn.run(cmd)
|
|
||||||
if result.stdout:
|
|
||||||
result = result.stdout
|
|
||||||
except Exception as e:
|
|
||||||
if e == "SSH connection closed":
|
|
||||||
return "Update completed."
|
|
||||||
# if the command fails, log it
|
|
||||||
logging.warning(f"{self} command {cmd} error: {e}")
|
|
||||||
|
|
||||||
# on the 3rd retry, return None
|
|
||||||
if i == 3:
|
|
||||||
return
|
|
||||||
continue
|
|
||||||
# return the result, either command output or None
|
|
||||||
return str(result)
|
|
||||||
|
|
||||||
async def update_to_plus(self):
|
|
||||||
result = await self.send_ssh_command("opkg update && opkg install bos_plus")
|
|
||||||
return result
|
|
||||||
|
|
||||||
async def check_light(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def get_config(self) -> None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_errors(self) -> List[MinerErrorData]:
|
|
||||||
return []
|
|
||||||
|
|
||||||
async def get_hostname(self) -> str:
|
|
||||||
return "?"
|
|
||||||
|
|
||||||
async def get_mac(self) -> str:
|
|
||||||
return "00:00:00:00:00:00"
|
|
||||||
|
|
||||||
async def get_model(self) -> str:
|
|
||||||
return "S9"
|
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def restart_backend(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def stop_mining(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def resume_mining(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_data(self) -> MinerData:
|
|
||||||
return MinerData(ip=str(self.ip))
|
|
||||||
@@ -1,399 +0,0 @@
|
|||||||
# 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 ipaddress
|
|
||||||
import logging
|
|
||||||
from typing import Union, List
|
|
||||||
|
|
||||||
|
|
||||||
from pyasic.API.btminer import BTMinerAPI
|
|
||||||
from pyasic.miners.base import BaseMiner
|
|
||||||
from pyasic.errors import APIError
|
|
||||||
|
|
||||||
from pyasic.data import MinerData
|
|
||||||
from pyasic.data.error_codes import WhatsminerError, MinerErrorData
|
|
||||||
from pyasic.config import MinerConfig
|
|
||||||
|
|
||||||
from pyasic.settings import PyasicSettings
|
|
||||||
|
|
||||||
|
|
||||||
class BTMiner(BaseMiner):
|
|
||||||
def __init__(self, ip: str) -> None:
|
|
||||||
super().__init__(ip)
|
|
||||||
self.ip = ipaddress.ip_address(ip)
|
|
||||||
self.api = BTMinerAPI(ip)
|
|
||||||
self.api_type = "BTMiner"
|
|
||||||
|
|
||||||
async def get_model(self) -> Union[str, None]:
|
|
||||||
"""Get miner model.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Miner model or None.
|
|
||||||
"""
|
|
||||||
if self.model:
|
|
||||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
|
||||||
return self.model
|
|
||||||
version_data = await self.api.devdetails()
|
|
||||||
if version_data:
|
|
||||||
self.model = version_data["DEVDETAILS"][0]["Model"].split("V")[0]
|
|
||||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
|
||||||
return self.model
|
|
||||||
logging.warning(f"Failed to get model for miner: {self}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_hostname(self) -> Union[str, None]:
|
|
||||||
"""Get miner hostname.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The hostname of the miner as a string or None.
|
|
||||||
"""
|
|
||||||
if self.hostname:
|
|
||||||
return self.hostname
|
|
||||||
try:
|
|
||||||
host_data = await self.api.get_miner_info()
|
|
||||||
if host_data:
|
|
||||||
host = host_data["Msg"]["hostname"]
|
|
||||||
logging.debug(f"Found hostname for {self.ip}: {host}")
|
|
||||||
self.hostname = host
|
|
||||||
return self.hostname
|
|
||||||
except APIError:
|
|
||||||
logging.info(f"Failed to get hostname for miner: {self}")
|
|
||||||
return None
|
|
||||||
except Exception:
|
|
||||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_mac(self) -> str:
|
|
||||||
"""Get the mac address of the miner.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The mac address of the miner as a string.
|
|
||||||
"""
|
|
||||||
mac = ""
|
|
||||||
data = await self.api.summary()
|
|
||||||
if data:
|
|
||||||
if data.get("SUMMARY"):
|
|
||||||
if len(data["SUMMARY"]) > 0:
|
|
||||||
_mac = data["SUMMARY"][0].get("MAC")
|
|
||||||
if _mac:
|
|
||||||
mac = _mac
|
|
||||||
if mac == "":
|
|
||||||
try:
|
|
||||||
data = await self.api.get_miner_info()
|
|
||||||
if data:
|
|
||||||
if "Msg" in data.keys():
|
|
||||||
if "mac" in data["Msg"].keys():
|
|
||||||
mac = data["Msg"]["mac"]
|
|
||||||
except APIError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return str(mac).upper()
|
|
||||||
|
|
||||||
async def _reset_api_pwd_to_admin(self, pwd: str):
|
|
||||||
try:
|
|
||||||
data = await self.api.update_pwd(pwd, "admin")
|
|
||||||
except APIError:
|
|
||||||
return False
|
|
||||||
if data:
|
|
||||||
if "Code" in data.keys():
|
|
||||||
if data["Code"] == 131:
|
|
||||||
return True
|
|
||||||
print(data)
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def check_light(self) -> bool:
|
|
||||||
data = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = await self.api.get_miner_info()
|
|
||||||
except APIError:
|
|
||||||
if not self.light:
|
|
||||||
self.light = False
|
|
||||||
if data:
|
|
||||||
if "Msg" in data.keys():
|
|
||||||
if "ledstat" in data["Msg"].keys():
|
|
||||||
if not data["Msg"]["ledstat"] == "auto":
|
|
||||||
self.light = True
|
|
||||||
if data["Msg"]["ledstat"] == "auto":
|
|
||||||
self.light = False
|
|
||||||
return self.light
|
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
|
||||||
try:
|
|
||||||
data = await self.api.set_led(auto=True)
|
|
||||||
except APIError:
|
|
||||||
return False
|
|
||||||
if data:
|
|
||||||
if "Code" in data.keys():
|
|
||||||
if data["Code"] == 131:
|
|
||||||
self.light = False
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
|
||||||
try:
|
|
||||||
data = await self.api.set_led(auto=False)
|
|
||||||
await self.api.set_led(
|
|
||||||
auto=False, color="green", start=0, period=1, duration=0
|
|
||||||
)
|
|
||||||
except APIError:
|
|
||||||
return False
|
|
||||||
if data:
|
|
||||||
if "Code" in data.keys():
|
|
||||||
if data["Code"] == 131:
|
|
||||||
self.light = True
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def get_errors(self) -> List[MinerErrorData]:
|
|
||||||
data = []
|
|
||||||
|
|
||||||
summary_data = await self.api.summary()
|
|
||||||
if summary_data[0].get("Error Code Count"):
|
|
||||||
for i in range(summary_data[0]["Error Code Count"]):
|
|
||||||
if summary_data[0].get(f"Error Code {i}"):
|
|
||||||
data.append(
|
|
||||||
WhatsminerError(error_code=summary_data[0][f"Error Code {i}"])
|
|
||||||
)
|
|
||||||
return data
|
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
|
||||||
data = await self.api.reboot()
|
|
||||||
if data.get("Msg"):
|
|
||||||
if data["Msg"] == "API command OK":
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def restart_backend(self) -> bool:
|
|
||||||
data = await self.api.restart()
|
|
||||||
if data.get("Msg"):
|
|
||||||
if data["Msg"] == "API command OK":
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def stop_mining(self) -> bool:
|
|
||||||
data = await self.api.power_off(respbefore=True)
|
|
||||||
if data.get("Msg"):
|
|
||||||
if data["Msg"] == "API command OK":
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def resume_mining(self) -> bool:
|
|
||||||
data = await self.api.power_on()
|
|
||||||
if data.get("Msg"):
|
|
||||||
if data["Msg"] == "API command OK":
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
|
||||||
conf = config.as_wm(user_suffix=user_suffix)
|
|
||||||
|
|
||||||
await self.api.update_pools(
|
|
||||||
conf[0]["url"],
|
|
||||||
conf[0]["user"],
|
|
||||||
conf[0]["pass"],
|
|
||||||
conf[1]["url"],
|
|
||||||
conf[1]["user"],
|
|
||||||
conf[1]["pass"],
|
|
||||||
conf[2]["url"],
|
|
||||||
conf[2]["user"],
|
|
||||||
conf[2]["pass"],
|
|
||||||
)
|
|
||||||
|
|
||||||
async def get_config(self) -> MinerConfig:
|
|
||||||
pools = None
|
|
||||||
cfg = MinerConfig()
|
|
||||||
|
|
||||||
try:
|
|
||||||
pools = await self.api.pools()
|
|
||||||
except APIError as e:
|
|
||||||
logging.warning(e)
|
|
||||||
|
|
||||||
if pools:
|
|
||||||
if "POOLS" in pools.keys():
|
|
||||||
cfg = cfg.from_api(pools["POOLS"])
|
|
||||||
return cfg
|
|
||||||
|
|
||||||
async def get_data(self) -> MinerData:
|
|
||||||
"""Get data from the miner.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
|
||||||
"""
|
|
||||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
|
||||||
|
|
||||||
mac = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
model = await self.get_model()
|
|
||||||
except APIError:
|
|
||||||
logging.info(f"Failed to get model: {self}")
|
|
||||||
model = None
|
|
||||||
data.model = "Whatsminer"
|
|
||||||
|
|
||||||
try:
|
|
||||||
hostname = await self.get_hostname()
|
|
||||||
except APIError:
|
|
||||||
logging.info(f"Failed to get hostname: {self}")
|
|
||||||
hostname = None
|
|
||||||
data.hostname = "Whatsminer"
|
|
||||||
|
|
||||||
if model:
|
|
||||||
data.model = model
|
|
||||||
|
|
||||||
if hostname:
|
|
||||||
data.hostname = hostname
|
|
||||||
|
|
||||||
data.fault_light = await self.check_light()
|
|
||||||
|
|
||||||
miner_data = None
|
|
||||||
for i in range(PyasicSettings().miner_get_data_retries):
|
|
||||||
try:
|
|
||||||
miner_data = await self.api.multicommand("summary", "devs", "pools")
|
|
||||||
if miner_data:
|
|
||||||
break
|
|
||||||
except APIError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not miner_data:
|
|
||||||
return data
|
|
||||||
|
|
||||||
summary = miner_data.get("summary")[0]
|
|
||||||
devs = miner_data.get("devs")[0]
|
|
||||||
pools = miner_data.get("pools")[0]
|
|
||||||
|
|
||||||
if summary:
|
|
||||||
summary_data = summary.get("SUMMARY")
|
|
||||||
if summary_data:
|
|
||||||
if len(summary_data) > 0:
|
|
||||||
wattage_limit = None
|
|
||||||
if summary_data[0].get("MAC"):
|
|
||||||
mac = summary_data[0]["MAC"]
|
|
||||||
|
|
||||||
if summary_data[0].get("Env Temp"):
|
|
||||||
data.env_temp = summary_data[0]["Env Temp"]
|
|
||||||
|
|
||||||
if summary_data[0].get("Power Limit"):
|
|
||||||
wattage_limit = summary_data[0]["Power Limit"]
|
|
||||||
|
|
||||||
data.fan_1 = summary_data[0]["Fan Speed In"]
|
|
||||||
data.fan_2 = summary_data[0]["Fan Speed Out"]
|
|
||||||
|
|
||||||
hr = summary_data[0].get("MHS 1m")
|
|
||||||
if hr:
|
|
||||||
data.hashrate = round(hr / 1000000, 2)
|
|
||||||
|
|
||||||
wattage = summary_data[0].get("Power")
|
|
||||||
if wattage:
|
|
||||||
data.wattage = round(wattage)
|
|
||||||
|
|
||||||
if not wattage_limit:
|
|
||||||
wattage_limit = round(wattage)
|
|
||||||
|
|
||||||
data.wattage_limit = wattage_limit
|
|
||||||
|
|
||||||
if summary_data[0].get("Error Code Count"):
|
|
||||||
for i in range(summary_data[0]["Error Code Count"]):
|
|
||||||
if summary_data[0].get(f"Error Code {i}"):
|
|
||||||
data.errors.append(
|
|
||||||
WhatsminerError(
|
|
||||||
error_code=summary_data[0][f"Error Code {i}"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if devs:
|
|
||||||
temp_data = devs.get("DEVS")
|
|
||||||
if temp_data:
|
|
||||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
|
||||||
for board in temp_data:
|
|
||||||
_id = board["ASC"]
|
|
||||||
chip_temp = round(board["Chip Temp Avg"])
|
|
||||||
board_temp = round(board["Temperature"])
|
|
||||||
hashrate = round(board["MHS 1m"] / 1000000, 2)
|
|
||||||
setattr(data, f"{board_map[_id]}_chip_temp", chip_temp)
|
|
||||||
setattr(data, f"{board_map[_id]}_temp", board_temp)
|
|
||||||
setattr(data, f"{board_map[_id]}_hashrate", hashrate)
|
|
||||||
|
|
||||||
if devs:
|
|
||||||
boards = devs.get("DEVS")
|
|
||||||
if boards:
|
|
||||||
if len(boards) > 0:
|
|
||||||
board_map = {0: "left_chips", 1: "center_chips", 2: "right_chips"}
|
|
||||||
if "ID" in boards[0].keys():
|
|
||||||
id_key = "ID"
|
|
||||||
else:
|
|
||||||
id_key = "ASC"
|
|
||||||
offset = boards[0][id_key]
|
|
||||||
for board in boards:
|
|
||||||
_id = board[id_key] - offset
|
|
||||||
chips = board["Effective Chips"]
|
|
||||||
setattr(data, board_map[_id], chips)
|
|
||||||
|
|
||||||
if pools:
|
|
||||||
pool_1 = None
|
|
||||||
pool_2 = None
|
|
||||||
pool_1_user = None
|
|
||||||
pool_2_user = None
|
|
||||||
pool_1_quota = 1
|
|
||||||
pool_2_quota = 1
|
|
||||||
quota = 0
|
|
||||||
for pool in pools.get("POOLS"):
|
|
||||||
if not pool_1_user:
|
|
||||||
pool_1_user = pool.get("User")
|
|
||||||
pool_1 = pool["URL"]
|
|
||||||
pool_1_quota = pool["Quota"]
|
|
||||||
elif not pool_2_user:
|
|
||||||
pool_2_user = pool.get("User")
|
|
||||||
pool_2 = pool["URL"]
|
|
||||||
pool_2_quota = pool["Quota"]
|
|
||||||
if not pool.get("User") == pool_1_user:
|
|
||||||
if not pool_2_user == pool.get("User"):
|
|
||||||
pool_2_user = pool.get("User")
|
|
||||||
pool_2 = pool["URL"]
|
|
||||||
pool_2_quota = pool["Quota"]
|
|
||||||
if pool_2_user and not pool_2_user == pool_1_user:
|
|
||||||
quota = f"{pool_1_quota}/{pool_2_quota}"
|
|
||||||
|
|
||||||
if pool_1:
|
|
||||||
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
|
|
||||||
"stratum2+tcp://", ""
|
|
||||||
)
|
|
||||||
data.pool_1_url = pool_1
|
|
||||||
|
|
||||||
if pool_1_user:
|
|
||||||
data.pool_1_user = pool_1_user
|
|
||||||
|
|
||||||
if pool_2:
|
|
||||||
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
|
|
||||||
"stratum2+tcp://", ""
|
|
||||||
)
|
|
||||||
data.pool_2_url = pool_2
|
|
||||||
|
|
||||||
if pool_2_user:
|
|
||||||
data.pool_2_user = pool_2_user
|
|
||||||
|
|
||||||
if quota:
|
|
||||||
data.pool_split = str(quota)
|
|
||||||
|
|
||||||
if not mac:
|
|
||||||
try:
|
|
||||||
mac = await self.get_mac()
|
|
||||||
except APIError:
|
|
||||||
logging.info(f"Failed to get mac: {self}")
|
|
||||||
mac = None
|
|
||||||
|
|
||||||
if mac:
|
|
||||||
data.mac = mac
|
|
||||||
|
|
||||||
return data
|
|
||||||
@@ -1,330 +0,0 @@
|
|||||||
# 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 ipaddress
|
|
||||||
import logging
|
|
||||||
from typing import Union, List
|
|
||||||
|
|
||||||
|
|
||||||
from pyasic.API.cgminer import CGMinerAPI
|
|
||||||
from pyasic.miners.base import BaseMiner
|
|
||||||
from pyasic.errors import APIError
|
|
||||||
from pyasic.config import MinerConfig
|
|
||||||
|
|
||||||
from pyasic.data import MinerData
|
|
||||||
from pyasic.data.error_codes import MinerErrorData
|
|
||||||
|
|
||||||
from pyasic.settings import PyasicSettings
|
|
||||||
|
|
||||||
|
|
||||||
class CGMiner(BaseMiner):
|
|
||||||
def __init__(self, ip: str) -> None:
|
|
||||||
super().__init__(ip)
|
|
||||||
self.ip = ipaddress.ip_address(ip)
|
|
||||||
self.api = CGMinerAPI(ip)
|
|
||||||
self.api_type = "CGMiner"
|
|
||||||
self.uname = "root"
|
|
||||||
self.pwd = "admin"
|
|
||||||
self.config = None
|
|
||||||
|
|
||||||
async def get_model(self) -> Union[str, None]:
|
|
||||||
"""Get miner model.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Miner model or None.
|
|
||||||
"""
|
|
||||||
if self.model:
|
|
||||||
return self.model
|
|
||||||
try:
|
|
||||||
version_data = await self.api.devdetails()
|
|
||||||
except APIError:
|
|
||||||
return None
|
|
||||||
if version_data:
|
|
||||||
self.model = version_data["DEVDETAILS"][0]["Model"].replace("Antminer ", "")
|
|
||||||
return self.model
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_hostname(self) -> Union[str, None]:
|
|
||||||
"""Get miner hostname.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The hostname of the miner as a string or "?"
|
|
||||||
"""
|
|
||||||
if self.hostname:
|
|
||||||
return self.hostname
|
|
||||||
try:
|
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
|
||||||
if conn is not None:
|
|
||||||
data = await conn.run("cat /proc/sys/kernel/hostname")
|
|
||||||
host = data.stdout.strip()
|
|
||||||
self.hostname = host
|
|
||||||
return self.hostname
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
except Exception:
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def send_ssh_command(self, cmd: str) -> Union[str, None]:
|
|
||||||
"""Send a command to the miner over ssh.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
cmd: The command to run.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Result of the command or None.
|
|
||||||
"""
|
|
||||||
result = None
|
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
|
||||||
for i in range(3):
|
|
||||||
try:
|
|
||||||
result = await conn.run(cmd)
|
|
||||||
result = result.stdout
|
|
||||||
except Exception as e:
|
|
||||||
print(f"{cmd} error: {e}")
|
|
||||||
if i == 3:
|
|
||||||
return
|
|
||||||
continue
|
|
||||||
return result
|
|
||||||
|
|
||||||
async def restart_backend(self) -> bool:
|
|
||||||
"""Restart cgminer hashing process. Wraps [`restart_cgminer`][pyasic.miners._backends.cgminer.CGMiner.restart_cgminer] to standardize."""
|
|
||||||
return await self.restart_cgminer()
|
|
||||||
|
|
||||||
async def restart_cgminer(self) -> bool:
|
|
||||||
"""Restart cgminer hashing process."""
|
|
||||||
commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"]
|
|
||||||
commands = ";".join(commands)
|
|
||||||
_ret = await self.send_ssh_command(commands)
|
|
||||||
if isinstance(_ret, str):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
|
||||||
"""Reboots power to the physical miner."""
|
|
||||||
logging.debug(f"{self}: Sending reboot command.")
|
|
||||||
_ret = await self.send_ssh_command("reboot")
|
|
||||||
logging.debug(f"{self}: Reboot command completed.")
|
|
||||||
if isinstance(_ret, str):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def resume_mining(self) -> bool:
|
|
||||||
commands = [
|
|
||||||
"mkdir -p /etc/tmp/",
|
|
||||||
'echo "*/3 * * * * /usr/bin/cgminer-monitor" > /etc/tmp/root',
|
|
||||||
"crontab -u root /etc/tmp/root",
|
|
||||||
"/usr/bin/cgminer-monitor >/dev/null 2>&1",
|
|
||||||
]
|
|
||||||
commands = ";".join(commands)
|
|
||||||
await self.send_ssh_command(commands)
|
|
||||||
return True
|
|
||||||
|
|
||||||
async def stop_mining(self) -> bool:
|
|
||||||
commands = [
|
|
||||||
"mkdir -p /etc/tmp/",
|
|
||||||
'echo "" > /etc/tmp/root',
|
|
||||||
"crontab -u root /etc/tmp/root",
|
|
||||||
"killall cgminer",
|
|
||||||
]
|
|
||||||
commands = ";".join(commands)
|
|
||||||
await self.send_ssh_command(commands)
|
|
||||||
return True
|
|
||||||
|
|
||||||
async def get_config(self) -> str:
|
|
||||||
"""Gets the config for the miner and sets it as `self.config`.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The config from `self.config`.
|
|
||||||
"""
|
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
|
||||||
command = "cat /etc/config/cgminer"
|
|
||||||
result = await conn.run(command, check=True)
|
|
||||||
self.config = result.stdout
|
|
||||||
return self.config
|
|
||||||
|
|
||||||
async def check_light(self) -> bool:
|
|
||||||
if not self.light:
|
|
||||||
self.light = False
|
|
||||||
return self.light
|
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def get_errors(self) -> List[MinerErrorData]:
|
|
||||||
return []
|
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_mac(self) -> str:
|
|
||||||
return "00:00:00:00:00:00"
|
|
||||||
|
|
||||||
async def get_data(self) -> MinerData:
|
|
||||||
"""Get data from the miner.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
|
||||||
"""
|
|
||||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
|
||||||
|
|
||||||
board_offset = -1
|
|
||||||
fan_offset = -1
|
|
||||||
|
|
||||||
model = await self.get_model()
|
|
||||||
hostname = await self.get_hostname()
|
|
||||||
mac = await self.get_mac()
|
|
||||||
|
|
||||||
if model:
|
|
||||||
data.model = model
|
|
||||||
|
|
||||||
if hostname:
|
|
||||||
data.hostname = hostname
|
|
||||||
|
|
||||||
if mac:
|
|
||||||
data.mac = mac
|
|
||||||
|
|
||||||
data.fault_light = await self.check_light()
|
|
||||||
|
|
||||||
miner_data = None
|
|
||||||
for i in range(PyasicSettings().miner_get_data_retries):
|
|
||||||
miner_data = await self.api.multicommand(
|
|
||||||
"summary", "pools", "stats", ignore_x19_error=True
|
|
||||||
)
|
|
||||||
if miner_data:
|
|
||||||
break
|
|
||||||
|
|
||||||
if not miner_data:
|
|
||||||
return data
|
|
||||||
|
|
||||||
summary = miner_data.get("summary")[0]
|
|
||||||
pools = miner_data.get("pools")[0]
|
|
||||||
stats = miner_data.get("stats")[0]
|
|
||||||
|
|
||||||
if summary:
|
|
||||||
hr = summary.get("SUMMARY")
|
|
||||||
if hr:
|
|
||||||
if len(hr) > 0:
|
|
||||||
hr = hr[0].get("GHS av")
|
|
||||||
if hr:
|
|
||||||
data.hashrate = round(hr / 1000, 2)
|
|
||||||
|
|
||||||
if stats:
|
|
||||||
boards = stats.get("STATS")
|
|
||||||
if boards:
|
|
||||||
if len(boards) > 0:
|
|
||||||
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
|
|
||||||
|
|
||||||
data.left_chips = boards[1].get(f"chain_acn{board_offset}")
|
|
||||||
data.center_chips = boards[1].get(f"chain_acn{board_offset+1}")
|
|
||||||
data.right_chips = boards[1].get(f"chain_acn{board_offset+2}")
|
|
||||||
|
|
||||||
data.left_board_hashrate = round(
|
|
||||||
float(boards[1].get(f"chain_rate{board_offset}")) / 1000, 2
|
|
||||||
)
|
|
||||||
data.center_board_hashrate = round(
|
|
||||||
float(boards[1].get(f"chain_rate{board_offset+1}")) / 1000, 2
|
|
||||||
)
|
|
||||||
data.right_board_hashrate = round(
|
|
||||||
float(boards[1].get(f"chain_rate{board_offset+2}")) / 1000, 2
|
|
||||||
)
|
|
||||||
|
|
||||||
if stats:
|
|
||||||
temp = stats.get("STATS")
|
|
||||||
if temp:
|
|
||||||
if len(temp) > 1:
|
|
||||||
for fan_num in range(1, 8, 4):
|
|
||||||
for _f_num in range(4):
|
|
||||||
f = temp[1].get(f"fan{fan_num + _f_num}")
|
|
||||||
if f and not f == 0 and fan_offset == -1:
|
|
||||||
fan_offset = fan_num
|
|
||||||
if fan_offset == -1:
|
|
||||||
fan_offset = 1
|
|
||||||
for fan in range(self.fan_count):
|
|
||||||
setattr(
|
|
||||||
data, f"fan_{fan + 1}", temp[1].get(f"fan{fan_offset+fan}")
|
|
||||||
)
|
|
||||||
|
|
||||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
|
||||||
env_temp_list = []
|
|
||||||
for item in range(3):
|
|
||||||
board_temp = temp[1].get(f"temp{item + board_offset}")
|
|
||||||
chip_temp = temp[1].get(f"temp2_{item + board_offset}")
|
|
||||||
setattr(data, f"{board_map[item]}_chip_temp", chip_temp)
|
|
||||||
setattr(data, f"{board_map[item]}_temp", board_temp)
|
|
||||||
if f"temp_pcb{item}" in temp[1].keys():
|
|
||||||
env_temp = temp[1][f"temp_pcb{item}"].split("-")[0]
|
|
||||||
if not env_temp == 0:
|
|
||||||
env_temp_list.append(int(env_temp))
|
|
||||||
data.env_temp = sum(env_temp_list) / len(env_temp_list)
|
|
||||||
|
|
||||||
if pools:
|
|
||||||
pool_1 = None
|
|
||||||
pool_2 = None
|
|
||||||
pool_1_user = None
|
|
||||||
pool_2_user = None
|
|
||||||
pool_1_quota = 1
|
|
||||||
pool_2_quota = 1
|
|
||||||
quota = 0
|
|
||||||
for pool in pools.get("POOLS"):
|
|
||||||
if not pool_1_user:
|
|
||||||
pool_1_user = pool.get("User")
|
|
||||||
pool_1 = pool["URL"]
|
|
||||||
if pool.get("Quota"):
|
|
||||||
pool_2_quota = pool.get("Quota")
|
|
||||||
elif not pool_2_user:
|
|
||||||
pool_2_user = pool.get("User")
|
|
||||||
pool_2 = pool["URL"]
|
|
||||||
if pool.get("Quota"):
|
|
||||||
pool_2_quota = pool.get("Quota")
|
|
||||||
if not pool.get("User") == pool_1_user:
|
|
||||||
if not pool_2_user == pool.get("User"):
|
|
||||||
pool_2_user = pool.get("User")
|
|
||||||
pool_2 = pool["URL"]
|
|
||||||
if pool.get("Quota"):
|
|
||||||
pool_2_quota = pool.get("Quota")
|
|
||||||
if pool_2_user and not pool_2_user == pool_1_user:
|
|
||||||
quota = f"{pool_1_quota}/{pool_2_quota}"
|
|
||||||
|
|
||||||
if pool_1:
|
|
||||||
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
|
|
||||||
"stratum2+tcp://", ""
|
|
||||||
)
|
|
||||||
data.pool_1_url = pool_1
|
|
||||||
|
|
||||||
if pool_1_user:
|
|
||||||
data.pool_1_user = pool_1_user
|
|
||||||
|
|
||||||
if pool_2:
|
|
||||||
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
|
|
||||||
"stratum2+tcp://", ""
|
|
||||||
)
|
|
||||||
data.pool_2_url = pool_2
|
|
||||||
|
|
||||||
if pool_2_user:
|
|
||||||
data.pool_2_user = pool_2_user
|
|
||||||
|
|
||||||
if quota:
|
|
||||||
data.pool_split = str(quota)
|
|
||||||
|
|
||||||
return data
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
# 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 BMMiner
|
|
||||||
import ipaddress
|
|
||||||
|
|
||||||
|
|
||||||
class Hiveon(BMMiner):
|
|
||||||
def __init__(self, ip: str) -> None:
|
|
||||||
super().__init__(ip)
|
|
||||||
self.ip = ipaddress.ip_address(ip)
|
|
||||||
self.api_type = "Hiveon"
|
|
||||||
self.uname = "root"
|
|
||||||
self.pwd = "admin"
|
|
||||||
|
|
||||||
async def get_board_info(self) -> dict:
|
|
||||||
"""Gets data on each board and chain in the miner."""
|
|
||||||
board_stats = await self.api.stats()
|
|
||||||
stats = board_stats["STATS"][1]
|
|
||||||
boards = {}
|
|
||||||
board_chains = {0: [2, 9, 10], 1: [3, 11, 12], 2: [4, 13, 14]}
|
|
||||||
for idx, board in enumerate(board_chains):
|
|
||||||
boards[board] = []
|
|
||||||
for chain in board_chains[board]:
|
|
||||||
count = stats[f"chain_acn{chain}"]
|
|
||||||
chips = stats[f"chain_acs{chain}"].replace(" ", "")
|
|
||||||
if not count == 18 or "x" in chips:
|
|
||||||
nominal = False
|
|
||||||
else:
|
|
||||||
nominal = True
|
|
||||||
boards[board].append(
|
|
||||||
{
|
|
||||||
"chain": chain,
|
|
||||||
"chip_count": count,
|
|
||||||
"chip_status": chips,
|
|
||||||
"nominal": nominal,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return boards
|
|
||||||
|
|
||||||
async def get_bad_boards(self) -> dict:
|
|
||||||
"""Checks for and provides list of non working boards."""
|
|
||||||
boards = await self.get_board_info()
|
|
||||||
bad_boards = {}
|
|
||||||
for board in boards.keys():
|
|
||||||
for chain in boards[board]:
|
|
||||||
if not chain["chip_count"] == 18 or "x" in chain["chip_status"]:
|
|
||||||
if board not in bad_boards.keys():
|
|
||||||
bad_boards[board] = []
|
|
||||||
bad_boards[board].append(chain)
|
|
||||||
return bad_boards
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# 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 .antminer import *
|
|
||||||
from .avalonminer import *
|
|
||||||
from .whatsminer import *
|
|
||||||
from .innosilicon import *
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class S17(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "S17"
|
|
||||||
self.nominal_chips = 48
|
|
||||||
self.fan_count = 4
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class S17Plus(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "S17+"
|
|
||||||
self.nominal_chips = 65
|
|
||||||
self.fan_count = 4
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class S17Pro(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "S17 Pro"
|
|
||||||
self.nominal_chips = 48
|
|
||||||
self.fan_count = 4
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class S17e(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "S17e"
|
|
||||||
self.nominal_chips = 135
|
|
||||||
self.fan_count = 4
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class T17(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "T17"
|
|
||||||
self.nominal_chips = 30
|
|
||||||
self.fan_count = 4
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class T17Plus(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "T17+"
|
|
||||||
self.nominal_chips = 44
|
|
||||||
self.fan_count = 4
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class T17e(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "T17e"
|
|
||||||
self.nominal_chips = 78
|
|
||||||
self.fan_count = 4
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# 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 S17
|
|
||||||
from .S17_Plus import S17Plus
|
|
||||||
from .S17_Pro import S17Pro
|
|
||||||
from .S17e import S17e
|
|
||||||
|
|
||||||
from .T17 import T17
|
|
||||||
from .T17_Plus import T17Plus
|
|
||||||
from .T17e import T17e
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class S19(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "S19"
|
|
||||||
self.nominal_chips = 76
|
|
||||||
self.fan_count = 4
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class S19Pro(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "S19 Pro"
|
|
||||||
self.nominal_chips = 114
|
|
||||||
self.fan_count = 4
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class S19XP(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "S19 XP"
|
|
||||||
self.nominal_chips = 110
|
|
||||||
self.fan_count = 4
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class S19a(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "S19a"
|
|
||||||
self.nominal_chips = 72
|
|
||||||
self.fan_count = 4
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class S19j(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "S19j"
|
|
||||||
self.nominal_chips = 114
|
|
||||||
self.fan_count = 4
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class S19jPro(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "S19j Pro"
|
|
||||||
self.nominal_chips = 126
|
|
||||||
self.fan_count = 4
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class T19(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "T19"
|
|
||||||
self.nominal_chips = 76
|
|
||||||
self.fan_count = 4
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# Copyright 2022 Upstream Data Inc
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from .S19 import S19
|
|
||||||
from .S19_Pro import S19Pro
|
|
||||||
|
|
||||||
from .S19j import S19j
|
|
||||||
from .S19j_Pro import S19jPro
|
|
||||||
|
|
||||||
from .S19_XP import S19XP
|
|
||||||
|
|
||||||
from .S19a import S19a
|
|
||||||
|
|
||||||
from .T19 import T19
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class S9(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "S9"
|
|
||||||
self.nominal_chips = 63
|
|
||||||
self.fan_count = 2
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class S9i(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "S9i"
|
|
||||||
self.nominal_chips = 63
|
|
||||||
self.fan_count = 2
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class T9(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "T9"
|
|
||||||
self.nominal_chips = 54
|
|
||||||
self.fan_count = 2
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# 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 S9
|
|
||||||
from .S9i import S9i
|
|
||||||
from .T9 import T9
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# 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 *
|
|
||||||
from .X17 import *
|
|
||||||
from .X19 import *
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class Avalon1026(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "Avalon 1026"
|
|
||||||
self.nominal_chips = 80
|
|
||||||
self.fan_count = 2
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class Avalon1047(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "Avalon 1047"
|
|
||||||
self.nominal_chips = 80
|
|
||||||
self.fan_count = 2
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class Avalon1066(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "Avalon 1066"
|
|
||||||
self.nominal_chips = 114
|
|
||||||
self.fan_count = 4
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# 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 .A1026 import Avalon1026
|
|
||||||
from .A1047 import Avalon1047
|
|
||||||
from .A1066 import Avalon1066
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class Avalon721(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "Avalon 721"
|
|
||||||
self.chip_count = 18 # This miner has 4 boards totaling 72
|
|
||||||
self.fan_count = 1 # also only 1 fan
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class Avalon741(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "Avalon 741"
|
|
||||||
self.chip_count = 22 # This miner has 4 boards totaling 88
|
|
||||||
self.fan_count = 1 # also only 1 fan
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class Avalon761(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "Avalon 761"
|
|
||||||
self.chip_count = 18 # This miner has 4 boards totaling 72
|
|
||||||
self.fan_count = 1 # also only 1 fan
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# 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 .A721 import Avalon721
|
|
||||||
from .A741 import Avalon741
|
|
||||||
from .A761 import Avalon761
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class Avalon821(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "Avalon 821"
|
|
||||||
self.chip_count = 26 # This miner has 4 boards totaling 104
|
|
||||||
self.fan_count = 1 # also only 1 fan
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class Avalon841(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "Avalon 841"
|
|
||||||
self.chip_count = 26 # This miner has 4 boards totaling 104
|
|
||||||
self.fan_count = 1 # also only 1 fan
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class Avalon851(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "Avalon 851"
|
|
||||||
self.chip_count = 26 # This miner has 4 boards totaling 104
|
|
||||||
self.fan_count = 1 # also only 1 fan
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# 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 .A821 import Avalon821
|
|
||||||
from .A841 import Avalon841
|
|
||||||
from .A851 import Avalon851
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.base import BaseMiner
|
|
||||||
|
|
||||||
|
|
||||||
class Avalon921(BaseMiner):
|
|
||||||
def __init__(self, ip: str):
|
|
||||||
super().__init__()
|
|
||||||
self.ip = ip
|
|
||||||
self.model = "Avalon 921"
|
|
||||||
self.chip_count = 26 # This miner has 4 boards totaling 104
|
|
||||||
self.fan_count = 1 # also only 1 fan
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user