Compare commits
1166 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
891e28bfe6 | ||
|
|
8e15b00e70 | ||
|
|
df71ab3282 | ||
|
|
1508f7873a | ||
|
|
77058ac692 | ||
|
|
1a4491ca56 | ||
|
|
d018724aa4 | ||
|
|
5b97bed704 | ||
|
|
55786b154d | ||
|
|
6ab9681dec | ||
|
|
89641c6316 | ||
|
|
136ff6a688 | ||
|
|
d918d93f4a | ||
|
|
8046c532a6 | ||
|
|
92820a362d | ||
|
|
9fd90031a9 | ||
|
|
2f2223a112 | ||
|
|
50e6cf9dfd | ||
|
|
1b5e3093e6 | ||
|
|
9e3578b4a2 | ||
|
|
a3087e1a96 | ||
|
|
4b16ea2ca2 | ||
|
|
5dd361c4ef | ||
|
|
098112742c | ||
|
|
cd31e0743e | ||
|
|
1a7d0bf7cc | ||
|
|
41b5ebf0f0 | ||
|
|
5436bede29 | ||
|
|
c01b3958dc | ||
|
|
c16367285f | ||
|
|
17eae253e6 | ||
|
|
ab30988614 | ||
|
|
9b8e547f86 | ||
|
|
3b8cbb9ff1 | ||
|
|
d39d278296 | ||
|
|
ea8b922367 | ||
|
|
0d1c8d80e0 | ||
|
|
494d25da97 | ||
|
|
0327d93a35 | ||
|
|
680584c468 | ||
|
|
c0dbafb198 | ||
|
|
97d2c4ac34 | ||
|
|
055d633c91 | ||
|
|
68c57f265f | ||
|
|
1ba0f8ed83 | ||
|
|
7f74b083d3 | ||
|
|
97c20dae0a | ||
|
|
09c7aff640 | ||
|
|
7e7cdc9615 | ||
|
|
b6fb0fd2b9 | ||
|
|
46788e7d14 | ||
|
|
5b4f84a241 | ||
|
|
0c56bfdf9e | ||
|
|
4a5d793cd6 | ||
|
|
1894ff1aea | ||
|
|
3ab9294000 | ||
|
|
5e0641634b | ||
|
|
a1975bc9b8 | ||
|
|
6a265f03f7 | ||
|
|
c3658f028f | ||
|
|
ba3c653a29 | ||
|
|
61fbc132ed | ||
|
|
3f9f232990 | ||
|
|
29c2398846 | ||
|
|
ecc161820d | ||
|
|
5fec3052f6 | ||
|
|
437ee774ab | ||
|
|
aed9e0e406 | ||
|
|
be96428823 | ||
|
|
446881b237 | ||
|
|
ceab8e55b5 | ||
|
|
e12f85c94d | ||
|
|
0c85f53177 | ||
|
|
0b524f9bd0 | ||
|
|
95db852636 | ||
|
|
93fa02412a | ||
|
|
edb77e9cd8 | ||
|
|
cbf7eeb08d | ||
|
|
d34b35a82d | ||
|
|
5d57f35475 | ||
|
|
c95491ea45 | ||
|
|
e9ec43fac9 | ||
|
|
42bde081c4 | ||
|
|
bfb72aec1b | ||
|
|
b2f36b2f0b | ||
|
|
f75c07401b | ||
|
|
01b96227e0 | ||
|
|
82552390c8 | ||
|
|
b0651e26b8 | ||
|
|
c00802e311 | ||
|
|
d66739e2c9 | ||
|
|
65ed565220 | ||
|
|
db6499800b | ||
|
|
cc97ceee61 | ||
|
|
3fa1cb18d9 | ||
|
|
cb3c50d007 | ||
|
|
2523ef8484 | ||
|
|
01342738b0 | ||
|
|
a9dee4a911 | ||
|
|
883ffe20b4 | ||
|
|
261527a380 | ||
|
|
924b62e0d5 | ||
|
|
76a870c2ed | ||
|
|
309356243b | ||
|
|
e9b4cc9bd6 | ||
|
|
648c54de93 | ||
|
|
e1ce96ab1b | ||
|
|
86860a8dc4 | ||
|
|
5212641f45 | ||
|
|
52432e6043 | ||
|
|
727e484860 | ||
|
|
6c091756d2 | ||
|
|
14533ce4fe | ||
|
|
82d1840039 | ||
|
|
8e6240cdba | ||
|
|
5749e173d1 | ||
|
|
7d682b62ac | ||
|
|
6739a1001f | ||
|
|
56e4a5307f | ||
|
|
88de27c9e7 | ||
|
|
a77113c4db | ||
|
|
c19945bb82 | ||
|
|
1756937d20 | ||
|
|
c7b7fe864b | ||
|
|
e7ebefd1bf | ||
|
|
4677efbc46 | ||
|
|
4b7a1a0495 | ||
|
|
cc4e7da4e5 | ||
|
|
a3d2d7d35e | ||
|
|
d67de98bd0 | ||
|
|
fd1a3e459b | ||
|
|
adcab694b5 | ||
|
|
2bb097272f | ||
|
|
896968dded | ||
|
|
56b8f7c5b3 | ||
|
|
0ed7559aef | ||
|
|
275d87e4fe | ||
|
|
c3ab814d77 | ||
|
|
05a8569205 | ||
|
|
b098cb8136 | ||
|
|
75fe7857e4 | ||
|
|
66797aced1 | ||
|
|
4a71e38078 | ||
|
|
9fb07e4fa3 | ||
|
|
74792771ec | ||
|
|
fa6e8a976d | ||
|
|
f20531cff5 | ||
|
|
8b1cbed9ce | ||
|
|
0194e13427 | ||
|
|
82d71abf54 | ||
|
|
e71cfadf6e | ||
|
|
18931c4e98 | ||
|
|
8622c080aa | ||
|
|
cb71b2a593 | ||
|
|
ff5956da41 | ||
|
|
acdafc2efd | ||
|
|
b8874092ad | ||
|
|
ad28ba0b3e | ||
|
|
0d90b60eef | ||
|
|
7c18c9f69c | ||
|
|
975560f46f | ||
|
|
bfe9cbf7d9 | ||
|
|
ccb5eb73db | ||
|
|
d143667bd6 | ||
|
|
87d809abc0 | ||
|
|
4dc5b1a541 | ||
|
|
ddd3e867f9 | ||
|
|
77480d3d69 | ||
|
|
0767c93002 | ||
|
|
e690e6dd3b | ||
|
|
d4665ed768 | ||
|
|
b90a92c0df | ||
|
|
50cfcf9796 | ||
|
|
5d204f09da | ||
|
|
4c0410322f | ||
|
|
fbb2b3f6e7 | ||
|
|
0f09fb49fc | ||
|
|
b0d063d6ed | ||
|
|
a68fe70af4 | ||
|
|
43c7ac281b | ||
|
|
a97ae55a06 | ||
|
|
4a3a6f4186 | ||
|
|
f976724ada | ||
|
|
2632bdaa30 | ||
|
|
91016d7b8c | ||
|
|
2b00e741ca | ||
|
|
d496c11d67 | ||
|
|
5880223517 | ||
|
|
394a5dcd0d | ||
|
|
7365275f46 | ||
|
|
0ecab5fdd4 | ||
|
|
ed0d9f73e4 | ||
|
|
28f4e16662 | ||
|
|
b9b0bff946 | ||
|
|
790718a5df | ||
|
|
96a0301f5e | ||
|
|
c57b019b7d | ||
|
|
af920c4dda | ||
|
|
f3d11788ed | ||
|
|
fd0e02af59 | ||
|
|
2a6c51d52c | ||
|
|
2d62e2070b | ||
|
|
b143bd70f0 | ||
|
|
605509c57c | ||
|
|
7036137b23 | ||
|
|
7a9ff535b4 | ||
|
|
f185bafe2a | ||
|
|
ab81d5d020 | ||
|
|
0965e6489b | ||
|
|
792e1c9cad | ||
|
|
a6721f971a | ||
|
|
8113d0e4e0 | ||
|
|
e3c7d3f8a2 | ||
|
|
6415de8c73 | ||
|
|
f2838cf31d | ||
|
|
fbd49b370d | ||
|
|
79f7296576 | ||
|
|
76f4ca5f89 | ||
|
|
477acda1c1 | ||
|
|
a57f343dcc | ||
|
|
36e9201ed4 | ||
|
|
c1525501d4 | ||
|
|
e4bb90a569 | ||
|
|
28642cc521 | ||
|
|
beae79ddec | ||
|
|
f02e10ab3d | ||
|
|
d0b9dff476 | ||
|
|
501e290839 | ||
|
|
a0daf37f80 | ||
|
|
8111b1ff4b | ||
|
|
754087afd6 | ||
|
|
5e16b6092c | ||
|
|
21636a75fa | ||
|
|
f124f5422a | ||
|
|
1e5d1a2528 | ||
|
|
1fcef07902 | ||
|
|
41e7dd8056 | ||
|
|
dccc35db5f | ||
|
|
0cfe59aa34 | ||
|
|
6fdd156fa3 | ||
|
|
e9fcf25ad3 | ||
|
|
a9422165ca | ||
|
|
0ea5ee8239 | ||
|
|
fba25cba61 | ||
|
|
343b5a1c50 | ||
|
|
63522aad81 | ||
|
|
b957aa7fba | ||
|
|
a71aa6868a | ||
|
|
6b50bf0cf7 | ||
|
|
d00444ec56 | ||
|
|
e7ed39fe39 | ||
|
|
168d68d0b2 | ||
|
|
63cddfdde3 | ||
|
|
4a642fd3da | ||
|
|
13c0407b2d | ||
|
|
794ed6d103 | ||
|
|
d0aeb5a6ce | ||
|
|
030f8c6079 | ||
|
|
7195e204ce | ||
|
|
962a328219 | ||
|
|
1cec2ca7f3 | ||
|
|
a3c4187411 | ||
|
|
18a2df5d9b | ||
|
|
6d66c793cb | ||
|
|
b434c8df1a | ||
|
|
2b8fa2fc2b | ||
|
|
1497d2abea | ||
|
|
a2ca79843d | ||
|
|
f6500e7d66 | ||
|
|
ea2fd0fc9a | ||
|
|
e2cbd30a99 | ||
|
|
151ea44b10 | ||
|
|
6487a0b08e | ||
|
|
552fdf9ec0 | ||
|
|
00cf1449f9 | ||
|
|
8ec88e385a | ||
|
|
cc29b2960a | ||
|
|
568ffd67c4 | ||
|
|
4b4670201a | ||
|
|
92f70c9a76 | ||
|
|
1d2dc3fddf | ||
|
|
c44150fd15 | ||
|
|
8664b53991 | ||
|
|
31aeca2340 | ||
|
|
34eec3ff2e | ||
|
|
e1416b5a4b | ||
|
|
3ca75729b9 | ||
|
|
73031eea65 | ||
|
|
1643c5b7ee | ||
|
|
ca5db726bd | ||
|
|
4bb4d32b48 | ||
|
|
fec7a89807 | ||
|
|
db2615a4eb | ||
|
|
eea5d5ba2a | ||
|
|
f405bbff4d | ||
|
|
dd8d895b50 | ||
|
|
dff4e98523 | ||
|
|
846bbb9033 | ||
|
|
e6f9a33b3c | ||
|
|
092126bded | ||
|
|
e5d5cb4dab | ||
|
|
bd76966d3a | ||
|
|
d46908a298 | ||
|
|
e4cec021b0 | ||
|
|
42d2d975db | ||
|
|
427f91d677 | ||
|
|
7a9c9237a3 | ||
|
|
9b431b020f | ||
|
|
ee1eece181 | ||
|
|
3627194f34 | ||
|
|
65cfb57605 | ||
|
|
8e4a547c77 | ||
|
|
a751efe7d5 | ||
|
|
e859895261 | ||
|
|
ae3d38603a | ||
|
|
fca72eb747 | ||
|
|
923e963369 | ||
|
|
7a3c9a3460 | ||
|
|
e649348af2 | ||
|
|
ba58e80ec3 | ||
|
|
45e2c9a403 | ||
|
|
bd9592c19c | ||
|
|
1bb597999d | ||
|
|
7803fa60f2 | ||
|
|
4adb7dc92c | ||
|
|
ba69a1de2c | ||
|
|
64265206c2 | ||
|
|
eec8f66b81 | ||
|
|
999e8ef318 | ||
|
|
30f385c2d9 | ||
|
|
87377fbe4c | ||
|
|
2a66602c2c | ||
|
|
8cc18ca272 | ||
|
|
677db8fd0d | ||
|
|
a458adc45f | ||
|
|
5a09ddcb04 | ||
|
|
4d9fde572e | ||
|
|
3e4b347506 | ||
|
|
927bbae0c1 | ||
|
|
7e3e1f19aa | ||
|
|
5a4b1b6ee1 | ||
|
|
25767aab8e | ||
|
|
b3a0949395 | ||
|
|
18e6fc2a3c | ||
|
|
4d45b6e50f | ||
|
|
eefb055a3f | ||
|
|
9c41a6b28f | ||
|
|
bf0e2e6cfe | ||
|
|
4a2adabe95 | ||
|
|
4031a42350 | ||
|
|
4698a806f0 | ||
|
|
aec53aa5f0 | ||
|
|
e15ddd020c | ||
|
|
6f4aead0d4 | ||
|
|
6b3bf31597 | ||
|
|
2ac118a008 | ||
|
|
c87880529c | ||
|
|
b12766321d | ||
|
|
3d6bee2d85 | ||
|
|
c8a8315ad0 | ||
|
|
dac9bcc3de | ||
|
|
7688288d05 | ||
|
|
46621d6b93 | ||
|
|
35700f7e57 | ||
|
|
08e6744595 | ||
|
|
2de3e5e328 | ||
|
|
51f2eb1b1d | ||
|
|
b4faf7c49e | ||
|
|
26d9562c18 | ||
|
|
d40d92c1ca | ||
|
|
7ea63643a9 | ||
|
|
0bd5c22681 | ||
|
|
8f0cf5b3a3 | ||
|
|
6458a71b5d | ||
|
|
dbdd23e37d | ||
|
|
313c324771 | ||
|
|
a9fd9343d8 | ||
|
|
8f41d4d0bc | ||
|
|
521853863b | ||
|
|
b7a5a647b3 | ||
|
|
4434f9ccad | ||
|
|
82a1cc3cfe | ||
|
|
2d92c2c0e2 | ||
|
|
6f10c91482 | ||
|
|
f2d6bce165 | ||
|
|
1ab05c7a5e | ||
|
|
61623cc44d | ||
|
|
a30a726324 | ||
|
|
0e90ad64cd | ||
|
|
53572c6236 | ||
|
|
67da56a03b | ||
|
|
be8633185d | ||
|
|
1d656da2a2 | ||
|
|
189deae3d1 | ||
|
|
46188ad52b | ||
|
|
19e232ddb9 | ||
|
|
5d90b7e938 | ||
|
|
3f90799544 | ||
|
|
1f70ec0d28 | ||
|
|
c26b78aa01 | ||
|
|
0debf16f7c | ||
|
|
d7f48d8f9f | ||
|
|
58c95559dd | ||
|
|
03caa9fe94 | ||
|
|
afd8697f07 | ||
|
|
fc77aded28 | ||
|
|
a5cafd1fb8 | ||
|
|
91d504fc1c | ||
|
|
db0444a8eb | ||
|
|
0cab872baf | ||
|
|
7f191eb2fd | ||
|
|
52adc2553b | ||
|
|
de49fd7e95 | ||
|
|
7eb61473a8 | ||
|
|
f6a134342a | ||
|
|
1d67e5ed68 | ||
|
|
7d4aa80966 | ||
|
|
742dde622b | ||
|
|
497aa6a42c | ||
|
|
40ebf42da1 | ||
|
|
2f8aea5285 | ||
|
|
53f545ba13 | ||
|
|
689b34611e | ||
|
|
d55c3f45ef | ||
|
|
5ac533616f | ||
|
|
96ea5f5d16 | ||
|
|
87526f5efc | ||
|
|
d31bafbc0e | ||
|
|
66bae47bb9 | ||
|
|
7a09b66d4e | ||
|
|
de5932184f | ||
|
|
2cba62e050 | ||
|
|
c7520d98e0 | ||
|
|
92e9f7bc08 | ||
|
|
e0becce349 | ||
|
|
a65c4ba215 | ||
|
|
4cd0c3357b | ||
|
|
d5cabf8af5 | ||
|
|
3120de757d | ||
|
|
0b69fe591e | ||
|
|
032288d062 | ||
|
|
1d6618c1c0 | ||
|
|
7126e03f0d | ||
|
|
c27556c809 | ||
|
|
99ff28d3e1 | ||
|
|
8ad6f60757 | ||
|
|
5f67b987a0 | ||
|
|
5842b3c4aa | ||
|
|
e2e1d2f2fd | ||
|
|
dd205c0f06 | ||
|
|
79e247c0cf | ||
|
|
836d045b65 | ||
|
|
2d029b65e6 | ||
|
|
993b7efeef | ||
|
|
7e02a6b932 | ||
|
|
60e38fb8bc | ||
|
|
4fc2757ffa | ||
|
|
c89b25d7c9 | ||
|
|
f2fc354f8c | ||
|
|
0e7eca339c | ||
|
|
5f76cb9f0a | ||
|
|
ed3a4bd32a | ||
|
|
a0a1b68f68 | ||
|
|
b7a81097a4 | ||
|
|
651bef8203 | ||
|
|
a9e5c99ab2 | ||
|
|
30216fdd5b | ||
|
|
341cc13d83 | ||
|
|
05a4ae6f04 | ||
|
|
5971d9fd83 | ||
|
|
3968f2275c | ||
|
|
84344ca883 | ||
|
|
59b80254eb | ||
|
|
06fdb19e0b | ||
|
|
75222e8cd2 | ||
|
|
5a067d60e7 | ||
|
|
2fbc4fcb4a | ||
|
|
13fe60504a | ||
|
|
cdc52c3605 | ||
|
|
a9db097355 | ||
|
|
ec5557cf88 | ||
|
|
a8ea84d2f3 | ||
|
|
58d369eedf | ||
|
|
f8f9dd7070 | ||
|
|
01b72e1ee6 | ||
|
|
e367e630b8 | ||
|
|
385cca6fc0 | ||
|
|
75a3a466a3 | ||
|
|
4bf08dbfe6 | ||
|
|
833d061315 | ||
|
|
a85558278d | ||
|
|
bf5087b06d | ||
|
|
ec58d13bae | ||
|
|
75993564ab | ||
|
|
f3ea169dec | ||
|
|
39b3fe5c25 | ||
|
|
3b2b586420 | ||
|
|
5c79c6cb0c | ||
|
|
bab4261bed | ||
|
|
e1d5c89388 | ||
|
|
5f6c8cca18 | ||
|
|
39db14b002 | ||
|
|
3be04c678b | ||
|
|
099ec35a8f | ||
|
|
113dfb9170 | ||
|
|
8d19e0ebbb | ||
|
|
ec064eba65 | ||
|
|
3451127761 | ||
|
|
b3be52ca77 | ||
|
|
b6ec6caa72 | ||
|
|
8e81e18622 | ||
|
|
6ea26e0e19 | ||
|
|
be446f94c1 | ||
|
|
3d94e30f22 | ||
|
|
d5a7ff3a46 | ||
|
|
bbfa97632d | ||
|
|
ecf0ce22d6 | ||
|
|
d56da007a5 | ||
|
|
2c86b2da7e | ||
|
|
c73b1ceb07 | ||
|
|
a320c8967d | ||
|
|
e21e340f60 | ||
|
|
f63d8f4b91 | ||
|
|
ba6a1606b6 | ||
|
|
51b0c0456f | ||
|
|
04bd03b496 | ||
|
|
bc5764c8ff | ||
|
|
1f2e066f4c | ||
|
|
74c457a694 | ||
|
|
4a1c53dfd7 | ||
|
|
81b77f8768 | ||
|
|
3b127b6862 | ||
|
|
2815d2ba11 | ||
|
|
1ff20fc6f0 | ||
|
|
797c847055 | ||
|
|
65c7f2f66f | ||
|
|
445d621590 | ||
|
|
d39ecfd6b4 | ||
|
|
36663471fb | ||
|
|
80293ac52f | ||
|
|
70b45f40f5 | ||
|
|
a511fabd9c | ||
|
|
8bc8f6f178 | ||
|
|
b790ad58a7 | ||
|
|
354ab793a2 | ||
|
|
59346d641f | ||
|
|
11d770771b | ||
|
|
1b6db7ed45 | ||
|
|
55c4e10fae | ||
|
|
77c06dad61 | ||
|
|
68d250d2f2 | ||
|
|
094a17ac68 | ||
|
|
dbcdeaa3de | ||
|
|
872cac811a | ||
|
|
d324c2fee9 | ||
|
|
577e8df612 | ||
|
|
4b54cf67ba | ||
|
|
0e00fe3114 | ||
|
|
15d1dc5bb6 | ||
|
|
2af0003843 | ||
|
|
3c227be170 | ||
|
|
e889780bad | ||
|
|
cc3d4fa805 | ||
|
|
743823f66e | ||
|
|
227e1e2d2d | ||
|
|
d6c8ff0910 | ||
|
|
bd20e051b0 | ||
|
|
2eb6697e9a | ||
|
|
c5817fcc36 | ||
|
|
f2391bcb2d | ||
|
|
bc3bd9e5da | ||
|
|
3f0959d75e | ||
|
|
31f7b56724 | ||
|
|
072954d755 | ||
|
|
d271e0f9c8 | ||
|
|
8f1408ce17 | ||
|
|
825d1f4cfb | ||
|
|
c6bcd7e05a | ||
|
|
5d80051f3b | ||
|
|
b71c448199 | ||
|
|
c82148412c | ||
|
|
a582ee63a0 | ||
|
|
db7c19c486 | ||
|
|
599f71da19 | ||
|
|
0995744d90 | ||
|
|
4073a27aba | ||
|
|
bec9c31c97 | ||
|
|
acdd615c53 | ||
|
|
8091617ee2 | ||
|
|
c25ff6fcef | ||
|
|
ab0dcd607b | ||
|
|
ce288e472f | ||
|
|
02d8f25daf | ||
|
|
a76d1c6149 | ||
|
|
17f5eade19 | ||
|
|
b6a2a5054b | ||
|
|
5984338c64 | ||
|
|
07d1c48e33 | ||
|
|
d2abae947c | ||
|
|
e4a0f2451a | ||
|
|
880c598b1a | ||
|
|
3632c2c4d8 | ||
|
|
09bc9686ae | ||
|
|
34584ab098 | ||
|
|
554d99ca08 | ||
|
|
5c5d688ffa | ||
|
|
c50d55e87c | ||
|
|
5e5516bfb3 | ||
|
|
4b068c57c5 | ||
|
|
203f199aec | ||
|
|
895f17aaf9 | ||
|
|
8a64ff3559 | ||
|
|
4c45d356c4 | ||
|
|
4dec329f11 | ||
|
|
b563ed118e | ||
|
|
75b2ec40b1 | ||
|
|
d9adaf6667 | ||
|
|
9343308f41 | ||
|
|
88769e40ae | ||
|
|
be45eb7400 | ||
|
|
2f719a03a4 | ||
|
|
64196f9754 | ||
|
|
49a77f1b79 | ||
|
|
3838c4f2f9 | ||
|
|
80d89c95b5 | ||
|
|
30cd8b5cfe | ||
|
|
c443170f78 | ||
|
|
a2c2aa2377 | ||
|
|
4f0eb49a02 | ||
|
|
a821357b4f | ||
|
|
3c7679a22d | ||
|
|
a52737e236 | ||
|
|
7c96bbe153 | ||
|
|
e8bbf22aa7 | ||
|
|
5ac8b27cb6 | ||
|
|
6c14902484 | ||
|
|
96aa346f00 | ||
|
|
c2b6cc7468 | ||
|
|
ac7f41be44 | ||
|
|
718b87fd12 | ||
|
|
5ad23c6cd0 | ||
|
|
66be443dc3 | ||
|
|
a9135e21d4 | ||
|
|
dd4c087749 | ||
|
|
aa1d7c1b6f | ||
|
|
b328a27f04 | ||
|
|
c5eed797ec | ||
|
|
4fd2199435 | ||
|
|
3226d47846 | ||
|
|
6c1931fe7e | ||
|
|
1dd87ac102 | ||
|
|
95d1e40b4f | ||
|
|
31682b7fae | ||
|
|
e6523fc7d5 | ||
|
|
91de12467b | ||
|
|
d81e3e9f88 | ||
|
|
49fc0f3c54 | ||
|
|
4b36044e56 | ||
|
|
90fb67f586 | ||
|
|
edf31ae7df | ||
|
|
af354fd8e2 | ||
|
|
6a2a3e836d | ||
|
|
41709e4706 | ||
|
|
b60c7a55d4 | ||
|
|
eed1973345 | ||
|
|
64774d2017 | ||
|
|
e9751d6cd1 | ||
|
|
e2b0a76e67 | ||
|
|
1c5c39fa97 | ||
|
|
27c48764a8 | ||
|
|
5e01f7517b | ||
|
|
569f659fac | ||
|
|
dd9c6f1f63 | ||
|
|
0958f47cfe | ||
|
|
3820b40f44 | ||
|
|
cce1917c00 | ||
|
|
2ee19f47e7 | ||
|
|
ff526a3273 | ||
|
|
7811245ec9 | ||
|
|
cbab76847a | ||
|
|
ce981d1787 | ||
|
|
4b5314a8f6 | ||
|
|
3be3086a38 | ||
|
|
a0c76fe24f | ||
|
|
acdcfd04cd | ||
|
|
91a5998b4e | ||
|
|
7292af450c | ||
|
|
307926afbb | ||
|
|
10293ae24a | ||
|
|
f820372532 | ||
|
|
22965ffefa | ||
|
|
34ca5ba68f | ||
|
|
468134e754 | ||
|
|
5327b3fe3d | ||
|
|
68b85aa7da | ||
|
|
b78652b279 | ||
|
|
832a276f4b | ||
|
|
2b82b29690 | ||
|
|
56dd1c80b5 | ||
|
|
d686cdacc8 | ||
|
|
aab8825997 | ||
|
|
4ed49c2321 | ||
|
|
c069468803 | ||
|
|
707cf8b848 | ||
|
|
170843aae7 | ||
|
|
f5acf9ec62 | ||
|
|
edaf89c73a | ||
|
|
ce34dfdde8 | ||
|
|
e45e51ce65 | ||
|
|
f1501718a3 | ||
|
|
831d6ee955 | ||
|
|
7be6596fdd | ||
|
|
928e0dd028 | ||
|
|
672e753afb | ||
|
|
269e6aac14 | ||
|
|
1a4f3f7dc7 | ||
|
|
b0337e8417 | ||
|
|
60f3687d02 | ||
|
|
a8c45cb95d | ||
|
|
aa9ba66f8e | ||
|
|
06cc84f16d | ||
|
|
067d5c98f5 | ||
|
|
b4b84c773f | ||
|
|
cd1768aae9 | ||
|
|
2ef85d3868 | ||
|
|
6f64cc5e0d | ||
|
|
d44907435c | ||
|
|
04ca75d00e | ||
|
|
b56e94ce8c | ||
|
|
e7d30aad84 | ||
|
|
194fb539a1 | ||
|
|
416ea2964b | ||
|
|
3234f7e06f | ||
|
|
8fb357544b | ||
|
|
34006941ad | ||
|
|
3c3c34c54b | ||
|
|
5a61a87766 | ||
|
|
ef9a026ee8 | ||
|
|
71c85e0603 | ||
|
|
c5224b808e | ||
|
|
e4c6d751a1 | ||
|
|
ff4dfa124b | ||
|
|
d0eb5119aa | ||
|
|
cfa51623c4 | ||
|
|
96bb56ebd1 | ||
|
|
cdd7beccbe | ||
|
|
1a544851df | ||
|
|
aa2dc5a53d | ||
|
|
361d6e07cc | ||
|
|
53a018f526 | ||
|
|
6c9a378eee | ||
|
|
be67ef3471 | ||
|
|
a094d28a36 | ||
|
|
4156f93c0d | ||
|
|
ed6eb11653 | ||
|
|
39299f2cfa | ||
|
|
c80ca1415a | ||
|
|
a8428a2739 | ||
|
|
895fb1b43e | ||
|
|
014896ae1b | ||
|
|
84ac991685 | ||
|
|
bb481553fa | ||
|
|
7ab3d8b54e | ||
|
|
36494f2aca | ||
|
|
bea44a72ea | ||
|
|
9da7b44177 | ||
|
|
e7f05f7a28 | ||
|
|
2d229be9fd | ||
|
|
de5038e57a | ||
|
|
8ad1b3f72a | ||
|
|
070fb26dbc | ||
|
|
80d9d7df1d | ||
|
|
928c24f56f | ||
|
|
6e7442f90d | ||
|
|
936474ed3b | ||
|
|
2e28060e05 | ||
|
|
07f92557c6 | ||
|
|
6f6f5743cf | ||
|
|
b89ea1fa92 | ||
|
|
3588197741 | ||
|
|
8adc3d2adf | ||
|
|
040c0b6842 | ||
|
|
550b4a97a1 | ||
|
|
d84d95fe5f | ||
|
|
0e5b811fb9 | ||
|
|
3d31179562 | ||
|
|
69f39bef0c | ||
|
|
1076dab7f5 | ||
|
|
3ae1f700c2 | ||
|
|
dc3f061b9b | ||
|
|
52758dd8b3 | ||
|
|
0e492f1cfd | ||
|
|
659dc55f3c | ||
|
|
eb9b29aca1 | ||
|
|
b045abe76e | ||
|
|
7a75818a20 | ||
|
|
d2be68d35e | ||
|
|
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 |
6
.github/workflows/python-publish.yml
vendored
6
.github/workflows/python-publish.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- name: Publish GH release
|
||||
uses: softprops/action-gh-release@v0.1.14
|
||||
uses: softprops/action-gh-release@v2.1.0
|
||||
- name: Build using poetry and publish to PyPi
|
||||
uses: JRubics/poetry-publish@v1.11
|
||||
uses: JRubics/poetry-publish@v2.0
|
||||
with:
|
||||
pypi_token: ${{ secrets.PYPI_API_KEY }}
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
ci:
|
||||
skip:
|
||||
- unittest
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.3.0
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: check-yaml
|
||||
name: check-yaml for mkdocs.yml
|
||||
files: ^mkdocs\.yml$
|
||||
args: [--unsafe]
|
||||
- id: check-yaml
|
||||
name: check-yaml for other YAML files
|
||||
exclude: ^mkdocs\.yml$
|
||||
- id: check-added-large-files
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.6.0
|
||||
rev: 24.10.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.10.1
|
||||
rev: 5.13.2
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort (python)
|
||||
@@ -24,4 +33,3 @@ repos:
|
||||
'types': [python]
|
||||
args: ["-p '*test.py'"] # Probably this option is absolutely not needed.
|
||||
pass_filenames: false
|
||||
stages: [commit]
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
# .readthedocs.yaml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Set the version of Python and other tools you might need
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
tools:
|
||||
python: "3.9"
|
||||
tools: { python: "3.11" }
|
||||
jobs:
|
||||
pre_create_environment:
|
||||
- asdf plugin add poetry
|
||||
- asdf install poetry latest
|
||||
- asdf global poetry latest
|
||||
- poetry config virtualenvs.create false
|
||||
post_install:
|
||||
- VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --only docs
|
||||
|
||||
mkdocs:
|
||||
configuration: mkdocs.yml
|
||||
|
||||
# Optionally declare the Python requirements required to build your docs
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
configuration: mkdocs.yml
|
||||
346
README.md
346
README.md
@@ -1,142 +1,286 @@
|
||||
# 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/)
|
||||
[](https://pypi.org/project/pyasic/)
|
||||
[](https://pyasic.readthedocs.io/en/latest/)
|
||||
[](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
|
||||
[](https://www.codefactor.io/repository/github/upstreamdata/pyasic)
|
||||
## Documentation and Supported Miners
|
||||
Documentation is located on Read the Docs as [pyasic](https://pyasic.readthedocs.io/en/latest/).
|
||||
[](https://pypi.org/project/pyasic/)
|
||||
[](https://pypi.org/project/pyasic/)
|
||||
|
||||
Supported miners are listed in the docs, [here](https://pyasic.readthedocs.io/en/latest/miners/supported_types/).
|
||||
[](https://pypi.org/project/pyasic/)
|
||||
[](https://www.codefactor.io/repository/github/upstreamdata/pyasic)
|
||||
[](https://github.com/UpstreamData/pyasic/commits/master/)
|
||||
|
||||
[](https://github.com/psf/black)
|
||||
[](https://docs.pyasic.org)
|
||||
[](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
|
||||
|
||||
---
|
||||
## Intro
|
||||
|
||||
Welcome to `pyasic`! `pyasic` uses an asynchronous method of communicating with ASIC miners on your network, which makes it super fast.
|
||||
|
||||
[Click here to view supported miner types](https://docs.pyasic.org/en/latest/miners/supported_types/)
|
||||
|
||||
---
|
||||
## Installation
|
||||
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).
|
||||
It is recommended to install `pyasic` in a [virtual environment](https://realpython.com/python-virtual-environments-a-primer/#what-other-popular-options-exist-aside-from-venv) to isolate it from the rest of your system. Options include:
|
||||
- [pypoetry](https://python-poetry.org/): the reccommended way, since pyasic already uses it by default
|
||||
|
||||
## 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.
|
||||
|
||||
Finally, initialize pre-commit hooks with `poetry run pre-commit install`.
|
||||
|
||||
### Documentation Testing
|
||||
Testing the documentation can be done by running `poetry run mkdocs serve`, whcih will serve the documentation locally on port 8000.
|
||||
|
||||
## Interfacing with miners programmatically
|
||||
|
||||
There are 2 main ways to get a miner (and the functions attached to it), via scanning or via the `MinerFactory()`.
|
||||
|
||||
#### Scanning for miners
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from pyasic.network import MinerNetwork
|
||||
|
||||
|
||||
# define asynchronous function to scan for miners
|
||||
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 (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
|
||||
miners: list = await net.scan_network_for_miners()
|
||||
|
||||
# We can now get data from any of these miners
|
||||
# To do them all we have to create a list of tasks and gather them
|
||||
tasks = [miner.get_data() for miner in miners]
|
||||
# Gather all tasks asynchronously and run them
|
||||
data = await asyncio.gather(*tasks)
|
||||
|
||||
# Data is now a list of MinerData, and we can reference any part of that
|
||||
# Print out all data for now
|
||||
for item in data:
|
||||
print(item)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(scan_and_get_data())
|
||||
```
|
||||
poetry install
|
||||
```
|
||||
|
||||
- [venv](https://docs.python.org/3/library/venv.html): included in Python standard library but has fewer features than other options
|
||||
- [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv): [pyenv](https://github.com/pyenv/pyenv) plugin for managing virtualenvs
|
||||
|
||||
#### Getting a miner if you know the IP
|
||||
```
|
||||
pyenv install <python version number>
|
||||
pyenv virtualenv <python version number> <env name>
|
||||
pyenv activate <env name>
|
||||
```
|
||||
|
||||
- [conda](https://docs.conda.io/en/latest/)
|
||||
|
||||
##### Installing `pyasic`
|
||||
|
||||
`python -m pip install pyasic` or `poetry install`
|
||||
|
||||
##### Additional Developer Setup
|
||||
```
|
||||
poetry install --with dev
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
---
|
||||
## 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.
|
||||
|
||||
##### Scanning for miners
|
||||
To scan for miners in `pyasic`, we use the class `MinerNetwork`, which abstracts the search, communication, identification, setup, and return of a miner to 1 command.
|
||||
The command `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
|
||||
|
||||
|
||||
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.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()
|
||||
print(miners)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(scan_miners()) # run the scan asynchronously with asyncio.run()
|
||||
```
|
||||
|
||||
---
|
||||
##### Creating miners based on IP
|
||||
If you already know the IP address of your miner or miners, you can use the `MinerFactory` to communicate and identify the miners, or an abstraction of its functionality, `get_miner()`.
|
||||
The function `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 import get_miner # handles miner creation
|
||||
|
||||
|
||||
async def get_miners(): # define async scan function to allow awaiting
|
||||
# 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()
|
||||
```
|
||||
|
||||
---
|
||||
## 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` with all data it can gather from the miner.
|
||||
Each piece of data in a `MinerData` instance can be referenced by getting it as an attribute, such as `MinerData().hashrate`.
|
||||
|
||||
##### One miner
|
||||
```python
|
||||
import asyncio
|
||||
from pyasic import get_miner
|
||||
|
||||
async def gather_miner_data():
|
||||
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
|
||||
from pyasic.network import MinerNetwork # miner network handles the scanning
|
||||
|
||||
|
||||
async def gather_miner_data(): # define async scan function to allow awaiting
|
||||
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])
|
||||
|
||||
for miner_data in all_miner_data:
|
||||
print(miner_data) # print out all the data one by one
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(gather_miner_data())
|
||||
```
|
||||
|
||||
---
|
||||
## 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`.
|
||||
|
||||
These functions are
|
||||
`check_light`,
|
||||
`fault_light_off`,
|
||||
`fault_light_on`,
|
||||
`get_config`,
|
||||
`get_data`,
|
||||
`get_errors`,
|
||||
`get_hostname`,
|
||||
`get_model`,
|
||||
`reboot`,
|
||||
`restart_backend`,
|
||||
`stop_mining`,
|
||||
`resume_mining`,
|
||||
`is_mining`,
|
||||
`send_config`, and
|
||||
`set_power_limit`.
|
||||
|
||||
##### Usage
|
||||
```python
|
||||
import asyncio
|
||||
from pyasic import get_miner
|
||||
|
||||
|
||||
# define asynchronous function to get miner and data
|
||||
async def get_miner_data(miner_ip: str):
|
||||
# Use MinerFactory to get miner
|
||||
# MinerFactory is a singleton, so we can just get the instance in place
|
||||
miner = await get_miner(miner_ip)
|
||||
async def set_fault_light():
|
||||
miner = await get_miner("192.168.1.20")
|
||||
|
||||
# Get data from the miner
|
||||
data = await miner.get_data()
|
||||
print(data)
|
||||
# call control function
|
||||
await miner.fault_light_on()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(get_miner_data("192.168.1.69"))
|
||||
asyncio.run(set_fault_light())
|
||||
```
|
||||
|
||||
### Advanced data gathering
|
||||
---
|
||||
## Helper dataclasses
|
||||
|
||||
If needed, this library exposes a wrapper for the miner API that can be used for advanced data gathering.
|
||||
##### `MinerConfig` and `MinerData`
|
||||
|
||||
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/).
|
||||
`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()`.
|
||||
|
||||
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
|
||||
##### MinerData
|
||||
|
||||
`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.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` 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
|
||||
from pyasic import MinerData
|
||||
|
||||
# examples of miner data
|
||||
d1 = MinerData("192.168.1.1")
|
||||
d2 = MinerData("192.168.1.2")
|
||||
|
||||
list_of_miner_data = [d1, d2]
|
||||
|
||||
average_data = sum(list_of_miner_data, start=MinerData("0.0.0.0"))/len(list_of_miner_data)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
##### MinerConfig
|
||||
|
||||
`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` 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` and will do the conversion to the right type for you.
|
||||
|
||||
You can use the `MinerConfig` as follows:
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from pyasic import get_miner
|
||||
|
||||
|
||||
async def get_api_commands(miner_ip: str):
|
||||
# Get the miner
|
||||
miner = await get_miner(miner_ip)
|
||||
async def set_fault_light():
|
||||
miner = await get_miner("192.168.1.20")
|
||||
|
||||
# List all available commands
|
||||
# Can also be called explicitly with the function miner.api.get_commands()
|
||||
print(miner.api.commands)
|
||||
# get config
|
||||
cfg = await miner.get_config()
|
||||
|
||||
# send config
|
||||
await miner.send_config(cfg)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(get_api_commands("192.168.1.69"))
|
||||
asyncio.run(set_fault_light())
|
||||
|
||||
```
|
||||
|
||||
#### Use miner API commands to gather data
|
||||
---
|
||||
## Settings
|
||||
|
||||
The miner API commands will raise an `APIError` if they fail with a bad status code, to bypass this you must send them manually by using `miner.api.send_command(command, ignore_errors=True)`
|
||||
`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
|
||||
import asyncio
|
||||
from pyasic import settings
|
||||
|
||||
from pyasic import get_miner
|
||||
|
||||
|
||||
async def get_api_commands(miner_ip: str):
|
||||
# Get the miner
|
||||
miner = await get_miner(miner_ip)
|
||||
|
||||
# Run the devdetails command
|
||||
# This is equivalent to await miner.api.send_command("devdetails")
|
||||
devdetails: dict = await miner.api.devdetails()
|
||||
print(devdetails)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(get_api_commands("192.168.1.69"))
|
||||
settings.update("default_antminer_web_password", "my_pwd")
|
||||
```
|
||||
|
||||
##### Default values:
|
||||
```
|
||||
"network_ping_retries": 1,
|
||||
"network_ping_timeout": 3,
|
||||
"network_scan_semaphore": None,
|
||||
"factory_get_retries": 1,
|
||||
"factory_get_timeout": 3,
|
||||
"get_data_retries": 1,
|
||||
"api_function_timeout": 5,
|
||||
"antminer_mining_mode_as_str": False,
|
||||
"default_whatsminer_rpc_password": "admin",
|
||||
"default_innosilicon_web_password": "admin",
|
||||
"default_antminer_web_password": "root",
|
||||
"default_bosminer_web_password": "root",
|
||||
"default_vnish_web_password": "admin",
|
||||
"default_goldshell_web_password": "123456789",
|
||||
"default_auradine_web_password": "admin",
|
||||
"default_epic_web_password": "letmein",
|
||||
"default_hive_web_password": "admin",
|
||||
"default_antminer_ssh_password": "miner",
|
||||
"default_bosminer_ssh_password": "root",
|
||||
|
||||
# ADVANCED
|
||||
# Only use this if you know what you are doing
|
||||
"socket_linger_time": 1000,
|
||||
```
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# pyasic
|
||||
## Miner APIs
|
||||
Each miner has a unique API that is used to communicate with it.
|
||||
Each of these API types has commands that differ between them, and some commands have data that others do not.
|
||||
Each miner that is a subclass of [`BaseMiner`][pyasic.miners.BaseMiner] should have an API linked to it as `Miner.api`.
|
||||
|
||||
All API implementations inherit from [`BaseMinerAPI`][pyasic.API.BaseMinerAPI], which implements the basic communications protocols.
|
||||
|
||||
[`BaseMinerAPI`][pyasic.API.BaseMinerAPI] should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
|
||||
[`BaseMinerAPI`][pyasic.API.BaseMinerAPI] cannot be instantiated directly, it will raise a `TypeError`.
|
||||
Use these instead -
|
||||
|
||||
#### [BMMiner API][pyasic.API.bmminer.BMMinerAPI]
|
||||
#### [BOSMiner API][pyasic.API.bosminer.BOSMinerAPI]
|
||||
#### [BTMiner API][pyasic.API.btminer.BTMinerAPI]
|
||||
#### [CGMiner API][pyasic.API.cgminer.CGMinerAPI]
|
||||
#### [Unknown API][pyasic.API.unknown.UnknownAPI]
|
||||
|
||||
<br>
|
||||
|
||||
## BaseMinerAPI
|
||||
::: pyasic.API.BaseMinerAPI
|
||||
handler: python
|
||||
options:
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# pyasic
|
||||
## Miner Data
|
||||
|
||||
## Miner Data
|
||||
::: pyasic.data.MinerData
|
||||
handler: python
|
||||
options:
|
||||
@@ -13,3 +13,10 @@
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## Fan Data
|
||||
::: pyasic.data.Fan
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
175
docs/generate_miners.py
Normal file
175
docs/generate_miners.py
Normal file
@@ -0,0 +1,175 @@
|
||||
import asyncio
|
||||
import importlib
|
||||
import os
|
||||
import warnings
|
||||
|
||||
from pyasic.miners.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.AURADINE:
|
||||
return "Stock Firmware Auradine Miners"
|
||||
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"
|
||||
case MinerTypes.MARATHON:
|
||||
return "Mara Firmware Miners"
|
||||
case MinerTypes.BITAXE:
|
||||
return "Stock Firmware BitAxe Miners"
|
||||
case MinerTypes.ICERIVER:
|
||||
return "Stock Firmware IceRiver Miners"
|
||||
case MinerTypes.HAMMER:
|
||||
return "Stock Firmware Hammer 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))
|
||||
403
docs/index.md
403
docs/index.md
@@ -1,253 +1,212 @@
|
||||
# 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/)
|
||||
[](https://pypi.org/project/pyasic/)
|
||||
[](https://pyasic.readthedocs.io/en/latest/)
|
||||
[](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
|
||||
[](https://www.codefactor.io/repository/github/upstreamdata/pyasic)
|
||||
|
||||
[](https://pypi.org/project/pyasic/)
|
||||
[](https://pypi.org/project/pyasic/)
|
||||
[](https://pypi.org/project/pyasic/)
|
||||
[](https://www.codefactor.io/repository/github/upstreamdata/pyasic)
|
||||
[](https://github.com/UpstreamData/pyasic/commits/master/)
|
||||
[](https://github.com/psf/black)
|
||||
[](https://pyasic.readthedocs.io/en/latest/)
|
||||
[](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
|
||||
|
||||
## 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.
|
||||
## Installation
|
||||
---
|
||||
It is recommended to install `pyasic` in a [virtual environment](https://realpython.com/python-virtual-environments-a-primer/#what-other-popular-options-exist-aside-from-venv) to isolate it from the rest of your system.
|
||||
`pyasic` can be installed directly from pip, either with `pip install pyasic`, or a different command if using a tool like `pypoetry`.
|
||||
|
||||
<br>
|
||||
## 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.
|
||||
|
||||
## 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.
|
||||
```python
|
||||
import asyncio # asyncio for handling the async part
|
||||
from pyasic.network import MinerNetwork # miner network handles the scanning
|
||||
### 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.
|
||||
```python3
|
||||
import asyncio# (1)!
|
||||
from pyasic.network import MinerNetwork# (2)!
|
||||
|
||||
|
||||
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
|
||||
async def scan_miners():# (3)!
|
||||
network = MinerNetwork.from_subnet("192.168.1.50/24")# (4)!
|
||||
|
||||
# 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()# (5)!
|
||||
print(miners)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(scan_miners()) # run the scan asynchronously with asyncio.run()
|
||||
asyncio.run(scan_miners())# (6)!
|
||||
```
|
||||
|
||||
<br>
|
||||
1. `asyncio` for handling the async part.
|
||||
2. `MinerNetwork` handles the scanning.
|
||||
3. Define an async function to allow awaiting.
|
||||
4. Create a miner network.
|
||||
You can pass in any IP and it will use that in a subnet with a /24 mask (255 IPs).
|
||||
This uses the 192.168.1.0-255 network.
|
||||
5. Scan for miners asynchronously.
|
||||
This will return the correct type of miners (if they are supported) with all functionality.
|
||||
6. Run the scan asynchronously with asyncio.run().
|
||||
|
||||
## 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.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
|
||||
import asyncio# (1)!
|
||||
from pyasic import get_miner# (2)!
|
||||
|
||||
|
||||
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")
|
||||
async def get_miners():# (3)!
|
||||
miner_1 = await get_miner("192.168.1.75")# (4)!
|
||||
miner_2 = await get_miner("192.168.1.76")
|
||||
print(miner_1, miner_2)
|
||||
|
||||
tasks = [get_miner("192.168.1.75"), get_miner("192.168.1.76")]
|
||||
miners = await asyncio.gather(*tasks)# (5)!
|
||||
print(miners)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(get_miners()) # get the miners asynchronously with asyncio.run()
|
||||
asyncio.run(get_miners())# (6)!
|
||||
```
|
||||
|
||||
<br>
|
||||
1. `asyncio` for handling the async part.
|
||||
2. `get_miner` handles the miner type selection.
|
||||
3. Define an async function to allow awaiting.
|
||||
4. Get the miner.
|
||||
5. 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.
|
||||
6. Get the miners asynchronously with asyncio.run().
|
||||
|
||||
## 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].
|
||||
```python
|
||||
import asyncio
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
|
||||
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
|
||||
### One miner
|
||||
```python
|
||||
import asyncio# (1)!
|
||||
from pyasic import get_miner# (2)!
|
||||
|
||||
|
||||
async def gather_miner_data():# (3)!
|
||||
miner = await get_miner("192.168.1.75")# (4)!
|
||||
if miner is not None:# (5)!
|
||||
miner_data = await miner.get_data()# (6)!
|
||||
print(miner_data)# (7)!
|
||||
print(miner_data.hashrate) # hashrate of the miner in TH/s
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(gather_miner_data())
|
||||
asyncio.run(gather_miner_data())# (9)!
|
||||
```
|
||||
|
||||
1. `asyncio` for handling the async part.
|
||||
2. `get_miner` handles the miner type selection.
|
||||
3. Define an async function to allow awaiting.
|
||||
4. Get the miner.
|
||||
5. Make sure the miner exists.
|
||||
If this result is `None`, the miner may be offline.
|
||||
6. Get data from the miner.
|
||||
7. All the data from the dataclass.
|
||||
8. Hashrate of the miner, with unit information.
|
||||
9. Get the miner data asynchronously with asyncio.run().
|
||||
|
||||
### 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
|
||||
from pyasic.network import MinerNetwork # miner network handles the scanning
|
||||
import asyncio# (1)!
|
||||
from pyasic.network import MinerNetwork# (2)!
|
||||
|
||||
|
||||
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()
|
||||
async def gather_miner_data():# (3)!
|
||||
network = MinerNetwork.from_subnet("192.168.1.50/24")# (4)!
|
||||
miners = await network.scan()# (5)!
|
||||
|
||||
# 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])
|
||||
|
||||
for miner_data in all_miner_data:
|
||||
print(miner_data) # print out all the data one by one
|
||||
print(miner_data)# (7)!
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(gather_miner_data())
|
||||
asyncio.run(gather_miner_data())# (8)!
|
||||
```
|
||||
|
||||
<br>
|
||||
1. `asyncio` for handling the async part.
|
||||
2. `MinerNetwork` handles the scanning.
|
||||
3. Define an async function to allow awaiting.
|
||||
4. Create a miner network.
|
||||
5. Scan for miners asynchronously.
|
||||
6. Use `asyncio.gather()` with all the miners' `get_data()` functions to make them run together.
|
||||
7. Print out the data one at a time.
|
||||
8. Get the miner data asynchronously with asyncio.run().
|
||||
|
||||
## 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 following control functions.
|
||||
|
||||
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),
|
||||
[`stop_mining`](#stop-mining),
|
||||
[`resume_mining`](#resume-mining),
|
||||
[`send_config`](#send-config), and
|
||||
[`set_power_limit`](#set-power-limit).
|
||||
[`check_light`][pyasic.miners.base.MinerProtocol.check_light],
|
||||
[`fault_light_off`][pyasic.miners.base.MinerProtocol.fault_light_off],
|
||||
[`fault_light_on`][pyasic.miners.base.MinerProtocol.fault_light_on],
|
||||
[`get_config`][pyasic.miners.base.MinerProtocol.get_config],
|
||||
[`get_data`][pyasic.miners.base.MinerProtocol.get_data],
|
||||
[`get_errors`][pyasic.miners.base.MinerProtocol.get_errors],
|
||||
[`get_hostname`][pyasic.miners.base.MinerProtocol.get_hostname],
|
||||
[`get_model`][pyasic.miners.base.MinerProtocol.get_model],
|
||||
[`reboot`][pyasic.miners.base.MinerProtocol.reboot],
|
||||
[`restart_backend`][pyasic.miners.base.MinerProtocol.restart_backend],
|
||||
[`stop_mining`][pyasic.miners.base.MinerProtocol.stop_mining],
|
||||
[`resume_mining`][pyasic.miners.base.MinerProtocol.resume_mining],
|
||||
[`is_mining`][pyasic.miners.base.MinerProtocol.is_mining],
|
||||
[`send_config`][pyasic.miners.base.MinerProtocol.send_config], and
|
||||
[`set_power_limit`][pyasic.miners.base.MinerProtocol.set_power_limit].
|
||||
|
||||
<br>
|
||||
### Usage
|
||||
```python
|
||||
import asyncio# (1)!
|
||||
from pyasic import get_miner# (2)!
|
||||
|
||||
### Check Light
|
||||
::: pyasic.miners.BaseMiner.check_light
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
async def set_fault_light():# (3)!
|
||||
miner = await get_miner("192.168.1.20")# (4)!
|
||||
|
||||
### Fault Light Off
|
||||
::: pyasic.miners.BaseMiner.fault_light_off
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
await miner.fault_light_on()# (5)!
|
||||
|
||||
<br>
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(set_fault_light())# (6)!
|
||||
```
|
||||
|
||||
### Fault Light On
|
||||
::: pyasic.miners.BaseMiner.fault_light_on
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
1. `asyncio` for handling the async part.
|
||||
2. `get_miner` handles the miner type selection.
|
||||
3. Define an async function to allow awaiting.
|
||||
4. Get the miner.
|
||||
5. Call the miner control function.
|
||||
6. Call the control function asynchronously with asyncio.run().
|
||||
|
||||
<br>
|
||||
|
||||
### Get Config
|
||||
::: pyasic.miners.BaseMiner.get_config
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
## Helper dataclasses
|
||||
---
|
||||
|
||||
<br>
|
||||
### [`MinerConfig`][pyasic.config.MinerConfig] and [`MinerData`][pyasic.data.MinerData]
|
||||
|
||||
### Get Data
|
||||
::: pyasic.miners.BaseMiner.get_data
|
||||
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 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>
|
||||
|
||||
### Set Power Limit
|
||||
::: pyasic.miners.BaseMiner.set_power_limit
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
## [`MinerConfig`][pyasic.config.MinerConfig] and [`MinerData`][pyasic.data.MinerData]
|
||||
|
||||
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>
|
||||
---
|
||||
|
||||
### [`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.
|
||||
[`MinerData`][pyasic.data.MinerData] is a return from the [`get_data()`][pyasic.miners.base.MinerProtocol.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
|
||||
@@ -262,13 +221,69 @@ 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] 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()`][pyasic.miners.base.MinerProtocol.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.
|
||||
In most cases these helper functions should not be used, as [`send_config()`][pyasic.miners.base.MinerProtocol.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_web_password", "my_pwd")
|
||||
```
|
||||
|
||||
### Default values:
|
||||
```
|
||||
"network_ping_retries": 1,
|
||||
"network_ping_timeout": 3,
|
||||
"network_scan_semaphore": None,
|
||||
"factory_get_retries": 1,
|
||||
"factory_get_timeout": 3,
|
||||
"get_data_retries": 1,
|
||||
"api_function_timeout": 5,
|
||||
"antminer_mining_mode_as_str": False,
|
||||
"default_whatsminer_rpc_password": "admin",
|
||||
"default_innosilicon_web_password": "admin",
|
||||
"default_antminer_web_password": "root",
|
||||
"default_bosminer_web_password": "root",
|
||||
"default_vnish_web_password": "admin",
|
||||
"default_goldshell_web_password": "123456789",
|
||||
"default_auradine_web_password": "admin",
|
||||
"default_epic_web_password": "letmein",
|
||||
"default_hive_web_password": "admin",
|
||||
"default_antminer_ssh_password": "miner",
|
||||
"default_bosminer_ssh_password": "root",
|
||||
|
||||
# ADVANCED
|
||||
# Only use this if you know what you are doing
|
||||
"socket_linger_time": 1000,
|
||||
```
|
||||
|
||||
17
docs/miners/antminer/X15.md
Normal file
17
docs/miners/antminer/X15.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# pyasic
|
||||
## X15 Models
|
||||
|
||||
## Z15 (Stock)
|
||||
::: pyasic.miners.antminer.cgminer.X15.Z15.CGMinerZ15
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## Z15 Pro (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X15.Z15.BMMinerZ15Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
@@ -1,117 +1,115 @@
|
||||
# pyasic
|
||||
## X17 Models
|
||||
|
||||
## S17
|
||||
|
||||
## S17 (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S17+
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17_Plus.BMMinerS17Plus
|
||||
## S17+ (Stock)
|
||||
::: 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
|
||||
## S17 Pro (Stock)
|
||||
::: 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
|
||||
## S17e (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T17
|
||||
|
||||
## T17 (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T17+
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17_Plus.BMMinerT17Plus
|
||||
## T17+ (Stock)
|
||||
::: 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
|
||||
## T17e (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## S17 (BOS)
|
||||
|
||||
## S17 (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S17+ (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17_Plus.BOSMinerS17Plus
|
||||
## S17+ (BOS+)
|
||||
::: 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
|
||||
## S17 Pro (BOS+)
|
||||
::: 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
|
||||
## S17e (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T17 (BOS)
|
||||
|
||||
## T17 (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T17+ (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.T17_Plus.BOSMinerT17Plus
|
||||
## T17+ (BOS+)
|
||||
::: 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
|
||||
## T17e (BOS+)
|
||||
::: 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
|
||||
|
||||
|
||||
@@ -1,102 +1,486 @@
|
||||
# pyasic
|
||||
## X19 Models
|
||||
|
||||
## S19
|
||||
|
||||
## S19 (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19_Pro.BMMinerS19Pro
|
||||
## S19L (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19L
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## S19a
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19a.BMMinerS19a
|
||||
## S19 Pro (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## S19j
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19j.BMMinerS19j
|
||||
## S19j (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19j
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19j_Pro.BMMinerS19jPro
|
||||
## S19i (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19i
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 XP
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19_XP.BMMinerS19XP
|
||||
## S19+ (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T19
|
||||
## S19j No PIC (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jNoPIC
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro+ (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19ProPlus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 XP (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19XP
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19a (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19a
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19a Pro (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19aPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Hydro (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19Hydro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro Hydro (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19ProHydro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro+ Hydro (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19ProPlusHydro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19K Pro (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19KPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j XP (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jXP
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T19 (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X19.T19.BMMinerT19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## S19 (BOS)
|
||||
|
||||
## S19 (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19_Pro.BOSMinerS19Pro
|
||||
## S19+ (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## S19j (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19j.BOSMinerS19j
|
||||
## S19 Pro (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19j_Pro.BOSMinerS19jPro
|
||||
## S19a (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19a
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T19 (BOS)
|
||||
## S19a Pro (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19aPro
|
||||
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
|
||||
|
||||
## 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
|
||||
|
||||
## S19j Pro No PIC (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jProNoPIC
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro+ (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jProPlus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro+ (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jProPlus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro+ No PIC (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jProPlusNoPIC
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19k Pro No PIC (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19kProNoPIC
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19k Pro No PIC (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19kProNoPIC
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 XP (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19XP
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro+ Hydro (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19ProPlusHydro
|
||||
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
|
||||
|
||||
## 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
|
||||
|
||||
## S19 Pro Hydro (VNish)
|
||||
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19ProHydro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19k Pro (VNish)
|
||||
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19kPro
|
||||
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
|
||||
|
||||
## S19j Pro+ (ePIC)
|
||||
::: pyasic.miners.antminer.epic.X19.S19.ePICS19jProPlus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19k Pro (ePIC)
|
||||
::: pyasic.miners.antminer.epic.X19.S19.ePICS19kPro
|
||||
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
|
||||
|
||||
## S19j Pro (Hive)
|
||||
::: pyasic.miners.antminer.hiveon.X19.S19.HiveonS19jPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 (Hive)
|
||||
::: pyasic.miners.antminer.hiveon.X19.S19.HiveonS19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 (LuxOS)
|
||||
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro (LuxOS)
|
||||
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro (LuxOS)
|
||||
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19jPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro+ (LuxOS)
|
||||
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19jProPlus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19k Pro (LuxOS)
|
||||
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19kPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 XP (LuxOS)
|
||||
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19XP
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T19 (LuxOS)
|
||||
::: pyasic.miners.antminer.luxos.X19.T19.LUXMinerT19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X19.S19.MaraS19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X19.S19.MaraS19Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X19.S19.MaraS19j
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j No PIC (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X19.S19.MaraS19jNoPIC
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X19.S19.MaraS19jPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 XP (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X19.S19.MaraS19XP
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19K Pro (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X19.S19.MaraS19KPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
87
docs/miners/antminer/X21.md
Normal file
87
docs/miners/antminer/X21.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# pyasic
|
||||
## X21 Models
|
||||
|
||||
## S21 (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S21 Pro (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T21 (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X21.T21.BMMinerT21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S21 (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T21 (BOS+)
|
||||
::: pyasic.miners.antminer.bosminer.X21.T21.BOSMinerT21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S21 (VNish)
|
||||
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S21 (ePIC)
|
||||
::: pyasic.miners.antminer.epic.X21.S21.ePICS21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S21 Pro (ePIC)
|
||||
::: pyasic.miners.antminer.epic.X21.S21.ePICS21Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T21 (ePIC)
|
||||
::: pyasic.miners.antminer.epic.X21.T21.ePICT21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S21 (LuxOS)
|
||||
::: pyasic.miners.antminer.luxos.X21.S21.LUXMinerS21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S21 (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X21.S21.MaraS21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T21 (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X21.T21.MaraT21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
52
docs/miners/antminer/X3.md
Normal file
52
docs/miners/antminer/X3.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# pyasic
|
||||
## X3 Models
|
||||
|
||||
## D3 (Stock)
|
||||
::: pyasic.miners.antminer.cgminer.X3.D3.CGMinerD3
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## HS3 (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X3.HS3.BMMinerHS3
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## L3+ (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X3.L3.BMMinerL3Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## KA3 (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X3.KA3.BMMinerKA3
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## KS3 (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X3.KS3.BMMinerKS3
|
||||
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
|
||||
|
||||
## L3+ (VNish)
|
||||
::: pyasic.miners.antminer.vnish.X3.L3.VnishL3Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
17
docs/miners/antminer/X5.md
Normal file
17
docs/miners/antminer/X5.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# pyasic
|
||||
## X5 Models
|
||||
|
||||
## DR5 (Stock)
|
||||
::: pyasic.miners.antminer.cgminer.X5.DR5.CGMinerDR5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## KS5 (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X5.KS5.BMMinerKS5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
31
docs/miners/antminer/X7.md
Normal file
31
docs/miners/antminer/X7.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# pyasic
|
||||
## X7 Models
|
||||
|
||||
## L7 (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X7.L7.BMMinerL7
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## K7 (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X7.K7.BMMinerK7
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## D7 (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X7.D7.BMMinerD7
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## L7 (VNish)
|
||||
::: pyasic.miners.antminer.vnish.X7.L7.VnishL7
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
@@ -1,36 +1,73 @@
|
||||
# pyasic
|
||||
## X9 Models
|
||||
|
||||
|
||||
## X9 (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X9.S9.BOSMinerS9
|
||||
## E9Pro (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X9.E9.BMMinerE9Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## D9 (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X9.D9.BMMinerD9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S9
|
||||
|
||||
## S9 (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S9i
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X9.S9i.BMMinerS9i
|
||||
## S9i (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9i
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T9
|
||||
## S9j (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9j
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T9 (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X9.T9.BMMinerT9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## L9 (Stock)
|
||||
::: pyasic.miners.antminer.bmminer.X9.L9.BMMinerL9
|
||||
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 (Hive)
|
||||
::: 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
|
||||
|
||||
|
||||
24
docs/miners/auradine/AD.md
Normal file
24
docs/miners/auradine/AD.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# pyasic
|
||||
## AD Models
|
||||
|
||||
## AT1500 (Stock)
|
||||
::: pyasic.miners.auradine.flux.AD.AT1.AuradineFluxAT1500
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## AT2860 (Stock)
|
||||
::: pyasic.miners.auradine.flux.AD.AT2.AuradineFluxAT2860
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## AT2880 (Stock)
|
||||
::: pyasic.miners.auradine.flux.AD.AT2.AuradineFluxAT2880
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
17
docs/miners/auradine/AI.md
Normal file
17
docs/miners/auradine/AI.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# pyasic
|
||||
## AI Models
|
||||
|
||||
## AI2500 (Stock)
|
||||
::: pyasic.miners.auradine.flux.AI.AI2.AuradineFluxAI2500
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## AI3680 (Stock)
|
||||
::: pyasic.miners.auradine.flux.AI.AI3.AuradineFluxAI3680
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
17
docs/miners/auradine/AT.md
Normal file
17
docs/miners/auradine/AT.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# pyasic
|
||||
## AT Models
|
||||
|
||||
## AD2500 (Stock)
|
||||
::: pyasic.miners.auradine.flux.AT.AD2.AuradineFluxAD2500
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## AD3500 (Stock)
|
||||
::: pyasic.miners.auradine.flux.AT.AD3.AuradineFluxAD3500
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
# pyasic
|
||||
## A10X Models
|
||||
|
||||
## A1026
|
||||
|
||||
## Avalon 1026 (Stock)
|
||||
::: pyasic.miners.avalonminer.cgminer.A10X.A1026.CGMinerAvalon1026
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A1047
|
||||
|
||||
## Avalon 1047 (Stock)
|
||||
::: pyasic.miners.avalonminer.cgminer.A10X.A1047.CGMinerAvalon1047
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A1066
|
||||
|
||||
## Avalon 1066 (Stock)
|
||||
::: pyasic.miners.avalonminer.cgminer.A10X.A1066.CGMinerAvalon1066
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
17
docs/miners/avalonminer/A11X.md
Normal file
17
docs/miners/avalonminer/A11X.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# pyasic
|
||||
## A11X Models
|
||||
|
||||
## Avalon 1126 Pro (Stock)
|
||||
::: pyasic.miners.avalonminer.cgminer.A11X.A1126.CGMinerAvalon1126Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## Avalon 1166 Pro (Stock)
|
||||
::: 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 (Stock)
|
||||
::: 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 (Stock)
|
||||
::: pyasic.miners.avalonminer.cgminer.A7X.A721.CGMinerAvalon721
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A741
|
||||
|
||||
## Avalon 741 (Stock)
|
||||
::: pyasic.miners.avalonminer.cgminer.A7X.A741.CGMinerAvalon741
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A761
|
||||
|
||||
## Avalon 761 (Stock)
|
||||
::: 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 (Stock)
|
||||
::: pyasic.miners.avalonminer.cgminer.A8X.A821.CGMinerAvalon821
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A841
|
||||
|
||||
## Avalon 841 (Stock)
|
||||
::: pyasic.miners.avalonminer.cgminer.A8X.A841.CGMinerAvalon841
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A851
|
||||
|
||||
## Avalon 851 (Stock)
|
||||
::: 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 (Stock)
|
||||
::: pyasic.miners.avalonminer.cgminer.A9X.A921.CGMinerAvalon921
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
10
docs/miners/avalonminer/nano.md
Normal file
10
docs/miners/avalonminer/nano.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# pyasic
|
||||
## nano Models
|
||||
|
||||
## Avalon Nano 3 (Stock)
|
||||
::: pyasic.miners.avalonminer.cgminer.nano.nano3.CGMinerAvalonNano3
|
||||
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,15 @@
|
||||
# pyasic
|
||||
## BOSMiner Backend
|
||||
|
||||
::: pyasic.miners._backends.bosminer.BOSMiner
|
||||
::: pyasic.miners.backends.braiins_os.BOSMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## BOSer Backend
|
||||
|
||||
::: pyasic.miners.backends.braiins_os.BOSer
|
||||
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
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# pyasic
|
||||
## BOSMinerAPI
|
||||
::: pyasic.API.bosminer.BOSMinerAPI
|
||||
## ePIC Backend
|
||||
|
||||
::: pyasic.miners.backends.epic.ePIC
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -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
|
||||
@@ -1,10 +1,12 @@
|
||||
# pyasic
|
||||
## Base Miner
|
||||
[`BaseMiner`][pyasic.miners.BaseMiner] is the basis for all miner classes, they all subclass (usually indirectly) from this class.
|
||||
[`BaseMiner`][pyasic.miners.base.BaseMiner] is the basis for all miner classes, they all subclass (usually indirectly) from this class.
|
||||
|
||||
You may not instantiate this class on its own, only subclass from it. Trying to instantiate an instance of this class will raise `TypeError`.
|
||||
This class inherits from the [`MinerProtocol`][pyasic.miners.base.MinerProtocol], which outlines functionality for miners.
|
||||
|
||||
::: pyasic.miners.BaseMiner
|
||||
You may not instantiate this class on its own, only subclass from it.
|
||||
|
||||
::: pyasic.miners.base.BaseMiner
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
31
docs/miners/bitaxe/BM.md
Normal file
31
docs/miners/bitaxe/BM.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# pyasic
|
||||
## BM Models
|
||||
|
||||
## Supra (Stock)
|
||||
::: pyasic.miners.bitaxe.espminer.BM.BM1368.BitAxeSupra
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## Ultra (Stock)
|
||||
::: pyasic.miners.bitaxe.espminer.BM.BM1366.BitAxeUltra
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## Max (Stock)
|
||||
::: pyasic.miners.bitaxe.espminer.BM.BM1397.BitAxeMax
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## Gamma (Stock)
|
||||
::: pyasic.miners.bitaxe.espminer.BM.BM1370.BitAxeGamma
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
17
docs/miners/blockminer/blockminer.md
Normal file
17
docs/miners/blockminer/blockminer.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# pyasic
|
||||
## blockminer Models
|
||||
|
||||
## BlockMiner 520i (ePIC)
|
||||
::: pyasic.miners.blockminer.epic.blockminer.blockminer.ePICBlockMiner520i
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## BlockMiner 720i (ePIC)
|
||||
::: pyasic.miners.blockminer.epic.blockminer.blockminer.ePICBlockMiner720i
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
17
docs/miners/braiins/BMM.md
Normal file
17
docs/miners/braiins/BMM.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# pyasic
|
||||
## BMM Models
|
||||
|
||||
## BMM100 (BOS+)
|
||||
::: pyasic.miners.braiins.braiins.BMM.BMM.BraiinsBMM100
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## BMM101 (BOS+)
|
||||
::: pyasic.miners.braiins.braiins.BMM.BMM.BraiinsBMM101
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
10
docs/miners/functions.md
Normal file
10
docs/miners/functions.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## Control functionality
|
||||
|
||||
All control functionality is outlined by the [`MinerProtocol`][pyasic.miners.base.MinerProtocol] class.
|
||||
|
||||
## Miner Protocol
|
||||
|
||||
::: pyasic.miners.base.MinerProtocol
|
||||
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 (Stock)
|
||||
::: pyasic.miners.goldshell.bfgminer.X5.CK5.GoldshellCK5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## HS5 (Stock)
|
||||
::: pyasic.miners.goldshell.bfgminer.X5.HS5.GoldshellHS5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## KD5 (Stock)
|
||||
::: pyasic.miners.goldshell.bfgminer.X5.KD5.GoldshellKD5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
17
docs/miners/goldshell/XBox.md
Normal file
17
docs/miners/goldshell/XBox.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# pyasic
|
||||
## XBox Models
|
||||
|
||||
## KD Box II (Stock)
|
||||
::: pyasic.miners.goldshell.bfgminer.XBox.KDBox.GoldshellKDBoxII
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## KD Box Pro (Stock)
|
||||
::: pyasic.miners.goldshell.bfgminer.XBox.KDBox.GoldshellKDBoxPro
|
||||
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 (Stock)
|
||||
::: pyasic.miners.goldshell.bfgminer.XMax.KDMax.GoldshellKDMax
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
10
docs/miners/hammer/DX.md
Normal file
10
docs/miners/hammer/DX.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# pyasic
|
||||
## DX Models
|
||||
|
||||
## D10 (Stock)
|
||||
::: pyasic.miners.hammer.blackminer.DX.D10.HammerD10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
66
docs/miners/iceriver/KSX.md
Normal file
66
docs/miners/iceriver/KSX.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# pyasic
|
||||
## KSX Models
|
||||
|
||||
## KS0 (Stock)
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS0.IceRiverKS0
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## KS1 (Stock)
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS1.IceRiverKS1
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## KS2 (Stock)
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS2.IceRiverKS2
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## KS3 (Stock)
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS3.IceRiverKS3
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## KS3L (Stock)
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS3.IceRiverKS3L
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## KS3M (Stock)
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS3.IceRiverKS3M
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## KS5 (Stock)
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS5.IceRiverKS5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## KS5L (Stock)
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS5.IceRiverKS5L
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## KS5M (Stock)
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS5.IceRiverKS5M
|
||||
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 (Stock)
|
||||
::: pyasic.miners.innosilicon.cgminer.A10X.A10X.InnosiliconA10X
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
17
docs/miners/innosilicon/A11X.md
Normal file
17
docs/miners/innosilicon/A11X.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# pyasic
|
||||
## A11X Models
|
||||
|
||||
## A11 (Stock)
|
||||
::: pyasic.miners.innosilicon.cgminer.A11X.A11.InnosiliconA11
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A11MX (Stock)
|
||||
::: pyasic.miners.innosilicon.cgminer.A11X.A11M.InnosiliconA11MX
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# pyasic
|
||||
## T3X Models
|
||||
|
||||
## T3H+
|
||||
|
||||
::: pyasic.miners.innosilicon.cgminer.T3X.T3H_Plus.CGMinerInnosiliconT3HPlus
|
||||
## T3H+ (Stock)
|
||||
::: pyasic.miners.innosilicon.cgminer.T3X.T3H.InnosiliconT3HPlus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
# pyasic
|
||||
## Miner Factory
|
||||
|
||||
::: pyasic.miners.miner_factory.MinerFactory
|
||||
[`MinerFactory`][pyasic.miners.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.miners.factory.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.factory.MinerFactory
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
<br>
|
||||
|
||||
## Get Miner
|
||||
::: pyasic.miners.get_miner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -9,12 +25,12 @@
|
||||
<br>
|
||||
|
||||
## AnyMiner
|
||||
::: pyasic.miners.miner_factory.AnyMiner
|
||||
::: pyasic.miners.base.AnyMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
[`AnyMiner`][pyasic.miners.miner_factory.AnyMiner] is a placeholder type variable used for typing returns of functions.
|
||||
A function returning [`AnyMiner`][pyasic.miners.miner_factory.AnyMiner] will always return a subclass of [`BaseMiner`][pyasic.miners.BaseMiner],
|
||||
[`AnyMiner`][pyasic.miners.base.AnyMiner] is a placeholder type variable used for typing returns of functions.
|
||||
A function returning [`AnyMiner`][pyasic.miners.base.AnyMiner] will always return a subclass of [`BaseMiner`][pyasic.miners.base.BaseMiner],
|
||||
and is used to specify a function returning some arbitrary type of miner class instance.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,90 +1,94 @@
|
||||
# pyasic
|
||||
## M2X Models
|
||||
|
||||
## M20V10
|
||||
|
||||
## M20 V10 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M20SV10
|
||||
|
||||
## M20S V10 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M20SV20
|
||||
|
||||
## M20S V20 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M20SV30
|
||||
|
||||
## M20S V30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M20S+V30
|
||||
## M20P V10 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20P.BTMinerM20PV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M20P V30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20P.BTMinerM20PV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M20S+ V30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlusV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M21V10
|
||||
|
||||
## M21 V10 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M21SV20
|
||||
|
||||
## M21S V20 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M21SV60
|
||||
|
||||
## M21S V60 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M21SV70
|
||||
|
||||
## M21S V70 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV70
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M21S+V20
|
||||
|
||||
## M21S+ V20 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlusV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M29V10
|
||||
|
||||
## M29 V10 (Stock)
|
||||
::: 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,242 +1,269 @@
|
||||
# pyasic
|
||||
## M5X Models
|
||||
|
||||
## M50VG30
|
||||
## M50 VE30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VE30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50 VG30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VG30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50VH10
|
||||
|
||||
## M50 VH10 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50VH20
|
||||
|
||||
## M50 VH20 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50VH30
|
||||
|
||||
## M50 VH30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50VH40
|
||||
|
||||
## M50 VH40 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50VH50
|
||||
|
||||
## M50 VH50 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50VH60
|
||||
|
||||
## M50 VH60 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50VH70
|
||||
|
||||
## M50 VH70 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH70
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50VH80
|
||||
|
||||
## M50 VH80 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH80
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50VJ10
|
||||
## M50 VH90 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH90
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50 VJ10 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50VJ20
|
||||
|
||||
## M50 VJ20 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50VJ30
|
||||
|
||||
## M50 VJ30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50SVJ10
|
||||
|
||||
## M50S VJ10 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50SVJ20
|
||||
|
||||
## M50S VJ20 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50SVJ30
|
||||
|
||||
## M50S VJ30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50SVH10
|
||||
|
||||
## M50S VH10 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50SVH20
|
||||
|
||||
## M50S VH20 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50SVH30
|
||||
|
||||
## M50S VH30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50SVH40
|
||||
|
||||
## M50S VH40 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50SVH50
|
||||
|
||||
## M50S VH50 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S+VH30
|
||||
|
||||
## M50S+ VH30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S+VH40
|
||||
|
||||
## M50S+ VH40 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S+VJ30
|
||||
|
||||
## M50S+ VJ30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVJ30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M53VH30
|
||||
## M50S+ VK20 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVK20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S++ VK10 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S++ VK20 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S++ VK30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M53 VH30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M53SVH30
|
||||
|
||||
## M53S VH30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M53S+VJ30
|
||||
## M53S VJ40 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVJ40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M53S+ VJ30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVJ30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M56VH30
|
||||
## M53S++ VK10 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVK10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M56 VH30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M56.BTMinerM56VH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M56SVH30
|
||||
|
||||
## M56S VH30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M56S.BTMinerM56SVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M56S+VJ30
|
||||
|
||||
## M56S+ VJ30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVJ30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M59VH30
|
||||
|
||||
## M59 VH30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M59.BTMinerM59VH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
136
docs/miners/whatsminer/M6X.md
Normal file
136
docs/miners/whatsminer/M6X.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# pyasic
|
||||
## M6X Models
|
||||
|
||||
## M60 VK10 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M60 VK20 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M60 VK30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M60 VK40 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M60S VK10 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M60S VK20 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M60S VK30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M60S VK40 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M63 VK10 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VK10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M63 VK20 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VK20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M63 VK30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VK30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M63S VK10 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M63S VK20 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M63S VK30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M66 VK20 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M66.BTMinerM66VK20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M66 VK30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M66.BTMinerM66VK30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M66S VK20 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M66S VK30 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M66S VK40 (Stock)
|
||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK40
|
||||
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
|
||||
@@ -1,3 +0,0 @@
|
||||
jinja2<3.1.0
|
||||
mkdocs
|
||||
mkdocstrings[python]
|
||||
27
docs/rpc/api.md
Normal file
27
docs/rpc/api.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# pyasic
|
||||
## Miner RPC APIs
|
||||
Each miner has a unique RPC API that is used to communicate with it.
|
||||
Each of these API types has commands that differ between them, and some commands have data that others do not.
|
||||
Each miner that is a subclass of [`BaseMiner`][pyasic.miners.base.BaseMiner] may have an API linked to it as `Miner.rpc`.
|
||||
|
||||
All RPC API implementations inherit from [`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI], which implements the basic communications protocols.
|
||||
|
||||
[`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI] should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
|
||||
[`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI] cannot be instantiated directly, it will raise a `TypeError`.
|
||||
Use these instead -
|
||||
|
||||
#### [BFGMiner API][pyasic.rpc.bfgminer.BFGMinerRPCAPI]
|
||||
#### [BMMiner API][pyasic.rpc.bmminer.BMMinerRPCAPI]
|
||||
#### [BOSMiner API][pyasic.rpc.bosminer.BOSMinerRPCAPI]
|
||||
#### [BTMiner API][pyasic.rpc.btminer.BTMinerRPCAPI]
|
||||
#### [CGMiner API][pyasic.rpc.cgminer.CGMinerRPCAPI]
|
||||
#### [LUXMiner API][pyasic.rpc.luxminer.LUXMinerRPCAPI]
|
||||
#### [Unknown API][pyasic.rpc.unknown.UnknownRPCAPI]
|
||||
|
||||
<br>
|
||||
|
||||
## BaseMinerRPCAPI
|
||||
::: pyasic.rpc.base.BaseMinerRPCAPI
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
7
docs/rpc/bfgminer.md
Normal file
7
docs/rpc/bfgminer.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## BFGMinerRPCAPI
|
||||
::: pyasic.rpc.bfgminer.BFGMinerRPCAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/rpc/bmminer.md
Normal file
7
docs/rpc/bmminer.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## BMMinerRPCAPI
|
||||
::: pyasic.rpc.bmminer.BMMinerRPCAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/rpc/bosminer.md
Normal file
7
docs/rpc/bosminer.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## BOSMinerRPCAPI
|
||||
::: pyasic.rpc.bosminer.BOSMinerRPCAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
@@ -1,6 +1,6 @@
|
||||
# pyasic
|
||||
## BTMinerAPI
|
||||
::: pyasic.API.btminer.BTMinerAPI
|
||||
## BTMinerRPCAPI
|
||||
::: pyasic.rpc.btminer.BTMinerRPCAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
7
docs/rpc/cgminer.md
Normal file
7
docs/rpc/cgminer.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## CGMinerRPCAPI
|
||||
::: pyasic.rpc.cgminer.CGMinerRPCAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/rpc/luxminer.md
Normal file
7
docs/rpc/luxminer.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## LUXMinerRPCAPI
|
||||
::: pyasic.rpc.luxminer.LUXMinerRPCAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/rpc/unknown.md
Normal file
7
docs/rpc/unknown.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## UnknownRPCAPI
|
||||
::: pyasic.rpc.unknown.UnknownRPCAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
41
docs/settings/settings.md
Normal file
41
docs/settings/settings.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# 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`
|
||||
- `antminer_mining_mode_as_str`
|
||||
- `default_whatsminer_rpc_password`
|
||||
- `default_innosilicon_web_password`
|
||||
- `default_antminer_web_password`
|
||||
- `default_bosminer_web_password`
|
||||
- `default_vnish_web_password`
|
||||
- `default_goldshell_web_password`
|
||||
- `default_auradine_web_password`
|
||||
- `default_epic_web_password`
|
||||
- `default_hive_web_password`
|
||||
- `default_antminer_ssh_password`
|
||||
- `default_bosminer_ssh_password`
|
||||
|
||||
|
||||
### 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
|
||||
14
docs/web/antminer.md
Normal file
14
docs/web/antminer.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# pyasic
|
||||
## AntminerModernWebAPI
|
||||
::: pyasic.web.antminer.AntminerModernWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## AntminerOldWebAPI
|
||||
::: pyasic.web.antminer.AntminerOldWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
27
docs/web/api.md
Normal file
27
docs/web/api.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# pyasic
|
||||
## Miner Web APIs
|
||||
Each miner has a unique Web API that is used to communicate with it.
|
||||
Each of these API types has commands that differ between them, and some commands have data that others do not.
|
||||
Each miner that is a subclass of [`BaseMiner`][pyasic.miners.base.BaseMiner] may have an API linked to it as `Miner.web`.
|
||||
|
||||
All API implementations inherit from [`BaseWebAPI`][pyasic.web.BaseWebAPI], which implements the basic communications protocols.
|
||||
|
||||
[`BaseWebAPI`][pyasic.web.BaseWebAPI] should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
|
||||
Use these instead -
|
||||
|
||||
#### [AntminerModerNWebAPI][pyasic.web.antminer.AntminerModernWebAPI]
|
||||
#### [AntminerOldWebAPI][pyasic.web.antminer.AntminerOldWebAPI]
|
||||
#### [AuradineWebAPI][pyasic.web.auradine.AuradineWebAPI]
|
||||
#### [ePICWebAPI][pyasic.web.epic.ePICWebAPI]
|
||||
#### [GoldshellWebAPI][pyasic.web.goldshell.GoldshellWebAPI]
|
||||
#### [InnosiliconWebAPI][pyasic.web.innosilicon.InnosiliconWebAPI]
|
||||
#### [MaraWebAPI][pyasic.web.marathon.MaraWebAPI]
|
||||
#### [VNishWebAPI][pyasic.web.vnish.VNishWebAPI]
|
||||
|
||||
<br>
|
||||
|
||||
## BaseWebAPI
|
||||
::: pyasic.web.BaseWebAPI
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
7
docs/web/auradine.md
Normal file
7
docs/web/auradine.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## AuradineWebAPI
|
||||
::: pyasic.web.auradine.AuradineWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
@@ -1,6 +1,6 @@
|
||||
# pyasic
|
||||
## CGMinerAPI
|
||||
::: pyasic.API.cgminer.CGMinerAPI
|
||||
## ePICWebAPI
|
||||
::: pyasic.web.epic.ePICWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
7
docs/web/goldshell.md
Normal file
7
docs/web/goldshell.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## GoldshellWebAPI
|
||||
::: pyasic.web.goldshell.GoldshellWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/web/innosilicon.md
Normal file
7
docs/web/innosilicon.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## InnosiliconWebAPI
|
||||
::: pyasic.web.innosilicon.InnosiliconWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
@@ -1,6 +1,6 @@
|
||||
# pyasic
|
||||
## UnknownAPI
|
||||
::: pyasic.API.unknown.UnknownAPI
|
||||
## MaraWebAPI
|
||||
::: pyasic.web.marathon.MaraWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -1,6 +1,6 @@
|
||||
# pyasic
|
||||
## BMMinerAPI
|
||||
::: pyasic.API.bmminer.BMMinerAPI
|
||||
## VNishWebAPI
|
||||
::: pyasic.web.vnish.VNishWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
145
mkdocs.yml
145
mkdocs.yml
@@ -1,46 +1,111 @@
|
||||
site_name: pyasic
|
||||
repo_url: https://github.com/UpstreamData/pyasic
|
||||
site_url: !ENV SITE_URL
|
||||
theme:
|
||||
name: material
|
||||
features:
|
||||
- content.code.copy
|
||||
- content.code.annotate
|
||||
palette:
|
||||
- media: "(prefers-color-scheme)"
|
||||
toggle:
|
||||
icon: material/brightness-auto
|
||||
name: Switch to light mode
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
toggle:
|
||||
icon: material/weather-night
|
||||
name: Switch to dark mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
toggle:
|
||||
icon: material/weather-sunny
|
||||
name: Switch to auto mode
|
||||
markdown_extensions:
|
||||
- pymdownx.highlight:
|
||||
anchor_linenums: true
|
||||
line_spans: __span
|
||||
pygments_lang_class: true
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.snippets
|
||||
- pymdownx.superfences
|
||||
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:
|
||||
- RPC APIs:
|
||||
- Intro: "rpc/api.md"
|
||||
- BFGMiner: "rpc/bfgminer.md"
|
||||
- BMMiner: "rpc/bmminer.md"
|
||||
- BOSMiner: "rpc/bosminer.md"
|
||||
- BTMiner: "rpc/btminer.md"
|
||||
- CGMiner: "rpc/cgminer.md"
|
||||
- LUXMiner: "rpc/luxminer.md"
|
||||
- Unknown: "rpc/unknown.md"
|
||||
- Web APIs:
|
||||
- Intro: "web/api.md"
|
||||
- Antminer: "web/antminer.md"
|
||||
- Auradine: "web/auradine.md"
|
||||
- ePIC: "web/epic.md"
|
||||
- Goldshell: "web/goldshell.md"
|
||||
- Innosilicon: "web/innosilicon.md"
|
||||
- Marathon: "web/marathon.md"
|
||||
- VNish: "web/vnish.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"
|
||||
- Antminer X21: "miners/antminer/X21.md"
|
||||
- Avalon Nano: "miners/avalonminer/nano.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"
|
||||
- Whatsminer M6X: "miners/whatsminer/M6X.md"
|
||||
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
||||
- Innosilicon A10X: "miners/innosilicon/A10X.md"
|
||||
- Innosilicon A11X: "miners/innosilicon/A11X.md"
|
||||
- Goldshell X5: "miners/goldshell/X5.md"
|
||||
- Goldshell XMax: "miners/goldshell/XMax.md"
|
||||
- Goldshell XBox: "miners/goldshell/XBox.md"
|
||||
- Auradine AD: "miners/auradine/AD.md"
|
||||
- Auradine AI: "miners/auradine/AI.md"
|
||||
- Auradine AT: "miners/auradine/AT.md"
|
||||
- Blockminer: "miners/blockminer/blockminer.md"
|
||||
- BitAxe BM: "miners/bitaxe/BM.md"
|
||||
- Hammer DX: "miners/hammer/DX.md"
|
||||
- Iceriver KSX: "miners/iceriver/KSX.md"
|
||||
- Base Miner: "miners/base_miner.md"
|
||||
- Settings:
|
||||
- Settings: "settings/settings.md"
|
||||
|
||||
plugins:
|
||||
- mkdocstrings
|
||||
|
||||
1926
poetry.lock
generated
1926
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,731 +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
|
||||
|
||||
from pyasic.API import APIError, BaseMinerAPI
|
||||
|
||||
|
||||
class BMMinerAPI(BaseMinerAPI):
|
||||
"""An abstraction of the BMMiner API.
|
||||
|
||||
Each method corresponds to an API command in BMMiner.
|
||||
|
||||
[BMMiner API documentation](https://github.com/jameshilliard/bmminer/blob/master/API-README)
|
||||
|
||||
This class abstracts use of the BMMiner 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 multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||
# make sure we can actually run each command, otherwise they will fail
|
||||
commands = self._check_commands(*commands)
|
||||
# standard multicommand format is "command1+command2"
|
||||
# doesn't work for S19 which uses the backup _x19_multicommand
|
||||
command = "+".join(commands)
|
||||
try:
|
||||
data = await self.send_command(command, allow_warning=allow_warning)
|
||||
except APIError:
|
||||
logging.debug(f"{self} - (Multicommand) - Handling X19 multicommand.")
|
||||
data = await self._x19_multicommand(
|
||||
*command.split("+"), allow_warning=allow_warning
|
||||
)
|
||||
return data
|
||||
|
||||
async def _x19_multicommand(self, *commands, allow_warning: bool = True):
|
||||
data = None
|
||||
try:
|
||||
data = {}
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
data[cmd] = []
|
||||
data[cmd].append(
|
||||
await self.send_command(cmd, allow_warning=allow_warning)
|
||||
)
|
||||
except APIError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logging.warning(
|
||||
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"
|
||||
)
|
||||
return data
|
||||
|
||||
async def version(self) -> dict:
|
||||
"""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 edevs(self, old: bool = False) -> dict:
|
||||
"""Get data on each PGA/ASC with their details, ignoring blacklisted and zombie devices.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
old: Include zombie devices that became zombies less than 'old' seconds ago
|
||||
|
||||
Returns:
|
||||
Data on each PGA/ASC with their details.
|
||||
</details>
|
||||
"""
|
||||
if old:
|
||||
return await self.send_command("edevs", parameters=old)
|
||||
else:
|
||||
return await self.send_command("edevs")
|
||||
|
||||
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 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 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 BMMiner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
A single "BYE" before BMMiner 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 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 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 BMMiner 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 estats(self, old: bool = False) -> dict:
|
||||
"""Get stats of each device/pool with more than 1 getwork, ignoring zombie devices.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
old: Include zombie devices that became zombies less than 'old' seconds ago.
|
||||
|
||||
Returns:
|
||||
Stats of each device/pool with more than 1 getwork, ignoring zombie devices.
|
||||
</details>
|
||||
"""
|
||||
if old:
|
||||
return await self.send_command("estats", parameters=old)
|
||||
else:
|
||||
return await self.send_command("estats")
|
||||
|
||||
async def check(self, command: str) -> dict:
|
||||
"""Check if the command command exists in BMMiner.
|
||||
<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 usbstats(self) -> dict:
|
||||
"""Get stats of all USB devices except ztex.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
The stats of all USB devices except ztex.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("usbstats")
|
||||
|
||||
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: 160 - 230 (multiple of 2)
|
||||
CMR -
|
||||
opt: clock
|
||||
val: 100 - 220
|
||||
```
|
||||
|
||||
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}")
|
||||
|
||||
async def hotplug(self, n: int) -> dict:
|
||||
"""Enable hotplug.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The device number to set hotplug on.
|
||||
|
||||
Returns:
|
||||
Information on hotplug status.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("hotplug", parameters=n)
|
||||
|
||||
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 ascenable(self, n: int) -> dict:
|
||||
"""Enable ASC device n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The device to enable.
|
||||
|
||||
Returns:
|
||||
Confirmation of enabling ASC device n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("ascenable", parameters=n)
|
||||
|
||||
async def ascdisable(self, n: int) -> dict:
|
||||
"""Disable ASC device n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The device to disable.
|
||||
|
||||
Returns:
|
||||
Confirmation of disabling ASC device n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("ascdisable", parameters=n)
|
||||
|
||||
async def ascidentify(self, n: int) -> dict:
|
||||
"""Identify ASC device n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The device to identify.
|
||||
|
||||
Returns:
|
||||
Confirmation of identifying ASC device n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("ascidentify", 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 ascset(self, n: int, opt: str, val: int = None) -> dict:
|
||||
"""Set ASC n option opt to value val.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Sets an option on the ASC n to a value. Allowed options are:
|
||||
```
|
||||
AVA+BTB -
|
||||
opt: freq
|
||||
val: 256 - 1024 (chip frequency)
|
||||
BTB -
|
||||
opt: millivolts
|
||||
val: 1000 - 1400 (core voltage)
|
||||
MBA -
|
||||
opt: reset
|
||||
val: 0 - # of chips (reset a chip)
|
||||
|
||||
opt: freq
|
||||
val: 0 - # of chips, 100 - 1400 (chip frequency)
|
||||
|
||||
opt: ledcount
|
||||
val: 0 - 100 (chip count for LED)
|
||||
|
||||
opt: ledlimit
|
||||
val: 0 - 200 (LED off below GH/s)
|
||||
|
||||
opt: spidelay
|
||||
val: 0 - 9999 (SPI per I/O delay)
|
||||
|
||||
opt: spireset
|
||||
val: i or s, 0 - 9999 (SPI regular reset)
|
||||
|
||||
opt: spisleep
|
||||
val: 0 - 9999 (SPI reset sleep in ms)
|
||||
BMA -
|
||||
opt: volt
|
||||
val: 0 - 9
|
||||
|
||||
opt: clock
|
||||
val: 0 - 15
|
||||
```
|
||||
|
||||
Parameters:
|
||||
n: The ASC 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 option opt to value val.
|
||||
</details>
|
||||
"""
|
||||
if val:
|
||||
return await self.send_command("ascset", parameters=f"{n},{opt},{val}")
|
||||
else:
|
||||
return await self.send_command("ascset", parameters=f"{n},{opt}")
|
||||
|
||||
async def lcd(self) -> dict:
|
||||
"""Get a general all-in-one status summary of the miner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
An all-in-one status summary of the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("lcd")
|
||||
|
||||
async def lockstats(self) -> dict:
|
||||
"""Write lockstats to STDERR.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
The result of writing the lock stats to STDERR.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("lockstats")
|
||||
@@ -13,45 +13,12 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
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 import settings
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import (
|
||||
BraiinsOSError,
|
||||
InnosiliconError,
|
||||
MinerData,
|
||||
WhatsminerError,
|
||||
X19Error,
|
||||
)
|
||||
from pyasic.data import MinerData
|
||||
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_listener import MinerListener
|
||||
from pyasic.miners import *
|
||||
from pyasic.network import MinerNetwork
|
||||
from pyasic.settings import PyasicSettings
|
||||
|
||||
__all__ = [
|
||||
"BMMinerAPI",
|
||||
"BOSMinerAPI",
|
||||
"BTMinerAPI",
|
||||
"CGMinerAPI",
|
||||
"UnknownAPI",
|
||||
"MinerConfig",
|
||||
"MinerData",
|
||||
"BraiinsOSError",
|
||||
"InnosiliconError",
|
||||
"WhatsminerError",
|
||||
"X19Error",
|
||||
"APIError",
|
||||
"APIWarning",
|
||||
"get_miner",
|
||||
"AnyMiner",
|
||||
"MinerFactory",
|
||||
"MinerListener",
|
||||
"MinerNetwork",
|
||||
"PyasicSettings",
|
||||
]
|
||||
from pyasic.rpc import *
|
||||
from pyasic.ssh import *
|
||||
from pyasic.web import *
|
||||
|
||||
@@ -14,521 +14,292 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import json
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
from dataclasses import asdict, dataclass, fields
|
||||
from typing import Dict, List, Literal
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
import toml
|
||||
import yaml
|
||||
from pyasic.config.fans import FanMode, FanModeConfig, FanModeNormal
|
||||
from pyasic.config.mining import MiningMode, MiningModeConfig
|
||||
from pyasic.config.mining.scaling import ScalingConfig
|
||||
from pyasic.config.pools import PoolConfig
|
||||
from pyasic.config.temperature import TemperatureConfig
|
||||
from pyasic.misc import merge_dicts
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Pool:
|
||||
"""A dataclass for pool information.
|
||||
class MinerConfig(BaseModel):
|
||||
"""Represents the configuration for a miner including pool configuration,
|
||||
fan mode, temperature settings, mining mode, and power scaling."""
|
||||
|
||||
Attributes:
|
||||
url: URL of the pool.
|
||||
username: Username on the pool.
|
||||
password: Worker password on the pool.
|
||||
"""
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
url: str = ""
|
||||
username: str = ""
|
||||
password: str = ""
|
||||
pools: PoolConfig = Field(default_factory=PoolConfig.default)
|
||||
fan_mode: FanMode = Field(default_factory=FanModeConfig.default)
|
||||
temperature: TemperatureConfig = Field(default_factory=TemperatureConfig.default)
|
||||
mining_mode: MiningMode = Field(default_factory=MiningModeConfig.default)
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
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
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
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 a 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
|
||||
|
||||
|
||||
@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
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
def __getitem__(self, item):
|
||||
try:
|
||||
return getattr(self, item)
|
||||
except AttributeError:
|
||||
raise KeyError
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
"""Convert the data in this class to a dict."""
|
||||
logging.debug(f"MinerConfig - (To Dict) - Dumping Dict config")
|
||||
data_dict = asdict(self)
|
||||
for key in asdict(self).keys():
|
||||
if data_dict[key] is None:
|
||||
del data_dict[key]
|
||||
return data_dict
|
||||
"""Converts the MinerConfig object to a dictionary."""
|
||||
return self.model_dump()
|
||||
|
||||
def as_toml(self) -> str:
|
||||
"""Convert the data in this class to toml."""
|
||||
logging.debug(f"MinerConfig - (To TOML) - Dumping TOML config")
|
||||
return toml.dumps(self.as_dict())
|
||||
|
||||
def as_yaml(self) -> str:
|
||||
"""Convert the data in this class to yaml."""
|
||||
logging.debug(f"MinerConfig - (To YAML) - Dumping YAML config")
|
||||
return yaml.dump(self.as_dict(), sort_keys=False)
|
||||
|
||||
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.
|
||||
|
||||
Parameters:
|
||||
data: The raw config data to convert.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (From Raw) - Loading raw config")
|
||||
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))
|
||||
|
||||
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):
|
||||
"""Convert list output from the `AnyMiner.api.pools()` command into a usable data and save it to this class.
|
||||
|
||||
Parameters:
|
||||
pools: The list of pool data to convert.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (From API) - Loading API config")
|
||||
_pools = []
|
||||
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.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (From Dict) - Loading Dict config")
|
||||
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.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (From TOML) - Loading TOML config")
|
||||
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.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (From YAML) - Loading YAML config")
|
||||
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
|
||||
|
||||
def as_wm(self, user_suffix: str = None) -> Dict[str, int]:
|
||||
"""Convert the data in this class to a config usable by a Whatsminer device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (As Whatsminer) - Generating Whatsminer config")
|
||||
def as_am_modern(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for modern Antminers."""
|
||||
return {
|
||||
"pools": self.pool_groups[0].as_wm(user_suffix=user_suffix),
|
||||
"wattage": self.autotuning_wattage,
|
||||
**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(),
|
||||
}
|
||||
|
||||
def as_wm(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for Whatsminers."""
|
||||
return {
|
||||
**self.fan_mode.as_wm(),
|
||||
**self.mining_mode.as_wm(),
|
||||
**self.pools.as_wm(user_suffix=user_suffix),
|
||||
**self.temperature.as_wm(),
|
||||
}
|
||||
|
||||
def as_am_old(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for old versions of Antminers."""
|
||||
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(),
|
||||
}
|
||||
|
||||
def as_goldshell(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for Goldshell miners."""
|
||||
return {
|
||||
**self.fan_mode.as_goldshell(),
|
||||
**self.mining_mode.as_goldshell(),
|
||||
**self.pools.as_goldshell(user_suffix=user_suffix),
|
||||
**self.temperature.as_goldshell(),
|
||||
}
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for Avalonminers."""
|
||||
return {
|
||||
**self.fan_mode.as_avalon(),
|
||||
**self.mining_mode.as_avalon(),
|
||||
**self.pools.as_avalon(user_suffix=user_suffix),
|
||||
**self.temperature.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.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (As Inno) - Generating Innosilicon config")
|
||||
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.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (As X19) - Generating X19 config")
|
||||
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.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (As Avalon) - Generating AvalonMiner config")
|
||||
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.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (As BOS) - Generating BOSMiner config")
|
||||
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,
|
||||
},
|
||||
"""Generates the configuration in the format suitable for Innosilicon miners."""
|
||||
return {
|
||||
**self.fan_mode.as_inno(),
|
||||
**self.mining_mode.as_inno(),
|
||||
**self.pools.as_inno(user_suffix=user_suffix),
|
||||
**self.temperature.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:
|
||||
"""Generates the configuration in the bosminer.toml format."""
|
||||
return {
|
||||
**merge_dicts(self.fan_mode.as_bosminer(), self.temperature.as_bosminer()),
|
||||
**self.mining_mode.as_bosminer(),
|
||||
**self.pools.as_bosminer(user_suffix=user_suffix),
|
||||
}
|
||||
|
||||
if self.asicboost:
|
||||
cfg["hash_chain_global"] = {}
|
||||
cfg["hash_chain_global"]["asic_boost"] = self.asicboost
|
||||
def as_boser(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for BOSer."""
|
||||
return {
|
||||
**self.fan_mode.as_boser(),
|
||||
**self.temperature.as_boser(),
|
||||
**self.mining_mode.as_boser(),
|
||||
**self.pools.as_boser(user_suffix=user_suffix),
|
||||
}
|
||||
|
||||
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
|
||||
def as_epic(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for ePIC miners."""
|
||||
return {
|
||||
**merge_dicts(self.fan_mode.as_epic(), self.temperature.as_epic()),
|
||||
**self.mining_mode.as_epic(),
|
||||
**self.pools.as_epic(user_suffix=user_suffix),
|
||||
}
|
||||
|
||||
return toml.dumps(cfg)
|
||||
def as_auradine(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for Auradine miners."""
|
||||
return {
|
||||
**self.fan_mode.as_auradine(),
|
||||
**self.temperature.as_auradine(),
|
||||
**self.mining_mode.as_auradine(),
|
||||
**self.pools.as_auradine(user_suffix=user_suffix),
|
||||
}
|
||||
|
||||
def as_mara(self, user_suffix: str = None) -> dict:
|
||||
return {
|
||||
**self.fan_mode.as_mara(),
|
||||
**self.temperature.as_mara(),
|
||||
**self.mining_mode.as_mara(),
|
||||
**self.pools.as_mara(user_suffix=user_suffix),
|
||||
}
|
||||
|
||||
def as_bitaxe(self, user_suffix: str = None) -> dict:
|
||||
return {
|
||||
**self.fan_mode.as_bitaxe(),
|
||||
**self.temperature.as_bitaxe(),
|
||||
**self.mining_mode.as_bitaxe(),
|
||||
**self.pools.as_bitaxe(user_suffix=user_suffix),
|
||||
}
|
||||
|
||||
def as_luxos(self, user_suffix: str = None) -> dict:
|
||||
return {
|
||||
**self.fan_mode.as_luxos(),
|
||||
**self.temperature.as_luxos(),
|
||||
**self.mining_mode.as_luxos(),
|
||||
**self.pools.as_luxos(user_suffix=user_suffix),
|
||||
}
|
||||
|
||||
def as_vnish(self, user_suffix: str = None) -> dict:
|
||||
main_cfg = {
|
||||
"miner": {
|
||||
**self.fan_mode.as_vnish(),
|
||||
**self.temperature.as_vnish(),
|
||||
**self.mining_mode.as_vnish(),
|
||||
**self.pools.as_vnish(user_suffix=user_suffix),
|
||||
}
|
||||
}
|
||||
if isinstance(self.fan_mode, FanModeNormal):
|
||||
main_cfg["miner"]["cooling"]["mode"]["param"] = self.temperature.target
|
||||
return main_cfg
|
||||
|
||||
def as_hammer(self, *args, **kwargs) -> dict:
|
||||
return self.as_am_modern(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from a dictionary."""
|
||||
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")),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_api(cls, api_pools: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from API pool data."""
|
||||
return cls(pools=PoolConfig.from_api(api_pools))
|
||||
|
||||
@classmethod
|
||||
def from_am_modern(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for modern Antminers."""
|
||||
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":
|
||||
"""Constructs a MinerConfig object from web configuration for old versions of Antminers."""
|
||||
return cls.from_am_modern(web_conf)
|
||||
|
||||
@classmethod
|
||||
def from_goldshell(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for Goldshell miners."""
|
||||
return cls(pools=PoolConfig.from_am_modern(web_conf))
|
||||
|
||||
@classmethod
|
||||
def from_inno(cls, web_pools: list) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for Innosilicon miners."""
|
||||
return cls(pools=PoolConfig.from_inno(web_pools))
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from the bosminer.toml file, same as the `as_bosminer` dumps a dict for writing to that file as toml."""
|
||||
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),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_boser(cls, grpc_miner_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from gRPC configuration for BOSer."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_boser(grpc_miner_conf),
|
||||
mining_mode=MiningModeConfig.from_boser(grpc_miner_conf),
|
||||
fan_mode=FanModeConfig.from_boser(grpc_miner_conf),
|
||||
temperature=TemperatureConfig.from_boser(grpc_miner_conf),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_epic(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for ePIC miners."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_epic(web_conf),
|
||||
fan_mode=FanModeConfig.from_epic(web_conf),
|
||||
temperature=TemperatureConfig.from_epic(web_conf),
|
||||
mining_mode=MiningModeConfig.from_epic(web_conf),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_settings: dict, web_presets: list[dict]) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web settings for VNish miners."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_vnish(web_settings),
|
||||
fan_mode=FanModeConfig.from_vnish(web_settings),
|
||||
temperature=TemperatureConfig.from_vnish(web_settings),
|
||||
mining_mode=MiningModeConfig.from_vnish(web_settings, web_presets),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_auradine(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for Auradine miners."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_api(web_conf["pools"]),
|
||||
fan_mode=FanModeConfig.from_auradine(web_conf["fan"]),
|
||||
mining_mode=MiningModeConfig.from_auradine(web_conf["mode"]),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_mara(cls, web_miner_config: dict) -> "MinerConfig":
|
||||
return cls(
|
||||
pools=PoolConfig.from_mara(web_miner_config),
|
||||
fan_mode=FanModeConfig.from_mara(web_miner_config),
|
||||
mining_mode=MiningModeConfig.from_mara(web_miner_config),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_bitaxe(cls, web_system_info: dict) -> "MinerConfig":
|
||||
return cls(
|
||||
pools=PoolConfig.from_bitaxe(web_system_info),
|
||||
fan_mode=FanModeConfig.from_bitaxe(web_system_info),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_iceriver(cls, web_userpanel: dict) -> "MinerConfig":
|
||||
return cls(
|
||||
pools=PoolConfig.from_iceriver(web_userpanel),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_luxos(
|
||||
cls, rpc_tempctrl: dict, rpc_fans: dict, rpc_pools: dict, rpc_groups: dict
|
||||
) -> "MinerConfig":
|
||||
return cls(
|
||||
temperature=TemperatureConfig.from_luxos(rpc_tempctrl=rpc_tempctrl),
|
||||
fan_mode=FanModeConfig.from_luxos(
|
||||
rpc_tempctrl=rpc_tempctrl, rpc_fans=rpc_fans
|
||||
),
|
||||
pools=PoolConfig.from_luxos(rpc_pools=rpc_pools, rpc_groups=rpc_groups),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_hammer(cls, *args, **kwargs) -> "MinerConfig":
|
||||
return cls.from_am_modern(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_hiveon_modern(cls, web_conf: dict) -> "MinerConfig":
|
||||
return cls.from_am_modern(web_conf)
|
||||
|
||||
138
pyasic/config/base.py
Normal file
138
pyasic/config/base.py
Normal file
@@ -0,0 +1,138 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class MinerConfigOption(Enum):
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: 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_boser(self) -> dict:
|
||||
return self.value.as_boser
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
return self.value.as_epic()
|
||||
|
||||
def as_vnish(self) -> dict:
|
||||
return self.value.as_vnish()
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return self.value.as_auradine()
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return self.value.as_mara()
|
||||
|
||||
def as_bitaxe(self) -> dict:
|
||||
return self.value.as_bitaxe()
|
||||
|
||||
def as_luxos(self) -> dict:
|
||||
return self.value.as_luxos()
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.value(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
pass
|
||||
|
||||
def __getitem__(self, item):
|
||||
try:
|
||||
return getattr(self, item)
|
||||
except AttributeError:
|
||||
raise KeyError
|
||||
|
||||
|
||||
class MinerConfigValue(BaseModel):
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None):
|
||||
return cls()
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
return self.model_dump()
|
||||
|
||||
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_boser(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_vnish(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_bitaxe(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_luxos(self) -> dict:
|
||||
return {}
|
||||
|
||||
def __getitem__(self, item):
|
||||
try:
|
||||
return getattr(self, item)
|
||||
except AttributeError:
|
||||
raise KeyError
|
||||
369
pyasic/config/fans.py
Normal file
369
pyasic/config/fans.py
Normal file
@@ -0,0 +1,369 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 __future__ import annotations
|
||||
|
||||
from typing import TypeVar, Union
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||
|
||||
|
||||
class FanModeNormal(MinerConfigValue):
|
||||
mode: str = Field(init=False, default="normal")
|
||||
minimum_fans: int = 1
|
||||
minimum_speed: int = 0
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "FanModeNormal":
|
||||
cls_conf = {}
|
||||
if dict_conf.get("minimum_fans") is not None:
|
||||
cls_conf["minimum_fans"] = dict_conf["minimum_fans"]
|
||||
if dict_conf.get("minimum_speed") is not None:
|
||||
cls_conf["minimum_speed"] = dict_conf["minimum_speed"]
|
||||
return cls(**cls_conf)
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_cooling_settings: dict) -> "FanModeNormal":
|
||||
cls_conf = {}
|
||||
if web_cooling_settings.get("fan_min_count") is not None:
|
||||
cls_conf["minimum_fans"] = web_cooling_settings["fan_min_count"]
|
||||
if web_cooling_settings.get("fan_min_duty") is not None:
|
||||
cls_conf["minimum_speed"] = web_cooling_settings["fan_min_duty"]
|
||||
return cls(**cls_conf)
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_fan_conf: dict):
|
||||
cls_conf = {}
|
||||
if toml_fan_conf.get("min_fans") is not None:
|
||||
cls_conf["minimum_fans"] = toml_fan_conf["min_fans"]
|
||||
return cls(**cls_conf)
|
||||
|
||||
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"},
|
||||
"fan_control": {"min_fans": self.minimum_fans},
|
||||
}
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
return {
|
||||
"fans": {
|
||||
"Auto": {
|
||||
"Idle Speed": (
|
||||
self.minimum_speed if not self.minimum_speed == 0 else 100
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {
|
||||
"general-config": {"environment-profile": "AirCooling"},
|
||||
"advance-config": {
|
||||
"override-fan-control": False,
|
||||
"fan-fixed-percent": 0,
|
||||
},
|
||||
}
|
||||
|
||||
def as_bitaxe(self) -> dict:
|
||||
return {"autoFanspeed": 1}
|
||||
|
||||
def as_luxos(self) -> dict:
|
||||
return {"fanset": {"speed": -1, "min_fans": self.minimum_fans}}
|
||||
|
||||
def as_vnish(self) -> dict:
|
||||
return {
|
||||
"cooling": {
|
||||
"fan_min_count": self.minimum_fans,
|
||||
"fan_min_duty": self.minimum_speed,
|
||||
"mode": {
|
||||
"name": "auto",
|
||||
"param": None, # Target temp, must be set later...
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FanModeManual(MinerConfigValue):
|
||||
mode: str = Field(init=False, default="manual")
|
||||
speed: int = 100
|
||||
minimum_fans: int = 1
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "FanModeManual":
|
||||
cls_conf = {}
|
||||
if dict_conf.get("speed") is not None:
|
||||
cls_conf["speed"] = dict_conf["speed"]
|
||||
if dict_conf.get("minimum_fans") is not None:
|
||||
cls_conf["minimum_fans"] = dict_conf["minimum_fans"]
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_cooling_settings: dict) -> "FanModeManual":
|
||||
cls_conf = {}
|
||||
if web_cooling_settings.get("fan_min_count") is not None:
|
||||
cls_conf["minimum_fans"] = web_cooling_settings["fan_min_count"]
|
||||
if web_cooling_settings["mode"].get("param") is not None:
|
||||
cls_conf["speed"] = web_cooling_settings["mode"]["param"]
|
||||
return cls(**cls_conf)
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwm": str(self.speed)}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {
|
||||
"temp_control": {"mode": "manual"},
|
||||
"fan_control": {"min_fans": self.minimum_fans, "speed": self.speed},
|
||||
}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"fan": {"percentage": self.speed}}
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
return {"fans": {"Manual": {"speed": self.speed}}}
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {
|
||||
"general-config": {"environment-profile": "AirCooling"},
|
||||
"advance-config": {
|
||||
"override-fan-control": True,
|
||||
"fan-fixed-percent": self.speed,
|
||||
},
|
||||
}
|
||||
|
||||
def as_bitaxe(self) -> dict:
|
||||
return {"autoFanspeed": 0, "fanspeed": self.speed}
|
||||
|
||||
def as_luxos(self) -> dict:
|
||||
return {"fanset": {"speed": self.speed, "min_fans": self.minimum_fans}}
|
||||
|
||||
def as_vnish(self) -> dict:
|
||||
return {
|
||||
"cooling": {
|
||||
"fan_min_count": self.minimum_fans,
|
||||
"fan_min_duty": self.speed,
|
||||
"mode": {
|
||||
"name": "manual",
|
||||
"param": self.speed, # Speed value
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FanModeImmersion(MinerConfigValue):
|
||||
mode: str = Field(init=False, default="immersion")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "FanModeImmersion":
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwm": "0"}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {
|
||||
"fan_control": {"min_fans": 0},
|
||||
}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"fan": {"percentage": 0}}
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {"general-config": {"environment-profile": "OilImmersionCooling"}}
|
||||
|
||||
def as_luxos(self) -> dict:
|
||||
return {"fanset": {"speed": 0, "min_fans": 0}}
|
||||
|
||||
def as_vnish(self) -> dict:
|
||||
return {"cooling": {"mode": {"name": "immers"}}}
|
||||
|
||||
|
||||
class FanModeConfig(MinerConfigOption):
|
||||
normal = FanModeNormal
|
||||
manual = FanModeManual
|
||||
immersion = FanModeImmersion
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
return cls.normal()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None):
|
||||
if dict_conf is None:
|
||||
return cls.default()
|
||||
|
||||
mode = dict_conf.get("mode")
|
||||
if mode is None:
|
||||
return cls.default()
|
||||
|
||||
cls_attr = getattr(cls, mode)
|
||||
if cls_attr is not None:
|
||||
return cls_attr().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:
|
||||
speed = int(web_conf["bitmain-fan-pwm"])
|
||||
if speed == 0:
|
||||
return cls.immersion()
|
||||
return cls.manual(speed=speed)
|
||||
else:
|
||||
return cls.normal()
|
||||
else:
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_epic(cls, web_conf: dict):
|
||||
try:
|
||||
fan_mode = web_conf["Fans"]["Fan Mode"]
|
||||
if fan_mode.get("Manual") is not None:
|
||||
return cls.manual(speed=fan_mode.get("Manual"))
|
||||
else:
|
||||
return cls.normal()
|
||||
except KeyError:
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_conf: dict):
|
||||
try:
|
||||
mode = toml_conf["temp_control"]["mode"]
|
||||
fan_config = toml_conf.get("fan_control", {})
|
||||
if mode == "auto":
|
||||
return cls.normal().from_bosminer(fan_config)
|
||||
elif mode == "manual":
|
||||
if toml_conf.get("fan_control"):
|
||||
return cls.manual().from_bosminer(fan_config)
|
||||
return cls.manual()
|
||||
elif mode == "disabled":
|
||||
return cls.immersion()
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
min_fans = toml_conf["fan_control"]["min_fans"]
|
||||
except KeyError:
|
||||
return cls.default()
|
||||
|
||||
if min_fans == 0:
|
||||
return cls.immersion()
|
||||
return cls.normal(minimum_fans=min_fans)
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_settings: dict):
|
||||
try:
|
||||
mode = web_settings["miner"]["cooling"]["mode"]["name"]
|
||||
except LookupError:
|
||||
return cls.default()
|
||||
|
||||
if mode == "auto":
|
||||
return cls.normal().from_vnish(web_settings["miner"]["cooling"])
|
||||
elif mode == "manual":
|
||||
return cls.manual().from_vnish(web_settings["miner"]["cooling"])
|
||||
elif mode == "immers":
|
||||
return cls.immersion()
|
||||
|
||||
@classmethod
|
||||
def from_boser(cls, grpc_miner_conf: dict):
|
||||
try:
|
||||
temperature_conf = grpc_miner_conf["temperature"]
|
||||
except LookupError:
|
||||
return cls.default()
|
||||
|
||||
keys = temperature_conf.keys()
|
||||
if "auto" in keys:
|
||||
if "minimumRequiredFans" in keys:
|
||||
return cls.normal(minimum_fans=temperature_conf["minimumRequiredFans"])
|
||||
return cls.normal()
|
||||
if "manual" in keys:
|
||||
conf = {}
|
||||
if "fanSpeedRatio" in temperature_conf["manual"].keys():
|
||||
conf["speed"] = int(temperature_conf["manual"]["fanSpeedRatio"])
|
||||
if "minimumRequiredFans" in keys:
|
||||
conf["minimum_fans"] = int(temperature_conf["minimumRequiredFans"])
|
||||
return cls.manual(**conf)
|
||||
|
||||
@classmethod
|
||||
def from_auradine(cls, web_fan: dict):
|
||||
try:
|
||||
fan_data = web_fan["Fan"][0]
|
||||
fan_1_max = fan_data["Max"]
|
||||
fan_1_target = fan_data["Target"]
|
||||
return cls.manual(speed=round((fan_1_target / fan_1_max) * 100))
|
||||
except LookupError:
|
||||
pass
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_mara(cls, web_config: dict):
|
||||
try:
|
||||
mode = web_config["general-config"]["environment-profile"]
|
||||
if mode == "AirCooling":
|
||||
if web_config["advance-config"]["override-fan-control"]:
|
||||
return cls.manual(
|
||||
speed=web_config["advance-config"]["fan-fixed-percent"]
|
||||
)
|
||||
return cls.normal()
|
||||
return cls.immersion()
|
||||
except LookupError:
|
||||
pass
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_bitaxe(cls, web_system_info: dict):
|
||||
if web_system_info["autofanspeed"] == 1:
|
||||
return cls.normal()
|
||||
else:
|
||||
return cls.manual(speed=web_system_info["fanspeed"])
|
||||
|
||||
@classmethod
|
||||
def from_luxos(cls, rpc_fans: dict, rpc_tempctrl: dict):
|
||||
try:
|
||||
mode = rpc_tempctrl["TEMPCTRL"][0]["Mode"]
|
||||
if mode == "Manual":
|
||||
speed = rpc_fans["FANS"][0]["Speed"]
|
||||
min_fans = rpc_fans["FANCTRL"][0]["MinFans"]
|
||||
if min_fans == 0 and speed == 0:
|
||||
return cls.immersion()
|
||||
return cls.manual(
|
||||
speed=speed,
|
||||
minimum_fans=min_fans,
|
||||
)
|
||||
return cls.normal(
|
||||
minimum_fans=rpc_fans["FANCTRL"][0]["MinFans"],
|
||||
)
|
||||
except LookupError:
|
||||
pass
|
||||
return cls.default()
|
||||
|
||||
|
||||
FanMode = TypeVar(
|
||||
"FanMode",
|
||||
bound=Union[FanModeNormal, FanModeManual, FanModeImmersion],
|
||||
)
|
||||
703
pyasic/config/mining/__init__.py
Normal file
703
pyasic/config/mining/__init__.py
Normal file
@@ -0,0 +1,703 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 __future__ import annotations
|
||||
|
||||
from dataclasses import field
|
||||
from typing import TypeVar, Union
|
||||
|
||||
from pyasic import settings
|
||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||
from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
|
||||
DpsHashrateTarget,
|
||||
DpsPowerTarget,
|
||||
DpsTarget,
|
||||
HashrateTargetMode,
|
||||
PerformanceMode,
|
||||
Power,
|
||||
PowerTargetMode,
|
||||
SaveAction,
|
||||
SetDpsRequest,
|
||||
SetPerformanceModeRequest,
|
||||
TeraHashrate,
|
||||
TunerPerformanceMode,
|
||||
)
|
||||
|
||||
from .algo import TunerAlgo, TunerAlgoType
|
||||
from .presets import MiningPreset
|
||||
from .scaling import ScalingConfig
|
||||
|
||||
|
||||
class MiningModeNormal(MinerConfigValue):
|
||||
mode: str = field(init=False, default="normal")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeNormal":
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"mode": self.mode}}
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
return {"ptune": {"enabled": False}}
|
||||
|
||||
def as_goldshell(self) -> dict:
|
||||
return {"settings": {"level": 0}}
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {
|
||||
"mode": {
|
||||
"work-mode-selector": "Stock",
|
||||
}
|
||||
}
|
||||
|
||||
def as_luxos(self) -> dict:
|
||||
return {"autotunerset": {"enabled": False}}
|
||||
|
||||
|
||||
class MiningModeSleep(MinerConfigValue):
|
||||
mode: str = field(init=False, default="sleep")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeSleep":
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "1"}
|
||||
return {"miner-mode": 1}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"sleep": "on"}}
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
return {"ptune": {"algo": "Sleep", "target": 0}}
|
||||
|
||||
def as_goldshell(self) -> dict:
|
||||
return {"settings": {"level": 3}}
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {
|
||||
"mode": {
|
||||
"work-mode-selector": "Sleep",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MiningModeLPM(MinerConfigValue):
|
||||
mode: str = field(init=False, default="low")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeLPM":
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "3"}
|
||||
return {"miner-mode": 3}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"mode": "eco"}}
|
||||
|
||||
def as_goldshell(self) -> dict:
|
||||
return {"settings": {"level": 1}}
|
||||
|
||||
|
||||
class MiningModeHPM(MinerConfigValue):
|
||||
mode: str = field(init=False, default="high")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHPM":
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"mode": "turbo"}}
|
||||
|
||||
|
||||
class MiningModePowerTune(MinerConfigValue):
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
mode: str = field(init=False, default="power_tuning")
|
||||
power: int | None = None
|
||||
algo: TunerAlgoType = field(default_factory=TunerAlgo.default)
|
||||
scaling: ScalingConfig | None = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModePowerTune":
|
||||
cls_conf = {}
|
||||
if dict_conf.get("power"):
|
||||
cls_conf["power"] = dict_conf["power"]
|
||||
if dict_conf.get("algo"):
|
||||
cls_conf["algo"] = TunerAlgo.from_dict(dict_conf["algo"])
|
||||
if dict_conf.get("scaling"):
|
||||
cls_conf["scaling"] = ScalingConfig.from_dict(dict_conf["scaling"])
|
||||
|
||||
return cls(**cls_conf)
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
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:
|
||||
tuning_cfg = {"enabled": True, "mode": "power_target"}
|
||||
if self.power is not None:
|
||||
tuning_cfg["power_target"] = self.power
|
||||
|
||||
cfg = {"autotuning": tuning_cfg}
|
||||
|
||||
if self.scaling is not None:
|
||||
scaling_cfg = {"enabled": True}
|
||||
if self.scaling.step is not None:
|
||||
scaling_cfg["power_step"] = self.scaling.step
|
||||
if self.scaling.minimum is not None:
|
||||
scaling_cfg["min_power_target"] = self.scaling.minimum
|
||||
if self.scaling.shutdown is not None:
|
||||
scaling_cfg = {**scaling_cfg, **self.scaling.shutdown.as_bosminer()}
|
||||
cfg["performance_scaling"] = scaling_cfg
|
||||
|
||||
return cfg
|
||||
|
||||
def as_boser(self) -> dict:
|
||||
cfg = {
|
||||
"set_performance_mode": SetPerformanceModeRequest(
|
||||
save_action=SaveAction.SAVE_AND_APPLY,
|
||||
mode=PerformanceMode(
|
||||
tuner_mode=TunerPerformanceMode(
|
||||
power_target=PowerTargetMode(
|
||||
power_target=Power(watt=self.power)
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
}
|
||||
if self.scaling is not None:
|
||||
sd_cfg = {}
|
||||
if self.scaling.shutdown is not None:
|
||||
sd_cfg = self.scaling.shutdown.as_boser()
|
||||
cfg["set_dps"] = (
|
||||
SetDpsRequest(
|
||||
enable=True,
|
||||
**sd_cfg,
|
||||
target=DpsTarget(
|
||||
power_target=DpsPowerTarget(
|
||||
power_step=Power(self.scaling.step),
|
||||
min_power_target=Power(self.scaling.minimum),
|
||||
)
|
||||
),
|
||||
),
|
||||
)
|
||||
return cfg
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"mode": "custom", "tune": "power", "power": self.power}}
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {
|
||||
"mode": {
|
||||
"work-mode-selector": "Auto",
|
||||
"concorde": {
|
||||
"mode-select": "PowerTarget",
|
||||
"power-target": self.power,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
def as_luxos(self) -> dict:
|
||||
return {"autotunerset": {"enabled": True}}
|
||||
|
||||
|
||||
class MiningModeHashrateTune(MinerConfigValue):
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
mode: str = field(init=False, default="hashrate_tuning")
|
||||
hashrate: int | None = None
|
||||
algo: TunerAlgoType = field(default_factory=TunerAlgo.default)
|
||||
scaling: ScalingConfig | None = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHashrateTune":
|
||||
cls_conf = {}
|
||||
if dict_conf.get("hashrate"):
|
||||
cls_conf["hashrate"] = dict_conf["hashrate"]
|
||||
if dict_conf.get("algo"):
|
||||
cls_conf["algo"] = TunerAlgo.from_dict(dict_conf["algo"])
|
||||
if dict_conf.get("scaling"):
|
||||
cls_conf["scaling"] = ScalingConfig.from_dict(dict_conf["scaling"])
|
||||
|
||||
return cls(**cls_conf)
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
conf = {"enabled": True, "mode": "hashrate_target"}
|
||||
if self.hashrate is not None:
|
||||
conf["hashrate_target"] = self.hashrate
|
||||
return {"autotuning": conf}
|
||||
|
||||
@property
|
||||
def as_boser(self) -> dict:
|
||||
cfg = {
|
||||
"set_performance_mode": SetPerformanceModeRequest(
|
||||
save_action=SaveAction.SAVE_AND_APPLY,
|
||||
mode=PerformanceMode(
|
||||
tuner_mode=TunerPerformanceMode(
|
||||
hashrate_target=HashrateTargetMode(
|
||||
hashrate_target=TeraHashrate(
|
||||
terahash_per_second=self.hashrate
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
if self.scaling is not None:
|
||||
sd_cfg = {}
|
||||
if self.scaling.shutdown is not None:
|
||||
sd_cfg = self.scaling.shutdown.as_boser()
|
||||
cfg["set_dps"] = (
|
||||
SetDpsRequest(
|
||||
enable=True,
|
||||
**sd_cfg,
|
||||
target=DpsTarget(
|
||||
hashrate_target=DpsHashrateTarget(
|
||||
hashrate_step=TeraHashrate(self.scaling.step),
|
||||
min_hashrate_target=TeraHashrate(self.scaling.minimum),
|
||||
)
|
||||
),
|
||||
),
|
||||
)
|
||||
return cfg
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"mode": "custom", "tune": "ths", "ths": self.hashrate}}
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
mode = {
|
||||
"ptune": {
|
||||
"algo": self.algo.as_epic(),
|
||||
"target": self.hashrate,
|
||||
}
|
||||
}
|
||||
if self.scaling is not None:
|
||||
if self.scaling.minimum is not None:
|
||||
mode["ptune"]["min_throttle"] = self.scaling.minimum
|
||||
if self.scaling.step is not None:
|
||||
mode["ptune"]["throttle_step"] = self.scaling.step
|
||||
return mode
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {
|
||||
"mode": {
|
||||
"work-mode-selector": "Auto",
|
||||
"concorde": {
|
||||
"mode-select": "Hashrate",
|
||||
"hash-target": self.hashrate,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
def as_luxos(self) -> dict:
|
||||
return {"autotunerset": {"enabled": True}}
|
||||
|
||||
|
||||
class MiningModePreset(MinerConfigValue):
|
||||
mode: str = field(init=False, default="preset")
|
||||
|
||||
active_preset: MiningPreset
|
||||
available_presets: list[MiningPreset] = field(default_factory=list)
|
||||
|
||||
def as_vnish(self) -> dict:
|
||||
return {"overclock": {**self.active_preset.as_vnish()}}
|
||||
|
||||
@classmethod
|
||||
def from_vnish(
|
||||
cls, web_overclock_settings: dict, web_presets: list[dict]
|
||||
) -> "MiningModePreset":
|
||||
active_preset = None
|
||||
for preset in web_presets:
|
||||
if preset["name"] == web_overclock_settings["preset"]:
|
||||
active_preset = preset
|
||||
return cls(
|
||||
active_preset=MiningPreset.from_vnish(active_preset),
|
||||
available_presets=[MiningPreset.from_vnish(p) for p in web_presets],
|
||||
)
|
||||
|
||||
|
||||
class ManualBoardSettings(MinerConfigValue):
|
||||
freq: float
|
||||
volt: float
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "ManualBoardSettings":
|
||||
return cls(freq=dict_conf["freq"], volt=dict_conf["volt"])
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_vnish(self) -> dict:
|
||||
return {"freq": self.freq}
|
||||
|
||||
|
||||
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: 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:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_vnish(self) -> dict:
|
||||
chains = [b.as_vnish() for b in self.boards.values() if b.freq != 0]
|
||||
return {
|
||||
"overclock": {
|
||||
"chains": chains if chains != [] else None,
|
||||
"globals": {
|
||||
"freq": int(self.global_freq),
|
||||
"volt": int(self.global_volt),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_overclock_settings: dict) -> "MiningModeManual":
|
||||
# will raise KeyError if it cant find the settings, values cannot be empty
|
||||
voltage = web_overclock_settings["globals"]["volt"]
|
||||
freq = web_overclock_settings["globals"]["freq"]
|
||||
boards = {
|
||||
idx: ManualBoardSettings(
|
||||
freq=board["freq"],
|
||||
volt=voltage if not board["freq"] == 0 else 0,
|
||||
)
|
||||
for idx, board in enumerate(web_overclock_settings["chains"])
|
||||
}
|
||||
return cls(global_freq=freq, global_volt=voltage, boards=boards)
|
||||
|
||||
@classmethod
|
||||
def from_epic(cls, epic_conf: dict) -> "MiningModeManual":
|
||||
voltage = 0
|
||||
freq = 0
|
||||
if epic_conf.get("HwConfig") is not None:
|
||||
freq = epic_conf["HwConfig"]["Boards Target Clock"][0]["Data"]
|
||||
if epic_conf.get("Power Supply Stats") is not None:
|
||||
voltage = epic_conf["Power Supply Stats"]["Target Voltage"]
|
||||
boards = {}
|
||||
if epic_conf.get("HBs") is not None:
|
||||
boards = {
|
||||
board["Index"]: ManualBoardSettings(
|
||||
freq=board["Core Clock Avg"], volt=board["Input Voltage"]
|
||||
)
|
||||
for board in epic_conf["HBs"]
|
||||
}
|
||||
return cls(global_freq=freq, global_volt=voltage, boards=boards)
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {
|
||||
"mode": {
|
||||
"work-mode-selector": "Fixed",
|
||||
"fixed": {
|
||||
"frequency": str(self.global_freq),
|
||||
"voltage": self.global_volt,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MiningModeConfig(MinerConfigOption):
|
||||
normal = MiningModeNormal
|
||||
low = MiningModeLPM
|
||||
high = MiningModeHPM
|
||||
sleep = MiningModeSleep
|
||||
power_tuning = MiningModePowerTune
|
||||
hashrate_tuning = MiningModeHashrateTune
|
||||
preset = MiningModePreset
|
||||
manual = MiningModeManual
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
return cls.normal()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None):
|
||||
if dict_conf is None:
|
||||
return cls.default()
|
||||
|
||||
mode = dict_conf.get("mode")
|
||||
if mode is None:
|
||||
return cls.default()
|
||||
|
||||
cls_attr = getattr(cls, mode)
|
||||
if cls_attr is not None:
|
||||
return cls_attr().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_epic(cls, web_conf: dict):
|
||||
try:
|
||||
tuner_running = web_conf["PerpetualTune"]["Running"]
|
||||
if tuner_running:
|
||||
algo_info = web_conf["PerpetualTune"]["Algorithm"]
|
||||
if algo_info.get("VoltageOptimizer") is not None:
|
||||
scaling_cfg = None
|
||||
if "Throttle Step" in algo_info["VoltageOptimizer"]:
|
||||
scaling_cfg = ScalingConfig(
|
||||
minimum=algo_info["VoltageOptimizer"].get(
|
||||
"Min Throttle Target"
|
||||
),
|
||||
step=algo_info["VoltageOptimizer"].get("Throttle Step"),
|
||||
)
|
||||
|
||||
return cls.hashrate_tuning(
|
||||
hashrate=algo_info["VoltageOptimizer"].get("Target"),
|
||||
algo=TunerAlgo.voltage_optimizer(),
|
||||
scaling=scaling_cfg,
|
||||
)
|
||||
elif algo_info.get("BoardTune") is not None:
|
||||
scaling_cfg = None
|
||||
if "Throttle Step" in algo_info["BoardTune"]:
|
||||
scaling_cfg = ScalingConfig(
|
||||
minimum=algo_info["BoardTune"].get("Min Throttle Target"),
|
||||
step=algo_info["BoardTune"].get("Throttle Step"),
|
||||
)
|
||||
|
||||
return cls.hashrate_tuning(
|
||||
hashrate=algo_info["BoardTune"].get("Target"),
|
||||
algo=TunerAlgo.board_tune(),
|
||||
scaling=scaling_cfg,
|
||||
)
|
||||
else:
|
||||
return cls.hashrate_tuning(
|
||||
hashrate=algo_info["ChipTune"].get("Target"),
|
||||
algo=TunerAlgo.chip_tune(),
|
||||
)
|
||||
else:
|
||||
return MiningModeManual.from_epic(web_conf)
|
||||
except KeyError:
|
||||
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(
|
||||
power=autotuning_conf["psu_power_limit"],
|
||||
scaling=ScalingConfig.from_bosminer(toml_conf, mode="power"),
|
||||
)
|
||||
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(
|
||||
power=autotuning_conf["power_target"],
|
||||
scaling=ScalingConfig.from_bosminer(toml_conf, mode="power"),
|
||||
)
|
||||
return cls.power_tuning(
|
||||
scaling=ScalingConfig.from_bosminer(toml_conf, mode="power"),
|
||||
)
|
||||
if mode == "hashrate_target":
|
||||
if autotuning_conf.get("hashrate_target") is not None:
|
||||
return cls.hashrate_tuning(
|
||||
hashrate=autotuning_conf["hashrate_target"],
|
||||
scaling=ScalingConfig.from_bosminer(toml_conf, mode="hashrate"),
|
||||
)
|
||||
return cls.hashrate_tuning(
|
||||
scaling=ScalingConfig.from_bosminer(toml_conf, mode="hashrate"),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_settings: dict, web_presets: list[dict]):
|
||||
try:
|
||||
mode_settings = web_settings["miner"]["overclock"]
|
||||
except KeyError:
|
||||
return cls.default()
|
||||
|
||||
if mode_settings["preset"] == "disabled":
|
||||
return MiningModeManual.from_vnish(mode_settings)
|
||||
else:
|
||||
return MiningModePreset.from_vnish(mode_settings, web_presets)
|
||||
|
||||
@classmethod
|
||||
def from_boser(cls, grpc_miner_conf: dict):
|
||||
try:
|
||||
tuner_conf = grpc_miner_conf["tuner"]
|
||||
if not tuner_conf.get("enabled", False):
|
||||
return cls.default()
|
||||
except LookupError:
|
||||
return cls.default()
|
||||
|
||||
if tuner_conf.get("tunerMode") is not None:
|
||||
if tuner_conf["tunerMode"] == 1:
|
||||
if tuner_conf.get("powerTarget") is not None:
|
||||
return cls.power_tuning(
|
||||
power=tuner_conf["powerTarget"]["watt"],
|
||||
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="power"),
|
||||
)
|
||||
return cls.power_tuning(
|
||||
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="power")
|
||||
)
|
||||
|
||||
if tuner_conf["tunerMode"] == 2:
|
||||
if tuner_conf.get("hashrateTarget") is not None:
|
||||
return cls.hashrate_tuning(
|
||||
hashrate=int(tuner_conf["hashrateTarget"]["terahashPerSecond"]),
|
||||
scaling=ScalingConfig.from_boser(
|
||||
grpc_miner_conf, mode="hashrate"
|
||||
),
|
||||
)
|
||||
return cls.hashrate_tuning(
|
||||
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="hashrate"),
|
||||
)
|
||||
|
||||
if tuner_conf.get("powerTarget") is not None:
|
||||
return cls.power_tuning(
|
||||
power=tuner_conf["powerTarget"]["watt"],
|
||||
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="power"),
|
||||
)
|
||||
|
||||
if tuner_conf.get("hashrateTarget") is not None:
|
||||
return cls.hashrate_tuning(
|
||||
hashrate=int(tuner_conf["hashrateTarget"]["terahashPerSecond"]),
|
||||
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="hashrate"),
|
||||
)
|
||||
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_auradine(cls, web_mode: dict):
|
||||
try:
|
||||
mode_data = web_mode["Mode"][0]
|
||||
if mode_data.get("Sleep") == "on":
|
||||
return cls.sleep()
|
||||
if mode_data.get("Mode") == "normal":
|
||||
return cls.normal()
|
||||
if mode_data.get("Mode") == "eco":
|
||||
return cls.low()
|
||||
if mode_data.get("Mode") == "turbo":
|
||||
return cls.high()
|
||||
if mode_data.get("Ths") is not None:
|
||||
return cls.hashrate_tuning(hashrate=mode_data["Ths"])
|
||||
if mode_data.get("Power") is not None:
|
||||
return cls.power_tuning(power=mode_data["Power"])
|
||||
except LookupError:
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_mara(cls, web_config: dict):
|
||||
try:
|
||||
mode = web_config["mode"]["work-mode-selector"]
|
||||
if mode == "Fixed":
|
||||
fixed_conf = web_config["mode"]["fixed"]
|
||||
return cls.manual(
|
||||
global_freq=int(fixed_conf["frequency"]),
|
||||
global_volt=fixed_conf["voltage"],
|
||||
)
|
||||
elif mode == "Stock":
|
||||
return cls.normal()
|
||||
elif mode == "Sleep":
|
||||
return cls.sleep()
|
||||
elif mode == "Auto":
|
||||
auto_conf = web_config["mode"]["concorde"]
|
||||
auto_mode = auto_conf["mode-select"]
|
||||
if auto_mode == "Hashrate":
|
||||
return cls.hashrate_tuning(hashrate=auto_conf["hash-target"])
|
||||
elif auto_mode == "PowerTarget":
|
||||
return cls.power_tuning(power=auto_conf["power-target"])
|
||||
except LookupError:
|
||||
pass
|
||||
return cls.default()
|
||||
|
||||
|
||||
MiningMode = TypeVar(
|
||||
"MiningMode",
|
||||
bound=Union[
|
||||
MiningModeNormal,
|
||||
MiningModeHPM,
|
||||
MiningModeLPM,
|
||||
MiningModeSleep,
|
||||
MiningModeManual,
|
||||
MiningModePowerTune,
|
||||
MiningModeHashrateTune,
|
||||
MiningModePreset,
|
||||
],
|
||||
)
|
||||
66
pyasic/config/mining/algo.py
Normal file
66
pyasic/config/mining/algo.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TypeVar, Union
|
||||
|
||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||
|
||||
|
||||
class StandardTuneAlgo(MinerConfigValue):
|
||||
mode: str = field(init=False, default="standard")
|
||||
|
||||
def as_epic(self) -> str:
|
||||
return VOptAlgo().as_epic()
|
||||
|
||||
|
||||
class VOptAlgo(MinerConfigValue):
|
||||
mode: str = field(init=False, default="voltage_optimizer")
|
||||
|
||||
def as_epic(self) -> str:
|
||||
return "VoltageOptimizer"
|
||||
|
||||
|
||||
class BoardTuneAlgo(MinerConfigValue):
|
||||
mode: str = field(init=False, default="board_tune")
|
||||
|
||||
def as_epic(self) -> str:
|
||||
return "BoardTune"
|
||||
|
||||
|
||||
class ChipTuneAlgo(MinerConfigValue):
|
||||
mode: str = field(init=False, default="chip_tune")
|
||||
|
||||
def as_epic(self) -> str:
|
||||
return "ChipTune"
|
||||
|
||||
|
||||
class TunerAlgo(MinerConfigOption):
|
||||
standard = StandardTuneAlgo
|
||||
voltage_optimizer = VOptAlgo
|
||||
board_tune = BoardTuneAlgo
|
||||
chip_tune = ChipTuneAlgo
|
||||
|
||||
@classmethod
|
||||
def default(cls) -> TunerAlgoType:
|
||||
return cls.standard()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> TunerAlgoType:
|
||||
mode = dict_conf.get("mode")
|
||||
if mode is None:
|
||||
return cls.default()
|
||||
|
||||
cls_attr = getattr(cls, mode)
|
||||
if cls_attr is not None:
|
||||
return cls_attr().from_dict(dict_conf)
|
||||
|
||||
|
||||
TunerAlgoType = TypeVar(
|
||||
"TunerAlgoType",
|
||||
bound=Union[
|
||||
StandardTuneAlgo,
|
||||
VOptAlgo,
|
||||
BoardTuneAlgo,
|
||||
ChipTuneAlgo,
|
||||
],
|
||||
)
|
||||
34
pyasic/config/mining/presets.py
Normal file
34
pyasic/config/mining/presets.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from pyasic.config.base import MinerConfigValue
|
||||
|
||||
|
||||
class MiningPreset(MinerConfigValue):
|
||||
name: str | None = None
|
||||
power: int | None = None
|
||||
hashrate: int | None = None
|
||||
tuned: bool | None = None
|
||||
modded_psu: bool = False
|
||||
|
||||
def as_vnish(self) -> dict:
|
||||
if self.name is not None:
|
||||
return {"preset": self.name}
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_preset: dict):
|
||||
name = web_preset["name"]
|
||||
hr_power_split = web_preset["pretty"].split("~")
|
||||
if len(hr_power_split) == 1:
|
||||
power = None
|
||||
hashrate = None
|
||||
else:
|
||||
power = hr_power_split[0].replace("watt", "").strip()
|
||||
hashrate = hr_power_split[1].replace("TH", "").replace(" LC", "").strip()
|
||||
tuned = web_preset["status"] == "tuned"
|
||||
modded_psu = web_preset["modded_psu_required"]
|
||||
return cls(
|
||||
name=name,
|
||||
power=power,
|
||||
hashrate=hashrate,
|
||||
tuned=tuned,
|
||||
modded_psu=modded_psu,
|
||||
)
|
||||
131
pyasic/config/mining/scaling.py
Normal file
131
pyasic/config/mining/scaling.py
Normal file
@@ -0,0 +1,131 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from pyasic.config.base import MinerConfigValue
|
||||
|
||||
|
||||
class ScalingShutdown(MinerConfigValue):
|
||||
enabled: bool = False
|
||||
duration: int = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "ScalingShutdown":
|
||||
return cls(
|
||||
enabled=dict_conf.get("enabled", False), duration=dict_conf.get("duration")
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, power_scaling_conf: dict):
|
||||
sd_enabled = power_scaling_conf.get("shutdown_enabled")
|
||||
if sd_enabled is not None:
|
||||
return cls(
|
||||
enabled=sd_enabled, duration=power_scaling_conf.get("shutdown_duration")
|
||||
)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def from_boser(cls, power_scaling_conf: dict):
|
||||
sd_enabled = power_scaling_conf.get("shutdownEnabled")
|
||||
if sd_enabled is not None:
|
||||
try:
|
||||
return cls(
|
||||
enabled=sd_enabled,
|
||||
duration=power_scaling_conf["shutdownDuration"]["hours"],
|
||||
)
|
||||
except KeyError:
|
||||
return cls(enabled=sd_enabled)
|
||||
return None
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
cfg = {"shutdown_enabled": self.enabled}
|
||||
|
||||
if self.duration is not None:
|
||||
cfg["shutdown_duration"] = self.duration
|
||||
|
||||
return cfg
|
||||
|
||||
def as_boser(self) -> dict:
|
||||
return {"enable_shutdown": self.enabled, "shutdown_duration": self.duration}
|
||||
|
||||
|
||||
class ScalingConfig(MinerConfigValue):
|
||||
step: int = None
|
||||
minimum: int = None
|
||||
shutdown: ScalingShutdown = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "ScalingConfig":
|
||||
cls_conf = {
|
||||
"step": dict_conf.get("step"),
|
||||
"minimum": dict_conf.get("minimum"),
|
||||
}
|
||||
shutdown = dict_conf.get("shutdown")
|
||||
if shutdown is not None:
|
||||
cls_conf["shutdown"] = ScalingShutdown.from_dict(shutdown)
|
||||
return cls(**cls_conf)
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_conf: dict, mode: str = None):
|
||||
if mode == "power":
|
||||
return cls._from_bosminer_power(toml_conf)
|
||||
if mode == "hashrate":
|
||||
# not implemented yet
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def _from_bosminer_power(cls, toml_conf: dict):
|
||||
power_scaling = toml_conf.get("power_scaling")
|
||||
if power_scaling is None:
|
||||
power_scaling = toml_conf.get("performance_scaling")
|
||||
if power_scaling is not None:
|
||||
enabled = power_scaling.get("enabled")
|
||||
if not enabled:
|
||||
return None
|
||||
power_step = power_scaling.get("power_step")
|
||||
min_power = power_scaling.get("min_psu_power_limit")
|
||||
if min_power is None:
|
||||
min_power = power_scaling.get("min_power_target")
|
||||
sd_mode = ScalingShutdown.from_bosminer(power_scaling)
|
||||
|
||||
return cls(step=power_step, minimum=min_power, shutdown=sd_mode)
|
||||
|
||||
@classmethod
|
||||
def from_boser(cls, grpc_miner_conf: dict, mode: str = None):
|
||||
if mode == "power":
|
||||
return cls._from_boser_power(grpc_miner_conf)
|
||||
if mode == "hashrate":
|
||||
# not implemented yet
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def _from_boser_power(cls, grpc_miner_conf: dict):
|
||||
try:
|
||||
dps_conf = grpc_miner_conf["dps"]
|
||||
if not dps_conf.get("enabled", False):
|
||||
return None
|
||||
except LookupError:
|
||||
return None
|
||||
|
||||
conf = {"shutdown": ScalingShutdown.from_boser(dps_conf)}
|
||||
|
||||
if dps_conf.get("minPowerTarget") is not None:
|
||||
conf["minimum"] = dps_conf["minPowerTarget"]["watt"]
|
||||
if dps_conf.get("powerStep") is not None:
|
||||
conf["step"] = dps_conf["powerStep"]["watt"]
|
||||
return cls(**conf)
|
||||
601
pyasic/config/pools.py
Normal file
601
pyasic/config/pools.py
Normal file
@@ -0,0 +1,601 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 __future__ import annotations
|
||||
|
||||
import random
|
||||
import string
|
||||
from typing import List
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from pyasic.config.base import MinerConfigValue
|
||||
from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
|
||||
PoolConfiguration,
|
||||
PoolGroupConfiguration,
|
||||
Quota,
|
||||
SaveAction,
|
||||
SetPoolGroupsRequest,
|
||||
)
|
||||
|
||||
|
||||
class Pool(MinerConfigValue):
|
||||
url: str
|
||||
user: str
|
||||
password: str
|
||||
|
||||
def as_am_modern(self, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
"url": self.url,
|
||||
"user": f"{self.user}{user_suffix or ''}",
|
||||
"pass": self.password,
|
||||
}
|
||||
|
||||
def as_wm(self, idx: int = 1, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
f"pool_{idx}": self.url,
|
||||
f"worker_{idx}": f"{self.user}{user_suffix or ''}",
|
||||
f"passwd_{idx}": self.password,
|
||||
}
|
||||
|
||||
def as_am_old(self, idx: int = 1, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
f"_ant_pool{idx}url": self.url,
|
||||
f"_ant_pool{idx}user": f"{self.user}{user_suffix or ''}",
|
||||
f"_ant_pool{idx}pw": self.password,
|
||||
}
|
||||
|
||||
def as_goldshell(self, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
"url": self.url,
|
||||
"user": f"{self.user}{user_suffix or ''}",
|
||||
"pass": self.password,
|
||||
}
|
||||
|
||||
def as_avalon(self, user_suffix: str | None = None) -> str:
|
||||
return ",".join([self.url, f"{self.user}{user_suffix or ''}", self.password])
|
||||
|
||||
def as_inno(self, idx: int = 1, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
f"Pool{idx}": self.url,
|
||||
f"UserName{idx}": f"{self.user}{user_suffix or ''}",
|
||||
f"Password{idx}": self.password,
|
||||
}
|
||||
|
||||
def as_bosminer(self, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
"url": self.url,
|
||||
"user": f"{self.user}{user_suffix or ''}",
|
||||
"password": self.password,
|
||||
}
|
||||
|
||||
def as_auradine(self, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
"url": self.url,
|
||||
"user": f"{self.user}{user_suffix or ''}",
|
||||
"pass": self.password,
|
||||
}
|
||||
|
||||
def as_epic(self, user_suffix: str | None = None):
|
||||
return {
|
||||
"pool": self.url,
|
||||
"login": f"{self.user}{user_suffix or ''}",
|
||||
"password": self.password,
|
||||
}
|
||||
|
||||
def as_mara(self, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
"url": self.url,
|
||||
"user": f"{self.user}{user_suffix or ''}",
|
||||
"pass": self.password,
|
||||
}
|
||||
|
||||
def as_bitaxe(self, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
"stratumURL": self.url,
|
||||
"stratumUser": f"{self.user}{user_suffix or ''}",
|
||||
"stratumPassword": self.password,
|
||||
}
|
||||
|
||||
def as_boser(self, user_suffix: str | None = None) -> PoolConfiguration:
|
||||
return PoolConfiguration(
|
||||
url=self.url,
|
||||
user=f"{self.user}{user_suffix or ''}",
|
||||
password=self.password,
|
||||
enabled=True,
|
||||
)
|
||||
|
||||
def as_vnish(self, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
"url": self.url,
|
||||
"user": f"{self.user}{user_suffix or ''}",
|
||||
"pass": self.password,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: 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_epic(cls, api_pool: dict) -> "Pool":
|
||||
return cls(
|
||||
url=api_pool["pool"], user=api_pool["login"], password=api_pool["password"]
|
||||
)
|
||||
|
||||
@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"],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_pool: dict) -> "Pool":
|
||||
return cls(
|
||||
url="stratum+tcp://" + web_pool["url"],
|
||||
user=web_pool["user"],
|
||||
password=web_pool["pass"],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_boser(cls, grpc_pool: dict) -> "Pool":
|
||||
return cls(
|
||||
url=grpc_pool["url"],
|
||||
user=grpc_pool["user"],
|
||||
password=grpc_pool["password"],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_mara(cls, web_pool: dict) -> "Pool":
|
||||
return cls(
|
||||
url=web_pool["url"],
|
||||
user=web_pool["user"],
|
||||
password=web_pool["pass"],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_bitaxe(cls, web_system_info: dict) -> "Pool":
|
||||
url = f"stratum+tcp://{web_system_info['stratumURL']}:{web_system_info['stratumPort']}"
|
||||
return cls(
|
||||
url=url,
|
||||
user=web_system_info["stratumUser"],
|
||||
password=web_system_info.get("stratumPassword", ""),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_luxos(cls, rpc_pools: dict) -> "Pool":
|
||||
return cls.from_api(rpc_pools)
|
||||
|
||||
@classmethod
|
||||
def from_iceriver(cls, web_pool: dict) -> "Pool":
|
||||
return cls(
|
||||
url=web_pool["addr"],
|
||||
user=web_pool["user"],
|
||||
password=web_pool["pass"],
|
||||
)
|
||||
|
||||
|
||||
class PoolGroup(MinerConfigValue):
|
||||
pools: list[Pool] = Field(default_factory=list)
|
||||
quota: int = 1
|
||||
name: str | None = 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 = 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(url="", user="", password="").as_am_modern())
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
def as_wm(self, user_suffix: str | None = 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(url="", user="", password="").as_wm(idx=idx + 1))
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
def as_am_old(self, user_suffix: str | None = 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(url="", user="", password="").as_am_old(idx=idx + 1)
|
||||
)
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
def as_goldshell(self, user_suffix: str | None = None) -> list:
|
||||
return [pool.as_goldshell(user_suffix) for pool in self.pools]
|
||||
|
||||
def as_avalon(self, user_suffix: str | None = None) -> str:
|
||||
if len(self.pools) > 0:
|
||||
return self.pools[0].as_avalon(user_suffix=user_suffix)
|
||||
return Pool(url="", user="", password="").as_avalon()
|
||||
|
||||
def as_inno(self, user_suffix: str | None = 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(url="", user="", password="").as_inno(idx=idx + 1))
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
def as_bosminer(self, user_suffix: str | None = 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": []}
|
||||
|
||||
def as_auradine(self, user_suffix: str | None = None) -> list:
|
||||
return [p.as_auradine(user_suffix=user_suffix) for p in self.pools]
|
||||
|
||||
def as_epic(self, user_suffix: str | None = None) -> list:
|
||||
return [p.as_epic(user_suffix=user_suffix) for p in self.pools]
|
||||
|
||||
def as_mara(self, user_suffix: str | None = None) -> list:
|
||||
return [p.as_mara(user_suffix=user_suffix) for p in self.pools]
|
||||
|
||||
def as_bitaxe(self, user_suffix: str | None = None) -> dict:
|
||||
return self.pools[0].as_bitaxe(user_suffix=user_suffix)
|
||||
|
||||
def as_boser(self, user_suffix: str | None = None) -> PoolGroupConfiguration:
|
||||
return PoolGroupConfiguration(
|
||||
name=self.name,
|
||||
quota=Quota(value=self.quota),
|
||||
pools=[p.as_boser() for p in self.pools],
|
||||
)
|
||||
|
||||
def as_vnish(self, user_suffix: str | None = None) -> dict:
|
||||
return {"pools": [p.as_vnish(user_suffix=user_suffix) for p in self.pools]}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: 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_epic(cls, api_pool_list: list) -> "PoolGroup":
|
||||
pools = []
|
||||
for pool in api_pool_list:
|
||||
pools.append(Pool.from_epic(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(pools=[Pool.from_goldshell(p) for p in web_pools])
|
||||
|
||||
@classmethod
|
||||
def from_inno(cls, web_pools: list) -> "PoolGroup":
|
||||
return cls(pools=[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()
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_settings_pools: dict) -> "PoolGroup":
|
||||
return cls(
|
||||
pools=[Pool.from_vnish(p) for p in web_settings_pools if p["url"] != ""]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_boser(cls, grpc_pool_group: dict) -> "PoolGroup":
|
||||
try:
|
||||
return cls(
|
||||
pools=[Pool.from_boser(p) for p in grpc_pool_group["pools"]],
|
||||
name=grpc_pool_group["name"],
|
||||
quota=(
|
||||
grpc_pool_group["quota"]["value"]
|
||||
if grpc_pool_group.get("quota") is not None
|
||||
else 1
|
||||
),
|
||||
)
|
||||
except LookupError:
|
||||
return cls()
|
||||
|
||||
@classmethod
|
||||
def from_mara(cls, web_config_pools: dict) -> "PoolGroup":
|
||||
return cls(pools=[Pool.from_mara(pool_conf) for pool_conf in web_config_pools])
|
||||
|
||||
@classmethod
|
||||
def from_bitaxe(cls, web_system_info: dict) -> "PoolGroup":
|
||||
return cls(pools=[Pool.from_bitaxe(web_system_info)])
|
||||
|
||||
@classmethod
|
||||
def from_iceriver(cls, web_userpanel: dict) -> "PoolGroup":
|
||||
return cls(
|
||||
pools=[
|
||||
Pool.from_iceriver(web_pool)
|
||||
for web_pool in web_userpanel["data"]["pools"]
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class PoolConfig(MinerConfigValue):
|
||||
groups: List[PoolGroup] = Field(default_factory=list)
|
||||
|
||||
@classmethod
|
||||
def default(cls) -> "PoolConfig":
|
||||
return cls()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: 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[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 = 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 = 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 = 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 = 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 = 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 = 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 = 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_boser(self, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
"set_pool_groups": SetPoolGroupsRequest(
|
||||
save_action=SaveAction.SAVE_AND_APPLY,
|
||||
pool_groups=[g.as_boser(user_suffix=user_suffix) for g in self.groups],
|
||||
)
|
||||
}
|
||||
|
||||
def as_auradine(self, user_suffix: str | None = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {
|
||||
"updatepools": {
|
||||
"pools": self.groups[0].as_auradine(user_suffix=user_suffix)
|
||||
}
|
||||
}
|
||||
return {"updatepools": {"pools": PoolGroup().as_auradine()}}
|
||||
|
||||
def as_epic(self, user_suffix: str | None = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {
|
||||
"pools": {
|
||||
"coin": "Btc",
|
||||
"stratum_configs": self.groups[0].as_epic(user_suffix=user_suffix),
|
||||
"unique_id": False,
|
||||
}
|
||||
}
|
||||
return {
|
||||
"pools": {
|
||||
"coin": "Btc",
|
||||
"stratum_configs": [PoolGroup().as_epic()],
|
||||
"unique_id": False,
|
||||
}
|
||||
}
|
||||
|
||||
def as_mara(self, user_suffix: str | None = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {"pools": self.groups[0].as_mara(user_suffix=user_suffix)}
|
||||
return {"pools": []}
|
||||
|
||||
def as_bitaxe(self, user_suffix: str | None = None) -> dict:
|
||||
return self.groups[0].as_bitaxe(user_suffix=user_suffix)
|
||||
|
||||
def as_luxos(self, user_suffix: str | None = None) -> dict:
|
||||
return {}
|
||||
|
||||
def as_vnish(self, user_suffix: str | None = None) -> dict:
|
||||
return self.groups[0].as_vnish(user_suffix=user_suffix)
|
||||
|
||||
@classmethod
|
||||
def from_api(cls, api_pools: dict) -> "PoolConfig":
|
||||
try:
|
||||
pool_data = api_pools["POOLS"]
|
||||
except KeyError:
|
||||
return PoolConfig.default()
|
||||
pool_data = sorted(pool_data, key=lambda x: int(x["POOL"]))
|
||||
|
||||
return cls(groups=[PoolGroup.from_api(pool_data)])
|
||||
|
||||
@classmethod
|
||||
def from_epic(cls, web_conf: dict) -> "PoolConfig":
|
||||
pool_data = web_conf["StratumConfigs"]
|
||||
return cls(groups=[PoolGroup.from_epic(pool_data)])
|
||||
|
||||
@classmethod
|
||||
def from_am_modern(cls, web_conf: dict) -> "PoolConfig":
|
||||
pool_data = web_conf["pools"]
|
||||
|
||||
return cls(groups=[PoolGroup.from_am_modern(pool_data)])
|
||||
|
||||
@classmethod
|
||||
def from_goldshell(cls, web_pools: list) -> "PoolConfig":
|
||||
return cls(groups=[PoolGroup.from_goldshell(web_pools)])
|
||||
|
||||
@classmethod
|
||||
def from_inno(cls, web_pools: list) -> "PoolConfig":
|
||||
return cls(groups=[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(groups=[PoolGroup.from_bosminer(g) for g in toml_conf["group"]])
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_settings: dict) -> "PoolConfig":
|
||||
try:
|
||||
return cls(groups=[PoolGroup.from_vnish(web_settings["miner"]["pools"])])
|
||||
except LookupError:
|
||||
return cls()
|
||||
|
||||
@classmethod
|
||||
def from_boser(cls, grpc_miner_conf: dict) -> "PoolConfig":
|
||||
try:
|
||||
return cls(
|
||||
groups=[
|
||||
PoolGroup.from_boser(group)
|
||||
for group in grpc_miner_conf["poolGroups"]
|
||||
]
|
||||
)
|
||||
except LookupError:
|
||||
return cls()
|
||||
|
||||
@classmethod
|
||||
def from_mara(cls, web_config: dict) -> "PoolConfig":
|
||||
return cls(groups=[PoolGroup.from_mara(web_config["pools"])])
|
||||
|
||||
@classmethod
|
||||
def from_bitaxe(cls, web_system_info: dict) -> "PoolConfig":
|
||||
return cls(groups=[PoolGroup.from_bitaxe(web_system_info)])
|
||||
|
||||
@classmethod
|
||||
def from_iceriver(cls, web_userpanel: dict) -> "PoolConfig":
|
||||
return cls(groups=[PoolGroup.from_iceriver(web_userpanel)])
|
||||
|
||||
@classmethod
|
||||
def from_luxos(cls, rpc_groups: dict, rpc_pools: dict) -> "PoolConfig":
|
||||
return cls(
|
||||
groups=[
|
||||
PoolGroup(
|
||||
pools=[
|
||||
Pool.from_luxos(pool)
|
||||
for pool in rpc_pools["POOLS"]
|
||||
if pool["GROUP"] == group["GROUP"]
|
||||
],
|
||||
name=group["Name"],
|
||||
quota=group["Quota"],
|
||||
)
|
||||
for group in rpc_groups["GROUPS"]
|
||||
]
|
||||
)
|
||||
157
pyasic/config/temperature.py
Normal file
157
pyasic/config/temperature.py
Normal file
@@ -0,0 +1,157 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from pyasic.config.base import MinerConfigValue
|
||||
|
||||
|
||||
class TemperatureConfig(MinerConfigValue):
|
||||
target: int | None = None
|
||||
hot: int | None = None
|
||||
danger: int | None = 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
|
||||
if len(temp_cfg) == 0:
|
||||
return {}
|
||||
return {"temp_control": temp_cfg}
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
temps_config = {"temps": {}, "fans": {"Auto": {}}}
|
||||
if self.target is not None:
|
||||
temps_config["fans"]["Auto"]["Target Temperature"] = self.target
|
||||
else:
|
||||
temps_config["fans"]["Auto"]["Target Temperature"] = 60
|
||||
if self.danger is not None:
|
||||
temps_config["temps"]["critical"] = self.danger
|
||||
if self.hot is not None:
|
||||
temps_config["temps"]["shutdown"] = self.hot
|
||||
return temps_config
|
||||
|
||||
def as_luxos(self) -> dict:
|
||||
return {"tempctrlset": [self.target or "", self.hot or "", self.danger or ""]}
|
||||
|
||||
def as_vnish(self) -> dict:
|
||||
return {"misc": {"restart_temp": self.danger}}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: 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"),
|
||||
)
|
||||
return cls()
|
||||
|
||||
@classmethod
|
||||
def from_epic(cls, web_conf: dict) -> "TemperatureConfig":
|
||||
try:
|
||||
dangerous_temp = web_conf["Misc"]["Critical Temp"]
|
||||
except KeyError:
|
||||
dangerous_temp = None
|
||||
try:
|
||||
hot_temp = web_conf["Misc"]["Shutdown Temp"]
|
||||
except KeyError:
|
||||
hot_temp = None
|
||||
# Need to do this in two blocks to avoid KeyError if one is missing
|
||||
try:
|
||||
target_temp = web_conf["Fans"]["Fan Mode"]["Auto"]["Target Temperature"]
|
||||
except KeyError:
|
||||
target_temp = None
|
||||
|
||||
return cls(target=target_temp, hot=hot_temp, danger=dangerous_temp)
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_settings: dict) -> "TemperatureConfig":
|
||||
try:
|
||||
dangerous_temp = web_settings["misc"]["restart_temp"]
|
||||
except KeyError:
|
||||
dangerous_temp = None
|
||||
try:
|
||||
if web_settings["miner"]["cooling"]["mode"]["name"] == "auto":
|
||||
return cls(
|
||||
target=web_settings["miner"]["cooling"]["mode"]["param"],
|
||||
danger=dangerous_temp,
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
return cls()
|
||||
|
||||
@classmethod
|
||||
def from_boser(cls, grpc_miner_conf: dict) -> "TemperatureConfig":
|
||||
try:
|
||||
temperature_conf = grpc_miner_conf["temperature"]
|
||||
except KeyError:
|
||||
return cls.default()
|
||||
|
||||
root_key = None
|
||||
for key in ["auto", "manual", "disabled"]:
|
||||
if key in temperature_conf.keys():
|
||||
root_key = key
|
||||
break
|
||||
if root_key is None:
|
||||
return cls.default()
|
||||
|
||||
conf = {}
|
||||
keys = temperature_conf[root_key].keys()
|
||||
if "targetTemperature" in keys:
|
||||
conf["target"] = int(
|
||||
temperature_conf[root_key]["targetTemperature"]["degreeC"]
|
||||
)
|
||||
if "hotTemperature" in keys:
|
||||
conf["hot"] = int(temperature_conf[root_key]["hotTemperature"]["degreeC"])
|
||||
if "dangerousTemperature" in keys:
|
||||
conf["danger"] = int(
|
||||
temperature_conf[root_key]["dangerousTemperature"]["degreeC"]
|
||||
)
|
||||
|
||||
return cls(**conf)
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_luxos(cls, rpc_tempctrl: dict) -> "TemperatureConfig":
|
||||
try:
|
||||
tempctrl_config = rpc_tempctrl["TEMPCTRL"][0]
|
||||
return cls(
|
||||
target=tempctrl_config.get("Target"),
|
||||
hot=tempctrl_config.get("Hot"),
|
||||
danger=tempctrl_config.get("Dangerous"),
|
||||
)
|
||||
except LookupError:
|
||||
pass
|
||||
return cls.default()
|
||||
@@ -13,158 +13,139 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import copy
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Union
|
||||
from typing import Any, List, Union
|
||||
|
||||
from pydantic import BaseModel, Field, computed_field
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.config.mining import MiningModePowerTune
|
||||
from pyasic.data.pools import PoolMetrics, Scheme
|
||||
from pyasic.device.algorithm.hashrate import AlgoHashRateType
|
||||
from pyasic.device.algorithm.hashrate.base import GenericHashrate
|
||||
|
||||
from ..device.algorithm.hashrate.unit.base import GenericUnit
|
||||
from .boards import HashBoard
|
||||
from .device import DeviceInfo
|
||||
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
|
||||
from .fans import Fan
|
||||
|
||||
|
||||
@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 = 0.0
|
||||
temp: int = -1
|
||||
chip_temp: int = -1
|
||||
chips: int = 0
|
||||
expected_chips: int = 0
|
||||
missing: bool = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class Fan:
|
||||
"""A Dataclass to standardize fan data.
|
||||
|
||||
Attributes:
|
||||
speed: The speed of the fan.
|
||||
"""
|
||||
|
||||
speed: int = -1
|
||||
|
||||
|
||||
@dataclass
|
||||
class MinerData:
|
||||
class MinerData(BaseModel):
|
||||
"""A Dataclass to standardize data returned from miners (specifically `AnyMiner().get_data()`)
|
||||
|
||||
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.
|
||||
device_info: Info about the device, such as model, make, and firmware.
|
||||
model: The model of the miner as a str.
|
||||
make: The make of the miner as a str.
|
||||
firmware: The firmware on the miner as a str.
|
||||
algo: The mining algorithm 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.
|
||||
nominal_hashrate: The factory nominal hashrate of the miner in TH/s as a float.
|
||||
left_board_hashrate: The hashrate of the left board of the miner in TH/s as a float.
|
||||
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.
|
||||
expected_hashrate: The factory nominal hashrate of the miner in TH/s as a float.
|
||||
hashboards: A list of [`HashBoard`][pyasic.data.HashBoard]s 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.
|
||||
voltage: Current output voltage of the PSU as an float.
|
||||
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.
|
||||
expected_fans: The number of fans expected on a miner.
|
||||
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.
|
||||
expected_chips: The expected number of chips in the miner as an int.
|
||||
percent_expected_chips: The percent of total chips out of the expected count. Calculated automatically.
|
||||
percent_expected_hashrate: The percent of total hashrate out of the expected hashrate. Calculated automatically.
|
||||
percent_expected_wattage: The percent of total wattage out of the expected 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.
|
||||
pool_1_user: The first pool user on the miner as a str.
|
||||
pool_2_url: The second pool url on the miner as a str.
|
||||
pool_2_user: The second pool user on the miner as a str.
|
||||
config: The parsed config of the miner, using [`MinerConfig`][pyasic.config.MinerConfig].
|
||||
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.
|
||||
pools: A list of PoolMetrics instances, each representing metrics for a pool.
|
||||
"""
|
||||
|
||||
# general
|
||||
ip: str
|
||||
datetime: datetime = None
|
||||
mac: str = "00:00:00:00:00:00"
|
||||
model: str = "Unknown"
|
||||
make: str = "Unknown"
|
||||
api_ver: str = "Unknown"
|
||||
fw_ver: str = "Unknown"
|
||||
hostname: str = "Unknown"
|
||||
hashrate: float = 0
|
||||
nominal_hashrate: float = 0
|
||||
hashboards: List[HashBoard] = field(default_factory=list)
|
||||
ideal_hashboards: int = 1
|
||||
left_board_hashrate: float = field(init=False)
|
||||
center_board_hashrate: float = field(init=False)
|
||||
right_board_hashrate: float = field(init=False)
|
||||
temperature_avg: int = field(init=False)
|
||||
env_temp: float = -1.0
|
||||
left_board_temp: int = field(init=False)
|
||||
left_board_chip_temp: int = field(init=False)
|
||||
center_board_temp: int = field(init=False)
|
||||
center_board_chip_temp: int = field(init=False)
|
||||
right_board_temp: int = field(init=False)
|
||||
right_board_chip_temp: int = field(init=False)
|
||||
wattage: int = -1
|
||||
wattage_limit: int = -1
|
||||
fans: List[Fan] = field(default_factory=list)
|
||||
fan_1: int = field(init=False)
|
||||
fan_2: int = field(init=False)
|
||||
fan_3: int = field(init=False)
|
||||
fan_4: int = field(init=False)
|
||||
fan_psu: int = -1
|
||||
left_chips: int = field(init=False)
|
||||
center_chips: int = field(init=False)
|
||||
right_chips: int = field(init=False)
|
||||
total_chips: int = field(init=False)
|
||||
ideal_chips: int = 1
|
||||
percent_ideal: float = field(init=False)
|
||||
nominal: int = field(init=False)
|
||||
pool_split: str = "0"
|
||||
pool_1_url: str = "Unknown"
|
||||
pool_1_user: str = "Unknown"
|
||||
pool_2_url: str = ""
|
||||
pool_2_user: str = ""
|
||||
raw_datetime: datetime = Field(
|
||||
exclude=True, default_factory=datetime.now(timezone.utc).astimezone, repr=False
|
||||
)
|
||||
|
||||
# about
|
||||
device_info: DeviceInfo | None = None
|
||||
mac: str | None = None
|
||||
api_ver: str | None = None
|
||||
fw_ver: str | None = None
|
||||
hostname: str | None = None
|
||||
|
||||
# hashrate
|
||||
raw_hashrate: AlgoHashRateType = Field(exclude=True, default=None, repr=False)
|
||||
|
||||
# expected
|
||||
expected_hashrate: AlgoHashRateType | None = None
|
||||
expected_hashboards: int | None = None
|
||||
expected_chips: int | None = None
|
||||
expected_fans: int | None = None
|
||||
|
||||
# temperature
|
||||
env_temp: float | None = None
|
||||
|
||||
# power
|
||||
wattage: int | None = None
|
||||
voltage: float | None = None
|
||||
raw_wattage_limit: int | None = Field(exclude=True, default=None, repr=False)
|
||||
|
||||
# fans
|
||||
fans: List[Fan] = Field(default_factory=list)
|
||||
fan_psu: int | None = None
|
||||
|
||||
# boards
|
||||
hashboards: List[HashBoard] = Field(default_factory=list)
|
||||
|
||||
# config
|
||||
config: MinerConfig | None = None
|
||||
fault_light: bool | None = None
|
||||
|
||||
# errors
|
||||
errors: List[
|
||||
Union[WhatsminerError, BraiinsOSError, X19Error, InnosiliconError]
|
||||
] = field(default_factory=list)
|
||||
fault_light: Union[bool, None] = None
|
||||
efficiency: int = field(init=False)
|
||||
Union[
|
||||
WhatsminerError,
|
||||
BraiinsOSError,
|
||||
X19Error,
|
||||
InnosiliconError,
|
||||
]
|
||||
] = Field(default_factory=list)
|
||||
|
||||
# mining state
|
||||
is_mining: bool = True
|
||||
uptime: int | None = None
|
||||
|
||||
# pools
|
||||
pools: list[PoolMetrics] = Field(default_factory=list)
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return [f.name for f in fields(cls)]
|
||||
return list(cls.model_fields.keys())
|
||||
|
||||
def __post_init__(self):
|
||||
self.datetime = datetime.now(timezone.utc).astimezone()
|
||||
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):
|
||||
def __getitem__(self, item: str):
|
||||
try:
|
||||
return getattr(self, item)
|
||||
except AttributeError:
|
||||
@@ -181,19 +162,19 @@ class MinerData:
|
||||
|
||||
def __floordiv__(self, other):
|
||||
cp = copy.deepcopy(self)
|
||||
for key in self:
|
||||
for key in self.fields():
|
||||
item = getattr(self, key)
|
||||
if isinstance(item, int):
|
||||
setattr(cp, key, item // other)
|
||||
if isinstance(item, float):
|
||||
setattr(cp, key, round(item / other, 2))
|
||||
setattr(cp, key, item / other)
|
||||
return cp
|
||||
|
||||
def __add__(self, other):
|
||||
if not isinstance(other, MinerData):
|
||||
raise TypeError("Cannot add MinerData to non MinerData type.")
|
||||
cp = copy.deepcopy(self)
|
||||
for key in self:
|
||||
for key in self.fields():
|
||||
item = getattr(self, key)
|
||||
other_item = getattr(other, key)
|
||||
if item is None:
|
||||
@@ -213,236 +194,152 @@ class MinerData:
|
||||
setattr(cp, key, item & other_item)
|
||||
return cp
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def fan_1(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.fans) > 0:
|
||||
return self.fans[0].speed
|
||||
def hashrate(self) -> AlgoHashRateType | None:
|
||||
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 sum(hr_data, start=self.device_info.algo.hashrate(rate=0))
|
||||
return self.raw_hashrate
|
||||
|
||||
@fan_1.setter
|
||||
def fan_1(self, val):
|
||||
pass
|
||||
@hashrate.setter
|
||||
def hashrate(self, val):
|
||||
self.raw_hashrate = val
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def fan_2(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.fans) > 1:
|
||||
return self.fans[1].speed
|
||||
def wattage_limit(self) -> int | None:
|
||||
if self.config is not None:
|
||||
if isinstance(self.config.mining_mode, MiningModePowerTune):
|
||||
return self.config.mining_mode.power
|
||||
return self.raw_wattage_limit
|
||||
|
||||
@fan_2.setter
|
||||
def fan_2(self, val):
|
||||
pass
|
||||
@wattage_limit.setter
|
||||
def wattage_limit(self, val: int):
|
||||
self.raw_wattage_limit = val
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def fan_3(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.fans) > 2:
|
||||
return self.fans[2].speed
|
||||
|
||||
@fan_3.setter
|
||||
def fan_3(self, val):
|
||||
pass
|
||||
def total_chips(self) -> int | None:
|
||||
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
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def fan_4(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.fans) > 3:
|
||||
return self.fans[3].speed
|
||||
|
||||
@fan_4.setter
|
||||
def fan_4(self, val):
|
||||
pass
|
||||
def nominal(self) -> bool | None:
|
||||
if self.total_chips is None or self.expected_chips is None:
|
||||
return None
|
||||
return self.expected_chips == self.total_chips
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def total_chips(self): # noqa - Skip PyCharm inspection
|
||||
return sum([hb.chips for hb in self.hashboards])
|
||||
|
||||
@total_chips.setter
|
||||
def total_chips(self, val):
|
||||
pass
|
||||
def percent_expected_chips(self) -> int | None:
|
||||
if self.total_chips is None or self.expected_chips is None:
|
||||
return None
|
||||
if self.total_chips == 0 or self.expected_chips == 0:
|
||||
return 0
|
||||
return round((self.total_chips / self.expected_chips) * 100)
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def left_chips(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) in [2, 3]:
|
||||
return self.hashboards[0].chips
|
||||
return 0
|
||||
|
||||
@left_chips.setter
|
||||
def left_chips(self, val):
|
||||
pass
|
||||
def percent_expected_hashrate(self) -> int | None:
|
||||
if self.hashrate is None or self.expected_hashrate is None:
|
||||
return None
|
||||
try:
|
||||
return round((self.hashrate / self.expected_hashrate) * 100)
|
||||
except ZeroDivisionError:
|
||||
return 0
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def center_chips(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 1:
|
||||
return self.hashboards[0].chips
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[1].chips
|
||||
return 0
|
||||
|
||||
@center_chips.setter
|
||||
def center_chips(self, val):
|
||||
pass
|
||||
def percent_expected_wattage(self) -> int | None:
|
||||
if self.wattage_limit is None or self.wattage is None:
|
||||
return None
|
||||
try:
|
||||
return round((self.wattage / self.wattage_limit) * 100)
|
||||
except ZeroDivisionError:
|
||||
return 0
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def right_chips(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 2:
|
||||
return self.hashboards[1].chips
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[2].chips
|
||||
return 0
|
||||
|
||||
@right_chips.setter
|
||||
def right_chips(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def left_board_hashrate(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) in [2, 3]:
|
||||
return self.hashboards[0].hashrate
|
||||
return 0
|
||||
|
||||
@left_board_hashrate.setter
|
||||
def left_board_hashrate(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def center_board_hashrate(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 1:
|
||||
return self.hashboards[0].hashrate
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[1].hashrate
|
||||
return 0
|
||||
|
||||
@center_board_hashrate.setter
|
||||
def center_board_hashrate(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def right_board_hashrate(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 2:
|
||||
return self.hashboards[1].hashrate
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[2].hashrate
|
||||
return 0
|
||||
|
||||
@right_board_hashrate.setter
|
||||
def right_board_hashrate(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def left_board_temp(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) in [2, 3]:
|
||||
return self.hashboards[0].temp
|
||||
return 0
|
||||
|
||||
@left_board_temp.setter
|
||||
def left_board_temp(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def center_board_temp(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 1:
|
||||
return self.hashboards[0].temp
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[1].temp
|
||||
return 0
|
||||
|
||||
@center_board_temp.setter
|
||||
def center_board_temp(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def right_board_temp(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 2:
|
||||
return self.hashboards[1].temp
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[2].temp
|
||||
return 0
|
||||
|
||||
@right_board_temp.setter
|
||||
def right_board_temp(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def left_board_chip_temp(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) in [2, 3]:
|
||||
return self.hashboards[0].chip_temp
|
||||
return 0
|
||||
|
||||
@left_board_chip_temp.setter
|
||||
def left_board_chip_temp(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def center_board_chip_temp(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 1:
|
||||
return self.hashboards[0].chip_temp
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[1].chip_temp
|
||||
return 0
|
||||
|
||||
@center_board_chip_temp.setter
|
||||
def center_board_chip_temp(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def right_board_chip_temp(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 2:
|
||||
return self.hashboards[1].chip_temp
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[2].chip_temp
|
||||
return 0
|
||||
|
||||
@right_board_chip_temp.setter
|
||||
def right_board_chip_temp(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def nominal(self): # noqa - Skip PyCharm inspection
|
||||
return self.ideal_chips == self.total_chips
|
||||
|
||||
@nominal.setter
|
||||
def nominal(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def percent_ideal(self): # noqa - Skip PyCharm inspection
|
||||
return round((self.total_chips / self.ideal_chips) * 100)
|
||||
|
||||
@percent_ideal.setter
|
||||
def percent_ideal(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def temperature_avg(self): # noqa - Skip PyCharm inspection
|
||||
def temperature_avg(self) -> int | None:
|
||||
total_temp = 0
|
||||
temp_count = 0
|
||||
for hb in self.hashboards:
|
||||
if hb.temp and not hb.temp == -1:
|
||||
if hb.temp is not None:
|
||||
total_temp += hb.temp
|
||||
temp_count += 1
|
||||
if not temp_count > 0:
|
||||
return 0
|
||||
return None
|
||||
return round(total_temp / temp_count)
|
||||
|
||||
@temperature_avg.setter
|
||||
def temperature_avg(self, val):
|
||||
pass
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def efficiency(self): # noqa - Skip PyCharm inspection
|
||||
if self.hashrate == 0:
|
||||
def efficiency(self) -> int | None:
|
||||
if self.hashrate is None or self.wattage is None:
|
||||
return None
|
||||
try:
|
||||
return round(self.wattage / float(self.hashrate))
|
||||
except ZeroDivisionError:
|
||||
return 0
|
||||
return round(self.wattage / self.hashrate)
|
||||
|
||||
@efficiency.setter
|
||||
def efficiency(self, val):
|
||||
pass
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def datetime(self) -> str:
|
||||
return self.raw_datetime.isoformat()
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def timestamp(self) -> int:
|
||||
return int(time.mktime(self.raw_datetime.timetuple()))
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def make(self) -> str:
|
||||
if self.device_info.make is not None:
|
||||
return str(self.device_info.make)
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def model(self) -> str:
|
||||
if self.device_info.model is not None:
|
||||
return str(self.device_info.model)
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def firmware(self) -> str:
|
||||
if self.device_info.firmware is not None:
|
||||
return str(self.device_info.firmware)
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def algo(self) -> str:
|
||||
if self.device_info.algo is not None:
|
||||
return str(self.device_info.algo)
|
||||
|
||||
def keys(self) -> list:
|
||||
return list(self.model_fields.keys())
|
||||
|
||||
def asdict(self) -> dict:
|
||||
return self.model_dump()
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
"""Get this dataclass as a dictionary.
|
||||
|
||||
Returns:
|
||||
A dictionary version of this class.
|
||||
"""
|
||||
logging.debug(f"MinerData - (To Dict) - Dumping Dict data")
|
||||
return asdict(self)
|
||||
return self.asdict()
|
||||
|
||||
def as_json(self) -> str:
|
||||
"""Get this dataclass as JSON.
|
||||
@@ -450,10 +347,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)
|
||||
return self.model_dump_json()
|
||||
|
||||
def as_csv(self) -> str:
|
||||
"""Get this dataclass as CSV.
|
||||
@@ -461,9 +355,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 = []
|
||||
for error in data["errors"]:
|
||||
errs.append(error["error_message"])
|
||||
@@ -480,37 +372,54 @@ 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:
|
||||
for attribute in self.fields():
|
||||
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)
|
||||
timestamp = str(int(time.mktime(self.datetime.timetuple()) * 1e9))
|
||||
timestamp = str(self.timestamp * 1e9)
|
||||
|
||||
return " ".join([tags_str, field_str, timestamp])
|
||||
|
||||
67
pyasic/data/boards.py
Normal file
67
pyasic/data/boards.py
Normal file
@@ -0,0 +1,67 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, field_serializer
|
||||
|
||||
from pyasic.device.algorithm.hashrate import AlgoHashRateType
|
||||
|
||||
|
||||
class HashBoard(BaseModel):
|
||||
"""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 expected chip count of the board as an int.
|
||||
serial_number: The serial number of the board.
|
||||
missing: Whether the board is returned from the miners data as a bool.
|
||||
tuned: Whether the board is tuned as a bool.
|
||||
active: Whether the board is currently tuning as a bool.
|
||||
voltage: Current input voltage of the board as a float.
|
||||
"""
|
||||
|
||||
slot: int = 0
|
||||
hashrate: AlgoHashRateType | None = None
|
||||
temp: float | None = None
|
||||
chip_temp: float | None = None
|
||||
chips: int | None = None
|
||||
expected_chips: int | None = None
|
||||
serial_number: str | None = None
|
||||
missing: bool = True
|
||||
tuned: bool | None = None
|
||||
active: bool | None = None
|
||||
voltage: float | None = 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}")
|
||||
31
pyasic/data/device.py
Normal file
31
pyasic/data/device.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from pydantic import BaseModel, ConfigDict, field_serializer
|
||||
|
||||
from pyasic.device.algorithm import MinerAlgoType
|
||||
from pyasic.device.firmware import MinerFirmware
|
||||
from pyasic.device.makes import MinerMake
|
||||
from pyasic.device.models import MinerModelType
|
||||
|
||||
|
||||
class DeviceInfo(BaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
make: MinerMake | None = None
|
||||
model: MinerModelType | None = None
|
||||
firmware: MinerFirmware | None = None
|
||||
algo: type[MinerAlgoType] | None = None
|
||||
|
||||
@field_serializer("make")
|
||||
def serialize_make(self, make: MinerMake, _info):
|
||||
return str(make)
|
||||
|
||||
@field_serializer("model")
|
||||
def serialize_model(self, model: MinerModelType, _info):
|
||||
return str(model)
|
||||
|
||||
@field_serializer("firmware")
|
||||
def serialize_firmware(self, firmware: MinerFirmware, _info):
|
||||
return str(firmware)
|
||||
|
||||
@field_serializer("algo")
|
||||
def serialize_algo(self, algo: MinerAlgoType, _info):
|
||||
return str(algo)
|
||||
@@ -13,12 +13,10 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from dataclasses import asdict, dataclass, fields
|
||||
from pyasic.data.error_codes.base import BaseMinerError
|
||||
|
||||
|
||||
@dataclass
|
||||
class X19Error:
|
||||
class X19Error(BaseMinerError):
|
||||
"""A Dataclass to handle error codes of X19 miners.
|
||||
|
||||
Attributes:
|
||||
@@ -28,10 +26,3 @@ class X19Error:
|
||||
|
||||
error_message: str
|
||||
error_code: int = 0
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
|
||||
18
pyasic/data/error_codes/base.py
Normal file
18
pyasic/data/error_codes/base.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class BaseMinerError(BaseModel):
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return list(cls.model_fields.keys())
|
||||
|
||||
def asdict(self) -> dict:
|
||||
return self.model_dump()
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
"""Get this dataclass as a dictionary.
|
||||
|
||||
Returns:
|
||||
A dictionary version of this class.
|
||||
"""
|
||||
return self.asdict()
|
||||
@@ -14,11 +14,10 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from dataclasses import asdict, dataclass, fields
|
||||
from pyasic.data.error_codes.base import BaseMinerError
|
||||
|
||||
|
||||
@dataclass
|
||||
class BraiinsOSError:
|
||||
class BraiinsOSError(BaseMinerError):
|
||||
"""A Dataclass to handle error codes of BraiinsOS+ miners.
|
||||
|
||||
Attributes:
|
||||
@@ -28,10 +27,3 @@ class BraiinsOSError:
|
||||
|
||||
error_message: str
|
||||
error_code: int = 0
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
|
||||
from pydantic import computed_field
|
||||
|
||||
from pyasic.data.error_codes.base import BaseMinerError
|
||||
|
||||
|
||||
@dataclass
|
||||
class InnosiliconError:
|
||||
class InnosiliconError(BaseMinerError):
|
||||
"""A Dataclass to handle error codes of Innosilicon miners.
|
||||
|
||||
Attributes:
|
||||
@@ -27,25 +29,14 @@ class InnosiliconError:
|
||||
"""
|
||||
|
||||
error_code: int
|
||||
error_message: str = field(init=False)
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def error_message(self): # noqa - Skip PyCharm inspection
|
||||
def error_message(self) -> str: # noqa - Skip PyCharm inspection
|
||||
if self.error_code in ERROR_CODES:
|
||||
return ERROR_CODES[self.error_code]
|
||||
return "Unknown error type."
|
||||
|
||||
@error_message.setter
|
||||
def error_message(self, val):
|
||||
pass
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
|
||||
|
||||
ERROR_CODES = {
|
||||
21: "The PLUG signal of the hash board is not detected.",
|
||||
|
||||
@@ -13,14 +13,12 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pydantic import computed_field
|
||||
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
|
||||
C_N_CODES = ["52", "53", "54", "55"]
|
||||
from pyasic.data.error_codes.base import BaseMinerError
|
||||
|
||||
|
||||
@dataclass
|
||||
class WhatsminerError:
|
||||
class WhatsminerError(BaseMinerError):
|
||||
"""A Dataclass to handle error codes of Whatsminers.
|
||||
|
||||
Attributes:
|
||||
@@ -29,18 +27,12 @@ class WhatsminerError:
|
||||
"""
|
||||
|
||||
error_code: int
|
||||
error_message: str = field(init=False)
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def error_message(self): # noqa - Skip PyCharm inspection
|
||||
if len(str(self.error_code)) > 3 and str(self.error_code)[:2] in C_N_CODES:
|
||||
# 55 error code base has chip numbers, so the format is
|
||||
# 55 -> board num len 1 -> chip num len 3
|
||||
err_type = 55
|
||||
def error_message(self) -> str: # noqa - Skip PyCharm inspection
|
||||
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:
|
||||
@@ -78,17 +70,12 @@ class WhatsminerError:
|
||||
except KeyError:
|
||||
return "Unknown error type."
|
||||
|
||||
@error_message.setter
|
||||
def error_message(self, val):
|
||||
pass
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
|
||||
|
||||
ERROR_CODES = {
|
||||
1: { # Fan error
|
||||
0: {0: "Fan unknown."},
|
||||
0: {
|
||||
0: "Fan unknown.",
|
||||
},
|
||||
1: { # Fan speed error of 1000+
|
||||
0: "Intake fan speed error.",
|
||||
1: "Exhaust fan speed error.",
|
||||
@@ -101,7 +88,9 @@ ERROR_CODES = {
|
||||
0: "Intake fan speed error. Fan speed deviates by more than 3000.",
|
||||
1: "Exhaust fan speed error. Fan speed deviates by more than 3000.",
|
||||
},
|
||||
4: {0: "Fan speed too high."}, # High speed
|
||||
4: {
|
||||
0: "Fan speed too high.",
|
||||
}, # High speed
|
||||
},
|
||||
2: { # Power error
|
||||
0: {
|
||||
@@ -126,6 +115,7 @@ ERROR_CODES = {
|
||||
6: "Power remained unchanged for a long time.",
|
||||
7: "Power set enable error.",
|
||||
8: "Power input voltage is lower than 230V for high power mode.",
|
||||
9: "Power input current is incorrect.",
|
||||
},
|
||||
3: {
|
||||
3: "Power output high temperature protection error.",
|
||||
@@ -159,6 +149,8 @@ ERROR_CODES = {
|
||||
6: {
|
||||
3: "Power communication warning.",
|
||||
4: "Power communication error.",
|
||||
5: "Power unknown error.",
|
||||
6: "Power unknown error.",
|
||||
7: "Power watchdog protection.",
|
||||
8: "Power output high current protection.",
|
||||
9: "Power input high current protection.",
|
||||
@@ -170,57 +162,134 @@ ERROR_CODES = {
|
||||
3: "Power input too high warning.",
|
||||
4: "Power fan warning.",
|
||||
5: "Power high temperature warning.",
|
||||
6: "Power unknown error.",
|
||||
7: "Power unknown error.",
|
||||
8: "Power unknown error.",
|
||||
9: "Power unknown error.",
|
||||
},
|
||||
8: {
|
||||
0: "Power unknown error.",
|
||||
1: "Power vendor status 1 bit 0 error.",
|
||||
2: "Power vendor status 1 bit 1 error.",
|
||||
3: "Power vendor status 1 bit 2 error.",
|
||||
4: "Power vendor status 1 bit 3 error.",
|
||||
5: "Power vendor status 1 bit 4 error.",
|
||||
6: "Power vendor status 1 bit 5 error.",
|
||||
7: "Power vendor status 1 bit 6 error.",
|
||||
8: "Power vendor status 1 bit 7 error.",
|
||||
9: "Power vendor status 2 bit 0 error.",
|
||||
},
|
||||
9: {
|
||||
0: "Power vendor status 2 bit 1 error.",
|
||||
1: "Power vendor status 2 bit 2 error.",
|
||||
2: "Power vendor status 2 bit 3 error.",
|
||||
3: "Power vendor status 2 bit 4 error.",
|
||||
4: "Power vendor status 2 bit 5 error.",
|
||||
5: "Power vendor status 2 bit 6 error.",
|
||||
6: "Power vendor status 2 bit 7 error.",
|
||||
},
|
||||
},
|
||||
3: { # temperature error
|
||||
0: { # sensor detection error
|
||||
"n": "Slot {n} temperature sensor detection error."
|
||||
"n": "Slot {n} temperature sensor detection error.",
|
||||
},
|
||||
2: { # temperature reading error
|
||||
"n": "Slot {n} temperature reading error.",
|
||||
9: "Control board temperature sensor communication error.",
|
||||
},
|
||||
5: {"n": "Slot {n} temperature protecting."}, # temperature protection
|
||||
6: {0: "Hashboard high temperature error."}, # high temp
|
||||
5: {
|
||||
"n": "Slot {n} temperature protecting.",
|
||||
}, # temperature protection
|
||||
6: {
|
||||
0: "Hashboard high temperature error.",
|
||||
1: "Hashboard high temperature error.",
|
||||
2: "Hashboard high temperature error.",
|
||||
3: "Hashboard high temperature error.",
|
||||
}, # high temp
|
||||
7: {
|
||||
0: "The environment temperature fluctuates too much.",
|
||||
}, # env temp
|
||||
8: {
|
||||
0: "Humidity sensor not found.",
|
||||
1: "Humidity sensor read error.",
|
||||
2: "Humidity sensor read error.",
|
||||
3: "Humidity sensor protecting.",
|
||||
},
|
||||
}, # humidity
|
||||
},
|
||||
4: { # EEPROM error
|
||||
0: {0: "Eeprom unknown error."},
|
||||
1: {"n": "Slot {n} eeprom detection error."}, # EEPROM detection error
|
||||
2: {"n": "Slot {n} eeprom parsing error."}, # EEPROM parsing error
|
||||
3: {"n": "Slot {n} chip bin type error."}, # chip bin error
|
||||
4: {"n": "Slot {n} eeprom chip number X error."}, # EEPROM chip number error
|
||||
5: {"n": "Slot {n} eeprom xfer error."}, # EEPROM xfer error
|
||||
0: {
|
||||
0: "Eeprom unknown error.",
|
||||
},
|
||||
1: {
|
||||
"n": "Slot {n} eeprom detection error.",
|
||||
}, # EEPROM detection error
|
||||
2: {
|
||||
"n": "Slot {n} eeprom parsing error.",
|
||||
}, # EEPROM parsing error
|
||||
3: {
|
||||
"n": "Slot {n} chip bin type error.",
|
||||
}, # chip bin error
|
||||
4: {
|
||||
"n": "Slot {n} eeprom chip number X error.",
|
||||
}, # EEPROM chip number error
|
||||
5: {
|
||||
"n": "Slot {n} eeprom xfer error.",
|
||||
}, # EEPROM xfer error
|
||||
},
|
||||
5: { # hashboard error
|
||||
0: {0: "Board unknown error."},
|
||||
1: {"n": "Slot {n} miner type error."}, # board miner type error
|
||||
2: {"n": "Slot {n} bin type error."}, # chip bin type error
|
||||
3: {"n": "Slot {n} not found."}, # board not found error
|
||||
4: {"n": "Slot {n} error reading chip id."}, # reading chip id error
|
||||
5: {"n": "Slot {n} has bad chips."}, # board has bad chips error
|
||||
6: {"n": "Slot {n} loss of balance error."}, # loss of balance error
|
||||
7: {"n": "Slot {n} xfer error chip."}, # xfer error
|
||||
8: {"n": "Slot {n} reset error."}, # reset error
|
||||
9: {"n": "Slot {n} frequency too low."}, # freq error
|
||||
0: {
|
||||
0: "Board unknown error.",
|
||||
},
|
||||
1: {
|
||||
"n": "Slot {n} miner type error.",
|
||||
}, # board miner type error
|
||||
2: {
|
||||
"n": "Slot {n} bin type error.",
|
||||
}, # chip bin type error
|
||||
3: {
|
||||
"n": "Slot {n} not found.",
|
||||
}, # board not found error
|
||||
4: {
|
||||
"n": "Slot {n} error reading chip id.",
|
||||
}, # reading chip id error
|
||||
5: {
|
||||
"n": "Slot {n} has bad chips.",
|
||||
}, # board has bad chips error
|
||||
6: {
|
||||
"n": "Slot {n} loss of balance error.",
|
||||
}, # loss of balance error
|
||||
7: {
|
||||
"n": "Slot {n} xfer error chip.",
|
||||
}, # xfer error
|
||||
8: {
|
||||
"n": "Slot {n} reset error.",
|
||||
}, # reset error
|
||||
9: {
|
||||
"n": "Slot {n} frequency too low.",
|
||||
}, # freq error
|
||||
},
|
||||
6: { # env temp error
|
||||
0: {0: "Environment temperature is too high."}, # normal env temp error
|
||||
0: {
|
||||
0: "Environment temperature is too high.",
|
||||
}, # normal env temp error
|
||||
1: { # high power env temp error
|
||||
0: "Environment temperature is too high for high performance mode."
|
||||
0: "Environment temperature is too high for high performance mode.",
|
||||
},
|
||||
},
|
||||
7: { # control board error
|
||||
0: {0: "MAC address invalid", 1: "Control board no support chip."},
|
||||
0: {
|
||||
0: "MAC address invalid",
|
||||
1: "Control board no support chip.",
|
||||
},
|
||||
1: {
|
||||
0: "Control board rebooted as an exception.",
|
||||
1: "Control board rebooted as exception and cpufreq reduced, please upgrade the firmware",
|
||||
2: "Control board rebooted as an exception.",
|
||||
3: "The network is unstable, change time.",
|
||||
4: "Unknown error.",
|
||||
},
|
||||
2: {
|
||||
"n": "Control board slot {n} frame error.",
|
||||
},
|
||||
},
|
||||
8: { # checksum error
|
||||
@@ -228,62 +297,152 @@ ERROR_CODES = {
|
||||
0: "CGMiner checksum error.",
|
||||
1: "System monitor checksum error.",
|
||||
2: "Remote daemon checksum error.",
|
||||
}
|
||||
},
|
||||
1: {0: "Air to liquid PCB serial # does not match."},
|
||||
},
|
||||
9: {0: {1: "Power rate error."}}, # power rate error
|
||||
9: {
|
||||
0: {0: "Unknown error.", 1: "Power rate error.", 2: "Unknown error."}
|
||||
}, # power rate error
|
||||
20: { # pool error
|
||||
1: {0: "All pools are disabled."}, # all disabled error
|
||||
2: {"n": "Pool {n} connection failed."}, # pool connection failed error
|
||||
3: {0: "High rejection rate on pool."}, # rejection rate error
|
||||
0: {
|
||||
0: "No pool information configured.",
|
||||
},
|
||||
1: {
|
||||
0: "All pools are disabled.",
|
||||
}, # all disabled error
|
||||
2: {
|
||||
"n": "Pool {n} connection failed.",
|
||||
}, # pool connection failed error
|
||||
3: {
|
||||
0: "High rejection rate on pool.",
|
||||
}, # rejection rate error
|
||||
4: { # asicboost not supported error
|
||||
0: "The pool does not support asicboost mode."
|
||||
0: "The pool does not support asicboost mode.",
|
||||
},
|
||||
},
|
||||
21: {1: {"n": "Slot {n} factory test step failed."}},
|
||||
21: {
|
||||
1: {
|
||||
"n": "Slot {n} factory test step failed.",
|
||||
}
|
||||
},
|
||||
23: { # hashrate error
|
||||
1: {0: "Hashrate is too low."},
|
||||
2: {0: "Hashrate is too low."},
|
||||
3: {0: "Hashrate loss is too high."},
|
||||
4: {0: "Hashrate loss is too high."},
|
||||
5: {0: "Hashrate loss."},
|
||||
1: {
|
||||
0: "Hashrate is too low.",
|
||||
},
|
||||
2: {
|
||||
0: "Hashrate is too low.",
|
||||
},
|
||||
3: {
|
||||
0: "Hashrate loss is too high.",
|
||||
},
|
||||
4: {
|
||||
0: "Hashrate loss is too high.",
|
||||
},
|
||||
5: {
|
||||
0: "Hashrate loss.",
|
||||
},
|
||||
},
|
||||
50: { # water velocity error/voltage error
|
||||
1: {"n": "Slot {n} chip voltage too low."},
|
||||
2: {"n": "Slot {n} chip voltage changed."},
|
||||
3: {"n": "Slot {n} chip temperature difference is too large."},
|
||||
4: {"n": "Slot {n} chip hottest temperature difference is too large."},
|
||||
7: {"n": "Slot {n} water velocity is abnormal."}, # abnormal water velocity
|
||||
8: {0: "Chip temp calibration failed, please restore factory settings."},
|
||||
9: {"n": "Slot {n} chip temp calibration check no balance."},
|
||||
1: {
|
||||
"n": "Slot {n} chip voltage too low.",
|
||||
},
|
||||
2: {
|
||||
"n": "Slot {n} chip voltage changed.",
|
||||
},
|
||||
3: {
|
||||
"n": "Slot {n} chip temperature difference is too large.",
|
||||
},
|
||||
4: {
|
||||
"n": "Slot {n} chip hottest temperature difference is too large.",
|
||||
},
|
||||
5: {"n": "Slot {n} stopped hashing, chips temperature protecting."},
|
||||
7: {
|
||||
"n": "Slot {n} water velocity is abnormal.",
|
||||
}, # abnormal water velocity
|
||||
8: {
|
||||
0: "Chip temp calibration failed, please restore factory settings.",
|
||||
},
|
||||
9: {
|
||||
"n": "Slot {n} chip temp calibration check no balance.",
|
||||
},
|
||||
},
|
||||
51: { # frequency error
|
||||
1: {"n": "Slot {n} frequency up timeout."}, # frequency up timeout
|
||||
7: {"n": "Slot {n} frequency up timeout."}, # frequency up timeout
|
||||
1: {
|
||||
"n": "Slot {n} frequency up timeout.",
|
||||
}, # frequency up timeout
|
||||
2: {"n": "Slot {n} too many CRC errors."},
|
||||
3: {"n": "Slot {n} unstable."},
|
||||
7: {
|
||||
"n": "Slot {n} frequency up timeout.",
|
||||
}, # frequency up timeout
|
||||
},
|
||||
52: {
|
||||
"n": {
|
||||
"c": "Slot {n} chip {c} error nonce.",
|
||||
},
|
||||
},
|
||||
53: {
|
||||
"n": {
|
||||
"c": "Slot {n} chip {c} too few nonce.",
|
||||
},
|
||||
},
|
||||
54: {
|
||||
"n": {
|
||||
"c": "Slot {n} chip {c} temp protected.",
|
||||
},
|
||||
},
|
||||
55: {
|
||||
"n": {
|
||||
"c": "Slot {n} chip {c} has been reset.",
|
||||
},
|
||||
},
|
||||
56: {
|
||||
"n": {
|
||||
"c": "Slot {n} chip {c} zero nonce.",
|
||||
},
|
||||
},
|
||||
52: {"n": {"c": "Slot {n} chip {c} error nonce."}},
|
||||
53: {"n": {"c": "Slot {n} chip {c} too few nonce."}},
|
||||
54: {"n": {"c": "Slot {n} chip {c} temp protected."}},
|
||||
55: {"n": {"c": "Slot {n} chip {c} has been reset."}},
|
||||
80: {
|
||||
0: {0: "The tool version is too low, please update."},
|
||||
1: {0: "Low freq."},
|
||||
2: {0: "Low hashrate."},
|
||||
3: {5: "High env temp."},
|
||||
0: {
|
||||
0: "The tool version is too low, please update.",
|
||||
},
|
||||
1: {
|
||||
0: "Low freq.",
|
||||
},
|
||||
2: {
|
||||
0: "Low hashrate.",
|
||||
},
|
||||
3: {
|
||||
5: "High env temp.",
|
||||
},
|
||||
},
|
||||
81: {
|
||||
0: {0: "Chip data error."},
|
||||
0: {
|
||||
0: "Chip data error.",
|
||||
},
|
||||
},
|
||||
82: {
|
||||
0: {0: "Power version error."},
|
||||
1: {0: "Miner type error."},
|
||||
2: {0: "Version info error."},
|
||||
0: {
|
||||
0: "Power version error.",
|
||||
},
|
||||
1: {
|
||||
0: "Miner type error.",
|
||||
},
|
||||
2: {
|
||||
0: "Version info error.",
|
||||
},
|
||||
},
|
||||
83: {
|
||||
0: {0: "Empty level error."},
|
||||
0: {
|
||||
0: "Empty level error.",
|
||||
},
|
||||
},
|
||||
84: {
|
||||
0: {0: "Old firmware."},
|
||||
1: {0: "Software version error."},
|
||||
0: {
|
||||
0: "Old firmware.",
|
||||
},
|
||||
1: {
|
||||
0: "Software version error.",
|
||||
},
|
||||
},
|
||||
85: {
|
||||
"n": {
|
||||
@@ -295,8 +454,12 @@ ERROR_CODES = {
|
||||
},
|
||||
},
|
||||
86: {
|
||||
0: {0: "Missing product serial #."},
|
||||
1: {0: "Missing product type."},
|
||||
0: {
|
||||
0: "Missing product serial #.",
|
||||
},
|
||||
1: {
|
||||
0: "Missing product type.",
|
||||
},
|
||||
2: {
|
||||
0: "Missing miner serial #.",
|
||||
1: "Wrong miner serial # length.",
|
||||
@@ -313,12 +476,34 @@ ERROR_CODES = {
|
||||
3: "Wrong power model rate.",
|
||||
4: "Wrong power model format.",
|
||||
},
|
||||
5: {0: "Wrong hash board struct."},
|
||||
6: {0: "Wrong miner cooling type."},
|
||||
7: {0: "Missing PCB serial #."},
|
||||
5: {
|
||||
0: "Wrong hash board struct.",
|
||||
},
|
||||
6: {
|
||||
0: "Wrong miner cooling type.",
|
||||
},
|
||||
7: {
|
||||
0: "Missing PCB serial #.",
|
||||
},
|
||||
},
|
||||
87: {
|
||||
0: {
|
||||
0: "Miner power mismatch.",
|
||||
},
|
||||
},
|
||||
90: {
|
||||
0: {
|
||||
0: "Process error, exited with signal: 3.",
|
||||
},
|
||||
1: {
|
||||
0: "Process error, exited with signal: 3.",
|
||||
},
|
||||
},
|
||||
99: {
|
||||
9: {
|
||||
9: "Miner unknown error.",
|
||||
},
|
||||
},
|
||||
87: {0: {0: "Miner power mismatch."}},
|
||||
99: {9: {9: "Miner unknown error."}},
|
||||
1000: {
|
||||
0: {
|
||||
0: "Security library error, please upgrade firmware",
|
||||
@@ -327,7 +512,11 @@ ERROR_CODES = {
|
||||
3: "/antiv/dig/pf_partial.dig illegal.",
|
||||
},
|
||||
},
|
||||
1001: {0: {0: "Security BTMiner removed, please upgrade firmware."}},
|
||||
1001: {
|
||||
0: {
|
||||
0: "Security BTMiner removed, please upgrade firmware.",
|
||||
},
|
||||
},
|
||||
1100: {
|
||||
0: {
|
||||
0: "Security illegal file, please upgrade firmware.",
|
||||
|
||||
44
pyasic/data/fans.py
Normal file
44
pyasic/data/fans.py
Normal file
@@ -0,0 +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. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Fan(BaseModel):
|
||||
"""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}")
|
||||
94
pyasic/data/pools.py
Normal file
94
pyasic/data/pools.py
Normal file
@@ -0,0 +1,94 @@
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from pydantic import BaseModel, computed_field, model_serializer
|
||||
from typing_extensions import Self
|
||||
|
||||
|
||||
class Scheme(Enum):
|
||||
STRATUM_V1 = "stratum+tcp"
|
||||
STRATUM_V2 = "stratum2+tcp"
|
||||
STRATUM_V1_SSL = "stratum+ssl"
|
||||
|
||||
|
||||
class PoolUrl(BaseModel):
|
||||
scheme: Scheme
|
||||
host: str
|
||||
port: int
|
||||
pubkey: Optional[str] = None
|
||||
|
||||
@model_serializer
|
||||
def serialize(self):
|
||||
return str(self)
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.scheme == Scheme.STRATUM_V2 and self.pubkey:
|
||||
return f"{self.scheme.value}://{self.host}:{self.port}/{self.pubkey}"
|
||||
else:
|
||||
return f"{self.scheme.value}://{self.host}:{self.port}"
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, url: str) -> Self | None:
|
||||
parsed_url = urlparse(url)
|
||||
if not parsed_url.hostname:
|
||||
return None
|
||||
if not parsed_url.scheme.strip() == "":
|
||||
scheme = Scheme(parsed_url.scheme)
|
||||
else:
|
||||
scheme = Scheme.STRATUM_V1
|
||||
host = parsed_url.hostname
|
||||
port = parsed_url.port
|
||||
pubkey = parsed_url.path.lstrip("/") if scheme == Scheme.STRATUM_V2 else None
|
||||
return cls(scheme=scheme, host=host, port=port, pubkey=pubkey)
|
||||
|
||||
|
||||
class PoolMetrics(BaseModel):
|
||||
"""A dataclass to standardize pool metrics returned from miners.
|
||||
Attributes:
|
||||
|
||||
accepted: Number of accepted shares.
|
||||
rejected: Number of rejected shares.
|
||||
get_failures: Number of failures in obtaining work from the pool.
|
||||
remote_failures: Number of failures communicating with the pool server.
|
||||
active: Indicates if the miner is connected to the stratum server.
|
||||
Alive : Indicates if a pool is alive.
|
||||
url: URL of the pool.
|
||||
index: Index of the pool.
|
||||
user: Username for the pool.
|
||||
pool_rejected_percent: Percentage of rejected shares by the pool.
|
||||
pool_stale_percent: Percentage of stale shares by the pool.
|
||||
"""
|
||||
|
||||
url: PoolUrl | None
|
||||
accepted: int | None = None
|
||||
rejected: int | None = None
|
||||
get_failures: int | None = None
|
||||
remote_failures: int | None = None
|
||||
active: bool | None = None
|
||||
alive: bool | None = None
|
||||
index: int | None = None
|
||||
user: str | None = None
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def pool_rejected_percent(self) -> float: # noqa - Skip PyCharm inspection
|
||||
"""Calculate and return the percentage of rejected shares"""
|
||||
return self._calculate_percentage(self.rejected, self.accepted + self.rejected)
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def pool_stale_percent(self) -> float: # noqa - Skip PyCharm inspection
|
||||
"""Calculate and return the percentage of stale shares."""
|
||||
return self._calculate_percentage(
|
||||
self.get_failures, self.accepted + self.rejected
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _calculate_percentage(value: int, total: int) -> float:
|
||||
"""Calculate the percentage."""
|
||||
if value is None or total is None:
|
||||
return 0
|
||||
if total == 0:
|
||||
return 0
|
||||
return (value / total) * 100
|
||||
4
pyasic/device/__init__.py
Normal file
4
pyasic/device/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .algorithm import MinerAlgo
|
||||
from .firmware import MinerFirmware
|
||||
from .makes import MinerMake
|
||||
from .models import MinerModel
|
||||
26
pyasic/device/algorithm/__init__.py
Normal file
26
pyasic/device/algorithm/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from .base import MinerAlgoType
|
||||
from .blake256 import Blake256Algo
|
||||
from .eaglesong import EaglesongAlgo
|
||||
from .equihash import EquihashAlgo
|
||||
from .ethash import EtHashAlgo
|
||||
from .handshake import HandshakeAlgo
|
||||
from .hashrate import *
|
||||
from .hashrate.unit import *
|
||||
from .kadena import KadenaAlgo
|
||||
from .kheavyhash import KHeavyHashAlgo
|
||||
from .scrypt import ScryptAlgo
|
||||
from .sha256 import SHA256Algo
|
||||
from .x11 import X11Algo
|
||||
|
||||
|
||||
class MinerAlgo:
|
||||
SHA256 = SHA256Algo
|
||||
SCRYPT = ScryptAlgo
|
||||
KHEAVYHASH = KHeavyHashAlgo
|
||||
KADENA = KadenaAlgo
|
||||
HANDSHAKE = HandshakeAlgo
|
||||
X11 = X11Algo
|
||||
BLAKE256 = Blake256Algo
|
||||
EAGLESONG = EaglesongAlgo
|
||||
ETHASH = EtHashAlgo
|
||||
EQUIHASH = EquihashAlgo
|
||||
16
pyasic/device/algorithm/base.py
Normal file
16
pyasic/device/algorithm/base.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .hashrate.base import AlgoHashRateType
|
||||
from .hashrate.unit.base import AlgoHashRateUnitType
|
||||
|
||||
|
||||
class MinerAlgoMeta(type):
|
||||
name: str
|
||||
|
||||
def __str__(cls):
|
||||
return cls.name
|
||||
|
||||
|
||||
class MinerAlgoType(metaclass=MinerAlgoMeta):
|
||||
hashrate: type[AlgoHashRateType]
|
||||
unit: type[AlgoHashRateUnitType]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user