Compare commits
551 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5c4bb10ee | ||
|
|
c4dfdda448 | ||
|
|
4459de2260 | ||
|
|
201cfd7ef9 | ||
|
|
4201905fdd | ||
|
|
497ffb5bc0 | ||
|
|
2f762c95db | ||
|
|
67aed79330 | ||
|
|
073e048726 | ||
|
|
02234f3d1e | ||
|
|
dc22df0280 | ||
|
|
02056b8c88 | ||
|
|
3a43cd293c | ||
|
|
6941d9f349 | ||
|
|
f6b0b64d86 | ||
|
|
8d68dd9dac | ||
|
|
27368a9bd2 | ||
|
|
c919b00312 | ||
|
|
f162529883 | ||
|
|
bb182bb22d | ||
|
|
af15c4fbd1 | ||
|
|
47c2eb9f0e | ||
|
|
1ab39f5873 | ||
|
|
43200a7354 | ||
|
|
4fc57832e1 | ||
|
|
9ee63cc3ab | ||
|
|
b22b506d55 | ||
|
|
468fba3465 | ||
|
|
0399094197 | ||
|
|
bfdfa8a6ab | ||
|
|
83d0d09b0d | ||
|
|
f892c3a0fd | ||
|
|
81b974f565 | ||
|
|
5eaf876c6d | ||
|
|
d7d1b845a7 | ||
|
|
242517a36a | ||
|
|
791249bf3d | ||
|
|
5a70a27f07 | ||
|
|
bca81f3bca | ||
|
|
6d75565baf | ||
|
|
9f42e6a3be | ||
|
|
362b204c91 | ||
|
|
952b660c05 | ||
|
|
fbd73881d4 | ||
|
|
68c4dadb63 | ||
|
|
87016670d4 | ||
|
|
8701bbe4e2 | ||
|
|
7d1f125b0b | ||
|
|
e433902bd5 | ||
|
|
a653772968 | ||
|
|
d8b938cd5b | ||
|
|
47d76e325a | ||
|
|
7ee7868094 | ||
|
|
3f1183a4f9 | ||
|
|
2b443497ea | ||
|
|
c3972f9524 | ||
|
|
92bbb21c11 | ||
|
|
1980ff2563 | ||
|
|
93d09a1483 | ||
|
|
690d0d99df | ||
|
|
78f689eb2c | ||
|
|
e68f188e8f | ||
|
|
7eda611fe9 | ||
|
|
1d12817942 | ||
|
|
b24efd4c69 | ||
|
|
5533135b05 | ||
|
|
475054fbe0 | ||
|
|
06bad1bbe0 | ||
|
|
f3746ff756 | ||
|
|
9f16d37c8b | ||
|
|
8a13c7940a | ||
|
|
8bea76ff67 | ||
|
|
1504bd744c | ||
|
|
6449f10615 | ||
|
|
d79509bda7 | ||
|
|
630b847466 | ||
|
|
ed11611919 | ||
|
|
e2431c938d | ||
|
|
60f4b4a5ed | ||
|
|
d41097af20 | ||
|
|
8a5d505731 | ||
|
|
36e76c6f41 | ||
|
|
717b9421dd | ||
|
|
d2f71e8c94 | ||
|
|
697991f28f | ||
|
|
b0e18ab766 | ||
|
|
e39a6921d0 | ||
|
|
aac1be0565 | ||
|
|
683fcb2138 | ||
|
|
9fbbef9b18 | ||
|
|
6e0b9a0a7b | ||
|
|
7f472f6f4f | ||
|
|
b7d7b33ab9 | ||
|
|
da11c0bb1f | ||
|
|
eae433d2bd | ||
|
|
c16bc37aff | ||
|
|
255b06ac9e | ||
|
|
29ec619126 | ||
|
|
247def04ff | ||
|
|
4600e7d953 | ||
|
|
850c266555 | ||
|
|
ad374fe2fb | ||
|
|
5ca39b6fe7 | ||
|
|
b50dd26e6f | ||
|
|
53eaccaa9b | ||
|
|
91f207316a | ||
|
|
1e37418909 | ||
|
|
4c09ba5529 | ||
|
|
7bab4747ad | ||
|
|
fd8cc7378c | ||
|
|
8aeef4d5e7 | ||
|
|
4bafde9da7 | ||
|
|
5a3107aecf | ||
|
|
7e758720f0 | ||
|
|
39e3e249f8 | ||
|
|
118c5b056e | ||
|
|
2c3b5599fe | ||
|
|
e421eaa324 | ||
|
|
75d6bc6808 | ||
|
|
98c547e416 | ||
|
|
45250e36e4 | ||
|
|
fa7544d052 | ||
|
|
53f3fc5ee9 | ||
|
|
1b36de4131 | ||
|
|
6f0c6f6284 | ||
|
|
b7dda5bf87 | ||
|
|
14f33a40c3 | ||
|
|
5c904aced0 | ||
|
|
53a3bbf531 | ||
|
|
50586f1ce7 | ||
|
|
9f6235a0fc | ||
|
|
4d21f150ce | ||
|
|
7c0dfc49dd | ||
|
|
269b13f6c1 | ||
|
|
a9bb7d2e5a | ||
|
|
11295f27a7 | ||
|
|
55aa3dd85b | ||
|
|
20272d4360 | ||
|
|
623dc92ef2 | ||
|
|
2d59394b1e | ||
|
|
26c2095ff1 | ||
|
|
ec7d241caa | ||
|
|
d0432ed1aa | ||
|
|
8c5503d002 | ||
|
|
6d6f950c95 | ||
|
|
30745e54ba | ||
|
|
c3fd94e79e | ||
|
|
2924a8d67b | ||
|
|
9f4c4bb9cf | ||
|
|
3d6eebf06e | ||
|
|
b3d9b6ff7e | ||
|
|
60facacc48 | ||
|
|
b8a6063838 | ||
|
|
bcba2be524 | ||
|
|
f7187d2017 | ||
|
|
d91b7c4406 | ||
|
|
248a7e6d69 | ||
|
|
4f2c3e772a | ||
|
|
95f7146eef | ||
|
|
9d5d19cc6b | ||
|
|
cc38129571 | ||
|
|
3dfd9f237d | ||
|
|
f3fe478dbb | ||
|
|
e10f32ae3d | ||
|
|
4e0924aa0e | ||
|
|
d0d3fd3117 | ||
|
|
4de950d8f4 | ||
|
|
03f2a1f9ba | ||
|
|
2653db90e3 | ||
|
|
ddc8c53eb9 | ||
|
|
eb5d1a24ea | ||
|
|
6c0e80265b | ||
|
|
ad3a4ae414 | ||
|
|
3484d43510 | ||
|
|
dd7e352391 | ||
|
|
a32b61fe5d | ||
|
|
597a178009 | ||
|
|
409b2527f0 | ||
|
|
58234fcf7f | ||
|
|
1bf863cca8 | ||
|
|
6482d04185 | ||
|
|
3b58b11501 | ||
|
|
7485b8ef77 | ||
|
|
d2bea227db | ||
|
|
1b7afaaf7e | ||
|
|
96898d639c | ||
|
|
eb439f4dcf | ||
|
|
69f4349393 | ||
|
|
e371bb577c | ||
|
|
2500ec3869 | ||
|
|
5be3187eec | ||
|
|
be1e9127b0 | ||
|
|
13572c4770 | ||
|
|
08fa3961fe | ||
|
|
b5d2809e9c | ||
|
|
aa538d3079 | ||
|
|
e1500bb75c | ||
|
|
7f00a65598 | ||
|
|
64c473a7d4 | ||
|
|
96d9fe8e6c | ||
|
|
0b27400d27 | ||
|
|
666b9dfe94 | ||
|
|
df3a080c9d | ||
|
|
bf3bd7c2b9 | ||
|
|
37fd60b462 | ||
|
|
2245904740 | ||
|
|
7b1b23016e | ||
|
|
b5fcd62e23 | ||
|
|
9057cde274 | ||
|
|
f6d35888fe | ||
|
|
f2abe9fd9e | ||
|
|
7d1a702804 | ||
|
|
65d1695ce4 | ||
|
|
65fd66b8bf | ||
|
|
5db52c46f3 | ||
|
|
d06cb19da3 | ||
|
|
4530d086da | ||
|
|
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 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ pyvenv.cfg
|
||||
.env/
|
||||
bin/
|
||||
lib/
|
||||
.idea/
|
||||
|
||||
@@ -9,6 +9,12 @@ repos:
|
||||
rev: 22.6.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.10.1
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort (python)
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: unittest
|
||||
|
||||
25
README.md
25
README.md
@@ -1,5 +1,5 @@
|
||||
# pyasic
|
||||
*A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH.*
|
||||
*A simplified and standardized interface for Bitcoin ASICs.*
|
||||
|
||||
[](https://github.com/psf/black)
|
||||
[](https://pypi.org/project/pyasic/)
|
||||
@@ -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).
|
||||
|
||||
## 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).
|
||||
|
||||
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
|
||||
```python
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from pyasic.network import MinerNetwork
|
||||
|
||||
@@ -43,7 +46,7 @@ from pyasic.network import MinerNetwork
|
||||
async def scan_and_get_data():
|
||||
# Define network range to be used for scanning
|
||||
# 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)
|
||||
# Scan the network for miners
|
||||
# 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
|
||||
```python
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from pyasic.miners import get_miner
|
||||
from pyasic import get_miner
|
||||
|
||||
|
||||
# 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.
|
||||
|
||||
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
|
||||
```python
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from pyasic.miners import get_miner
|
||||
from pyasic import get_miner
|
||||
|
||||
|
||||
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)
|
||||
|
||||
# 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__":
|
||||
@@ -117,9 +123,8 @@ The miner API commands will raise an `APIError` if they fail with a bad status c
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from pyasic.miners import get_miner
|
||||
from pyasic import get_miner
|
||||
|
||||
|
||||
async def get_api_commands(miner_ip: str):
|
||||
|
||||
@@ -10,10 +10,12 @@ All API implementations inherit from [`BaseMinerAPI`][pyasic.API.BaseMinerAPI],
|
||||
[`BaseMinerAPI`][pyasic.API.BaseMinerAPI] cannot be instantiated directly, it will raise a `TypeError`.
|
||||
Use these instead -
|
||||
|
||||
#### [BFGMiner API][pyasic.API.bfgminer.BFGMinerAPI]
|
||||
#### [BMMiner API][pyasic.API.bmminer.BMMinerAPI]
|
||||
#### [BOSMiner API][pyasic.API.bosminer.BOSMinerAPI]
|
||||
#### [BTMiner API][pyasic.API.btminer.BTMinerAPI]
|
||||
#### [CGMiner API][pyasic.API.cgminer.CGMinerAPI]
|
||||
#### [LUXMiner API][pyasic.API.luxminer.LUXMinerAPI]
|
||||
#### [Unknown API][pyasic.API.unknown.UnknownAPI]
|
||||
|
||||
<br>
|
||||
|
||||
7
docs/API/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
|
||||
7
docs/API/luxminer.md
Normal file
7
docs/API/luxminer.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## LUXMinerAPI
|
||||
::: pyasic.API.luxminer.LUXMinerAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
@@ -6,19 +6,3 @@
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## Pool Groups
|
||||
|
||||
::: pyasic.config._PoolGroup
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## Pools
|
||||
|
||||
::: pyasic.config._Pool
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
@@ -6,3 +6,10 @@
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## HashBoard Data
|
||||
::: pyasic.data.HashBoard
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
165
docs/generate_miners.py
Normal file
165
docs/generate_miners.py
Normal file
@@ -0,0 +1,165 @@
|
||||
import asyncio
|
||||
import importlib
|
||||
import os
|
||||
import warnings
|
||||
|
||||
from pyasic.miners.miner_factory import MINER_CLASSES, MinerTypes
|
||||
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
|
||||
def path(cls):
|
||||
module = importlib.import_module(cls.__module__)
|
||||
return module.__name__ + "." + cls.__name__
|
||||
|
||||
|
||||
def make(cls):
|
||||
p = path(cls)
|
||||
return p.split(".")[2]
|
||||
|
||||
|
||||
def model_type(cls):
|
||||
p = path(cls)
|
||||
return p.split(".")[4]
|
||||
|
||||
|
||||
def backend_str(backend: MinerTypes) -> str:
|
||||
match backend:
|
||||
case MinerTypes.ANTMINER:
|
||||
return "Stock Firmware Antminers"
|
||||
case MinerTypes.AVALONMINER:
|
||||
return "Stock Firmware Avalonminers"
|
||||
case MinerTypes.VNISH:
|
||||
return "Vnish Firmware Miners"
|
||||
case MinerTypes.EPIC:
|
||||
return "ePIC Firmware Miners"
|
||||
case MinerTypes.BRAIINS_OS:
|
||||
return "BOS+ Firmware Miners"
|
||||
case MinerTypes.HIVEON:
|
||||
return "HiveOS Firmware Miners"
|
||||
case MinerTypes.INNOSILICON:
|
||||
return "Stock Firmware Innosilicons"
|
||||
case MinerTypes.WHATSMINER:
|
||||
return "Stock Firmware Whatsminers"
|
||||
case MinerTypes.GOLDSHELL:
|
||||
return "Stock Firmware Goldshells"
|
||||
case MinerTypes.LUX_OS:
|
||||
return "LuxOS Firmware Miners"
|
||||
|
||||
|
||||
def create_url_str(mtype: str):
|
||||
return (
|
||||
mtype.lower()
|
||||
.replace(" ", "-")
|
||||
.replace("(", "")
|
||||
.replace(")", "")
|
||||
.replace("+", "_1")
|
||||
)
|
||||
|
||||
|
||||
HEADER_FORMAT = "# pyasic\n## {} Models\n\n"
|
||||
MINER_HEADER_FORMAT = "## {}\n"
|
||||
DATA_FORMAT = """::: {}
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
"""
|
||||
SUPPORTED_TYPES_HEADER = """# pyasic
|
||||
## Supported Miners
|
||||
|
||||
Supported miner types are here on this list. If your miner (or miner version) is not on this list, please feel free to [open an issue on GitHub](https://github.com/UpstreamData/pyasic/issues) to get it added.
|
||||
|
||||
##### pyasic currently supports the following miners and subtypes:
|
||||
<style>
|
||||
details {
|
||||
margin:0px;
|
||||
padding-top:0px;
|
||||
padding-bottom:0px;
|
||||
}
|
||||
</style>
|
||||
"""
|
||||
BACKEND_TYPE_HEADER = """
|
||||
<details>
|
||||
<summary>{}:</summary>
|
||||
<ul>"""
|
||||
|
||||
MINER_TYPE_HEADER = """
|
||||
<details>
|
||||
<summary>{} Series:</summary>
|
||||
<ul>"""
|
||||
|
||||
MINER_DETAILS = """
|
||||
<li><a href="../{}/{}#{}">{}</a></li>"""
|
||||
|
||||
MINER_TYPE_CLOSER = """
|
||||
</ul>
|
||||
</details>"""
|
||||
BACKEND_TYPE_CLOSER = """
|
||||
</ul>
|
||||
</details>"""
|
||||
|
||||
m_data = {}
|
||||
|
||||
|
||||
for m in MINER_CLASSES:
|
||||
for t in MINER_CLASSES[m]:
|
||||
if t is not None:
|
||||
miner = MINER_CLASSES[m][t]
|
||||
if make(miner) not in m_data:
|
||||
m_data[make(miner)] = {}
|
||||
if model_type(miner) not in m_data[make(miner)]:
|
||||
m_data[make(miner)][model_type(miner)] = []
|
||||
m_data[make(miner)][model_type(miner)].append(miner)
|
||||
|
||||
|
||||
async def create_directory_structure(directory, data):
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
|
||||
for key, value in data.items():
|
||||
subdirectory = os.path.join(directory, key)
|
||||
if isinstance(value, dict):
|
||||
await create_directory_structure(subdirectory, value)
|
||||
elif isinstance(value, list):
|
||||
file_path = os.path.join(subdirectory + ".md")
|
||||
|
||||
with open(file_path, "w") as file:
|
||||
file.write(HEADER_FORMAT.format(key))
|
||||
for item in value:
|
||||
header = await item("1.1.1.1").get_model()
|
||||
file.write(MINER_HEADER_FORMAT.format(header))
|
||||
file.write(DATA_FORMAT.format(path(item)))
|
||||
|
||||
|
||||
async def create_supported_types(directory):
|
||||
with open(os.path.join(directory, "supported_types.md"), "w") as file:
|
||||
file.write(SUPPORTED_TYPES_HEADER)
|
||||
for mback in MINER_CLASSES:
|
||||
backend_types = {}
|
||||
file.write(BACKEND_TYPE_HEADER.format(backend_str(mback)))
|
||||
for mtype in MINER_CLASSES[mback]:
|
||||
if mtype is None:
|
||||
continue
|
||||
m = MINER_CLASSES[mback][mtype]
|
||||
if model_type(m) not in backend_types:
|
||||
backend_types[model_type(m)] = []
|
||||
backend_types[model_type(m)].append(m)
|
||||
|
||||
for mtype in backend_types:
|
||||
file.write(MINER_TYPE_HEADER.format(mtype))
|
||||
for minstance in backend_types[mtype]:
|
||||
model = await minstance("1.1.1.1").get_model()
|
||||
file.write(
|
||||
MINER_DETAILS.format(
|
||||
make(minstance), mtype, create_url_str(model), model
|
||||
)
|
||||
)
|
||||
file.write(MINER_TYPE_CLOSER)
|
||||
file.write(BACKEND_TYPE_CLOSER)
|
||||
|
||||
|
||||
root_directory = os.path.join(os.getcwd(), "miners")
|
||||
asyncio.run(create_directory_structure(root_directory, m_data))
|
||||
asyncio.run(create_supported_types(root_directory))
|
||||
305
docs/index.md
305
docs/index.md
@@ -8,18 +8,21 @@
|
||||
[](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
|
||||
[](https://www.codefactor.io/repository/github/upstreamdata/pyasic)
|
||||
|
||||
---
|
||||
## Intro
|
||||
Welcome to pyasic! Pyasic uses an asynchronous method of communicating with asic miners on your network, which makes it super fast.
|
||||
---
|
||||
Welcome to `pyasic`! `pyasic` uses an asynchronous method of communicating with ASIC miners on your network, which makes it super fast.
|
||||
|
||||
[Supported Miner Types](miners/supported_types.md)
|
||||
[Click here to view supported miner types](miners/supported_types.md)
|
||||
|
||||
Getting started with pyasic is easy. First, find your miner (or miners) on the network by scanning for them or getting the correct class automatically for them if you know the IP.
|
||||
---
|
||||
## Getting started
|
||||
---
|
||||
Getting started with `pyasic` is easy. First, find your miner (or miners) on the network by scanning for them or getting the correct class automatically for them if you know the IP.
|
||||
|
||||
<br>
|
||||
|
||||
## Scanning for miners
|
||||
To scan for miners in pyasic, we use the class [`MinerNetwork`][pyasic.network.MinerNetwork], which abstracts the search, communication, identification, setup, and return of a miner to 1 command.
|
||||
The command [`MinerNetwork().scan_network_for_miners()`][pyasic.network.MinerNetwork.scan_network_for_miners] returns a list that contains any miners found.
|
||||
##### Scanning for miners
|
||||
To scan for miners in `pyasic`, we use the class [`MinerNetwork`][pyasic.network.MinerNetwork], which abstracts the search, communication, identification, setup, and return of a miner to 1 command.
|
||||
The command [`MinerNetwork.scan()`][pyasic.network.MinerNetwork.scan] returns a list that contains any miners found.
|
||||
```python
|
||||
import asyncio # asyncio for handling the async part
|
||||
from pyasic.network import MinerNetwork # miner network handles the scanning
|
||||
@@ -28,60 +31,70 @@ from pyasic.network import MinerNetwork # miner network handles the scanning
|
||||
async def scan_miners(): # define async scan function to allow awaiting
|
||||
# create a miner network
|
||||
# you can pass in any IP and it will use that in a subnet with a /24 mask (255 IPs).
|
||||
network = MinerNetwork("192.168.1.50") # this uses the 192.168.1.0-255 network
|
||||
network = MinerNetwork.from_subnet("192.168.1.50/24") # this uses the 192.168.1.0-255 network
|
||||
|
||||
# scan for miners asynchronously
|
||||
# this will return the correct type of miners if they are supported with all functionality.
|
||||
miners = await network.scan_network_for_miners()
|
||||
miners = await network.scan()
|
||||
print(miners)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(scan_miners()) # run the scan asynchronously with asyncio.run()
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## 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.
|
||||
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.
|
||||
---
|
||||
##### Creating miners based on IP
|
||||
If you already know the IP address of your miner or miners, you can use the [`MinerFactory`][pyasic.miners.miner_factory.MinerFactory] to communicate and identify the miners, or an abstraction of its functionality, [`get_miner()`][pyasic.miners.get_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
|
||||
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
|
||||
# get the miner with miner factory
|
||||
# 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()
|
||||
miner_1 = await MinerFactory().get_miner("192.168.1.75")
|
||||
miner_2 = await MinerFactory().get_miner("192.168.1.76")
|
||||
# get the miner with the miner factory
|
||||
# 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(), or just get_miner()
|
||||
miner_1 = await get_miner("192.168.1.75")
|
||||
miner_2 = await get_miner("192.168.1.76")
|
||||
print(miner_1, miner_2)
|
||||
|
||||
# can also gather these, since they are async
|
||||
# gathering them will get them both at the same time
|
||||
# this makes it much faster to get a lot of miners at a time
|
||||
tasks = [get_miner("192.168.1.75"), get_miner("192.168.1.76")]
|
||||
miners = await asyncio.gather(*tasks)
|
||||
print(miners)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(get_miners()) # get the miners asynchronously with asyncio.run()
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## 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()`.
|
||||
This function will return a instance of the dataclass [`MinerData`][pyasic.data.MinerData] with all data it can gather from the miner.
|
||||
---
|
||||
## Data gathering
|
||||
---
|
||||
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 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].
|
||||
|
||||
##### One miner
|
||||
```python
|
||||
import asyncio
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
from pyasic import get_miner
|
||||
|
||||
async def gather_miner_data():
|
||||
miner = await MinerFactory().get_miner("192.168.1.75")
|
||||
miner_data = await miner.get_data()
|
||||
print(miner_data) # all data from the dataclass
|
||||
print(miner_data.hashrate) # hashrate of the miner in TH/s
|
||||
miner = await get_miner("192.168.1.75")
|
||||
if miner is not None:
|
||||
miner_data = await miner.get_data()
|
||||
print(miner_data) # all data from the dataclass
|
||||
print(miner_data.hashrate) # hashrate of the miner in TH/s
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(gather_miner_data())
|
||||
```
|
||||
|
||||
---
|
||||
##### Multiple miners
|
||||
You can do something similar with multiple miners, with only needing to make a small change to get all the data at once.
|
||||
```python
|
||||
import asyncio # asyncio for handling the async part
|
||||
@@ -89,8 +102,8 @@ from pyasic.network import MinerNetwork # miner network handles the scanning
|
||||
|
||||
|
||||
async def gather_miner_data(): # define async scan function to allow awaiting
|
||||
network = MinerNetwork("192.168.1.50")
|
||||
miners = await network.scan_network_for_miners()
|
||||
network = MinerNetwork.from_subnet("192.168.1.50/24")
|
||||
miners = await network.scan()
|
||||
|
||||
# we need to asyncio.gather() all the miners get_data() functions to make them run together
|
||||
all_miner_data = await asyncio.gather(*[miner.get_data() for miner in miners])
|
||||
@@ -102,143 +115,60 @@ if __name__ == "__main__":
|
||||
asyncio.run(gather_miner_data())
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## Controlling miners via pyasic
|
||||
Every miner class in pyasic must implement all the control functions defined in [`BaseMiner`][pyasic.miners.BaseMiner].
|
||||
---
|
||||
## Miner control
|
||||
---
|
||||
`pyasic` exposes a standard interface for each miner using control functions.
|
||||
Every miner class in `pyasic` must implement all the control functions defined in [`BaseMiner`][pyasic.miners.BaseMiner].
|
||||
|
||||
These functions are
|
||||
[`check_light`](#check-light),
|
||||
[`fault_light_off`](#fault-light-off),
|
||||
[`fault_light_on`](#fault-light-on),
|
||||
[`get_config`](#get-config),
|
||||
[`get_data`](#get-data),
|
||||
[`get_errors`](#get-errors),
|
||||
[`get_hostname`](#get-hostname),
|
||||
[`get_model`](#get-model),
|
||||
[`reboot`](#reboot),
|
||||
[`restart_backend`](#restart-backend), and
|
||||
[`stop_mining`](#stop-mining), and
|
||||
[`resume_mining`](#resume-mining), and
|
||||
[`send_config`](#send-config).
|
||||
[`check_light`][pyasic.miners.BaseMiner.check_light],
|
||||
[`fault_light_off`][pyasic.miners.BaseMiner.fault_light_off],
|
||||
[`fault_light_on`][pyasic.miners.BaseMiner.fault_light_on],
|
||||
[`get_config`][pyasic.miners.BaseMiner.get_config],
|
||||
[`get_data`][pyasic.miners.BaseMiner.get_data],
|
||||
[`get_errors`][pyasic.miners.BaseMiner.get_errors],
|
||||
[`get_hostname`][pyasic.miners.BaseMiner.get_hostname],
|
||||
[`get_model`][pyasic.miners.BaseMiner.get_model],
|
||||
[`reboot`][pyasic.miners.BaseMiner.reboot],
|
||||
[`restart_backend`][pyasic.miners.BaseMiner.restart_backend],
|
||||
[`stop_mining`][pyasic.miners.BaseMiner.stop_mining],
|
||||
[`resume_mining`][pyasic.miners.BaseMiner.resume_mining],
|
||||
[`is_mining`][pyasic.miners.BaseMiner.is_mining],
|
||||
[`send_config`][pyasic.miners.BaseMiner.send_config], and
|
||||
[`set_power_limit`][pyasic.miners.BaseMiner.set_power_limit].
|
||||
|
||||
<br>
|
||||
##### Usage
|
||||
```python
|
||||
import asyncio
|
||||
from pyasic import get_miner
|
||||
|
||||
### Check Light
|
||||
::: pyasic.miners.BaseMiner.check_light
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
async def set_fault_light():
|
||||
miner = await get_miner("192.168.1.20")
|
||||
|
||||
### Fault Light Off
|
||||
::: pyasic.miners.BaseMiner.fault_light_off
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
# call control function
|
||||
await miner.fault_light_on()
|
||||
|
||||
<br>
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(set_fault_light())
|
||||
```
|
||||
|
||||
### Fault Light On
|
||||
::: pyasic.miners.BaseMiner.fault_light_on
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
---
|
||||
## Helper dataclasses
|
||||
---
|
||||
|
||||
<br>
|
||||
##### [`MinerConfig`][pyasic.config.MinerConfig] and [`MinerData`][pyasic.data.MinerData]
|
||||
|
||||
### Get Config
|
||||
::: pyasic.miners.BaseMiner.get_config
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
`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>
|
||||
---
|
||||
|
||||
### Get Data
|
||||
::: pyasic.miners.BaseMiner.get_data
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Get Errors
|
||||
::: pyasic.miners.BaseMiner.get_errors
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Get Hostname
|
||||
::: pyasic.miners.BaseMiner.get_hostname
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Get Model
|
||||
::: pyasic.miners.BaseMiner.get_model
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Reboot
|
||||
::: pyasic.miners.BaseMiner.reboot
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Restart Backend
|
||||
::: pyasic.miners.BaseMiner.restart_backend
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Stop Mining
|
||||
::: pyasic.miners.BaseMiner.stop_mining
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Resume Mining
|
||||
::: pyasic.miners.BaseMiner.resume_mining
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Send Config
|
||||
::: pyasic.miners.BaseMiner.send_config
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
## [`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.
|
||||
|
||||
<br>
|
||||
|
||||
### [`MinerData`][pyasic.data.MinerData]
|
||||
##### [`MinerData`][pyasic.data.MinerData]
|
||||
|
||||
[`MinerData`][pyasic.data.MinerData] is a return from the [`get_data()`](#get-data) function, and is used to have a consistent dataset across all returns.
|
||||
|
||||
You can call [`MinerData.asdict()`][pyasic.data.MinerData.asdict] to get the dataclass as a dictionary, and there are many other helper functions contained in the class to convert to different data formats.
|
||||
You can call [`MinerData.as_dict()`][pyasic.data.MinerData.as_dict] to get the dataclass as a dictionary, and there are many other helper functions contained in the class to convert to different data formats.
|
||||
|
||||
[`MinerData`][pyasic.data.MinerData] instances can also be added to each other to combine their data and can be divided by a number to divide all their data, allowing you to get average data from many miners by doing -
|
||||
```python
|
||||
@@ -253,13 +183,64 @@ list_of_miner_data = [d1, d2]
|
||||
average_data = sum(list_of_miner_data, start=MinerData("0.0.0.0"))/len(list_of_miner_data)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<br>
|
||||
##### [`MinerConfig`][pyasic.config.MinerConfig]
|
||||
|
||||
### [`MinerConfig`][pyasic.config.MinerConfig]
|
||||
|
||||
[`MinerConfig`][pyasic.config.MinerConfig] is pyasic's way to represent a configuration file from a miner.
|
||||
It is the return from [`get_config()`](#get-config).
|
||||
[`MinerConfig`][pyasic.config.MinerConfig] is `pyasic`'s way to represent a configuration file from a miner.
|
||||
It is designed to unionize the configuration of all supported miner types, and is the return from [`get_config()`](#get-config).
|
||||
|
||||
Each miner has a unique way to convert the [`MinerConfig`][pyasic.config.MinerConfig] to their specific type, there are helper functions in the class.
|
||||
In most cases these helper functions should not be used, as [`send_config()`](#send-config) takes a [`MinerConfig`][pyasic.config.MinerConfig] and will do the conversion to the right type for you.
|
||||
|
||||
You can use the [`MinerConfig`][pyasic.config.MinerConfig] as follows:
|
||||
```python
|
||||
import asyncio
|
||||
from pyasic import get_miner
|
||||
|
||||
|
||||
async def set_fault_light():
|
||||
miner = await get_miner("192.168.1.20")
|
||||
|
||||
# get config
|
||||
cfg = await miner.get_config()
|
||||
|
||||
# send config
|
||||
await miner.send_config(cfg)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(set_fault_light())
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
## Settings
|
||||
---
|
||||
`pyasic` has settings designed to make using large groups of miners easier. You can set the default password for all types of miners using the `pyasic.settings` module, used as follows:
|
||||
|
||||
```python
|
||||
from pyasic import settings
|
||||
|
||||
settings.update("default_antminer_password", "my_pwd")
|
||||
```
|
||||
|
||||
##### Default values:
|
||||
```
|
||||
"network_ping_retries": 1,
|
||||
"network_ping_timeout": 3,
|
||||
"network_scan_threads": 300,
|
||||
"factory_get_retries": 1,
|
||||
"factory_get_timeout": 3,
|
||||
"get_data_retries": 1,
|
||||
"api_function_timeout": 5,
|
||||
"default_whatsminer_password": "admin",
|
||||
"default_innosilicon_password": "admin",
|
||||
"default_antminer_password": "root",
|
||||
"default_bosminer_password": "root",
|
||||
"default_vnish_password": "admin",
|
||||
"default_goldshell_password": "123456789",
|
||||
|
||||
# ADVANCED
|
||||
# Only use this if you know what you are doing
|
||||
"socket_linger_time": 1000,
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
## S17
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17
|
||||
handler: python
|
||||
options:
|
||||
@@ -10,31 +9,27 @@
|
||||
heading_level: 4
|
||||
|
||||
## S17+
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17_Plus.BMMinerS17Plus
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S17 Pro
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17_Pro.BMMinerS17Pro
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S17e
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17e.BMMinerS17e
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T17
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17
|
||||
handler: python
|
||||
options:
|
||||
@@ -42,25 +37,20 @@
|
||||
heading_level: 4
|
||||
|
||||
## T17+
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17_Plus.BMMinerT17Plus
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## T17e
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17e.BMMinerT17e
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## S17 (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17
|
||||
handler: python
|
||||
options:
|
||||
@@ -68,31 +58,27 @@
|
||||
heading_level: 4
|
||||
|
||||
## S17+ (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17_Plus.BOSMinerS17Plus
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S17 Pro (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17_Pro.BOSMinerS17Pro
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S17e (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17e.BOSMinerS17e
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T17 (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17
|
||||
handler: python
|
||||
options:
|
||||
@@ -100,18 +86,30 @@
|
||||
heading_level: 4
|
||||
|
||||
## T17+ (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.T17_Plus.BOSMinerT17Plus
|
||||
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## T17e (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.T17e.BOSMinerT17e
|
||||
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
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,97 @@
|
||||
## X19 Models
|
||||
|
||||
## S19
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19L
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19L
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19_Pro.BMMinerS19Pro
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## S19a
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19a.BMMinerS19a
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## 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
|
||||
## S19i
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19i
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19+
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j No PIC
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jNoPIC
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro+
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19ProPlus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19j_Pro.BMMinerS19jPro
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## 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
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T19
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.T19.BMMinerT19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## S19 (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19
|
||||
handler: python
|
||||
options:
|
||||
@@ -69,34 +100,135 @@
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19_Pro.BOSMinerS19Pro
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## 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
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19j_Pro.BOSMinerS19jPro
|
||||
## S19j Pro (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T19 (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X19.T19.BOSMinerT19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
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
|
||||
|
||||
## S19 (ePIC)
|
||||
::: pyasic.miners.antminer.epic.X19.S19.ePICS19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro (ePIC)
|
||||
::: pyasic.miners.antminer.epic.X19.S19.ePICS19Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j (ePIC)
|
||||
::: pyasic.miners.antminer.epic.X19.S19.ePICS19j
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro (ePIC)
|
||||
::: pyasic.miners.antminer.epic.X19.S19.ePICS19jPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 XP (ePIC)
|
||||
::: pyasic.miners.antminer.epic.X19.S19.ePICS19XP
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
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
|
||||
## X9 Models
|
||||
|
||||
|
||||
## X9 (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X9.S9.BOSMinerS9
|
||||
## E9Pro
|
||||
::: pyasic.miners.antminer.bmminer.X9.E9.BMMinerE9Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## S9
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9
|
||||
handler: python
|
||||
options:
|
||||
@@ -20,17 +16,44 @@
|
||||
heading_level: 4
|
||||
|
||||
## 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
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T9
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X9.T9.BMMinerT9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
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
|
||||
## A10X Models
|
||||
|
||||
## A1026
|
||||
|
||||
## Avalon 1026
|
||||
::: pyasic.miners.avalonminer.cgminer.A10X.A1026.CGMinerAvalon1026
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A1047
|
||||
|
||||
## Avalon 1047
|
||||
::: pyasic.miners.avalonminer.cgminer.A10X.A1047.CGMinerAvalon1047
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A1066
|
||||
|
||||
## Avalon 1066
|
||||
::: pyasic.miners.avalonminer.cgminer.A10X.A1066.CGMinerAvalon1066
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
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
|
||||
## A7X Models
|
||||
|
||||
## A721
|
||||
|
||||
## Avalon 721
|
||||
::: pyasic.miners.avalonminer.cgminer.A7X.A721.CGMinerAvalon721
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A741
|
||||
|
||||
## Avalon 741
|
||||
::: pyasic.miners.avalonminer.cgminer.A7X.A741.CGMinerAvalon741
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A761
|
||||
|
||||
## Avalon 761
|
||||
::: pyasic.miners.avalonminer.cgminer.A7X.A761.CGMinerAvalon761
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
# pyasic
|
||||
## A8X Models
|
||||
|
||||
## A821
|
||||
|
||||
## Avalon 821
|
||||
::: pyasic.miners.avalonminer.cgminer.A8X.A821.CGMinerAvalon821
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A841
|
||||
|
||||
## Avalon 841
|
||||
::: pyasic.miners.avalonminer.cgminer.A8X.A841.CGMinerAvalon841
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A851
|
||||
|
||||
## Avalon 851
|
||||
::: pyasic.miners.avalonminer.cgminer.A8X.A851.CGMinerAvalon851
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# pyasic
|
||||
## A9X Models
|
||||
|
||||
## A921
|
||||
|
||||
## Avalon 921
|
||||
::: pyasic.miners.avalonminer.cgminer.A9X.A921.CGMinerAvalon921
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
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
|
||||
## BMMiner Backend
|
||||
|
||||
::: pyasic.miners._backends.bmminer.BMMiner
|
||||
::: pyasic.miners.backends.bmminer.BMMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# pyasic
|
||||
## BOSMiner Backend
|
||||
|
||||
::: pyasic.miners._backends.bosminer.BOSMiner
|
||||
::: pyasic.miners.backends.bosminer.BOSMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# pyasic
|
||||
## BTMiner Backend
|
||||
|
||||
::: pyasic.miners._backends.btminer.BTMiner
|
||||
::: pyasic.miners.backends.btminer.BTMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# pyasic
|
||||
## CGMiner Backend
|
||||
|
||||
::: pyasic.miners._backends.cgminer.CGMiner
|
||||
::: pyasic.miners.backends.cgminer.CGMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
8
docs/miners/backends/epic.md
Normal file
8
docs/miners/backends/epic.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# pyasic
|
||||
## ePIC Backend
|
||||
|
||||
::: pyasic.miners.backends.epic.ePIC
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
@@ -1,7 +1,7 @@
|
||||
# pyasic
|
||||
## Hiveon Backend
|
||||
|
||||
::: pyasic.miners._backends.hiveon.Hiveon
|
||||
::: pyasic.miners.backends.hiveon.Hiveon
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
8
docs/miners/backends/luxminer.md
Normal file
8
docs/miners/backends/luxminer.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# pyasic
|
||||
## LUXMiner Backend
|
||||
|
||||
::: pyasic.miners.backends.luxminer.LUXMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
8
docs/miners/backends/vnish.md
Normal file
8
docs/miners/backends/vnish.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# pyasic
|
||||
## VNish Backend
|
||||
|
||||
::: pyasic.miners.backends.vnish.VNish
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
91
docs/miners/functions.md
Normal file
91
docs/miners/functions.md
Normal file
@@ -0,0 +1,91 @@
|
||||
## Control functionality
|
||||
|
||||
### Check Light
|
||||
::: pyasic.miners.BaseMiner.check_light
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Fault Light Off
|
||||
::: pyasic.miners.BaseMiner.fault_light_off
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Fault Light On
|
||||
::: pyasic.miners.BaseMiner.fault_light_on
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Get Config
|
||||
::: pyasic.miners.BaseMiner.get_config
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Get Data
|
||||
::: pyasic.miners.BaseMiner.get_data
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Get Errors
|
||||
::: pyasic.miners.BaseMiner.get_errors
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Get Hostname
|
||||
::: pyasic.miners.BaseMiner.get_hostname
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Get Model
|
||||
::: pyasic.miners.BaseMiner.get_model
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Reboot
|
||||
::: pyasic.miners.BaseMiner.reboot
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Restart Backend
|
||||
::: pyasic.miners.BaseMiner.restart_backend
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Stop Mining
|
||||
::: pyasic.miners.BaseMiner.stop_mining
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Resume Mining
|
||||
::: pyasic.miners.BaseMiner.resume_mining
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Is Mining
|
||||
::: pyasic.miners.BaseMiner.is_mining
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Send Config
|
||||
::: pyasic.miners.BaseMiner.send_config
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Set Power Limit
|
||||
::: pyasic.miners.BaseMiner.set_power_limit
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
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
|
||||
|
||||
## T3H+
|
||||
|
||||
::: pyasic.miners.innosilicon.cgminer.T3X.T3H_Plus.CGMinerInnosiliconT3HPlus
|
||||
::: pyasic.miners.innosilicon.cgminer.T3X.T3H.CGMinerT3HPlus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
# pyasic
|
||||
## Miner Factory
|
||||
|
||||
[`MinerFactory`][pyasic.miners.miner_factory.MinerFactory] is the way to create miner types in `pyasic`. The most important method is [`get_miner()`][pyasic.get_miner], which is mapped to [`pyasic.get_miner()`][pyasic.get_miner], and should be used from there.
|
||||
|
||||
The instance used for [`pyasic.get_miner()`][pyasic.get_miner] is `pyasic.miner_factory`.
|
||||
|
||||
[`MinerFactory`][pyasic.MinerFactory] also keeps a cache, which can be cleared if needed with `pyasic.miner_factory.clear_cached_miners()`.
|
||||
|
||||
Finally, there is functionality to get multiple miners without using `asyncio.gather()` explicitly. Use `pyasic.miner_factory.get_multiple_miners()` with a list of IPs as strings to get a list of miner instances. You can also get multiple miners with an `AsyncGenerator` by using `pyasic.miner_factory.get_miner_generator()`.
|
||||
|
||||
::: pyasic.miners.miner_factory.MinerFactory
|
||||
handler: python
|
||||
options:
|
||||
@@ -8,6 +16,14 @@
|
||||
heading_level: 4
|
||||
<br>
|
||||
|
||||
## Get Miner
|
||||
::: pyasic.miners.get_miner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
<br>
|
||||
|
||||
## AnyMiner
|
||||
::: pyasic.miners.miner_factory.AnyMiner
|
||||
handler: python
|
||||
|
||||
@@ -11,37 +11,74 @@ details {
|
||||
padding-bottom:0px;
|
||||
}
|
||||
</style>
|
||||
<details style="margin:0px; padding-top:0px; padding-bottom:0px;">
|
||||
<summary>Braiins OS+ Devices:</summary>
|
||||
|
||||
<details>
|
||||
<summary>Stock Firmware Antminers:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>X19 Series:</summary>
|
||||
<summary>X3 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X19#s19-bos">S19</a></li>
|
||||
<li><a href="../antminer/X19#s19-pro-bos">S19 Pro</a></li>
|
||||
<li><a href="../antminer/X19#s19j-bos">S19j</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro-bos">S19j Pro</a></li>
|
||||
<li><a href="../antminer/X19#t19-bos">T19</a></li>
|
||||
<li><a href="../antminer/X3#d3">D3</a></li>
|
||||
<li><a href="../antminer/X3#hs3">HS3</a></li>
|
||||
<li><a href="../antminer/X3#l3_1">L3+</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X17 Series:</summary>
|
||||
<summary>X5 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X17#s17-bos">S17</a></li>
|
||||
<li><a href="../antminer/X17#s17-plus-bos">S17+</a></li>
|
||||
<li><a href="../antminer/X17#s17-pro-bos">S17 Pro</a></li>
|
||||
<li><a href="../antminer/X17#s17e-bos">S17e</a></li>
|
||||
<li><a href="../antminer/X17#t17-bos">T17</a></li>
|
||||
<li><a href="../antminer/X17#t17-plus-bos">T17+</a></li>
|
||||
<li><a href="../antminer/X17#t17e-bos">T17e</a></li>
|
||||
<li><a href="../antminer/X5#dr5">DR5</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X7 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X7#l7">L7</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X9 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X9#s9-bos">S9</a></li>
|
||||
<li><a href="../antminer/X9#s9-bos">S9i</a></li>
|
||||
<li><a href="../antminer/X9#s9-bos">S9j</a></li>
|
||||
<li><a href="../antminer/X9#e9pro">E9Pro</a></li>
|
||||
<li><a href="../antminer/X9#s9">S9</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#s19i">S19i</a></li>
|
||||
<li><a href="../antminer/X19#s19_1">S19+</a></li>
|
||||
<li><a href="../antminer/X19#s19j-no-pic">S19j No PIC</a></li>
|
||||
<li><a href="../antminer/X19#s19-pro_1">S19 Pro+</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro">S19j Pro</a></li>
|
||||
<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>
|
||||
</details>
|
||||
</ul>
|
||||
@@ -50,137 +87,211 @@ details {
|
||||
<summary>Stock Firmware Whatsminers:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>M5X Series:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M5X/#m50">M50</a></summary>
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M5X/#m50vh50">VH50</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
<summary>M3X Series:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M3X/#m30s">M30S</a></summary>
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M3X/#m30sve10">VE10</a></li>
|
||||
<li><a href="../whatsminer/M3X/#m30svg20">VG20</a></li>
|
||||
<li><a href="../whatsminer/M3X/#m30sve20">VE20</a></li>
|
||||
<li><a href="../whatsminer/M3X/#m30sv50">V50</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M3X/#m30s_1">M30S+</a></summary>
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M3X/#m30svf20">VF20</a></li>
|
||||
<li><a href="../whatsminer/M3X/#m30sve40">VE40</a></li>
|
||||
<li><a href="../whatsminer/M3X/#m30svg60">VG60</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M3X/#m30s_2">M30S++</a></summary>
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M3X/#m30svg30">VG30</a></li>
|
||||
<li><a href="../whatsminer/M3X/#m30svg40">VG40</a></li>
|
||||
<li><a href="../whatsminer/M3X/#m30svh60">VH60</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M3X/#m31s">M31S</a></summary>
|
||||
</details>
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M3X/#m31s_1">M31S+</a></summary>
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M3X/#m31sve20">VE20</a></li>
|
||||
<li><a href="../whatsminer/M3X/#m31sv30">V30</a></li>
|
||||
<li><a href="../whatsminer/M3X/#m31sv40">V40</a></li>
|
||||
<li><a href="../whatsminer/M3X/#m31sv60">V60</a></li>
|
||||
<li><a href="../whatsminer/M3X/#m31sv80">V80</a></li>
|
||||
<li><a href="../whatsminer/M3X/#m31sv90">V90</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M3X/#m32">M32</a></summary>
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M3X/#m32v20">V20</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M3X/#m32s">M32S</a></summary>
|
||||
</details>
|
||||
</ul>
|
||||
<summary>M2X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M2X#m20-v10">M20 V10</a></li>
|
||||
<li><a href="../whatsminer/M2X#m20s-v10">M20S V10</a></li>
|
||||
<li><a href="../whatsminer/M2X#m20s-v20">M20S V20</a></li>
|
||||
<li><a href="../whatsminer/M2X#m20s-v30">M20S V30</a></li>
|
||||
<li><a href="../whatsminer/M2X#m20p-v10">M20P V10</a></li>
|
||||
<li><a href="../whatsminer/M2X#m20p-v30">M20P V30</a></li>
|
||||
<li><a href="../whatsminer/M2X#m20s_1-v30">M20S+ V30</a></li>
|
||||
<li><a href="../whatsminer/M2X#m21-v10">M21 V10</a></li>
|
||||
<li><a href="../whatsminer/M2X#m21s-v20">M21S V20</a></li>
|
||||
<li><a href="../whatsminer/M2X#m21s-v60">M21S V60</a></li>
|
||||
<li><a href="../whatsminer/M2X#m21s-v70">M21S V70</a></li>
|
||||
<li><a href="../whatsminer/M2X#m21s_1-v20">M21S+ V20</a></li>
|
||||
<li><a href="../whatsminer/M2X#m29-v10">M29 V10</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>M2X Series:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M2X/#m20">M20</a></summary>
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M2X/#m20v10">V10</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M2X/#m20s">M20S</a></summary>
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M2X/#m20sv10">V10</a></li>
|
||||
<li><a href="../whatsminer/M2X/#m20sv20">V20</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M2X/#m20s_1">M20S+</a></summary>
|
||||
</details>
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M2X/#m21">M21</a></summary>
|
||||
</details>
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M2X/#m21s">M21S</a></summary>
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M2X/#m21sv20">V20</a></li>
|
||||
<li><a href="../whatsminer/M2X/#m21sv60">V60</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary><a href="../whatsminer/M2X/#m21s_1">M21S+</a></summary>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Stock Firmware Antminers:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>X19 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X19/#s19">S19</a></li>
|
||||
<li><a href="../antminer/X19/#s19-pro">S19 Pro</a></li>
|
||||
<li><a href="../antminer/X19/#s19a">S19a</a></li>
|
||||
<li><a href="../antminer/X19/#s19j">S19j</a></li>
|
||||
<li><a href="../antminer/X19/#s19j-pro">S19j Pro</a></li>
|
||||
<li><a href="../antminer/X19/#s19-xp">S19 XP</a></li>
|
||||
<li><a href="../antminer/X19/#t19">T19</a></li>
|
||||
</ul>
|
||||
<summary>M3X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M3X#m30-v10">M30 V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30-v20">M30 V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30k-v10">M30K V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30l-v10">M30L V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v10">M30S V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v20">M30S V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v30">M30S V30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v40">M30S V40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v50">M30S V50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v60">M30S V60</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v70">M30S V70</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v80">M30S V80</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-ve10">M30S VE10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-ve20">M30S VE20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-ve30">M30S VE30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-ve40">M30S VE40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-ve50">M30S VE50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-ve60">M30S VE60</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-ve70">M30S VE70</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vf10">M30S VF10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vf20">M30S VF20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vf30">M30S VF30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vg10">M30S VG10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vg20">M30S VG20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vg30">M30S VG30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vg40">M30S VG40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vh10">M30S VH10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vh20">M30S VH20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vh30">M30S VH30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vh40">M30S VH40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vh50">M30S VH50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vh60">M30S VH60</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vi20">M30S VI20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v10">M30S+ V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v20">M30S+ V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v30">M30S+ V30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v40">M30S+ V40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v50">M30S+ V50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v60">M30S+ V60</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v70">M30S+ V70</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v80">M30S+ V80</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v90">M30S+ V90</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v100">M30S+ V100</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-ve30">M30S+ VE30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-ve40">M30S+ VE40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-ve50">M30S+ VE50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-ve60">M30S+ VE60</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-ve70">M30S+ VE70</a></li>
|
||||
<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-vg20">M30S+ VG20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vg30">M30S+ VG30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vg40">M30S+ VG40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vg50">M30S+ VG50</a></li>
|
||||
<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#m31h-v10">M31H V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31h-v40">M31H V40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30l-v10">M30L V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-v10">M31S V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-v20">M31S V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-v30">M31S V30</a></li>
|
||||
<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#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-vg20">M33S+ VG20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33s_1-vh20">M33S+ VH20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33s_1-vh30">M33S+ VH30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33s_1_1-vh20">M33S++ VH20</a></li>
|
||||
<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-v10">M39 V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m39-v20">M39 V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m39-v30">M39 V30</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>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>X9 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X9/#s9">S9</a></li>
|
||||
<li><a href="../antminer/X9/#s9i">S9i</a></li>
|
||||
<li><a href="../antminer/X9/#t9">T9</a></li>
|
||||
</ul>
|
||||
<summary>M5X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M5X#m50-ve30">M50 VE30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vg30">M50 VG30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh10">M50 VH10</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh20">M50 VH20</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh30">M50 VH30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh40">M50 VH40</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh50">M50 VH50</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh60">M50 VH60</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh70">M50 VH70</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh80">M50 VH80</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vj10">M50 VJ10</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vj20">M50 VJ20</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vj30">M50 VJ30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s-vj10">M50S VJ10</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s-vj20">M50S VJ20</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s-vj30">M50S VJ30</a></li>
|
||||
<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>
|
||||
</ul>
|
||||
</details>
|
||||
@@ -188,45 +299,185 @@ details {
|
||||
<summary>Stock Firmware Avalonminers:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>A7X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../avalonminer/A7X/#a721">A721</a></li>
|
||||
<li><a href="../avalonminer/A7X/#a741">A741</a></li>
|
||||
<li><a href="../avalonminer/A7X/#a761">A761</a></li>
|
||||
</ul>
|
||||
<summary>A7X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../avalonminer/A7X#avalon-721">Avalon 721</a></li>
|
||||
<li><a href="../avalonminer/A7X#avalon-741">Avalon 741</a></li>
|
||||
<li><a href="../avalonminer/A7X#avalon-761">Avalon 761</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>A8X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../avalonminer/A8X/#a821">A821</a></li>
|
||||
<li><a href="../avalonminer/A8X/#a841">A841</a></li>
|
||||
<li><a href="../avalonminer/A8X/#a851">A851</a></li>
|
||||
</ul>
|
||||
<summary>A8X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../avalonminer/A8X#avalon-821">Avalon 821</a></li>
|
||||
<li><a href="../avalonminer/A8X#avalon-841">Avalon 841</a></li>
|
||||
<li><a href="../avalonminer/A8X#avalon-851">Avalon 851</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>A9X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../avalonminer/A9X/#a921">A921</a></li>
|
||||
</ul>
|
||||
<summary>A9X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../avalonminer/A9X#avalon-921">Avalon 921</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>A10X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../avalonminer/A10X/#a1026">A1026</a></li>
|
||||
<li><a href="../avalonminer/A10X/#a1047">A1047</a></li>
|
||||
<li><a href="../avalonminer/A10X/#a1066">A1066</a></li>
|
||||
</ul>
|
||||
<summary>A10X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../avalonminer/A10X#avalon-1026">Avalon 1026</a></li>
|
||||
<li><a href="../avalonminer/A10X#avalon-1047">Avalon 1047</a></li>
|
||||
<li><a href="../avalonminer/A10X#avalon-1066">Avalon 1066</a></li>
|
||||
</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>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Stock Firmware Innosilicon Miners:</summary>
|
||||
<summary>Stock Firmware Innosilicons:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>T3X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../innosilicon/T3X/#t3h">T3H+</a></li>
|
||||
</ul>
|
||||
<summary>T3X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../innosilicon/T3X#t3h_1">T3H+</a></li>
|
||||
</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#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>ePIC Firmware Miners:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>X19 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X19#s19-epic">S19 (ePIC)</a></li>
|
||||
<li><a href="../antminer/X19#s19-pro-epic">S19 Pro (ePIC)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-epic">S19j (ePIC)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro-epic">S19j Pro (ePIC)</a></li>
|
||||
<li><a href="../antminer/X19#s19-xp-epic">S19 XP (ePIC)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>HiveOS Firmware Miners:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<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>
|
||||
</ul>
|
||||
</details>
|
||||
@@ -1,91 +1,94 @@
|
||||
# pyasic
|
||||
## M2X Models
|
||||
|
||||
## M20
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M20V10
|
||||
|
||||
## M20 V10
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M20S
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20S
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M20SV10
|
||||
|
||||
## M20S V10
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M20SV20
|
||||
|
||||
## M20S V20
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M20S+
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlus
|
||||
## M20S V30
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M21
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21
|
||||
## M20P V10
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20P.BTMinerM20PV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## M21S
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21S
|
||||
## M20P V30
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20P.BTMinerM20PV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M21SV20
|
||||
## M20S+ V30
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlusV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M21 V10
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M21S V20
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M21SV60
|
||||
|
||||
## M21S V60
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M21S+
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlus
|
||||
## M21S V70
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV70
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
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
@@ -1,18 +1,248 @@
|
||||
# pyasic
|
||||
## M5X Models
|
||||
|
||||
## M50
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50
|
||||
## M50 VE30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VE30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50VH50
|
||||
## 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
|
||||
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
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# pyasic
|
||||
## Miner Network Range
|
||||
|
||||
[`MinerNetworkRange`][pyasic.network.net_range.MinerNetworkRange] is a class used by [`MinerNetwork`][pyasic.network.MinerNetwork] to handle any constructor stings.
|
||||
The goal is to emulate what is produced by `ipaddress.ip_network` by allowing [`MinerNetwork`][pyasic.network.MinerNetwork] to get a list of hosts.
|
||||
This allows this class to be the [`MinerNetwork.network`][pyasic.network.MinerNetwork] and hence be used for scanning.
|
||||
|
||||
::: pyasic.network.net_range.MinerNetworkRange
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
35
docs/settings/settings.md
Normal file
35
docs/settings/settings.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# pyasic
|
||||
## settings
|
||||
|
||||
All settings here are global settings for all of pyasic. Set these settings with `update(key, value)`.
|
||||
|
||||
Settings options:
|
||||
- `network_ping_retries`
|
||||
- `network_ping_timeout`
|
||||
- `network_scan_threads`
|
||||
- `factory_get_retries`
|
||||
- `factory_get_timeout`
|
||||
- `get_data_retries`
|
||||
- `api_function_timeout`
|
||||
- `default_whatsminer_password`
|
||||
- `default_innosilicon_password`
|
||||
- `default_antminer_password`
|
||||
- `default_bosminer_password`
|
||||
- `default_vnish_password`
|
||||
- `default_goldshell_password`
|
||||
- `socket_linger_time`
|
||||
|
||||
|
||||
### get
|
||||
::: pyasic.settings.get
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
### update
|
||||
::: pyasic.settings.update
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
95
mkdocs.yml
95
mkdocs.yml
@@ -1,46 +1,61 @@
|
||||
site_name: pyasic
|
||||
repo_url: https://github.com/UpstreamData/pyasic
|
||||
nav:
|
||||
- Introduction: "index.md"
|
||||
- Miners:
|
||||
- Supported Miners: "miners/supported_types.md"
|
||||
- Miner Factory: "miners/miner_factory.md"
|
||||
- Backends:
|
||||
- BMMiner: "miners/backends/bmminer.md"
|
||||
- BOSMiner: "miners/backends/bosminer.md"
|
||||
- BTMiner: "miners/backends/btminer.md"
|
||||
- CGMiner: "miners/backends/cgminer.md"
|
||||
- Hiveon: "miners/backends/hiveon.md"
|
||||
- Classes:
|
||||
- Antminer X9: "miners/antminer/X9.md"
|
||||
- Antminer X17: "miners/antminer/X17.md"
|
||||
- Antminer X19: "miners/antminer/X19.md"
|
||||
- Avalon 7X: "miners/avalonminer/A7X.md"
|
||||
- Avalon 8X: "miners/avalonminer/A8X.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"
|
||||
- Network:
|
||||
- Miner Network: "network/miner_network.md"
|
||||
- Miner Network Range: "network/miner_network_range.md"
|
||||
- Dataclasses:
|
||||
- Miner Data: "data/miner_data.md"
|
||||
- Error Codes: "data/error_codes.md"
|
||||
- Miner Config: "config/miner_config.md"
|
||||
- Advanced:
|
||||
- Miner APIs:
|
||||
- Intro: "API/api.md"
|
||||
- BMMiner: "API/bmminer.md"
|
||||
- BOSMiner: "API/bosminer.md"
|
||||
- BTMiner: "API/btminer.md"
|
||||
- CGMiner: "API/cgminer.md"
|
||||
- Unknown: "API/unknown.md"
|
||||
|
||||
- Base Miner: "miners/base_miner.md"
|
||||
|
||||
- Introduction: "index.md"
|
||||
- Miners:
|
||||
- Supported Miners: "miners/supported_types.md"
|
||||
- Standard Functionality: "miners/functions.md"
|
||||
- Miner Factory: "miners/miner_factory.md"
|
||||
- Network:
|
||||
- Miner Network: "network/miner_network.md"
|
||||
- Dataclasses:
|
||||
- Miner Data: "data/miner_data.md"
|
||||
- Error Codes: "data/error_codes.md"
|
||||
- Miner Config: "config/miner_config.md"
|
||||
- Advanced:
|
||||
- Miner APIs:
|
||||
- Intro: "API/api.md"
|
||||
- BFGMiner: "API/bfgminer.md"
|
||||
- BMMiner: "API/bmminer.md"
|
||||
- BOSMiner: "API/bosminer.md"
|
||||
- BTMiner: "API/btminer.md"
|
||||
- CGMiner: "API/cgminer.md"
|
||||
- LUXMiner: "API/luxminer.md"
|
||||
- Unknown: "API/unknown.md"
|
||||
- Backends:
|
||||
- BMMiner: "miners/backends/bmminer.md"
|
||||
- BOSMiner: "miners/backends/bosminer.md"
|
||||
- BFGMiner: "miners/backends/bfgminer.md"
|
||||
- BTMiner: "miners/backends/btminer.md"
|
||||
- CGMiner: "miners/backends/cgminer.md"
|
||||
- LUXMiner: "miners/backends/luxminer.md"
|
||||
- VNish: "miners/backends/vnish.md"
|
||||
- ePIC: "miners/backends/epic.md"
|
||||
- Hiveon: "miners/backends/hiveon.md"
|
||||
- Classes:
|
||||
- Antminer X3: "miners/antminer/X3.md"
|
||||
- Antminer X5: "miners/antminer/X5.md"
|
||||
- Antminer X7: "miners/antminer/X7.md"
|
||||
- Antminer X9: "miners/antminer/X9.md"
|
||||
- Antminer X15: "miners/antminer/X15.md"
|
||||
- Antminer X17: "miners/antminer/X17.md"
|
||||
- Antminer X19: "miners/antminer/X19.md"
|
||||
- Avalon 7X: "miners/avalonminer/A7X.md"
|
||||
- Avalon 8X: "miners/avalonminer/A8X.md"
|
||||
- Avalon 9X: "miners/avalonminer/A9X.md"
|
||||
- Avalon 10X: "miners/avalonminer/A10X.md"
|
||||
- Avalon 11X: "miners/avalonminer/A11X.md"
|
||||
- Avalon 12X: "miners/avalonminer/A12X.md"
|
||||
- Whatsminer M2X: "miners/whatsminer/M2X.md"
|
||||
- Whatsminer M3X: "miners/whatsminer/M3X.md"
|
||||
- Whatsminer M5X: "miners/whatsminer/M5X.md"
|
||||
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
||||
- Innosilicon A10X: "miners/innosilicon/A10X.md"
|
||||
- Goldshell X5: "miners/goldshell/X5.md"
|
||||
- Goldshell XMax: "miners/goldshell/XMax.md"
|
||||
- Base Miner: "miners/base_miner.md"
|
||||
- Settings:
|
||||
- Settings: "settings/settings.md"
|
||||
|
||||
plugins:
|
||||
- mkdocstrings
|
||||
|
||||
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
|
||||
#
|
||||
# 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.
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import ipaddress
|
||||
import warnings
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import warnings
|
||||
from typing import Union
|
||||
|
||||
from pyasic.errors import APIError, APIWarning
|
||||
@@ -29,11 +32,119 @@ class BaseMinerAPI:
|
||||
# ip address of the miner
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
|
||||
self.pwd = "admin"
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls is BaseMinerAPI:
|
||||
raise TypeError(f"Only children of '{cls.__name__}' may be instantiated")
|
||||
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
|
||||
validation = self._validate_command_output(data)
|
||||
if not validation[0]:
|
||||
if not ignore_errors:
|
||||
# validate the command succeeded
|
||||
raise APIError(validation[1])
|
||||
if allow_warning:
|
||||
logging.warning(
|
||||
f"{self.ip}: API Command Error: {command}: {validation[1]}"
|
||||
)
|
||||
|
||||
logging.debug(f"{self} - (Send Command) - Received data.")
|
||||
return data
|
||||
|
||||
# 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 e.message is not None:
|
||||
if ":" in e.message:
|
||||
err_command = e.message.split(":")[0]
|
||||
if err_command in commands:
|
||||
commands.remove(err_command)
|
||||
continue
|
||||
return {command: [{}] for command in commands}
|
||||
logging.debug(f"{self} - (Multicommand) - Received data")
|
||||
data["multicommand"] = True
|
||||
return data
|
||||
|
||||
async def _handle_multicommand(self, command: str, allow_warning: bool = True):
|
||||
try:
|
||||
data = await self.send_command(command, allow_warning=allow_warning)
|
||||
if not "+" in command:
|
||||
return {command: [data]}
|
||||
return data
|
||||
|
||||
except APIError:
|
||||
if "+" in command:
|
||||
return {command: [{}] for command in command.split("+")}
|
||||
return {command: [{}]}
|
||||
|
||||
@property
|
||||
def commands(self) -> list:
|
||||
return self.get_commands()
|
||||
|
||||
def get_commands(self) -> list:
|
||||
"""Get a list of command accessible to a specific type of API on the miner.
|
||||
|
||||
@@ -45,6 +156,7 @@ class BaseMinerAPI:
|
||||
for func in
|
||||
# each function in self
|
||||
dir(self)
|
||||
if not func == "commands"
|
||||
if callable(getattr(self, func)) and
|
||||
# no __ or _ methods
|
||||
not func.startswith("__") and not func.startswith("_") and
|
||||
@@ -58,9 +170,10 @@ class BaseMinerAPI:
|
||||
]
|
||||
|
||||
def _check_commands(self, *commands):
|
||||
allowed_commands = self.get_commands()
|
||||
allowed_commands = self.commands
|
||||
return_commands = []
|
||||
for command in [*commands]:
|
||||
|
||||
for command in commands:
|
||||
if command in allowed_commands:
|
||||
return_commands.append(command)
|
||||
else:
|
||||
@@ -71,102 +184,65 @@ If you are sure you want to use this command please use API.send_command("{comma
|
||||
)
|
||||
return return_commands
|
||||
|
||||
async def multicommand(self, *commands: str) -> dict:
|
||||
"""Creates and sends multiple commands as one command to the miner.
|
||||
|
||||
Parameters:
|
||||
*commands: The commands to send as a multicommand to the miner.
|
||||
"""
|
||||
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)
|
||||
except APIError:
|
||||
return {}
|
||||
logging.debug(f"{self.ip}: Received multicommand data.")
|
||||
return data
|
||||
|
||||
async def _send_bytes(self, data: bytes) -> bytes:
|
||||
async def _send_bytes(
|
||||
self,
|
||||
data: bytes,
|
||||
timeout: int = 100,
|
||||
) -> bytes:
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Sending")
|
||||
try:
|
||||
# get reader and writer streams
|
||||
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
|
||||
# handle OSError 121
|
||||
except OSError as e:
|
||||
if e.winerror == "121":
|
||||
logging.warning("Semaphore Timeout has Expired.")
|
||||
if e.errno == 121:
|
||||
logging.warning(
|
||||
f"{self} - ([Hidden] Send Bytes) - Semaphore timeout expired."
|
||||
)
|
||||
return b"{}"
|
||||
|
||||
# send the command
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Writing")
|
||||
writer.write(data)
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Draining")
|
||||
await writer.drain()
|
||||
|
||||
# instantiate data
|
||||
ret_data = b""
|
||||
try:
|
||||
# TO address a situation where a whatsminer has an unknown PW -AND-
|
||||
# Fix for stupid whatsminer bug, reboot/restart seem to not load properly in the loop
|
||||
# have to receive, save the data, check if there is more data by reading with a short timeout
|
||||
# append that data if there is more, and then onto the main loop.
|
||||
# the password timeout might need to be longer than 1, but it seems to work for now.
|
||||
ret_data = await asyncio.wait_for(reader.read(1), timeout=1)
|
||||
except asyncio.TimeoutError:
|
||||
return b"{}"
|
||||
try:
|
||||
ret_data += await asyncio.wait_for(reader.read(4096), timeout=timeout)
|
||||
except ConnectionAbortedError:
|
||||
return b"{}"
|
||||
|
||||
# loop to receive all the data
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Receiving")
|
||||
try:
|
||||
while True:
|
||||
d = await reader.read(4096)
|
||||
if not d:
|
||||
break
|
||||
ret_data += d
|
||||
try:
|
||||
d = await asyncio.wait_for(reader.read(4096), timeout=timeout)
|
||||
if not 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:
|
||||
logging.warning(f"{self.ip}: API Command Error: - {e}")
|
||||
logging.warning(f"{self} - ([Hidden] Send Bytes) - API Command Error {e}")
|
||||
|
||||
# close the connection
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Closing")
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
|
||||
return ret_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.
|
||||
"""
|
||||
# create the command
|
||||
cmd = {"command": command}
|
||||
if parameters:
|
||||
cmd["parameter"] = parameters
|
||||
|
||||
# send the command
|
||||
data = await self._send_bytes(json.dumps(cmd).encode("utf-8"))
|
||||
|
||||
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 not x19_command:
|
||||
logging.warning(
|
||||
f"{self.ip}: API Command Error: {command}: {validation[1]}"
|
||||
)
|
||||
raise APIError(validation[1])
|
||||
|
||||
return data
|
||||
|
||||
async def send_privileged_command(self, *args, **kwargs) -> dict:
|
||||
return await self.send_command(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _validate_command_output(data: dict) -> tuple:
|
||||
# check if the data returned is correct or an error
|
||||
@@ -181,13 +257,22 @@ If you are sure you want to use this command please use API.send_command("{comma
|
||||
# this is an error
|
||||
return False, f"{key}: " + data[key][0]["STATUS"][0]["Msg"]
|
||||
elif "id" not in data.keys():
|
||||
if isinstance(data["STATUS"], list):
|
||||
if data["STATUS"][0].get("STATUS", None) in ["S", "I"]:
|
||||
return True, None
|
||||
else:
|
||||
return False, data["STATUS"][0]["Msg"]
|
||||
|
||||
if data["STATUS"] not in ["S", "I"]:
|
||||
return False, data["Msg"]
|
||||
else:
|
||||
# make sure the command succeeded
|
||||
if type(data["STATUS"]) == str:
|
||||
if isinstance(data["STATUS"], str):
|
||||
if data["STATUS"] in ["RESTART"]:
|
||||
return True, None
|
||||
elif isinstance(data["STATUS"], dict):
|
||||
if data["STATUS"].get("STATUS") in ["S", "I"]:
|
||||
return True, None
|
||||
elif data["STATUS"][0]["STATUS"] not in ("S", "I"):
|
||||
# this is an error
|
||||
if data["STATUS"][0]["STATUS"] not in ("S", "I"):
|
||||
@@ -196,7 +281,6 @@ If you are sure you want to use this command please use API.send_command("{comma
|
||||
|
||||
@staticmethod
|
||||
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
|
||||
@@ -212,8 +296,12 @@ If you are sure you want to use this command please use API.send_command("{comma
|
||||
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(","):
|
||||
@@ -223,6 +311,10 @@ If you are sure you want to use this command please use API.send_command("{comma
|
||||
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:
|
||||
parsed_data = json.loads(str_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 asyncio
|
||||
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:
|
||||
tasks = []
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
tasks.append(
|
||||
asyncio.create_task(self._handle_multicommand(cmd, allow_warning=True))
|
||||
)
|
||||
|
||||
all_data = await asyncio.gather(*tasks)
|
||||
|
||||
data = {}
|
||||
for item in all_data:
|
||||
data.update(item)
|
||||
|
||||
return data
|
||||
|
||||
async def version(self) -> dict:
|
||||
"""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,20 +1,22 @@
|
||||
# Copyright 2022 Upstream Data Inc
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from pyasic.API import BaseMinerAPI
|
||||
from pyasic.API import APIError
|
||||
from pyasic.API import APIError, BaseMinerAPI
|
||||
|
||||
|
||||
class BMMinerAPI(BaseMinerAPI):
|
||||
@@ -35,38 +37,40 @@ class BMMinerAPI(BaseMinerAPI):
|
||||
port: The port to reference the API on. Default is 4028.
|
||||
"""
|
||||
|
||||
def __init__(self, ip: str, port: int = 4028) -> None:
|
||||
super().__init__(ip, port)
|
||||
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 multicommand(
|
||||
self, *commands: str, ignore_x19_error: bool = False
|
||||
) -> dict:
|
||||
logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}")
|
||||
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"
|
||||
# doesnt work for S19 which uses the backup _x19_multicommand
|
||||
# doesn't work for S19 which uses the backup _x19_multicommand
|
||||
command = "+".join(commands)
|
||||
try:
|
||||
data = await self.send_command(command, x19_command=ignore_x19_error)
|
||||
data = await self.send_command(command, allow_warning=allow_warning)
|
||||
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.")
|
||||
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):
|
||||
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}")
|
||||
async def _x19_multicommand(self, *commands, allow_warning: bool = True) -> dict:
|
||||
tasks = []
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
tasks.append(
|
||||
asyncio.create_task(self._handle_multicommand(cmd, allow_warning=True))
|
||||
)
|
||||
|
||||
all_data = await asyncio.gather(*tasks)
|
||||
|
||||
data = {}
|
||||
for item in all_data:
|
||||
data.update(item)
|
||||
|
||||
return data
|
||||
|
||||
async def version(self) -> dict:
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
# 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.
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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.API import BaseMinerAPI
|
||||
|
||||
@@ -33,8 +35,9 @@ class BOSMinerAPI(BaseMinerAPI):
|
||||
port: The port to reference the API on. Default is 4028.
|
||||
"""
|
||||
|
||||
def __init__(self, ip: str, port: int = 4028):
|
||||
super().__init__(ip, port)
|
||||
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 asccount(self) -> dict:
|
||||
"""Get data on the number of ASC devices and their info.
|
||||
@@ -166,7 +169,7 @@ class BOSMinerAPI(BaseMinerAPI):
|
||||
return await self.send_command("estats")
|
||||
|
||||
async def check(self, command: str) -> dict:
|
||||
"""Check if the command command exists in BOSMiner.
|
||||
"""Check if the command `command` exists in BOSMiner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
|
||||
@@ -1,42 +1,51 @@
|
||||
# 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.
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
import json
|
||||
import hashlib
|
||||
import binascii
|
||||
import base64
|
||||
import binascii
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
from typing import Union
|
||||
import re
|
||||
from typing import Literal, Union
|
||||
|
||||
from passlib.handlers.md5_crypt import md5_crypt
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from passlib.handlers.md5_crypt import md5_crypt
|
||||
|
||||
from pyasic.errors import APIError
|
||||
from pyasic import settings
|
||||
from pyasic.API import BaseMinerAPI
|
||||
from pyasic.settings import PyasicSettings
|
||||
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.misc import api_min_version
|
||||
|
||||
### IMPORTANT ###
|
||||
# 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
|
||||
# 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
|
||||
# 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.
|
||||
|
||||
PrePowerOnMessage = Union[
|
||||
Literal["wait for adjust temp"],
|
||||
Literal["adjust complete"],
|
||||
Literal["adjust continue"],
|
||||
]
|
||||
|
||||
|
||||
def _crypt(word: str, salt: str) -> str:
|
||||
"""Encrypts a word with a salt, using a standard salt format.
|
||||
@@ -83,7 +92,7 @@ def _add_to_16(string: str) -> bytes:
|
||||
def parse_btminer_priviledge_data(token_data: dict, data: dict):
|
||||
"""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.
|
||||
|
||||
Parameters:
|
||||
@@ -125,6 +134,7 @@ def create_privileged_cmd(token_data: dict, command: dict) -> bytes:
|
||||
Returns:
|
||||
The encrypted privileged command to be sent to the miner.
|
||||
"""
|
||||
logging.debug(f"(Create Prilileged Command) - Creating Privileged Command")
|
||||
# add token to command
|
||||
command["token"] = token_data["host_sign"]
|
||||
# encode host_passwd data and get hexdigest
|
||||
@@ -180,25 +190,86 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
def __init__(
|
||||
self,
|
||||
ip: str,
|
||||
api_ver: str = "0.0.0",
|
||||
port: int = 4028,
|
||||
pwd: str = PyasicSettings().global_whatsminer_password,
|
||||
pwd: str = settings.get("default_whatsminer_password", "admin"),
|
||||
):
|
||||
super().__init__(ip, port)
|
||||
self.pwd = pwd
|
||||
self.current_token = None
|
||||
self.api_ver = api_ver
|
||||
|
||||
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_" and the "status" command aren't supported, but we can fake that
|
||||
|
||||
tasks = []
|
||||
|
||||
for command in list(commands):
|
||||
if command.startswith("get_") or command == "status":
|
||||
commands.remove(command)
|
||||
# send seperately and append later
|
||||
tasks.append(
|
||||
asyncio.create_task(
|
||||
self._handle_multicommand(command, allow_warning=allow_warning)
|
||||
)
|
||||
)
|
||||
|
||||
command = "+".join(commands)
|
||||
tasks.append(
|
||||
asyncio.create_task(
|
||||
self._handle_multicommand(command, allow_warning=allow_warning)
|
||||
)
|
||||
)
|
||||
|
||||
all_data = await asyncio.gather(*tasks)
|
||||
|
||||
logging.debug(f"{self} - (Multicommand) - Received data")
|
||||
|
||||
data = {}
|
||||
for item in all_data:
|
||||
data.update(item)
|
||||
|
||||
data["multicommand"] = True
|
||||
return data
|
||||
|
||||
async def send_privileged_command(
|
||||
self, command: Union[str, bytes], ignore_errors: bool = False, **kwargs
|
||||
self,
|
||||
command: Union[str, bytes],
|
||||
ignore_errors: bool = False,
|
||||
timeout: int = 10,
|
||||
**kwargs,
|
||||
) -> dict:
|
||||
command = {"cmd": command}
|
||||
for kwarg in kwargs:
|
||||
if kwargs[kwarg]:
|
||||
command[kwarg] = kwargs[kwarg]
|
||||
logging.debug(
|
||||
f"{self} - (Send Privileged Command) - {command} " + f"with args {kwargs}"
|
||||
if len(kwargs) > 0
|
||||
else ""
|
||||
)
|
||||
command = {"cmd": command, **kwargs}
|
||||
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
|
||||
data = await self._send_bytes(enc_command)
|
||||
logging.debug(f"{self} - (Send Privileged Command) - Sending")
|
||||
try:
|
||||
data = await self._send_bytes(enc_command, timeout)
|
||||
except (asyncio.CancelledError, asyncio.TimeoutError):
|
||||
if ignore_errors:
|
||||
return {}
|
||||
raise APIError("No data was returned from the API.")
|
||||
|
||||
if not data:
|
||||
if ignore_errors:
|
||||
return {}
|
||||
raise APIError("No data was returned from the API.")
|
||||
data = self._load_api_data(data)
|
||||
|
||||
try:
|
||||
@@ -224,6 +295,13 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
An encoded token and md5 password, which are used for the privileged API.
|
||||
</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
|
||||
data = await self.send_command("get_token")
|
||||
|
||||
@@ -245,7 +323,11 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
self.current_token = {
|
||||
"host_sign": host_sign,
|
||||
"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
|
||||
|
||||
#### PRIVILEGED COMMANDS ####
|
||||
@@ -341,7 +423,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
the password of the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of powering on.
|
||||
</details>
|
||||
"""
|
||||
@@ -356,7 +437,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of resetting the LED.
|
||||
</details>
|
||||
"""
|
||||
@@ -384,12 +464,11 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
duration: LED on time in the cycle in ms.
|
||||
start: LED on time offset in the cycle in ms.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting the LED.
|
||||
</details>
|
||||
"""
|
||||
if auto:
|
||||
return await self.send_privileged_command("set_led", param=auto)
|
||||
return await self.send_privileged_command("set_led", param="auto")
|
||||
return await self.send_privileged_command(
|
||||
"set_led", color=color, period=period, duration=duration, start=start
|
||||
)
|
||||
@@ -403,29 +482,62 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting low power mode.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_privileged_command("set_low_power")
|
||||
|
||||
async def set_high_power(self) -> dict:
|
||||
"""Set low power mode on the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set low power mode on the miner using the API, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
A reply informing of the status of setting low power mode.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_privileged_command("set_high_power")
|
||||
|
||||
async def set_normal_power(self) -> dict:
|
||||
"""Set low power mode on the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set low power mode on the miner using the API, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
A reply informing of the status of setting low power mode.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_privileged_command("set_normal_power")
|
||||
|
||||
async def update_firmware(self): # noqa - static
|
||||
"""Not implemented."""
|
||||
# to be determined if this will be added later
|
||||
# requires a file stream in bytes
|
||||
return NotImplementedError
|
||||
|
||||
async def reboot(self) -> dict:
|
||||
async def reboot(self, timeout: int = 10) -> dict:
|
||||
"""Reboot the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of the reboot.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_privileged_command("reboot")
|
||||
try:
|
||||
d = await asyncio.wait_for(
|
||||
self.send_privileged_command("reboot"), timeout=timeout
|
||||
)
|
||||
except (asyncio.CancelledError, asyncio.TimeoutError):
|
||||
return {}
|
||||
else:
|
||||
return d
|
||||
|
||||
async def factory_reset(self) -> dict:
|
||||
"""Reset the miner to factory defaults.
|
||||
@@ -433,7 +545,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of the reset.
|
||||
</details>
|
||||
"""
|
||||
@@ -454,7 +565,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
old_pwd: The old admin password.
|
||||
new_pwd: The new password to set.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting the password.
|
||||
"""
|
||||
self.pwd = old_pwd
|
||||
@@ -473,6 +583,23 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
self.pwd = new_pwd
|
||||
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:
|
||||
"""Update the target frequency.
|
||||
|
||||
@@ -486,7 +613,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
Parameters:
|
||||
percent: The frequency % to set.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting the frequency.
|
||||
</details>
|
||||
"""
|
||||
@@ -510,7 +636,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of enabling fast boot.
|
||||
</details>
|
||||
"""
|
||||
@@ -526,7 +651,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of disabling fast boot.
|
||||
</details>
|
||||
"""
|
||||
@@ -542,7 +666,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
password of the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of enabling web pools.
|
||||
</details>
|
||||
"""
|
||||
@@ -558,7 +681,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
password of the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of disabling web pools.
|
||||
</details>
|
||||
"""
|
||||
@@ -576,14 +698,13 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
Parameters:
|
||||
hostname: The new hostname to use.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting the hostname.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_privileged_command("set_hostname", hostname=hostname)
|
||||
|
||||
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>
|
||||
<summary>Expand</summary>
|
||||
@@ -594,7 +715,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
Parameters:
|
||||
percent: The power percentage to set.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting the power percentage.
|
||||
</details>
|
||||
"""
|
||||
@@ -607,7 +727,7 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
)
|
||||
return await self.send_privileged_command("set_power_pct", percent=str(percent))
|
||||
|
||||
async def pre_power_on(self, complete: bool, msg: str) -> dict:
|
||||
async def pre_power_on(self, complete: bool, msg: PrePowerOnMessage) -> dict:
|
||||
"""Configure or check status of pre power on.
|
||||
|
||||
<details>
|
||||
@@ -623,12 +743,11 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
* `adjust complete`
|
||||
* `adjust continue`
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of pre power on.
|
||||
</details>
|
||||
"""
|
||||
|
||||
if not msg == "wait for adjust temp" or "adjust complete" or "adjust continue":
|
||||
if msg not in ("wait for adjust temp", "adjust complete", "adjust continue"):
|
||||
raise APIError(
|
||||
"Message is incorrect, please choose one of "
|
||||
'["wait for adjust temp", '
|
||||
@@ -643,6 +762,155 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
"pre_power_on", complete="false", msg=msg
|
||||
)
|
||||
|
||||
### ADDED IN V2.0.5 Whatsminer API ###
|
||||
|
||||
@api_min_version("2.0.5")
|
||||
async def set_power_pct_v2(self, percent: int) -> dict:
|
||||
"""Set the power percentage of the miner based on current power. Used for temporary adjustment. Added in API v2.0.5.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set the power percentage of the miner, only works after changing
|
||||
the password of the miner using the Whatsminer tool.
|
||||
|
||||
Parameters:
|
||||
percent: The power percentage to set.
|
||||
Returns:
|
||||
A reply informing of the status of setting the power percentage.
|
||||
</details>
|
||||
"""
|
||||
|
||||
if not 0 < percent < 100:
|
||||
raise APIError(
|
||||
f"Power PCT % is outside of the allowed "
|
||||
f"range. Please set a % between 0 and "
|
||||
f"100"
|
||||
)
|
||||
return await self.send_privileged_command(
|
||||
"set_power_pct_v2", percent=str(percent)
|
||||
)
|
||||
|
||||
@api_min_version("2.0.5")
|
||||
async def set_temp_offset(self, temp_offset: int) -> dict:
|
||||
"""Set the offset of miner hash board target temperature.
|
||||
|
||||
<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 ####
|
||||
|
||||
async def summary(self) -> dict:
|
||||
@@ -651,7 +919,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Summary status of the miner.
|
||||
</details>
|
||||
"""
|
||||
@@ -663,7 +930,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Pool status of the miner.
|
||||
</details>
|
||||
"""
|
||||
@@ -675,7 +941,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Data on each PGA/ASC with their details.
|
||||
</details>
|
||||
"""
|
||||
@@ -687,7 +952,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Data on each PGA/ASC with their details.
|
||||
</details>
|
||||
"""
|
||||
@@ -699,7 +963,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Data on all devices with their static details.
|
||||
</details>
|
||||
"""
|
||||
@@ -711,7 +974,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Data on the PSU and power information.
|
||||
</details>
|
||||
"""
|
||||
@@ -727,7 +989,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
with the other miner APIs.
|
||||
|
||||
Returns:
|
||||
|
||||
Version data for the miner.
|
||||
</details>
|
||||
"""
|
||||
@@ -739,7 +1000,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Version data for the miner.
|
||||
</details>
|
||||
"""
|
||||
@@ -751,7 +1011,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
BTMiner status and firmware version.
|
||||
</details>
|
||||
"""
|
||||
@@ -763,8 +1022,21 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
General miner info.
|
||||
</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
|
||||
#
|
||||
# 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.
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from pyasic.API import BaseMinerAPI
|
||||
from pyasic.API import APIError, BaseMinerAPI
|
||||
|
||||
|
||||
class CGMinerAPI(BaseMinerAPI):
|
||||
@@ -33,8 +37,39 @@ class CGMinerAPI(BaseMinerAPI):
|
||||
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)
|
||||
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:
|
||||
tasks = []
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
tasks.append(
|
||||
asyncio.create_task(self._handle_multicommand(cmd, allow_warning=True))
|
||||
)
|
||||
|
||||
all_data = await asyncio.gather(*tasks)
|
||||
|
||||
data = {}
|
||||
for item in all_data:
|
||||
data.update(item)
|
||||
|
||||
return data
|
||||
|
||||
async def version(self) -> dict:
|
||||
"""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
|
||||
#
|
||||
# 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.
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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.API import BaseMinerAPI
|
||||
|
||||
@@ -18,13 +20,14 @@ from pyasic.API import BaseMinerAPI
|
||||
class UnknownAPI(BaseMinerAPI):
|
||||
"""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
|
||||
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)
|
||||
self.api_ver = api_ver
|
||||
|
||||
async def asccount(self) -> dict:
|
||||
return await self.send_command("asccount")
|
||||
|
||||
@@ -1,43 +1,39 @@
|
||||
# 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.
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 import settings
|
||||
from pyasic.API.bmminer import BMMinerAPI
|
||||
from pyasic.API.bosminer import BOSMinerAPI
|
||||
from pyasic.API.btminer import BTMinerAPI
|
||||
from pyasic.API.cgminer import CGMinerAPI
|
||||
from pyasic.API.unknown import UnknownAPI
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
|
||||
from pyasic.data import (
|
||||
MinerData,
|
||||
BraiinsOSError,
|
||||
InnosiliconError,
|
||||
MinerData,
|
||||
WhatsminerError,
|
||||
X19Error,
|
||||
)
|
||||
|
||||
from pyasic.errors import APIError, APIWarning
|
||||
|
||||
from pyasic.miners import get_miner
|
||||
from pyasic.miners.base import AnyMiner
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
from pyasic.miners.miner_factory import MinerFactory, miner_factory
|
||||
from pyasic.miners.miner_listener import MinerListener
|
||||
|
||||
from pyasic.network import MinerNetwork
|
||||
|
||||
from pyasic.settings import PyasicSettings
|
||||
|
||||
__all__ = [
|
||||
"BMMinerAPI",
|
||||
"BOSMinerAPI",
|
||||
@@ -55,7 +51,8 @@ __all__ = [
|
||||
"get_miner",
|
||||
"AnyMiner",
|
||||
"MinerFactory",
|
||||
"miner_factory",
|
||||
"MinerListener",
|
||||
"MinerNetwork",
|
||||
"PyasicSettings",
|
||||
"settings",
|
||||
]
|
||||
|
||||
@@ -1,498 +1,162 @@
|
||||
# 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.
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 copy import deepcopy
|
||||
from dataclasses import asdict, dataclass
|
||||
|
||||
from dataclasses import dataclass, asdict
|
||||
from typing import Literal, List
|
||||
import random
|
||||
import string
|
||||
|
||||
import toml
|
||||
import yaml
|
||||
import json
|
||||
import time
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Pool:
|
||||
"""A dataclass for pool information.
|
||||
|
||||
Attributes:
|
||||
url: URL of the pool.
|
||||
username: Username on the pool.
|
||||
password: Worker password on the pool.
|
||||
"""
|
||||
|
||||
url: str = ""
|
||||
username: str = ""
|
||||
password: str = ""
|
||||
|
||||
def from_dict(self, data: dict):
|
||||
"""Convert raw pool data as a dict to usable data and save it to this class.
|
||||
|
||||
Parameters:
|
||||
data: The raw config data to convert.
|
||||
"""
|
||||
for key in data.keys():
|
||||
if key == "url":
|
||||
self.url = data[key]
|
||||
if key in ["user", "username"]:
|
||||
self.username = data[key]
|
||||
if key in ["pass", "password"]:
|
||||
self.password = data[key]
|
||||
return self
|
||||
|
||||
def as_wm(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a dict usable by an Whatsminer 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_x19(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a dict usable by an X19 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:
|
||||
"""Convert the data in this class to a dict usable by an Innosilicon device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
username = self.username
|
||||
if user_suffix:
|
||||
username = f"{username}{user_suffix}"
|
||||
|
||||
pool = {
|
||||
f"Pool": self.url,
|
||||
f"UserName": username,
|
||||
f"Password": self.password,
|
||||
}
|
||||
return pool
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a string usable by an Avalonminer device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
username = self.username
|
||||
if user_suffix:
|
||||
username = f"{username}{user_suffix}"
|
||||
|
||||
pool = ",".join([self.url, username, self.password])
|
||||
return pool
|
||||
|
||||
def as_bos(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a dict usable by an BOSMiner 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, "password": self.password}
|
||||
return pool
|
||||
|
||||
|
||||
@dataclass
|
||||
class _PoolGroup:
|
||||
"""A dataclass for pool group information.
|
||||
|
||||
Attributes:
|
||||
quota: The group quota.
|
||||
group_name: The name of the pool group.
|
||||
pools: A list of pools in this group.
|
||||
"""
|
||||
|
||||
quota: int = 1
|
||||
group_name: str = None
|
||||
pools: List[_Pool] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if not self.group_name:
|
||||
self.group_name = "".join(
|
||||
random.choice(string.ascii_uppercase + string.digits) for _ in range(6)
|
||||
) # generate random pool group name in case it isn't set
|
||||
|
||||
def from_dict(self, data: dict):
|
||||
"""Convert raw pool group data as a dict to usable data and save it to this class.
|
||||
|
||||
Parameters:
|
||||
data: The raw config data to convert.
|
||||
"""
|
||||
pools = []
|
||||
for key in data.keys():
|
||||
if key in ["name", "group_name"]:
|
||||
self.group_name = data[key]
|
||||
if key == "quota":
|
||||
self.quota = data[key]
|
||||
if key in ["pools", "pool"]:
|
||||
for pool in data[key]:
|
||||
pools.append(_Pool().from_dict(pool))
|
||||
self.pools = pools
|
||||
return self
|
||||
|
||||
def as_x19(self, user_suffix: str = None) -> List[dict]:
|
||||
"""Convert the data in this class to a list usable by an X19 device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
pools = []
|
||||
for pool in self.pools[:3]:
|
||||
pools.append(pool.as_x19(user_suffix=user_suffix))
|
||||
return pools
|
||||
|
||||
def as_inno(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a list usable by an Innosilicon device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
pools = {}
|
||||
for idx, pool in enumerate(self.pools[:3]):
|
||||
pool_data = pool.as_inno(user_suffix=user_suffix)
|
||||
for key in pool_data:
|
||||
pools[f"{key}{idx+1}"] = pool_data[key]
|
||||
return pools
|
||||
|
||||
def as_wm(self, user_suffix: str = None) -> List[dict]:
|
||||
"""Convert the data in this class to a list usable by an Whatsminer device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
pools = []
|
||||
for pool in self.pools[:3]:
|
||||
pools.append(pool.as_wm(user_suffix=user_suffix))
|
||||
while len(pools) < 3:
|
||||
pools.append({"url": None, "user": None, "pass": None})
|
||||
return pools
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a dict usable by an Avalonminer device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
pool = self.pools[0].as_avalon(user_suffix=user_suffix)
|
||||
return pool
|
||||
|
||||
def as_bos(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a dict usable by an BOSMiner device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
group = {
|
||||
"name": self.group_name,
|
||||
"quota": self.quota,
|
||||
"pool": [pool.as_bos(user_suffix=user_suffix) for pool in self.pools],
|
||||
}
|
||||
return group
|
||||
from pyasic.config.fans import FanModeConfig
|
||||
from pyasic.config.mining import MiningModeConfig
|
||||
from pyasic.config.pools import PoolConfig
|
||||
from pyasic.config.power_scaling import PowerScalingConfig, PowerScalingShutdown
|
||||
from pyasic.config.temperature import TemperatureConfig
|
||||
|
||||
|
||||
@dataclass
|
||||
class MinerConfig:
|
||||
"""A dataclass for miner configuration information.
|
||||
|
||||
Attributes:
|
||||
pool_groups: A list of pool groups in this config.
|
||||
temp_mode: The temperature control mode.
|
||||
temp_target: The target temp.
|
||||
temp_hot: The hot temp (100% fans).
|
||||
temp_dangerous: The dangerous temp (shutdown).
|
||||
minimum_fans: The minimum numbers of fans to run the miner.
|
||||
fan_speed: Manual fan speed to run the fan at (only if temp_mode == "manual").
|
||||
asicboost: Whether or not to enable asicboost.
|
||||
autotuning_enabled: Whether or not to enable autotuning.
|
||||
autotuning_wattage: The wattage to use when autotuning.
|
||||
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_min_power: The minimum power to reduce autotuning to.
|
||||
dps_shutdown_enabled: Whether or not to shutdown the miner when `dps_min_power` is reached.
|
||||
dps_shutdown_duration: The amount of time to shutdown for (in hours).
|
||||
"""
|
||||
|
||||
pool_groups: List[_PoolGroup] = None
|
||||
|
||||
temp_mode: Literal["auto", "manual", "disabled"] = "auto"
|
||||
temp_target: float = 70.0
|
||||
temp_hot: float = 80.0
|
||||
temp_dangerous: float = 100.0
|
||||
|
||||
minimum_fans: int = None
|
||||
fan_speed: Literal[tuple(range(101))] = None # noqa - Ignore weird Literal usage
|
||||
|
||||
asicboost: bool = None
|
||||
|
||||
autotuning_enabled: bool = True
|
||||
autotuning_wattage: int = 900
|
||||
|
||||
dps_enabled: bool = None
|
||||
dps_power_step: int = None
|
||||
dps_min_power: int = None
|
||||
dps_shutdown_enabled: bool = None
|
||||
dps_shutdown_duration: float = None
|
||||
pools: PoolConfig = PoolConfig.default()
|
||||
fan_mode: FanModeConfig = FanModeConfig.default()
|
||||
temperature: TemperatureConfig = TemperatureConfig.default()
|
||||
mining_mode: MiningModeConfig = MiningModeConfig.default()
|
||||
power_scaling: PowerScalingConfig = PowerScalingConfig.default()
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
"""Convert the data in this class to a dict."""
|
||||
data_dict = asdict(self)
|
||||
for key in asdict(self).keys():
|
||||
if data_dict[key] is None:
|
||||
del data_dict[key]
|
||||
return data_dict
|
||||
return asdict(self)
|
||||
|
||||
def as_toml(self) -> str:
|
||||
"""Convert the data in this class to toml."""
|
||||
return toml.dumps(self.as_dict())
|
||||
def as_am_modern(self, user_suffix: str = None) -> dict:
|
||||
return {
|
||||
**self.fan_mode.as_am_modern(),
|
||||
"freq-level": "100",
|
||||
**self.mining_mode.as_am_modern(),
|
||||
**self.pools.as_am_modern(user_suffix=user_suffix),
|
||||
**self.temperature.as_am_modern(),
|
||||
**self.power_scaling.as_am_modern(),
|
||||
}
|
||||
|
||||
def as_yaml(self) -> str:
|
||||
"""Convert the data in this class to yaml."""
|
||||
return yaml.dump(self.as_dict(), sort_keys=False)
|
||||
def as_wm(self, user_suffix: str = None) -> dict:
|
||||
return {
|
||||
**self.fan_mode.as_wm(),
|
||||
**self.mining_mode.as_wm(),
|
||||
**self.pools.as_wm(user_suffix=user_suffix),
|
||||
**self.temperature.as_wm(),
|
||||
**self.power_scaling.as_wm(),
|
||||
}
|
||||
|
||||
def from_raw(self, data: dict):
|
||||
"""Convert raw config data as a dict to usable data and save it to this class.
|
||||
This should be able to handle any raw config file from any miner supported by pyasic.
|
||||
def as_am_old(self, user_suffix: str = None) -> dict:
|
||||
return {
|
||||
**self.fan_mode.as_am_old(),
|
||||
**self.mining_mode.as_am_old(),
|
||||
**self.pools.as_am_old(user_suffix=user_suffix),
|
||||
**self.temperature.as_am_old(),
|
||||
**self.power_scaling.as_am_old(),
|
||||
}
|
||||
|
||||
Parameters:
|
||||
data: The raw config data to convert.
|
||||
"""
|
||||
pool_groups = []
|
||||
for key in data.keys():
|
||||
if key == "pools":
|
||||
pool_groups.append(_PoolGroup().from_dict({"pools": data[key]}))
|
||||
elif key == "group":
|
||||
for group in data[key]:
|
||||
pool_groups.append(_PoolGroup().from_dict(group))
|
||||
def as_goldshell(self, user_suffix: str = None) -> dict:
|
||||
return {
|
||||
**self.fan_mode.as_goldshell(),
|
||||
**self.mining_mode.as_goldshell(),
|
||||
**self.pools.as_goldshell(user_suffix=user_suffix),
|
||||
**self.temperature.as_goldshell(),
|
||||
**self.power_scaling.as_goldshell(),
|
||||
}
|
||||
|
||||
if key == "bitmain-fan-ctrl":
|
||||
if data[key]:
|
||||
self.temp_mode = "manual"
|
||||
if data.get("bitmain-fan-pwm"):
|
||||
self.fan_speed = int(data["bitmain-fan-pwm"])
|
||||
elif key == "bitmain-work-mode":
|
||||
if data[key]:
|
||||
if data[key] == 1:
|
||||
self.autotuning_wattage = 0
|
||||
elif key == "fan_control":
|
||||
for _key in data[key].keys():
|
||||
if _key == "min_fans":
|
||||
self.minimum_fans = data[key][_key]
|
||||
elif _key == "speed":
|
||||
self.fan_speed = data[key][_key]
|
||||
elif key == "temp_control":
|
||||
for _key in data[key].keys():
|
||||
if _key == "mode":
|
||||
self.temp_mode = data[key][_key]
|
||||
elif _key == "target_temp":
|
||||
self.temp_target = data[key][_key]
|
||||
elif _key == "hot_temp":
|
||||
self.temp_hot = data[key][_key]
|
||||
elif _key == "dangerous_temp":
|
||||
self.temp_dangerous = data[key][_key]
|
||||
|
||||
if key == "hash_chain_global":
|
||||
if data[key].get("asic_boost"):
|
||||
self.asicboost = data[key]["asic_boost"]
|
||||
|
||||
if key == "autotuning":
|
||||
for _key in data[key].keys():
|
||||
if _key == "enabled":
|
||||
self.autotuning_enabled = data[key][_key]
|
||||
elif _key == "psu_power_limit":
|
||||
self.autotuning_wattage = data[key][_key]
|
||||
|
||||
if key == "power_scaling":
|
||||
for _key in data[key].keys():
|
||||
if _key == "enabled":
|
||||
self.dps_enabled = data[key][_key]
|
||||
elif _key == "power_step":
|
||||
self.dps_power_step = data[key][_key]
|
||||
elif _key == "min_psu_power_limit":
|
||||
self.dps_min_power = data[key][_key]
|
||||
elif _key == "shutdown_enabled":
|
||||
self.dps_shutdown_enabled = data[key][_key]
|
||||
elif _key == "shutdown_duration":
|
||||
self.dps_shutdown_duration = data[key][_key]
|
||||
|
||||
self.pool_groups = pool_groups
|
||||
return self
|
||||
|
||||
def from_api(self, pools: list):
|
||||
_pools = []
|
||||
for pool in pools:
|
||||
url = pool.get("URL")
|
||||
user = pool.get("User")
|
||||
_pools.append({"url": url, "user": user, "pass": "123"})
|
||||
self.pool_groups = [_PoolGroup().from_dict({"pools": _pools})]
|
||||
return self
|
||||
|
||||
def from_dict(self, data: dict):
|
||||
"""Convert an output dict of this class back into usable data and save it to this class.
|
||||
|
||||
Parameters:
|
||||
data: The dict config data to convert.
|
||||
"""
|
||||
pool_groups = []
|
||||
for group in data["pool_groups"]:
|
||||
pool_groups.append(_PoolGroup().from_dict(group))
|
||||
for key in data.keys():
|
||||
if getattr(self, key) and not key == "pool_groups":
|
||||
setattr(self, key, data[key])
|
||||
self.pool_groups = pool_groups
|
||||
return self
|
||||
|
||||
def from_toml(self, data: str):
|
||||
"""Convert output toml of this class back into usable data and save it to this class.
|
||||
|
||||
Parameters:
|
||||
data: The toml config data to convert.
|
||||
"""
|
||||
return self.from_dict(toml.loads(data))
|
||||
|
||||
def from_yaml(self, data: str):
|
||||
"""Convert output yaml of this class back into usable data and save it to this class.
|
||||
|
||||
Parameters:
|
||||
data: The yaml config data to convert.
|
||||
"""
|
||||
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
|
||||
|
||||
def as_wm(self, user_suffix: str = None) -> List[dict]:
|
||||
"""Convert the data in this class to a config usable by an Whatsminer device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
return self.pool_groups[0].as_wm(user_suffix=user_suffix)
|
||||
def as_avalon(self, user_suffix: str = None) -> dict:
|
||||
return {
|
||||
**self.fan_mode.as_avalon(),
|
||||
**self.mining_mode.as_avalon(),
|
||||
**self.pools.as_avalon(user_suffix=user_suffix),
|
||||
**self.temperature.as_avalon(),
|
||||
**self.power_scaling.as_avalon(),
|
||||
}
|
||||
|
||||
def as_inno(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a config usable by an Innosilicon device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
return self.pool_groups[0].as_inno(user_suffix=user_suffix)
|
||||
|
||||
def as_x19(self, user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a config usable by an X19 device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
cfg = {
|
||||
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
|
||||
"bitmain-fan-ctrl": False,
|
||||
"bitmain-fan-pwn": 100,
|
||||
"miner-mode": 0, # Normal Mode
|
||||
}
|
||||
if self.autotuning_wattage == 0:
|
||||
cfg["miner-mode"] = 1 # Sleep Mode
|
||||
|
||||
if not self.temp_mode == "auto":
|
||||
cfg["bitmain-fan-ctrl"] = True
|
||||
|
||||
if self.fan_speed:
|
||||
cfg["bitmain-fan-ctrl"] = str(self.fan_speed)
|
||||
|
||||
return json.dumps(cfg)
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a config usable by an Avalonminer device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
cfg = self.pool_groups[0].as_avalon(user_suffix=user_suffix)
|
||||
return cfg
|
||||
|
||||
def as_bos(self, model: str = "S9", user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a config usable by an BOSMiner device.
|
||||
|
||||
Parameters:
|
||||
model: The model of the miner to be used in the format portion of the config.
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
cfg = {
|
||||
"format": {
|
||||
"version": "1.2+",
|
||||
"model": f"Antminer {model}",
|
||||
"generator": "Upstream Config Utility",
|
||||
"timestamp": int(time.time()),
|
||||
},
|
||||
"group": [
|
||||
group.as_bos(user_suffix=user_suffix) for group in self.pool_groups
|
||||
],
|
||||
"temp_control": {
|
||||
"mode": self.temp_mode,
|
||||
"target_temp": self.temp_target,
|
||||
"hot_temp": self.temp_hot,
|
||||
"dangerous_temp": self.temp_dangerous,
|
||||
},
|
||||
return {
|
||||
**self.fan_mode.as_inno(),
|
||||
**self.mining_mode.as_inno(),
|
||||
**self.pools.as_inno(user_suffix=user_suffix),
|
||||
**self.temperature.as_inno(),
|
||||
**self.power_scaling.as_inno(),
|
||||
}
|
||||
|
||||
if self.autotuning_enabled or self.autotuning_wattage:
|
||||
cfg["autotuning"] = {}
|
||||
if self.autotuning_enabled:
|
||||
cfg["autotuning"]["enabled"] = self.autotuning_enabled
|
||||
if self.autotuning_wattage:
|
||||
cfg["autotuning"]["psu_power_limit"] = self.autotuning_wattage
|
||||
def as_bosminer(self, user_suffix: str = None) -> dict:
|
||||
return {
|
||||
**merge(self.fan_mode.as_bosminer(), self.temperature.as_bosminer()),
|
||||
**self.mining_mode.as_bosminer(),
|
||||
**self.pools.as_bosminer(user_suffix=user_suffix),
|
||||
**self.power_scaling.as_bosminer(),
|
||||
}
|
||||
|
||||
if self.asicboost:
|
||||
cfg["hash_chain_global"] = {}
|
||||
cfg["hash_chain_global"]["asic_boost"] = self.asicboost
|
||||
def as_bos_grpc(self, user_suffix: str = None) -> dict:
|
||||
return {
|
||||
**self.fan_mode.as_bos_grpc(),
|
||||
**self.temperature.as_bos_grpc(),
|
||||
**self.mining_mode.as_bos_grpc(),
|
||||
**self.pools.as_bos_grpc(user_suffix=user_suffix),
|
||||
**self.power_scaling.as_bos_grpc(),
|
||||
}
|
||||
|
||||
if any(
|
||||
[
|
||||
getattr(self, item)
|
||||
for item in [
|
||||
"dps_enabled",
|
||||
"dps_power_step",
|
||||
"dps_min_power",
|
||||
"dps_shutdown_enabled",
|
||||
"dps_shutdown_duration",
|
||||
]
|
||||
]
|
||||
):
|
||||
cfg["power_scaling"] = {}
|
||||
if self.dps_enabled:
|
||||
cfg["power_scaling"]["enabled"] = self.dps_enabled
|
||||
if self.dps_power_step:
|
||||
cfg["power_scaling"]["power_step"] = self.dps_power_step
|
||||
if self.dps_min_power:
|
||||
cfg["power_scaling"]["min_psu_power_limit"] = self.dps_min_power
|
||||
if self.dps_shutdown_enabled:
|
||||
cfg["power_scaling"]["shutdown_enabled"] = self.dps_shutdown_enabled
|
||||
if self.dps_shutdown_duration:
|
||||
cfg["power_scaling"]["shutdown_duration"] = self.dps_shutdown_duration
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict) -> "MinerConfig":
|
||||
return cls(
|
||||
pools=PoolConfig.from_dict(dict_conf.get("pools")),
|
||||
mining_mode=MiningModeConfig.from_dict(dict_conf.get("mining_mode")),
|
||||
fan_mode=FanModeConfig.from_dict(dict_conf.get("fan_mode")),
|
||||
temperature=TemperatureConfig.from_dict(dict_conf.get("temperature")),
|
||||
power_scaling=PowerScalingConfig.from_dict(dict_conf.get("power_scaling")),
|
||||
)
|
||||
|
||||
return toml.dumps(cfg)
|
||||
@classmethod
|
||||
def from_api(cls, api_pools: dict) -> "MinerConfig":
|
||||
return cls(pools=PoolConfig.from_api(api_pools))
|
||||
|
||||
@classmethod
|
||||
def from_am_modern(cls, web_conf: dict) -> "MinerConfig":
|
||||
return cls(
|
||||
pools=PoolConfig.from_am_modern(web_conf),
|
||||
mining_mode=MiningModeConfig.from_am_modern(web_conf),
|
||||
fan_mode=FanModeConfig.from_am_modern(web_conf),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_am_old(cls, web_conf: dict) -> "MinerConfig":
|
||||
return cls.from_am_modern(web_conf)
|
||||
|
||||
@classmethod
|
||||
def from_goldshell(cls, web_conf: dict) -> "MinerConfig":
|
||||
return cls(pools=PoolConfig.from_am_modern(web_conf))
|
||||
|
||||
@classmethod
|
||||
def from_inno(cls, web_pools: list) -> "MinerConfig":
|
||||
return cls(pools=PoolConfig.from_inno(web_pools))
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_conf: dict) -> "MinerConfig":
|
||||
return cls(
|
||||
pools=PoolConfig.from_bosminer(toml_conf),
|
||||
mining_mode=MiningModeConfig.from_bosminer(toml_conf),
|
||||
fan_mode=FanModeConfig.from_bosminer(toml_conf),
|
||||
temperature=TemperatureConfig.from_bosminer(toml_conf),
|
||||
power_scaling=PowerScalingConfig.from_bosminer(toml_conf),
|
||||
)
|
||||
|
||||
|
||||
def merge(a: dict, b: dict) -> dict:
|
||||
result = deepcopy(a)
|
||||
for b_key, b_val in b.items():
|
||||
a_val = result.get(b_key)
|
||||
if isinstance(a_val, dict) and isinstance(b_val, dict):
|
||||
result[b_key] = merge(a_val, b_val)
|
||||
else:
|
||||
result[b_key] = deepcopy(b_val)
|
||||
return result
|
||||
|
||||
89
pyasic/config/base.py
Normal file
89
pyasic/config/base.py
Normal file
@@ -0,0 +1,89 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 dataclasses import asdict, dataclass
|
||||
from enum import Enum
|
||||
from typing import Union
|
||||
|
||||
|
||||
class MinerConfigOption(Enum):
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
||||
return cls.default()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return self.value.as_am_modern()
|
||||
|
||||
def as_am_old(self) -> dict:
|
||||
return self.value.as_am_old()
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return self.value.as_wm()
|
||||
|
||||
def as_inno(self) -> dict:
|
||||
return self.value.as_inno()
|
||||
|
||||
def as_goldshell(self) -> dict:
|
||||
return self.value.as_goldshell()
|
||||
|
||||
def as_avalon(self) -> dict:
|
||||
return self.value.as_avalon()
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return self.value.as_bosminer()
|
||||
|
||||
def as_bos_grpc(self) -> dict:
|
||||
return self.value.as_bos_grpc()
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.value(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class MinerConfigValue:
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
||||
return cls()
|
||||
|
||||
def as_dict(self):
|
||||
return asdict(self)
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_am_old(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_inno(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_goldshell(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_avalon(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_bos_grpc(self) -> dict:
|
||||
return {}
|
||||
134
pyasic/config/fans.py
Normal file
134
pyasic/config/fans.py
Normal file
@@ -0,0 +1,134 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 dataclasses import dataclass, field
|
||||
from typing import Union
|
||||
|
||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||
|
||||
|
||||
@dataclass
|
||||
class FanModeNormal(MinerConfigValue):
|
||||
mode: str = field(init=False, default="normal")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeNormal":
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": False, "bitmain-fan-pwn": "100"}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {"temp_control": {"mode": "auto"}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class FanModeManual(MinerConfigValue):
|
||||
mode: str = field(init=False, default="manual")
|
||||
minimum_fans: int = 1
|
||||
speed: int = 100
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeManual":
|
||||
cls_conf = {}
|
||||
if dict_conf.get("min_fans") is not None:
|
||||
cls_conf["minimum_fans"] = dict_conf["minimum_fans"]
|
||||
if dict_conf.get("speed") is not None:
|
||||
cls_conf["speed"] = dict_conf["speed"]
|
||||
return cls(**cls_conf)
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_fan_conf: dict) -> "FanModeManual":
|
||||
cls_conf = {}
|
||||
if toml_fan_conf.get("min_fans") is not None:
|
||||
cls_conf["minimum_fans"] = toml_fan_conf["min_fans"]
|
||||
if toml_fan_conf.get("speed") is not None:
|
||||
cls_conf["speed"] = toml_fan_conf["speed"]
|
||||
return cls(**cls_conf)
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwn": str(self.speed)}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {
|
||||
"temp_control": {"mode": "manual"},
|
||||
"fan_control": {"min_fans": self.minimum_fans, "speed": self.speed},
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class FanModeImmersion(MinerConfigValue):
|
||||
mode: str = field(init=False, default="immersion")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeImmersion":
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwn": "0"}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {"temp_control": {"mode": "disabled"}}
|
||||
|
||||
|
||||
class FanModeConfig(MinerConfigOption):
|
||||
normal = FanModeNormal
|
||||
manual = FanModeManual
|
||||
immersion = FanModeImmersion
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
return cls.normal()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
||||
if dict_conf is None:
|
||||
return cls.default()
|
||||
|
||||
mode = dict_conf.get("mode")
|
||||
if mode is None:
|
||||
return cls.default()
|
||||
|
||||
clsattr = getattr(cls, mode)
|
||||
if clsattr is not None:
|
||||
return clsattr().from_dict(dict_conf)
|
||||
|
||||
@classmethod
|
||||
def from_am_modern(cls, web_conf: dict):
|
||||
if web_conf.get("bitmain-fan-ctrl") is not None:
|
||||
fan_manual = web_conf["bitmain-fan-ctrl"]
|
||||
if fan_manual:
|
||||
return cls.manual(speed=web_conf["bitmain-fan-pwm"])
|
||||
else:
|
||||
return cls.normal()
|
||||
else:
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_conf: dict):
|
||||
if toml_conf.get("temp_control") is None:
|
||||
return cls.default()
|
||||
if toml_conf["temp_control"].get("mode") is None:
|
||||
return cls.default()
|
||||
|
||||
mode = toml_conf["temp_control"]["mode"]
|
||||
if mode == "auto":
|
||||
return cls.normal()
|
||||
elif mode == "manual":
|
||||
if toml_conf.get("fan_control"):
|
||||
return cls.manual().from_bosminer(toml_conf["fan_control"])
|
||||
return cls.manual()
|
||||
elif mode == "disabled":
|
||||
return cls.immersion()
|
||||
213
pyasic/config/mining.py
Normal file
213
pyasic/config/mining.py
Normal file
@@ -0,0 +1,213 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 dataclasses import dataclass, field
|
||||
from typing import Union
|
||||
|
||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeNormal(MinerConfigValue):
|
||||
mode: str = field(init=False, default="normal")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeNormal":
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"miner-mode": "0"}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeSleep(MinerConfigValue):
|
||||
mode: str = field(init=False, default="sleep")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeSleep":
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"miner-mode": "1"}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeLPM(MinerConfigValue):
|
||||
mode: str = field(init=False, default="low")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeLPM":
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"miner-mode": "3"}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeHPM(MinerConfigValue):
|
||||
mode: str = field(init=False, default="high")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeHPM":
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self):
|
||||
return {"miner-mode": "0"}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModePowerTune(MinerConfigValue):
|
||||
mode: str = field(init=False, default="power_tuning")
|
||||
power: int = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModePowerTune":
|
||||
return cls(dict_conf.get("power"))
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"miner-mode": "0"}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
if self.power is not None:
|
||||
return {"mode": self.mode, self.mode: {"wattage": self.power}}
|
||||
return {}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {"autotuning": {"enabled": True, "psu_power_limit": self.power}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeHashrateTune(MinerConfigValue):
|
||||
mode: str = field(init=False, default="hashrate_tuning")
|
||||
hashrate: int = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeHashrateTune":
|
||||
return cls(dict_conf.get("hashrate"))
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"miner-mode": "0"}
|
||||
|
||||
|
||||
@dataclass
|
||||
class ManualBoardSettings(MinerConfigValue):
|
||||
freq: float
|
||||
volt: float
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "ManualBoardSettings":
|
||||
return cls(freq=dict_conf["freq"], volt=dict_conf["volt"])
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"miner-mode": "0"}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeManual(MinerConfigValue):
|
||||
mode: str = field(init=False, default="manual")
|
||||
|
||||
global_freq: float
|
||||
global_volt: float
|
||||
boards: dict[int, ManualBoardSettings] = field(default_factory=dict)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeManual":
|
||||
return cls(
|
||||
global_freq=dict_conf["global_freq"],
|
||||
global_volt=dict_conf["global_volt"],
|
||||
boards={i: ManualBoardSettings.from_dict(dict_conf[i]) for i in dict_conf},
|
||||
)
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"miner-mode": "0"}
|
||||
|
||||
|
||||
class MiningModeConfig(MinerConfigOption):
|
||||
normal = MiningModeNormal
|
||||
low = MiningModeLPM
|
||||
high = MiningModeHPM
|
||||
sleep = MiningModeSleep
|
||||
power_tuning = MiningModePowerTune
|
||||
hashrate_tuning = MiningModeHashrateTune
|
||||
manual = MiningModeManual
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
return cls.normal()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
||||
if dict_conf is None:
|
||||
return cls.default()
|
||||
|
||||
mode = dict_conf.get("mode")
|
||||
if mode is None:
|
||||
return cls.default()
|
||||
|
||||
clsattr = getattr(cls, mode)
|
||||
if clsattr is not None:
|
||||
return clsattr().from_dict(dict_conf)
|
||||
|
||||
@classmethod
|
||||
def from_am_modern(cls, web_conf: dict):
|
||||
if web_conf.get("bitmain-work-mode") is not None:
|
||||
work_mode = web_conf["bitmain-work-mode"]
|
||||
if work_mode == "":
|
||||
return cls.default()
|
||||
if int(work_mode) == 0:
|
||||
return cls.normal()
|
||||
elif int(work_mode) == 1:
|
||||
return cls.sleep()
|
||||
elif int(work_mode) == 3:
|
||||
return cls.low()
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_conf: dict):
|
||||
if toml_conf.get("autotuning") is None:
|
||||
return cls.default()
|
||||
autotuning_conf = toml_conf["autotuning"]
|
||||
|
||||
if autotuning_conf.get("enabled") is None:
|
||||
return cls.default()
|
||||
if not autotuning_conf["enabled"]:
|
||||
return cls.default()
|
||||
|
||||
if autotuning_conf.get("psu_power_limit") is not None:
|
||||
# old autotuning conf
|
||||
return cls.power_tuning(autotuning_conf["psu_power_limit"])
|
||||
if autotuning_conf.get("mode") is not None:
|
||||
# new autotuning conf
|
||||
mode = autotuning_conf["mode"]
|
||||
if mode == "power_target":
|
||||
if autotuning_conf.get("power_target") is not None:
|
||||
return cls.power_tuning(autotuning_conf["power_target"])
|
||||
return cls.power_tuning()
|
||||
if mode == "hashrate_target":
|
||||
if autotuning_conf.get("hashrate_target") is not None:
|
||||
return cls.hashrate_tuning(autotuning_conf["hashrate_target"])
|
||||
return cls.hashrate_tuning()
|
||||
356
pyasic/config/pools.py
Normal file
356
pyasic/config/pools.py
Normal file
@@ -0,0 +1,356 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 random
|
||||
import string
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Union
|
||||
|
||||
from pyasic.config.base import MinerConfigValue
|
||||
|
||||
|
||||
@dataclass
|
||||
class Pool(MinerConfigValue):
|
||||
url: str
|
||||
user: str
|
||||
password: str
|
||||
|
||||
def as_am_modern(self, user_suffix: str = None):
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
"url": self.url,
|
||||
"user": f"{self.user}{user_suffix}",
|
||||
"pass": self.password,
|
||||
}
|
||||
return {"url": self.url, "user": self.user, "pass": self.password}
|
||||
|
||||
def as_wm(self, idx: int, user_suffix: str = None):
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
f"pool_{idx}": self.url,
|
||||
f"worker_{idx}": f"{self.user}{user_suffix}",
|
||||
f"passwd_{idx}": self.password,
|
||||
}
|
||||
return {
|
||||
f"pool_{idx}": self.url,
|
||||
f"worker_{idx}": self.user,
|
||||
f"passwd_{idx}": self.password,
|
||||
}
|
||||
|
||||
def as_am_old(self, idx: int, user_suffix: str = None):
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
f"_ant_pool{idx}url": self.url,
|
||||
f"_ant_pool{idx}user": f"{self.user}{user_suffix}",
|
||||
f"_ant_pool{idx}pw": self.password,
|
||||
}
|
||||
return {
|
||||
f"_ant_pool{idx}url": self.url,
|
||||
f"_ant_pool{idx}user": self.user,
|
||||
f"_ant_pool{idx}pw": self.password,
|
||||
}
|
||||
|
||||
def as_goldshell(self, user_suffix: str = None):
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
"url": self.url,
|
||||
"user": f"{self.user}{user_suffix}",
|
||||
"pass": self.password,
|
||||
}
|
||||
return {"url": self.url, "user": self.user, "pass": self.password}
|
||||
|
||||
def as_avalon(self, user_suffix: str = None):
|
||||
if user_suffix is not None:
|
||||
return ",".join([self.url, f"{self.user}{user_suffix}", self.password])
|
||||
return ",".join([self.url, self.user, self.password])
|
||||
|
||||
def as_inno(self, idx: int, user_suffix: str = None):
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
f"Pool{idx}": self.url,
|
||||
f"UserName{idx}": f"{self.user}{user_suffix}",
|
||||
f"Password{idx}": self.password,
|
||||
}
|
||||
return {
|
||||
f"Pool{idx}": self.url,
|
||||
f"UserName{idx}": self.user,
|
||||
f"Password{idx}": self.password,
|
||||
}
|
||||
|
||||
def as_bosminer(self, user_suffix: str = None):
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
"url": self.url,
|
||||
"user": f"{self.user}{user_suffix}",
|
||||
"password": self.password,
|
||||
}
|
||||
return {"url": self.url, "user": self.user, "password": self.password}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "Pool":
|
||||
return cls(
|
||||
url=dict_conf["url"], user=dict_conf["user"], password=dict_conf["password"]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_api(cls, api_pool: dict) -> "Pool":
|
||||
return cls(url=api_pool["URL"], user=api_pool["User"], password="x")
|
||||
|
||||
@classmethod
|
||||
def from_am_modern(cls, web_pool: dict) -> "Pool":
|
||||
return cls(
|
||||
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
||||
)
|
||||
|
||||
# TODO: check if this is accurate, user/username, pass/password
|
||||
@classmethod
|
||||
def from_goldshell(cls, web_pool: dict) -> "Pool":
|
||||
return cls(
|
||||
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_inno(cls, web_pool: dict) -> "Pool":
|
||||
return cls(
|
||||
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_pool_conf: dict) -> "Pool":
|
||||
return cls(
|
||||
url=toml_pool_conf["url"],
|
||||
user=toml_pool_conf["user"],
|
||||
password=toml_pool_conf["password"],
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PoolGroup(MinerConfigValue):
|
||||
pools: list[Pool] = field(default_factory=list)
|
||||
quota: int = 1
|
||||
name: str = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.name is None:
|
||||
self.name = "".join(
|
||||
random.choice(string.ascii_uppercase + string.digits) for _ in range(6)
|
||||
) # generate random pool group name in case it isn't set
|
||||
|
||||
def as_am_modern(self, user_suffix: str = None) -> list:
|
||||
pools = []
|
||||
idx = 0
|
||||
while idx < 3:
|
||||
if len(self.pools) > idx:
|
||||
pools.append(self.pools[idx].as_am_modern(user_suffix=user_suffix))
|
||||
else:
|
||||
pools.append(Pool("", "", "").as_am_modern())
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
def as_wm(self, user_suffix: str = None) -> dict:
|
||||
pools = {}
|
||||
idx = 0
|
||||
while idx < 3:
|
||||
if len(self.pools) > idx:
|
||||
pools.update(
|
||||
**self.pools[idx].as_wm(idx=idx + 1, user_suffix=user_suffix)
|
||||
)
|
||||
else:
|
||||
pools.update(**Pool("", "", "").as_wm(idx=idx + 1))
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
def as_am_old(self, user_suffix: str = None) -> dict:
|
||||
pools = {}
|
||||
idx = 0
|
||||
while idx < 3:
|
||||
if len(self.pools) > idx:
|
||||
pools.update(
|
||||
**self.pools[idx].as_am_old(idx=idx + 1, user_suffix=user_suffix)
|
||||
)
|
||||
else:
|
||||
pools.update(**Pool("", "", "").as_am_old(idx=idx + 1))
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
def as_goldshell(self, user_suffix: str = None) -> list:
|
||||
return [pool.as_goldshell(user_suffix) for pool in self.pools]
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> dict:
|
||||
if len(self.pools) > 0:
|
||||
return self.pools[0].as_avalon(user_suffix=user_suffix)
|
||||
return Pool("", "", "").as_avalon()
|
||||
|
||||
def as_inno(self, user_suffix: str = None) -> dict:
|
||||
pools = {}
|
||||
idx = 0
|
||||
while idx < 3:
|
||||
if len(self.pools) > idx:
|
||||
pools.update(
|
||||
**self.pools[idx].as_inno(idx=idx + 1, user_suffix=user_suffix)
|
||||
)
|
||||
else:
|
||||
pools.update(**Pool("", "", "").as_inno(idx=idx + 1))
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
def as_bosminer(self, user_suffix: str = None) -> dict:
|
||||
if len(self.pools) > 0:
|
||||
conf = {
|
||||
"name": self.name,
|
||||
"pool": [
|
||||
pool.as_bosminer(user_suffix=user_suffix) for pool in self.pools
|
||||
],
|
||||
}
|
||||
if self.quota is not None:
|
||||
conf["quota"] = self.quota
|
||||
return conf
|
||||
return {"name": "Group", "pool": []}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "PoolGroup":
|
||||
cls_conf = {}
|
||||
|
||||
if dict_conf.get("quota") is not None:
|
||||
cls_conf["quota"] = dict_conf["quota"]
|
||||
if dict_conf.get("name") is not None:
|
||||
cls_conf["name"] = dict_conf["name"]
|
||||
cls_conf["pools"] = [Pool.from_dict(p) for p in dict_conf["pools"]]
|
||||
return cls(**cls_conf)
|
||||
|
||||
@classmethod
|
||||
def from_api(cls, api_pool_list: list) -> "PoolGroup":
|
||||
pools = []
|
||||
for pool in api_pool_list:
|
||||
pools.append(Pool.from_api(pool))
|
||||
return cls(pools=pools)
|
||||
|
||||
@classmethod
|
||||
def from_am_modern(cls, web_pool_list: list) -> "PoolGroup":
|
||||
pools = []
|
||||
for pool in web_pool_list:
|
||||
pools.append(Pool.from_am_modern(pool))
|
||||
return cls(pools=pools)
|
||||
|
||||
@classmethod
|
||||
def from_goldshell(cls, web_pools: list) -> "PoolGroup":
|
||||
return cls([Pool.from_goldshell(p) for p in web_pools])
|
||||
|
||||
@classmethod
|
||||
def from_inno(cls, web_pools: list) -> "PoolGroup":
|
||||
return cls([Pool.from_inno(p) for p in web_pools])
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_group_conf: dict) -> "PoolGroup":
|
||||
if toml_group_conf.get("pool") is not None:
|
||||
return cls(
|
||||
name=toml_group_conf["name"],
|
||||
quota=toml_group_conf.get("quota"),
|
||||
pools=[Pool.from_bosminer(p) for p in toml_group_conf["pool"]],
|
||||
)
|
||||
return cls()
|
||||
|
||||
|
||||
@dataclass
|
||||
class PoolConfig(MinerConfigValue):
|
||||
groups: list[PoolGroup] = field(default_factory=list)
|
||||
|
||||
@classmethod
|
||||
def default(cls) -> "PoolConfig":
|
||||
return cls()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "PoolConfig":
|
||||
if dict_conf is None:
|
||||
return cls.default()
|
||||
|
||||
return cls(groups=[PoolGroup.from_dict(g) for g in dict_conf["groups"]])
|
||||
|
||||
@classmethod
|
||||
def simple(cls, pools: list[Union[Pool, dict[str, str]]]) -> "PoolConfig":
|
||||
group_pools = []
|
||||
for pool in pools:
|
||||
if isinstance(pool, dict):
|
||||
pool = Pool(**pool)
|
||||
group_pools.append(pool)
|
||||
return cls(groups=[PoolGroup(pools=group_pools)])
|
||||
|
||||
def as_am_modern(self, user_suffix: str = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {"pools": self.groups[0].as_am_modern(user_suffix=user_suffix)}
|
||||
return {"pools": PoolGroup().as_am_modern()}
|
||||
|
||||
def as_wm(self, user_suffix: str = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {"pools": self.groups[0].as_wm(user_suffix=user_suffix)}
|
||||
return {"pools": PoolGroup().as_wm()}
|
||||
|
||||
def as_am_old(self, user_suffix: str = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return self.groups[0].as_am_old(user_suffix=user_suffix)
|
||||
return PoolGroup().as_am_old()
|
||||
|
||||
def as_goldshell(self, user_suffix: str = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {"pools": self.groups[0].as_goldshell(user_suffix=user_suffix)}
|
||||
return {"pools": PoolGroup().as_goldshell()}
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {"pools": self.groups[0].as_avalon(user_suffix=user_suffix)}
|
||||
return {"pools": PoolGroup().as_avalon()}
|
||||
|
||||
def as_inno(self, user_suffix: str = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return self.groups[0].as_inno(user_suffix=user_suffix)
|
||||
return PoolGroup().as_inno()
|
||||
|
||||
def as_bosminer(self, user_suffix: str = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {
|
||||
"group": [g.as_bosminer(user_suffix=user_suffix) for g in self.groups]
|
||||
}
|
||||
return {"group": [PoolGroup().as_bosminer()]}
|
||||
|
||||
def as_bos_grpc(self, user_suffix: str = None) -> dict:
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def from_api(cls, api_pools: dict) -> "PoolConfig":
|
||||
pool_data = api_pools["POOLS"]
|
||||
pool_data = sorted(pool_data, key=lambda x: int(x["POOL"]))
|
||||
|
||||
return cls([PoolGroup.from_api(pool_data)])
|
||||
|
||||
@classmethod
|
||||
def from_am_modern(cls, web_conf: dict) -> "PoolConfig":
|
||||
pool_data = web_conf["pools"]
|
||||
|
||||
return cls([PoolGroup.from_am_modern(pool_data)])
|
||||
|
||||
@classmethod
|
||||
def from_goldshell(cls, web_pools: list) -> "PoolConfig":
|
||||
return cls([PoolGroup.from_goldshell(web_pools)])
|
||||
|
||||
@classmethod
|
||||
def from_inno(cls, web_pools: list) -> "PoolConfig":
|
||||
return cls([PoolGroup.from_inno(web_pools)])
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_conf: dict) -> "PoolConfig":
|
||||
if toml_conf.get("group") is None:
|
||||
return cls()
|
||||
|
||||
return cls([PoolGroup.from_bosminer(g) for g in toml_conf["group"]])
|
||||
189
pyasic/config/power_scaling.py
Normal file
189
pyasic/config/power_scaling.py
Normal file
@@ -0,0 +1,189 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 dataclasses import dataclass, field
|
||||
from typing import Union
|
||||
|
||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||
from pyasic.web.bosminer.proto.braiins.bos.v1 import DpsPowerTarget, DpsTarget, Hours
|
||||
|
||||
|
||||
@dataclass
|
||||
class PowerScalingShutdownEnabled(MinerConfigValue):
|
||||
mode: str = field(init=False, default="enabled")
|
||||
duration: int = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "PowerScalingShutdownEnabled":
|
||||
return cls(duration=dict_conf.get("duration"))
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
cfg = {"shutdown_enabled": True}
|
||||
|
||||
if self.duration is not None:
|
||||
cfg["shutdown_duration"] = self.duration
|
||||
|
||||
return cfg
|
||||
|
||||
def as_bos_grpc(self) -> dict:
|
||||
cfg = {"enable_shutdown ": True}
|
||||
|
||||
if self.duration is not None:
|
||||
cfg["shutdown_duration"] = Hours(self.duration)
|
||||
|
||||
return cfg
|
||||
|
||||
|
||||
@dataclass
|
||||
class PowerScalingShutdownDisabled(MinerConfigValue):
|
||||
mode: str = field(init=False, default="disabled")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "PowerScalingShutdownDisabled":
|
||||
return cls()
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {"shutdown_enabled": False}
|
||||
|
||||
def as_bos_grpc(self) -> dict:
|
||||
return {"enable_shutdown ": False}
|
||||
|
||||
|
||||
class PowerScalingShutdown(MinerConfigOption):
|
||||
enabled = PowerScalingShutdownEnabled
|
||||
disabled = PowerScalingShutdownDisabled
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
||||
if dict_conf is None:
|
||||
return cls.default()
|
||||
|
||||
mode = dict_conf.get("mode")
|
||||
if mode is None:
|
||||
return cls.default()
|
||||
|
||||
clsattr = getattr(cls, mode)
|
||||
if clsattr is not None:
|
||||
return clsattr().from_dict(dict_conf)
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, power_scaling_conf: dict):
|
||||
sd_enabled = power_scaling_conf.get("shutdown_enabled")
|
||||
if sd_enabled is not None:
|
||||
if sd_enabled:
|
||||
return cls.enabled(power_scaling_conf.get("shutdown_duration"))
|
||||
else:
|
||||
return cls.disabled()
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
class PowerScalingEnabled(MinerConfigValue):
|
||||
mode: str = field(init=False, default="enabled")
|
||||
power_step: int = None
|
||||
minimum_power: int = None
|
||||
shutdown_enabled: Union[
|
||||
PowerScalingShutdownEnabled, PowerScalingShutdownDisabled
|
||||
] = None
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, power_scaling_conf: dict) -> "PowerScalingEnabled":
|
||||
power_step = power_scaling_conf.get("power_step")
|
||||
min_power = power_scaling_conf.get("min_psu_power_limit")
|
||||
sd_mode = PowerScalingShutdown.from_bosminer(power_scaling_conf)
|
||||
|
||||
return cls(
|
||||
power_step=power_step, minimum_power=min_power, shutdown_enabled=sd_mode
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "PowerScalingEnabled":
|
||||
cls_conf = {
|
||||
"power_step": dict_conf.get("power_step"),
|
||||
"minimum_power": dict_conf.get("minimum_power"),
|
||||
}
|
||||
shutdown_enabled = dict_conf.get("shutdown_enabled")
|
||||
if shutdown_enabled is not None:
|
||||
cls_conf["shutdown_enabled"] = PowerScalingShutdown.from_dict(
|
||||
shutdown_enabled
|
||||
)
|
||||
return cls(**cls_conf)
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
cfg = {"enabled": True}
|
||||
if self.power_step is not None:
|
||||
cfg["power_step"] = self.power_step
|
||||
if self.minimum_power is not None:
|
||||
cfg["min_psu_power_limit"] = self.minimum_power
|
||||
|
||||
if self.shutdown_enabled is not None:
|
||||
cfg = {**cfg, **self.shutdown_enabled.as_bosminer()}
|
||||
|
||||
return {"power_scaling": cfg}
|
||||
|
||||
def as_bos_grpc(self) -> dict:
|
||||
cfg = {"enable": True}
|
||||
target_conf = {}
|
||||
if self.power_step is not None:
|
||||
target_conf["power_step"] = self.power_step
|
||||
if self.minimum_power is not None:
|
||||
target_conf["min_power_target"] = self.minimum_power
|
||||
|
||||
cfg["target"] = DpsTarget(power_target=DpsPowerTarget(**target_conf))
|
||||
|
||||
if self.shutdown_enabled is not None:
|
||||
cfg = {**cfg, **self.shutdown_enabled.as_bos_grpc()}
|
||||
|
||||
return {"dps": cfg}
|
||||
|
||||
|
||||
@dataclass
|
||||
class PowerScalingDisabled(MinerConfigValue):
|
||||
mode: str = field(init=False, default="disabled")
|
||||
|
||||
|
||||
class PowerScalingConfig(MinerConfigOption):
|
||||
enabled = PowerScalingEnabled
|
||||
disabled = PowerScalingDisabled
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
return cls.disabled()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
||||
if dict_conf is None:
|
||||
return cls.default()
|
||||
|
||||
mode = dict_conf.get("mode")
|
||||
if mode is None:
|
||||
return cls.default()
|
||||
|
||||
clsattr = getattr(cls, mode)
|
||||
if clsattr is not None:
|
||||
return clsattr().from_dict(dict_conf)
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_conf: dict):
|
||||
power_scaling = toml_conf.get("power_scaling")
|
||||
if power_scaling is not None:
|
||||
enabled = power_scaling.get("enabled")
|
||||
if enabled is not None:
|
||||
if enabled:
|
||||
return cls.enabled().from_bosminer(power_scaling)
|
||||
else:
|
||||
return cls.disabled()
|
||||
|
||||
return cls.default()
|
||||
58
pyasic/config/temperature.py
Normal file
58
pyasic/config/temperature.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 dataclasses import dataclass
|
||||
from typing import Union
|
||||
|
||||
from pyasic.config.base import MinerConfigValue
|
||||
|
||||
|
||||
@dataclass
|
||||
class TemperatureConfig(MinerConfigValue):
|
||||
target: int = None
|
||||
hot: int = None
|
||||
danger: int = None
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
return cls()
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
temp_cfg = {}
|
||||
if self.target is not None:
|
||||
temp_cfg["target_temp"] = self.target
|
||||
if self.hot is not None:
|
||||
temp_cfg["hot_temp"] = self.hot
|
||||
if self.danger is not None:
|
||||
temp_cfg["dangerous_temp"] = self.danger
|
||||
return {"temp_control": temp_cfg}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "TemperatureConfig":
|
||||
return cls(
|
||||
target=dict_conf.get("target"),
|
||||
hot=dict_conf.get("hot"),
|
||||
danger=dict_conf.get("danger"),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_conf: dict) -> "TemperatureConfig":
|
||||
temp_control = toml_conf.get("temp_control")
|
||||
if temp_control is not None:
|
||||
return cls(
|
||||
target=temp_control.get("target_temp"),
|
||||
hot=temp_control.get("hot_temp"),
|
||||
danger=temp_control.get("dangerous_temp"),
|
||||
)
|
||||
@@ -1,25 +1,92 @@
|
||||
# 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.
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 Union, List
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from datetime import datetime, timezone
|
||||
import time
|
||||
import json
|
||||
import copy
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, List, Union
|
||||
|
||||
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
|
||||
@@ -29,33 +96,28 @@ class MinerData:
|
||||
Attributes:
|
||||
ip: The IP of the miner as a str.
|
||||
datetime: The time and date this data was generated.
|
||||
uptime: The uptime of the miner in seconds.
|
||||
mac: The MAC address of the miner as a str.
|
||||
model: The model of the miner as a str.
|
||||
make: The make of the miner as a str.
|
||||
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.
|
||||
hashrate: The hashrate of the miner in TH/s as a float.
|
||||
left_board_hashrate: The hashrate of the left board of the miner in TH/s as a float.
|
||||
center_board_hashrate: The hashrate of the center board of the miner in TH/s as a float.
|
||||
right_board_hashrate: The hashrate of the right board of the miner in TH/s as a float.
|
||||
hashrate: The hashrate of the miner in TH/s as a float. Calculated automatically.
|
||||
_hashrate: Backup for hashrate found via API instead of hashboards.
|
||||
nominal_hashrate: The factory nominal hashrate of the miner in TH/s as a float.
|
||||
hashboards: A list of hashboards on the miner with their statistics.
|
||||
temperature_avg: The average temperature across the boards. Calculated automatically.
|
||||
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_limit: Power limit of the miner as an int.
|
||||
fan_1: The speed of the first fan as an int.
|
||||
fan_2: The speed of the second fan as an int.
|
||||
fan_3: The speed of the third fan as an int.
|
||||
fan_4: The speed of the fourth fan as an int.
|
||||
fans: A list of fans on the miner with their speeds.
|
||||
fan_psu: The speed of the PSU on the fan if the miner collects it.
|
||||
left_chips: The number of chips online in the left board as an int.
|
||||
center_chips: The number of chips online in the left board as an int.
|
||||
right_chips: The number of chips online in the left board as an int.
|
||||
total_chips: The total number of chips on all boards. Calculated automatically.
|
||||
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.
|
||||
pool_split: The pool split as a str.
|
||||
pool_1_url: The first pool url on the miner as a str.
|
||||
@@ -63,41 +125,37 @@ class MinerData:
|
||||
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.
|
||||
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.
|
||||
is_mining: Whether the miner is mining.
|
||||
"""
|
||||
|
||||
ip: str
|
||||
datetime: datetime = None
|
||||
mac: str = "00:00:00:00:00:00"
|
||||
model: str = "Unknown"
|
||||
hostname: str = "Unknown"
|
||||
hashrate: float = 0
|
||||
left_board_hashrate: float = 0.0
|
||||
center_board_hashrate: float = 0.0
|
||||
right_board_hashrate: float = 0.0
|
||||
uptime: int = None
|
||||
mac: str = None
|
||||
model: str = None
|
||||
make: str = None
|
||||
api_ver: str = None
|
||||
fw_ver: str = None
|
||||
hostname: str = None
|
||||
hashrate: float = field(init=False)
|
||||
_hashrate: float = None
|
||||
nominal_hashrate: float = None
|
||||
hashboards: List[HashBoard] = field(default_factory=list)
|
||||
ideal_hashboards: int = None
|
||||
temperature_avg: int = field(init=False)
|
||||
env_temp: float = -1.0
|
||||
left_board_temp: int = -1
|
||||
left_board_chip_temp: int = -1
|
||||
center_board_temp: int = -1
|
||||
center_board_chip_temp: int = -1
|
||||
right_board_temp: int = -1
|
||||
right_board_chip_temp: int = -1
|
||||
wattage: int = -1
|
||||
wattage_limit: int = -1
|
||||
fan_1: int = -1
|
||||
fan_2: int = -1
|
||||
fan_3: int = -1
|
||||
fan_4: int = -1
|
||||
fan_psu: int = -1
|
||||
left_chips: int = 0
|
||||
center_chips: int = 0
|
||||
right_chips: int = 0
|
||||
env_temp: float = None
|
||||
wattage: int = None
|
||||
wattage_limit: int = None
|
||||
fans: List[Fan] = field(default_factory=list)
|
||||
fan_psu: int = None
|
||||
total_chips: int = field(init=False)
|
||||
ideal_chips: int = 1
|
||||
percent_ideal: float = field(init=False)
|
||||
nominal: int = field(init=False)
|
||||
ideal_chips: int = None
|
||||
percent_ideal_chips: float = field(init=False)
|
||||
percent_ideal_hashrate: float = field(init=False)
|
||||
percent_ideal_wattage: float = field(init=False)
|
||||
nominal: bool = field(init=False)
|
||||
pool_split: str = "0"
|
||||
pool_1_url: str = "Unknown"
|
||||
pool_1_user: str = "Unknown"
|
||||
@@ -108,11 +166,25 @@ class MinerData:
|
||||
] = field(default_factory=list)
|
||||
fault_light: Union[bool, None] = None
|
||||
efficiency: int = field(init=False)
|
||||
is_mining: bool = True
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return [f.name for f in fields(cls)]
|
||||
|
||||
def __post_init__(self):
|
||||
self.datetime = datetime.now(timezone.utc).astimezone()
|
||||
|
||||
def __getitem__(self, item):
|
||||
def get(self, __key: str, default: Any = None):
|
||||
try:
|
||||
val = self.__getitem__(__key)
|
||||
if val is None:
|
||||
return default
|
||||
return val
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def __getitem__(self, item: str):
|
||||
try:
|
||||
return getattr(self, item)
|
||||
except AttributeError:
|
||||
@@ -161,9 +233,31 @@ class MinerData:
|
||||
setattr(cp, key, item & other_item)
|
||||
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), 2)
|
||||
return self._hashrate
|
||||
|
||||
@hashrate.setter
|
||||
def hashrate(self, val):
|
||||
self._hashrate = val
|
||||
|
||||
@property
|
||||
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 sum(chip_data)
|
||||
return None
|
||||
|
||||
@total_chips.setter
|
||||
def total_chips(self, val):
|
||||
@@ -171,6 +265,8 @@ class MinerData:
|
||||
|
||||
@property
|
||||
def nominal(self): # noqa - Skip PyCharm inspection
|
||||
if self.total_chips is None or self.ideal_chips is None:
|
||||
return None
|
||||
return self.ideal_chips == self.total_chips
|
||||
|
||||
@nominal.setter
|
||||
@@ -178,27 +274,51 @@ class MinerData:
|
||||
pass
|
||||
|
||||
@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)
|
||||
|
||||
@percent_ideal.setter
|
||||
def percent_ideal(self, val):
|
||||
@percent_ideal_chips.setter
|
||||
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
|
||||
|
||||
@property
|
||||
def temperature_avg(self): # noqa - Skip PyCharm inspection
|
||||
total_temp = 0
|
||||
temp_count = 0
|
||||
for temp in [
|
||||
self.left_board_chip_temp,
|
||||
self.center_board_chip_temp,
|
||||
self.right_board_chip_temp,
|
||||
]:
|
||||
if temp and not temp == -1:
|
||||
total_temp += temp
|
||||
for hb in self.hashboards:
|
||||
if hb.temp is not None:
|
||||
total_temp += hb.temp
|
||||
temp_count += 1
|
||||
if not temp_count > 0:
|
||||
return 0
|
||||
return None
|
||||
return round(total_temp / temp_count)
|
||||
|
||||
@temperature_avg.setter
|
||||
@@ -207,7 +327,9 @@ class MinerData:
|
||||
|
||||
@property
|
||||
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 round(self.wattage / self.hashrate)
|
||||
|
||||
@@ -216,12 +338,16 @@ class MinerData:
|
||||
pass
|
||||
|
||||
def asdict(self) -> dict:
|
||||
logging.debug(f"MinerData - (To Dict) - Dumping Dict data")
|
||||
return asdict(self)
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
"""Get this dataclass as a dictionary.
|
||||
|
||||
Returns:
|
||||
A dictionary version of this class.
|
||||
"""
|
||||
return asdict(self)
|
||||
return self.asdict()
|
||||
|
||||
def as_json(self) -> str:
|
||||
"""Get this dataclass as JSON.
|
||||
@@ -229,6 +355,7 @@ class MinerData:
|
||||
Returns:
|
||||
A JSON version of this class.
|
||||
"""
|
||||
logging.debug(f"MinerData - (To JSON) - Dumping JSON data")
|
||||
data = self.asdict()
|
||||
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
|
||||
return json.dumps(data)
|
||||
@@ -239,6 +366,7 @@ class MinerData:
|
||||
Returns:
|
||||
A CSV version of this class with no headers.
|
||||
"""
|
||||
logging.debug(f"MinerData - (To CSV) - Dumping CSV data")
|
||||
data = self.asdict()
|
||||
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
|
||||
errs = []
|
||||
@@ -257,33 +385,52 @@ class MinerData:
|
||||
Returns:
|
||||
A influxdb line protocol version of this class.
|
||||
"""
|
||||
logging.debug(f"MinerData - (To InfluxDB) - Dumping InfluxDB data")
|
||||
tag_data = [measurement_name]
|
||||
field_data = []
|
||||
|
||||
tags = ["ip", "mac", "model", "hostname"]
|
||||
for attribute in self:
|
||||
if attribute in tags:
|
||||
escaped_data = self[attribute].replace(" ", "\\ ")
|
||||
escaped_data = self.get(attribute, "Unknown").replace(" ", "\\ ")
|
||||
tag_data.append(f"{attribute}={escaped_data}")
|
||||
continue
|
||||
if isinstance(self[attribute], str):
|
||||
elif str(attribute).startswith("_"):
|
||||
continue
|
||||
elif isinstance(self[attribute], str):
|
||||
field_data.append(f'{attribute}="{self[attribute]}"')
|
||||
continue
|
||||
if isinstance(self[attribute], bool):
|
||||
elif isinstance(self[attribute], bool):
|
||||
field_data.append(f"{attribute}={str(self[attribute]).lower()}")
|
||||
continue
|
||||
if isinstance(self[attribute], int):
|
||||
elif isinstance(self[attribute], int):
|
||||
field_data.append(f"{attribute}={self[attribute]}")
|
||||
continue
|
||||
if isinstance(self[attribute], float):
|
||||
elif isinstance(self[attribute], float):
|
||||
field_data.append(f"{attribute}={self[attribute]}")
|
||||
continue
|
||||
if attribute == "fault_light" and not self[attribute]:
|
||||
field_data.append(f"{attribute}=false")
|
||||
continue
|
||||
if attribute == "errors":
|
||||
elif attribute == "errors":
|
||||
for idx, item in enumerate(self[attribute]):
|
||||
field_data.append(f'error_{idx+1}="{item.error_message}"')
|
||||
elif attribute == "hashboards":
|
||||
for idx, item in enumerate(self[attribute]):
|
||||
field_data.append(
|
||||
f"hashboard_{idx+1}_hashrate={item.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)
|
||||
field_str = ",".join(field_data)
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
# 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.
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 dataclasses import dataclass, asdict
|
||||
from dataclasses import asdict, dataclass, fields
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -21,9 +23,15 @@ class X19Error:
|
||||
|
||||
Attributes:
|
||||
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_code: int = 0
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
# Copyright 2022 Upstream Data Inc
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from .whatsminer import WhatsminerError
|
||||
from .bos import BraiinsOSError
|
||||
from .X19 import X19Error
|
||||
from .innosilicon import InnosiliconError
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 TypeVar
|
||||
|
||||
from .bos import BraiinsOSError
|
||||
from .innosilicon import InnosiliconError
|
||||
from .whatsminer import WhatsminerError
|
||||
from .X19 import X19Error
|
||||
|
||||
MinerErrorData = TypeVar(
|
||||
"MinerErrorData", WhatsminerError, BraiinsOSError, X19Error, InnosiliconError
|
||||
)
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
# 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.
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 dataclasses import dataclass, asdict
|
||||
from dataclasses import asdict, dataclass, fields
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -21,9 +23,15 @@ class BraiinsOSError:
|
||||
|
||||
Attributes:
|
||||
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_code: int = 0
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
# 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.
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 dataclasses import dataclass, field, asdict
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -27,6 +29,10 @@ class InnosiliconError:
|
||||
error_code: int
|
||||
error_message: str = field(init=False)
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
@property
|
||||
def error_message(self): # noqa - Skip PyCharm inspection
|
||||
if self.error_code in ERROR_CODES:
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
# 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.
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 dataclasses import dataclass, field, asdict
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -27,11 +29,50 @@ class WhatsminerError:
|
||||
error_code: int
|
||||
error_message: str = field(init=False)
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
@property
|
||||
def error_message(self): # noqa - Skip PyCharm inspection
|
||||
if self.error_code in ERROR_CODES:
|
||||
return ERROR_CODES[self.error_code]
|
||||
return "Unknown error type."
|
||||
if len(str(self.error_code)) == 6 and not str(self.error_code)[:1] == "1":
|
||||
err_type = int(str(self.error_code)[:2])
|
||||
err_subtype = int(str(self.error_code)[2:3])
|
||||
err_value = int(str(self.error_code)[3:])
|
||||
else:
|
||||
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
|
||||
def error_message(self, val):
|
||||
@@ -42,131 +83,455 @@ class WhatsminerError:
|
||||
|
||||
|
||||
ERROR_CODES = {
|
||||
110: "Intake fan speed error.",
|
||||
111: "Exhaust fan speed error.",
|
||||
120: "Intake fan speed error. Fan speed deviates by more than 2000.",
|
||||
121: "Exhaust fan speed error. Fan speed deviates by more than 2000.",
|
||||
130: "Intake fan speed error. Fan speed deviates by more than 3000.",
|
||||
131: "Exhaust fan speed error. Fan speed deviates by more than 3000.",
|
||||
140: "Fan speed too high.",
|
||||
200: "Power probing error. No power found.",
|
||||
201: "Power supply and configuration file don't match.",
|
||||
202: "Power output voltage error.",
|
||||
203: "Power protecting due to high environment temperature.",
|
||||
204: "Power current protecting due to high environment temperature.",
|
||||
205: "Power current error.",
|
||||
206: "Power input low voltage error.",
|
||||
207: "Power input current protecting due to bad power input.",
|
||||
210: "Power error.",
|
||||
213: "Power input voltage and current do not match power output.",
|
||||
216: "Power remained unchanged for a long time.",
|
||||
217: "Power set enable error.",
|
||||
218: "Power input voltage is lower than 230V for high power mode.",
|
||||
233: "Power output high temperature protection error.",
|
||||
234: "Power output high temperature protection error.",
|
||||
235: "Power output high temperature protection error.",
|
||||
236: "Power output high current protection error.",
|
||||
237: "Power output high current protection error.",
|
||||
238: "Power output high current protection error.",
|
||||
239: "Power output high voltage protection error.",
|
||||
240: "Power output low voltage protection error.",
|
||||
241: "Power output current imbalance error.",
|
||||
243: "Power input high temperature protection error.",
|
||||
244: "Power input high temperature protection error.",
|
||||
245: "Power input high temperature protection error.",
|
||||
246: "Power input high voltage protection error.",
|
||||
247: "Power input high voltage protection error.",
|
||||
248: "Power input high current protection error.",
|
||||
249: "Power input high current protection error.",
|
||||
250: "Power input low voltage protection error.",
|
||||
251: "Power input low voltage protection error.",
|
||||
253: "Power supply fan error.",
|
||||
254: "Power supply fan error.",
|
||||
255: "Power output high power protection error.",
|
||||
256: "Power output high power protection error.",
|
||||
257: "Input over current protection of power supply on primary side.",
|
||||
263: "Power communication warning.",
|
||||
264: "Power communication error.",
|
||||
267: "Power watchdog protection.",
|
||||
268: "Power output high current protection.",
|
||||
269: "Power input high current protection.",
|
||||
270: "Power input high voltage protection.",
|
||||
271: "Power input low voltage protection.",
|
||||
272: "Excessive power supply output warning.",
|
||||
273: "Power input too high warning.",
|
||||
274: "Power fan warning.",
|
||||
275: "Power high temperature warning.",
|
||||
300: "Right board temperature sensor detection error.",
|
||||
301: "Center board temperature sensor detection error.",
|
||||
302: "Left board temperature sensor detection error.",
|
||||
320: "Right board temperature reading error.",
|
||||
321: "Center board temperature reading error.",
|
||||
322: "Left board temperature reading error.",
|
||||
329: "Control board temperature sensor communication error.",
|
||||
350: "Right board temperature protecting.",
|
||||
351: "Center board temperature protecting.",
|
||||
352: "Left board temperature protecting.",
|
||||
360: "Hashboard high temperature error.",
|
||||
410: "Right board eeprom detection error.",
|
||||
411: "Center board eeprom detection error.",
|
||||
412: "Left board eeprom detection error.",
|
||||
420: "Right board eeprom parsing error.",
|
||||
421: "Center board eeprom parsing error.",
|
||||
422: "Left board eeprom parsing error.",
|
||||
430: "Right board chip bin type error.",
|
||||
431: "Center board chip bin type error.",
|
||||
432: "Left board chip bin type error.",
|
||||
440: "Right board eeprom chip number X error.",
|
||||
441: "Center board eeprom chip number X error.",
|
||||
442: "Left board eeprom chip number X error.",
|
||||
450: "Right board eeprom xfer error.",
|
||||
451: "Center board eeprom xfer error.",
|
||||
452: "Left board eeprom xfer error.",
|
||||
510: "Right board miner type error.",
|
||||
511: "Center board miner type error.",
|
||||
512: "Left board miner type error.",
|
||||
520: "Right board bin type error.",
|
||||
521: "Center board bin type error.",
|
||||
522: "Left board bin type error.",
|
||||
530: "Right board not found.",
|
||||
531: "Center board not found.",
|
||||
532: "Left board not found.",
|
||||
540: "Right board error reading chip id.",
|
||||
541: "Center board error reading chip id.",
|
||||
542: "Left board error reading chip id.",
|
||||
550: "Right board has bad chips.",
|
||||
551: "Center board has bad chips.",
|
||||
552: "Left board has bad chips.",
|
||||
560: "Right board loss of balance error.",
|
||||
561: "Center board loss of balance error.",
|
||||
562: "Left board loss of balance error.",
|
||||
600: "Environment temperature is too high.",
|
||||
610: "Environment temperature is too high for high performance mode.",
|
||||
701: "Control board no support chip.",
|
||||
710: "Control board rebooted as an exception.",
|
||||
712: "Control board rebooted as an exception.",
|
||||
800: "CGMiner checksum error.",
|
||||
801: "System monitor checksum error.",
|
||||
802: "Remote daemon checksum error.",
|
||||
2010: "All pools are disabled.",
|
||||
2020: "Pool 0 connection failed.",
|
||||
2021: "Pool 1 connection failed.",
|
||||
2022: "Pool 2 connection failed.",
|
||||
2023: "Pool 3 connection failed.",
|
||||
2030: "High rejection rate on pool.",
|
||||
2040: "The pool does not support asicboost mode.",
|
||||
2310: "Hashrate is too low.",
|
||||
2320: "Hashrate is too low.",
|
||||
2340: "Hashrate loss is too high.",
|
||||
2350: "Hashrate loss is too high.",
|
||||
5070: "Right hashboard water velocity is abnormal.",
|
||||
5071: "Center hashboard water velocity is abnormal.",
|
||||
5072: "Left hashboard water velocity is abnormal.",
|
||||
5110: "Right hashboard frequency up timeout.",
|
||||
5111: "Center hashboard frequency up timeout.",
|
||||
5112: "Left hashboard frequency up timeout.",
|
||||
8410: "Software version error.",
|
||||
100001: "/antiv/signature illegal.",
|
||||
100002: "/antiv/dig/init.d illegal.",
|
||||
100003: "/antiv/dig/pf_partial.dig illegal.",
|
||||
1: { # Fan error
|
||||
0: {
|
||||
0: "Fan unknown.",
|
||||
},
|
||||
1: { # Fan speed error of 1000+
|
||||
0: "Intake fan speed error.",
|
||||
1: "Exhaust fan speed error.",
|
||||
},
|
||||
2: { # Fan speed error of 2000+
|
||||
0: "Intake fan speed error. Fan speed deviates by more than 2000.",
|
||||
1: "Exhaust fan speed error. Fan speed deviates by more than 2000.",
|
||||
},
|
||||
3: { # Fan speed error of 3000+
|
||||
0: "Intake fan speed error. Fan speed deviates by more than 3000.",
|
||||
1: "Exhaust fan speed error. Fan speed deviates by more than 3000.",
|
||||
},
|
||||
4: {
|
||||
0: "Fan speed too high.",
|
||||
}, # High speed
|
||||
},
|
||||
2: { # Power error
|
||||
0: {
|
||||
0: "Power probing error. No power found.",
|
||||
1: "Power supply and configuration file don't match.",
|
||||
2: "Power output voltage error.",
|
||||
3: "Power protecting due to high environment temperature.",
|
||||
4: "Power current protecting due to high environment temperature.",
|
||||
5: "Power current error.",
|
||||
6: "Power input low voltage error.",
|
||||
7: "Power input current protecting due to bad power input.",
|
||||
8: "Power power error.",
|
||||
9: "Power voltage offset error.",
|
||||
},
|
||||
1: {
|
||||
0: "Power error.",
|
||||
1: "Power iout error, please reboot.",
|
||||
2: "Power vout error, reach vout border. Border: [1150, 1500]",
|
||||
3: "Power input voltage and current do not match power output.",
|
||||
4: "Power pin did not change.",
|
||||
5: "Power vout set error.",
|
||||
6: "Power remained unchanged for a long time.",
|
||||
7: "Power set enable error.",
|
||||
8: "Power input voltage is lower than 230V for high power mode.",
|
||||
9: "Power input current is incorrect.",
|
||||
},
|
||||
3: {
|
||||
3: "Power output high temperature protection error.",
|
||||
4: "Power output high temperature protection error.",
|
||||
5: "Power output high temperature protection error.",
|
||||
6: "Power output high current protection error.",
|
||||
7: "Power output high current protection error.",
|
||||
8: "Power output high current protection error.",
|
||||
9: "Power output high voltage protection error.",
|
||||
},
|
||||
4: {
|
||||
0: "Power output low voltage protection error.",
|
||||
1: "Power output current imbalance error.",
|
||||
3: "Power input high temperature protection error.",
|
||||
4: "Power input high temperature protection error.",
|
||||
5: "Power input high temperature protection error.",
|
||||
6: "Power input high voltage protection error.",
|
||||
7: "Power input high voltage protection error.",
|
||||
8: "Power input high current protection error.",
|
||||
9: "Power input high current protection error.",
|
||||
},
|
||||
5: {
|
||||
0: "Power input low voltage protection error.",
|
||||
1: "Power input low voltage protection error.",
|
||||
3: "Power supply fan error.",
|
||||
4: "Power supply fan error.",
|
||||
5: "Power output high power protection error.",
|
||||
6: "Power output high power protection error.",
|
||||
7: "Input over current protection of power supply on primary side.",
|
||||
},
|
||||
6: {
|
||||
3: "Power communication warning.",
|
||||
4: "Power communication error.",
|
||||
5: "Power unknown error.",
|
||||
6: "Power unknown error.",
|
||||
7: "Power watchdog protection.",
|
||||
8: "Power output high current protection.",
|
||||
9: "Power input high current protection.",
|
||||
},
|
||||
7: {
|
||||
0: "Power input high voltage protection.",
|
||||
1: "Power input low voltage protection.",
|
||||
2: "Excessive power supply output warning.",
|
||||
3: "Power input too high warning.",
|
||||
4: "Power fan warning.",
|
||||
5: "Power high temperature warning.",
|
||||
6: "Power unknown error.",
|
||||
7: "Power unknown error.",
|
||||
8: "Power unknown error.",
|
||||
9: "Power unknown error.",
|
||||
},
|
||||
8: {
|
||||
0: "Power unknown error.",
|
||||
1: "Power vendor status 1 bit 0 error.",
|
||||
2: "Power vendor status 1 bit 1 error.",
|
||||
3: "Power vendor status 1 bit 2 error.",
|
||||
4: "Power vendor status 1 bit 3 error.",
|
||||
5: "Power vendor status 1 bit 4 error.",
|
||||
6: "Power vendor status 1 bit 5 error.",
|
||||
7: "Power vendor status 1 bit 6 error.",
|
||||
8: "Power vendor status 1 bit 7 error.",
|
||||
9: "Power vendor status 2 bit 0 error.",
|
||||
},
|
||||
9: {
|
||||
0: "Power vendor status 2 bit 1 error.",
|
||||
1: "Power vendor status 2 bit 2 error.",
|
||||
2: "Power vendor status 2 bit 3 error.",
|
||||
3: "Power vendor status 2 bit 4 error.",
|
||||
4: "Power vendor status 2 bit 5 error.",
|
||||
5: "Power vendor status 2 bit 6 error.",
|
||||
6: "Power vendor status 2 bit 7 error.",
|
||||
},
|
||||
},
|
||||
3: { # temperature error
|
||||
0: { # sensor detection error
|
||||
"n": "Slot {n} temperature sensor detection error.",
|
||||
},
|
||||
2: { # temperature reading error
|
||||
"n": "Slot {n} temperature reading error.",
|
||||
9: "Control board temperature sensor communication error.",
|
||||
},
|
||||
5: {
|
||||
"n": "Slot {n} temperature protecting.",
|
||||
}, # temperature protection
|
||||
6: {
|
||||
0: "Hashboard high temperature error.",
|
||||
1: "Hashboard high temperature error.",
|
||||
2: "Hashboard high temperature error.",
|
||||
3: "Hashboard high temperature error.",
|
||||
}, # high temp
|
||||
7: {
|
||||
0: "The environment temperature fluctuates too much.",
|
||||
}, # env temp
|
||||
8: {
|
||||
0: "Humidity sensor not found.",
|
||||
1: "Humidity sensor read error.",
|
||||
2: "Humidity sensor read error.",
|
||||
3: "Humidity sensor protecting.",
|
||||
}, # humidity
|
||||
},
|
||||
4: { # EEPROM error
|
||||
0: {
|
||||
0: "Eeprom unknown error.",
|
||||
},
|
||||
1: {
|
||||
"n": "Slot {n} eeprom detection error.",
|
||||
}, # EEPROM detection error
|
||||
2: {
|
||||
"n": "Slot {n} eeprom parsing error.",
|
||||
}, # EEPROM parsing error
|
||||
3: {
|
||||
"n": "Slot {n} chip bin type error.",
|
||||
}, # chip bin error
|
||||
4: {
|
||||
"n": "Slot {n} eeprom chip number X error.",
|
||||
}, # EEPROM chip number error
|
||||
5: {
|
||||
"n": "Slot {n} eeprom xfer error.",
|
||||
}, # EEPROM xfer error
|
||||
},
|
||||
5: { # hashboard error
|
||||
0: {
|
||||
0: "Board unknown error.",
|
||||
},
|
||||
1: {
|
||||
"n": "Slot {n} miner type error.",
|
||||
}, # board miner type error
|
||||
2: {
|
||||
"n": "Slot {n} bin type error.",
|
||||
}, # chip bin type error
|
||||
3: {
|
||||
"n": "Slot {n} not found.",
|
||||
}, # board not found error
|
||||
4: {
|
||||
"n": "Slot {n} error reading chip id.",
|
||||
}, # reading chip id error
|
||||
5: {
|
||||
"n": "Slot {n} has bad chips.",
|
||||
}, # board has bad chips error
|
||||
6: {
|
||||
"n": "Slot {n} loss of balance error.",
|
||||
}, # loss of balance error
|
||||
7: {
|
||||
"n": "Slot {n} xfer error chip.",
|
||||
}, # xfer error
|
||||
8: {
|
||||
"n": "Slot {n} reset error.",
|
||||
}, # reset error
|
||||
9: {
|
||||
"n": "Slot {n} frequency too low.",
|
||||
}, # freq error
|
||||
},
|
||||
6: { # env temp error
|
||||
0: {
|
||||
0: "Environment temperature is too high.",
|
||||
}, # normal env temp error
|
||||
1: { # high power env temp error
|
||||
0: "Environment temperature is too high for high performance mode.",
|
||||
},
|
||||
},
|
||||
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.",
|
||||
3: "The network is unstable, change time.",
|
||||
4: "Unknown error.",
|
||||
},
|
||||
2: {
|
||||
"n": "Control board slot {n} frame error.",
|
||||
},
|
||||
},
|
||||
8: { # checksum error
|
||||
0: {
|
||||
0: "CGMiner checksum error.",
|
||||
1: "System monitor checksum error.",
|
||||
2: "Remote daemon checksum error.",
|
||||
},
|
||||
1: {0: "Air to liquid PCB serial # does not match."},
|
||||
},
|
||||
9: {
|
||||
0: {0: "Unknown error.", 1: "Power rate error.", 2: "Unknown error."}
|
||||
}, # power rate error
|
||||
20: { # pool error
|
||||
0: {
|
||||
0: "No pool information configured.",
|
||||
},
|
||||
1: {
|
||||
0: "All pools are disabled.",
|
||||
}, # all disabled error
|
||||
2: {
|
||||
"n": "Pool {n} connection failed.",
|
||||
}, # pool connection failed error
|
||||
3: {
|
||||
0: "High rejection rate on pool.",
|
||||
}, # rejection rate error
|
||||
4: { # asicboost not supported error
|
||||
0: "The pool does not support asicboost mode.",
|
||||
},
|
||||
},
|
||||
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.",
|
||||
},
|
||||
5: {"n": "Slot {n} stopped hashing, chips temperature protecting."},
|
||||
7: {
|
||||
"n": "Slot {n} water velocity is abnormal.",
|
||||
}, # abnormal water velocity
|
||||
8: {
|
||||
0: "Chip temp calibration failed, please restore factory settings.",
|
||||
},
|
||||
9: {
|
||||
"n": "Slot {n} chip temp calibration check no balance.",
|
||||
},
|
||||
},
|
||||
51: { # frequency error
|
||||
1: {
|
||||
"n": "Slot {n} frequency up timeout.",
|
||||
}, # frequency up timeout
|
||||
2: {"n": "Slot {n} too many CRC errors."},
|
||||
3: {"n": "Slot {n} unstable."},
|
||||
7: {
|
||||
"n": "Slot {n} frequency up timeout.",
|
||||
}, # frequency up timeout
|
||||
},
|
||||
52: {
|
||||
"n": {
|
||||
"c": "Slot {n} chip {c} error nonce.",
|
||||
},
|
||||
},
|
||||
53: {
|
||||
"n": {
|
||||
"c": "Slot {n} chip {c} too few nonce.",
|
||||
},
|
||||
},
|
||||
54: {
|
||||
"n": {
|
||||
"c": "Slot {n} chip {c} temp protected.",
|
||||
},
|
||||
},
|
||||
55: {
|
||||
"n": {
|
||||
"c": "Slot {n} chip {c} has been reset.",
|
||||
},
|
||||
},
|
||||
56: {
|
||||
"n": {
|
||||
"c": "Slot {n} chip {c} zero nonce.",
|
||||
},
|
||||
},
|
||||
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.",
|
||||
},
|
||||
},
|
||||
90: {
|
||||
0: {
|
||||
0: "Process error, exited with signal: 3.",
|
||||
},
|
||||
1: {
|
||||
0: "Process error, exited with signal: 3.",
|
||||
},
|
||||
},
|
||||
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
|
||||
#
|
||||
# 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.
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
|
||||
class APIError(Exception):
|
||||
@@ -22,11 +24,27 @@ class APIError(Exception):
|
||||
|
||||
def __str__(self):
|
||||
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}"
|
||||
else:
|
||||
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):
|
||||
def __init__(self, *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,43 +1,44 @@
|
||||
# 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.
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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.settings import PyasicSettings
|
||||
|
||||
|
||||
def init_logger():
|
||||
if PyasicSettings().logfile:
|
||||
logging.basicConfig(
|
||||
filename="logfile.txt",
|
||||
filemode="a",
|
||||
format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
||||
datefmt="%x %X",
|
||||
)
|
||||
else:
|
||||
logging.basicConfig(
|
||||
format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
||||
datefmt="%x %X",
|
||||
)
|
||||
# if PyasicSettings().logfile:
|
||||
# logging.basicConfig(
|
||||
# filename="logfile.txt",
|
||||
# filemode="a",
|
||||
# format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
||||
# datefmt="%x %X",
|
||||
# )
|
||||
# else:
|
||||
logging.basicConfig(
|
||||
format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
||||
datefmt="%x %X",
|
||||
)
|
||||
|
||||
_logger = logging.getLogger()
|
||||
|
||||
if PyasicSettings().debug:
|
||||
_logger.setLevel(logging.DEBUG)
|
||||
logging.getLogger("asyncssh").setLevel(logging.DEBUG)
|
||||
else:
|
||||
_logger.setLevel(logging.WARNING)
|
||||
logging.getLogger("asyncssh").setLevel(logging.WARNING)
|
||||
# if PyasicSettings().debug:
|
||||
# _logger.setLevel(logging.DEBUG)
|
||||
# logging.getLogger("asyncssh").setLevel(logging.DEBUG)
|
||||
# else:
|
||||
_logger.setLevel(logging.WARNING)
|
||||
logging.getLogger("asyncssh").setLevel(logging.WARNING)
|
||||
|
||||
return _logger
|
||||
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
# Copyright 2022 Upstream Data Inc
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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
|
||||
from typing import Union
|
||||
|
||||
from pyasic.miners.base import BaseMiner, AnyMiner
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
from pyasic.miners.base import AnyMiner, BaseMiner
|
||||
from pyasic.miners.miner_factory import miner_factory
|
||||
|
||||
|
||||
# abstracted version of get miner that is easier to access
|
||||
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,417 +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}"):
|
||||
if not summary_data[0][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]
|
||||
try:
|
||||
psu_data = await self.api.get_psu()
|
||||
except APIError:
|
||||
psu_data = None
|
||||
|
||||
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"]
|
||||
|
||||
if summary_data[0].get("Power Fanspeed"):
|
||||
data.fan_psu = summary_data[0]["Power Fanspeed"]
|
||||
|
||||
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}"):
|
||||
if not summary_data[0][f"Error Code {i}"] == "":
|
||||
data.errors.append(
|
||||
WhatsminerError(
|
||||
error_code=summary_data[0][
|
||||
f"Error Code {i}"
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
if psu_data:
|
||||
psu = psu_data.get("Msg")
|
||||
if psu:
|
||||
if psu.get("fan_speed"):
|
||||
data.fan_psu = psu["fan_speed"]
|
||||
|
||||
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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user