Compare commits
1670 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1466039e08 | ||
|
|
0aa72c6c85 | ||
|
|
24134a5991 | ||
|
|
038208efa6 | ||
|
|
7e319b79df | ||
|
|
a0fdec3ce5 | ||
|
|
1a8928de18 | ||
|
|
bce0058930 | ||
|
|
850656fce4 | ||
|
|
8bb35d6d7c | ||
|
|
e85f06dbc2 | ||
|
|
a566801316 | ||
|
|
e1a9cc5d19 | ||
|
|
27bb06de2b | ||
|
|
debd4d2d4d | ||
|
|
56ad6cbc6f | ||
|
|
3fa54213bf | ||
|
|
076958ec0e | ||
|
|
5319089ebe | ||
|
|
76a77b51e8 | ||
|
|
b099ff45d2 | ||
|
|
9bc3cc221a | ||
|
|
6418c2e102 | ||
|
|
aa9f3b2c45 | ||
|
|
bb1c98f061 | ||
|
|
d984431fe5 | ||
|
|
f1e4feb91e | ||
|
|
90c8986900 | ||
|
|
5457ae6cd5 | ||
|
|
aa3d105fcb | ||
|
|
77f59f6db6 | ||
|
|
3fa0d96fbb | ||
|
|
e55477a8b8 | ||
|
|
7d5744ae28 | ||
|
|
d4500be10c | ||
|
|
7ef2540133 | ||
|
|
1ea4f4e124 | ||
|
|
a8a0e4a5fe | ||
|
|
5f2f6e01da | ||
|
|
41b4c23d45 | ||
|
|
b4687f18fd | ||
|
|
2437421005 | ||
|
|
40ebc2773f | ||
|
|
b8ae238d23 | ||
|
|
2920639b70 | ||
|
|
bd9144b3de | ||
|
|
8f7a67d4dc | ||
|
|
a62bea33a7 | ||
|
|
406bcd179c | ||
|
|
aa87ef7d71 | ||
|
|
ec88fbf6aa | ||
|
|
e23c86a944 | ||
|
|
b81276cb19 | ||
|
|
8dcc72b1bb | ||
|
|
540572356f | ||
|
|
83035a869b | ||
|
|
4c104a59ff | ||
|
|
e708ae3728 | ||
|
|
a4352816ee | ||
|
|
336bd9c002 | ||
|
|
e3c917efde | ||
|
|
4d71012ed6 | ||
|
|
1acdba8ae0 | ||
|
|
5567f26c03 | ||
|
|
0027485582 | ||
|
|
c9bcb7bab6 | ||
|
|
0ee261930e | ||
|
|
3cfc8dded9 | ||
|
|
655cf6d0ac | ||
|
|
640dc6d8c2 | ||
|
|
a572fedb4d | ||
|
|
6c46a7cd71 | ||
|
|
49f42172da | ||
|
|
83be80e4bc | ||
|
|
0be9e9d519 | ||
|
|
4694c0f774 | ||
|
|
11012b310b | ||
|
|
8852fab3ee | ||
|
|
ec1b2ca162 | ||
|
|
1f9d4c8c10 | ||
|
|
5bb04b2af4 | ||
|
|
2e192a1536 | ||
|
|
4da8044bc7 | ||
|
|
76078e4d0e | ||
|
|
276a476fab | ||
|
|
e0abed4f93 | ||
|
|
4a67bd5d99 | ||
|
|
eb48d04939 | ||
|
|
c4697728e4 | ||
|
|
de64172073 | ||
|
|
4d7a13433b | ||
|
|
e14a4791b2 | ||
|
|
0e76d4550b | ||
|
|
2d424025e9 | ||
|
|
bf4903ce4b | ||
|
|
4f7f6bf045 | ||
|
|
824890ec97 | ||
|
|
ce9d7ffb0f | ||
|
|
183b4934c1 | ||
|
|
3d2b260b17 | ||
|
|
f88c1734eb | ||
|
|
b897ca8363 | ||
|
|
dba341fdae | ||
|
|
837794bd57 | ||
|
|
36d16c7235 | ||
|
|
7797023689 | ||
|
|
1021f200ec | ||
|
|
197d6568e3 | ||
|
|
71c8905674 | ||
|
|
15c3806fbf | ||
|
|
a5c42c9c2b | ||
|
|
90d8a795e6 | ||
|
|
07cf1b134c | ||
|
|
53e1f33fa6 | ||
|
|
16ab2bd4e3 | ||
|
|
de728f12d2 | ||
|
|
8092e12dfb | ||
|
|
e70c3e9f79 | ||
|
|
39a97f2914 | ||
|
|
35af74ad1a | ||
|
|
15fa91fb98 | ||
|
|
084987a3e1 | ||
|
|
e93cc77a58 | ||
|
|
788d43c51c | ||
|
|
74c22b82ce | ||
|
|
1f46ce1b9a | ||
|
|
7964336a0c | ||
|
|
a24fc07c2a | ||
|
|
4c64481d3b | ||
|
|
66fb5834f0 | ||
|
|
db05cc1d97 | ||
|
|
418e3ce26e | ||
|
|
5a0bb11a44 | ||
|
|
39f9d087db | ||
|
|
067a376f94 | ||
|
|
a5d6e122f9 | ||
|
|
b160fd75ba | ||
|
|
d1007d3ae8 | ||
|
|
33803e89e2 | ||
|
|
10a44b9877 | ||
|
|
bbd883f639 | ||
|
|
8e2ad478e9 | ||
|
|
957981a9c6 | ||
|
|
13a67dfdd1 | ||
|
|
e86f2b62c5 | ||
|
|
88b4d2cac3 | ||
|
|
5842ef3d97 | ||
|
|
339a689267 | ||
|
|
a0764806c4 | ||
|
|
6eec0f6d44 | ||
|
|
0854f7833c | ||
|
|
b6edc85679 | ||
|
|
ff11ebc304 | ||
|
|
f3681f1aa5 | ||
|
|
1a7411edb3 | ||
|
|
f2a4a5d524 | ||
|
|
624a3c5919 | ||
|
|
2ec8054d24 | ||
|
|
d148ccfe5f | ||
|
|
b6c29d16f9 | ||
|
|
53a3bb13af | ||
|
|
16e74e659c | ||
|
|
730caca23f | ||
|
|
dc126b2953 | ||
|
|
51abdf0b2d | ||
|
|
b367b2d293 | ||
|
|
96f52a4b35 | ||
|
|
5236e02af2 | ||
|
|
9ee52f77f9 | ||
|
|
26389f8ba0 | ||
|
|
8a0605bd3b | ||
|
|
fe9894919e | ||
|
|
bb399fe362 | ||
|
|
7ca7fe3e7e | ||
|
|
748279c25d | ||
|
|
23890fa10a | ||
|
|
8ab7df516e | ||
|
|
f7a0188104 | ||
|
|
66a8932ea3 | ||
|
|
56536fd258 | ||
|
|
1b4e6d4da0 | ||
|
|
2019bdaff2 | ||
|
|
b903cc6e5f | ||
|
|
c1a01b5f7b | ||
|
|
3427a8d15a | ||
|
|
25c08b9bc0 | ||
|
|
01263da52b | ||
|
|
5081319a2f | ||
|
|
ec5be00065 | ||
|
|
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 | ||
|
|
1cd2566d0a | ||
|
|
1f1e5f23a2 | ||
|
|
3394234e4f | ||
|
|
e9882124ff | ||
|
|
1e5b19c149 | ||
|
|
c9339fec2f | ||
|
|
018c09e84f | ||
|
|
46e7f9a569 | ||
|
|
996ab58252 | ||
|
|
04974d5287 | ||
|
|
1a8a5ccb0e | ||
|
|
4c61c4c345 | ||
|
|
a6d6bfe73d | ||
|
|
f159e9ccb4 | ||
|
|
8ae2932274 | ||
|
|
52786ba954 | ||
|
|
f5cc526e8a | ||
|
|
a616aaba04 | ||
|
|
15b4177ce4 | ||
|
|
5b382cdb0b | ||
|
|
16622099c8 | ||
|
|
a75434fe7b | ||
|
|
020558ed4d | ||
|
|
39a82d03bc | ||
|
|
41ecb5dbc6 | ||
|
|
2d057ca9f6 | ||
|
|
b71b23d2a0 | ||
|
|
b32649435d | ||
|
|
c0096126df | ||
|
|
d632360932 | ||
|
|
400001fa38 | ||
|
|
4ff32a8081 | ||
|
|
33b4ae2f2f | ||
|
|
62194bd627 | ||
|
|
83bb2950fa | ||
|
|
262dee3cfd | ||
|
|
7ccf6ed610 | ||
|
|
21b189f5a8 | ||
|
|
d8d8a050ce | ||
|
|
b9ca810903 | ||
|
|
6ad750d3e9 | ||
|
|
8423b64825 | ||
|
|
742ddef227 | ||
|
|
85d7f0abfb | ||
|
|
c4b4fa293d | ||
|
|
0204abfead | ||
|
|
3510f7b9d3 | ||
|
|
2d4c063dfa | ||
|
|
67b3e2f312 | ||
|
|
82006de30f | ||
|
|
5bde9d7fe6 | ||
|
|
f7cd428366 | ||
|
|
d5e797de9e | ||
|
|
b9d5e7b206 | ||
|
|
2d8c7eb4fd | ||
|
|
f69e07fe68 | ||
|
|
84aab38954 | ||
|
|
dcf37481bd | ||
|
|
1a9cca84d5 | ||
|
|
c5272d67de | ||
|
|
3bcfb14177 | ||
|
|
566280f280 | ||
|
|
a814f7eefb | ||
|
|
097b8ed534 | ||
|
|
da47d72749 | ||
|
|
abd4d18a01 | ||
|
|
2adbce3c21 | ||
|
|
c41324b324 | ||
|
|
151a4f6c2d | ||
|
|
d23777a83f | ||
|
|
bb0aee8337 | ||
|
|
2a23acb73a | ||
|
|
ad5eb0cef6 | ||
|
|
07dd8f55fe | ||
|
|
bafa91cc47 | ||
|
|
16399510fa | ||
|
|
e9f54eec0a | ||
|
|
fbbbc9f215 | ||
|
|
69e4f575c0 | ||
|
|
e95659d2e0 | ||
|
|
35f34310ec | ||
|
|
acc18e20fd | ||
|
|
72460eac08 | ||
|
|
3e5f9d4eca | ||
|
|
e873fa252c | ||
|
|
ff2c083a19 | ||
|
|
a30a84c34b | ||
|
|
97d2023298 | ||
|
|
1ce8430a14 | ||
|
|
1c0b638818 | ||
|
|
e852588eeb | ||
|
|
08b9bfd854 | ||
|
|
7ee2f3a29a | ||
|
|
5ee6a38f39 | ||
|
|
8f0bfd5f83 | ||
|
|
c54f39fc77 | ||
|
|
eb40769ccf | ||
|
|
418c62b40d | ||
|
|
198a480c35 | ||
|
|
2d4bf4e847 | ||
|
|
774e3d1a62 | ||
|
|
ade3cd6fee | ||
|
|
28dc3ccb84 | ||
|
|
6ca8582ec3 | ||
|
|
74b4aeb44a | ||
|
|
9c7ab5ac57 | ||
|
|
65ecf1fea2 | ||
|
|
44142c658b | ||
|
|
25a205ce6c | ||
|
|
25094084cf | ||
|
|
4eac601153 | ||
|
|
f0d8d66b9b | ||
|
|
cfa550f8c0 | ||
|
|
91f6a5bf41 | ||
|
|
464bd6be65 | ||
|
|
031d7e2186 | ||
|
|
126b0d124c | ||
|
|
81c84a3e8f | ||
|
|
406d5bd549 | ||
|
|
cd5fe09fd9 | ||
|
|
766fc4efed | ||
|
|
b70fed40c8 | ||
|
|
255d98fd08 | ||
|
|
78304631f7 | ||
|
|
ab230844fc | ||
|
|
8a58cb9fd3 | ||
|
|
70b6ed73dc | ||
|
|
d2400bf44e | ||
|
|
db780fe876 | ||
|
|
cd84ae828a | ||
|
|
970d5d1031 | ||
|
|
da0e327ec7 | ||
|
|
4c84a8d572 | ||
|
|
1cfd895deb | ||
|
|
d0da08eb10 | ||
|
|
ee4f2cd87d | ||
|
|
8a6577c8aa | ||
|
|
ed5eae4187 | ||
|
|
f3b25027ad | ||
|
|
228daecbbf | ||
|
|
81aa569d07 | ||
|
|
aba8d7b8b9 | ||
|
|
449403bc57 | ||
|
|
997a1bbe31 | ||
|
|
2c7e0e3efe | ||
|
|
6051a46654 | ||
|
|
c7847d2789 | ||
|
|
215af72a6b | ||
|
|
b33586f8eb | ||
|
|
8db81d9b3c | ||
|
|
cf3b2fedf4 | ||
|
|
81db6d8e28 | ||
|
|
d47e59d057 | ||
|
|
8388d2f4ac | ||
|
|
f5ec513ce0 | ||
|
|
8975842f0b | ||
|
|
c6ca1df112 | ||
|
|
92f58a9682 | ||
|
|
5dc19dbd78 | ||
|
|
b0fc11bcc1 | ||
|
|
23ebe460a4 | ||
|
|
92db8c8161 | ||
|
|
d9b522734a | ||
|
|
e02457f0b2 | ||
|
|
104b4c18c2 | ||
|
|
1970f8c924 | ||
|
|
e431a06ac5 | ||
|
|
6da9b58088 | ||
|
|
3108443041 | ||
|
|
37ebb9e6c3 | ||
|
|
6c2e0f59b1 | ||
|
|
6eb9cdce90 | ||
|
|
e96d9447d1 | ||
|
|
d3cca11322 | ||
|
|
86155db455 | ||
|
|
5b078b4b27 | ||
|
|
9672dd6873 | ||
|
|
1587f65196 | ||
|
|
5ff10c0cdd | ||
|
|
aa0a028564 | ||
|
|
82f6d2f274 | ||
|
|
833de3ab43 | ||
|
|
08d273c7c1 | ||
|
|
aac7598187 | ||
|
|
5c0ac4e665 | ||
|
|
7bd5e49412 | ||
|
|
53ff3c5f79 | ||
|
|
36ae6e5272 | ||
|
|
fb3dffb216 | ||
|
|
ff6a6d2ec6 | ||
|
|
f4bbc2c3e5 | ||
|
|
4dbfdbe29c | ||
|
|
5e2a18f91e | ||
|
|
3363bdc592 | ||
|
|
08180a2d59 | ||
|
|
1a64ff4038 | ||
|
|
8ad90a6abb | ||
|
|
8cdd5ff015 | ||
|
|
a08f434e1f | ||
|
|
9acd6d2fea | ||
|
|
0a1cdea2e3 | ||
|
|
73b1a0493c | ||
|
|
d3a5517fa9 | ||
|
|
ad42251ee9 | ||
|
|
0670938ed3 | ||
|
|
3e96889976 | ||
|
|
5a7b43ad74 | ||
|
|
08c4863a2e | ||
|
|
4dbab75cf4 | ||
|
|
a90ad3ba6e | ||
|
|
98a94ce4a6 | ||
|
|
f0a8b6e1c7 | ||
|
|
e07bd3bffb | ||
|
|
dcce944390 | ||
|
|
03ecd118a3 | ||
|
|
97c0331762 | ||
|
|
eda9804dea | ||
|
|
e94c81ce44 | ||
|
|
c95c58138e | ||
|
|
03c93b4de1 | ||
|
|
ff0d15c365 | ||
|
|
eadcb76d31 | ||
|
|
b7ce9288f8 | ||
|
|
e077a099d9 | ||
|
|
8542acfb01 | ||
|
|
0d80ce5a0e | ||
|
|
ddcafe0f2b | ||
|
|
ea195b34db | ||
|
|
7377cb0d26 | ||
|
|
24b66de971 | ||
|
|
62d664a14c | ||
|
|
03b9a90f68 | ||
|
|
fefe0324b9 | ||
|
|
62b14a78b7 | ||
|
|
0ff505bbb4 | ||
|
|
b6c8c930a2 | ||
|
|
903bb93c4e | ||
|
|
59667cf104 | ||
|
|
3fd1b41bec | ||
|
|
6569107f64 | ||
|
|
9d746a6dcb | ||
|
|
fce4c07c32 | ||
|
|
094857758a | ||
|
|
2a49b89849 | ||
|
|
4ecd135734 | ||
|
|
836defc216 | ||
|
|
f8f777b5b5 | ||
|
|
b15e0a7363 | ||
|
|
5c1d06f743 | ||
|
|
51de56feb3 | ||
|
|
256a4ac909 | ||
|
|
09800c8ad2 | ||
|
|
83a7d8c60f | ||
|
|
ee2698be50 | ||
|
|
dc43087b0d | ||
|
|
4fa3511725 | ||
|
|
4b9ae70424 | ||
|
|
74ebffb4fc | ||
|
|
54206da449 | ||
|
|
dd54ff7ee4 | ||
|
|
abef0c3d59 | ||
|
|
957c9a3678 | ||
|
|
50ccfec1b3 | ||
|
|
8e7d6751e2 | ||
|
|
b77c4173c6 | ||
|
|
4941cffb70 | ||
|
|
81d5d23189 | ||
|
|
9da5a836ce | ||
|
|
c9a536fc60 | ||
|
|
fa172b56b0 | ||
|
|
ee45f2342e | ||
|
|
1f59ef025d | ||
|
|
d6a153144f | ||
|
|
99001e2e13 | ||
|
|
92b847656e | ||
|
|
a41525e828 | ||
|
|
5e9588cc56 | ||
|
|
b8239703c1 | ||
|
|
5d49135b59 | ||
|
|
3a5a76080b | ||
|
|
f23e10d629 | ||
|
|
b7d4891140 | ||
|
|
5f5cbd9060 | ||
|
|
8379359caf | ||
|
|
62238192ce | ||
|
|
1997003643 | ||
|
|
3a81844898 | ||
|
|
0ac80fb205 | ||
|
|
9494018c12 | ||
|
|
0bc86c98c5 | ||
|
|
f0d69c9ca7 | ||
|
|
b81590bd2e | ||
|
|
a53e01df6f | ||
|
|
f63e063954 | ||
|
|
9cbaf7076a | ||
|
|
daa5ac5870 | ||
|
|
0b8c08016b | ||
|
|
8c768d351b | ||
|
|
c9e7fa2629 | ||
|
|
9d3f2b5968 | ||
|
|
283e3d5e11 | ||
|
|
add4b575c2 | ||
|
|
af2f1e9ad5 | ||
|
|
8258320a7b | ||
|
|
a5dc7f485b | ||
|
|
025b5bf6f0 | ||
|
|
3d3064d78e | ||
|
|
2e3991355b | ||
|
|
73a4cf5834 | ||
|
|
b120064e80 | ||
|
|
3ec833e700 | ||
|
|
29aeea1194 | ||
|
|
994d53ae3b | ||
|
|
a95333eb1c | ||
|
|
c5f2d71791 |
17
.coveragerc
Normal file
17
.coveragerc
Normal file
@@ -0,0 +1,17 @@
|
||||
[report]
|
||||
exclude_lines =
|
||||
# Skip @abstractmethod
|
||||
@abstractmethod
|
||||
@abc.abstractmethod
|
||||
|
||||
# Don't complain if tests don't hit defensive assertion code:
|
||||
raise AssertionError
|
||||
raise NotImplementedError
|
||||
|
||||
# Don't complain about missing debug-only code:
|
||||
def __repr__
|
||||
if self\.debug
|
||||
|
||||
# Don't complain if non-runnable code isn't run:
|
||||
if 0:
|
||||
if __name__ == .__main__.:
|
||||
8
.github/workflows/python-publish.yml
vendored
8
.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 }}
|
||||
pypi_token: ${{ secrets.PYPI_API_KEY }}
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,3 +6,5 @@ pyvenv.cfg
|
||||
.env/
|
||||
bin/
|
||||
lib/
|
||||
.idea/
|
||||
.vs/
|
||||
49
.pre-commit-config.yaml
Normal file
49
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,49 @@
|
||||
ci:
|
||||
skip:
|
||||
- poetry-lock
|
||||
- unittest
|
||||
- generate-docs
|
||||
repos:
|
||||
- repo: https://github.com/python-poetry/poetry
|
||||
rev: 2.1.2
|
||||
hooks:
|
||||
- id: poetry-check
|
||||
- id: poetry-lock
|
||||
- id: poetry-install
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
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: 25.1.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 6.0.1
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort (python)
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: unittest
|
||||
name: unittest
|
||||
entry: python -m unittest discover
|
||||
language: system
|
||||
types: [ python ]
|
||||
args: ["-p '*test.py'"] # Probably this option is absolutely not needed.
|
||||
pass_filenames: false
|
||||
- id: generate-docs
|
||||
name: generate-docs
|
||||
entry: python docs/generate_miners.py
|
||||
language: system
|
||||
types: [ python ]
|
||||
pass_filenames: false
|
||||
@@ -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
|
||||
176
LICENSE.txt
Normal file
176
LICENSE.txt
Normal file
@@ -0,0 +1,176 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
382
README.md
382
README.md
@@ -1,169 +1,293 @@
|
||||
# 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/)
|
||||
## Documentation
|
||||
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/)
|
||||
|
||||
## Usage
|
||||
[](https://pypi.org/project/pyasic/)
|
||||
[](https://www.codefactor.io/repository/github/upstreamdata/pyasic)
|
||||
[](https://github.com/UpstreamData/pyasic/commits/master/)
|
||||
|
||||
### Standard Usage
|
||||
You can install pyasic directly from pip with the command `pip install pyasic`
|
||||
[](https://github.com/psf/black)
|
||||
[](https://docs.pyasic.org)
|
||||
[](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
|
||||
|
||||
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)
|
||||
---
|
||||
## Intro
|
||||
|
||||
### Developers
|
||||
To use this repo, first download it, create a virtual environment, enter the virtual environment, and install relevant packages by navigating to this directory and running ```pip install -r requirements.txt``` on Windows or ```pip3 install -r requirements.txt``` on Mac or UNIX if the first command fails.
|
||||
Welcome to `pyasic`! `pyasic` uses an asynchronous method of communicating with ASIC miners on your network, which makes it super fast.
|
||||
|
||||
You can also use poetry by initializing and running ```poetry install```
|
||||
[Click here to view supported miner types](https://docs.pyasic.org/en/latest/miners/supported_types/)
|
||||
|
||||
### Interfacing with miners programmatically
|
||||
---
|
||||
## Installation
|
||||
|
||||
##### Note: If you are trying to interface with Whatsminers, there is a bug in the way they are interacted with on Windows, so to fix that you need to change the event loop policy using this code:
|
||||
```python
|
||||
# need to import these 2 libraries, you need asyncio anyway so make sure you have sys imported
|
||||
import sys
|
||||
import asyncio
|
||||
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. Use version 2.0+
|
||||
|
||||
# if the computer is windows, set the event loop policy to a WindowsSelector policy
|
||||
if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith('win'):
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
```
|
||||
poetry install
|
||||
```
|
||||
|
||||
##### It is likely a good idea to use this code in your program anyway to be preventative.
|
||||
<br>
|
||||
- [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
|
||||
|
||||
To write your own custom programs with this repo, you have many options.
|
||||
|
||||
It is recommended that you explore the files in this repo to familiarize yourself with them, try starting with the miners module and going from there.
|
||||
|
||||
There are 2 main ways to get a miner and it's functions via scanning or via the MinerFactory.
|
||||
|
||||
#### Scanning for miners
|
||||
```python
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from pyasic.network import MinerNetwork
|
||||
|
||||
# Fix whatsminer bug
|
||||
# if the computer is windows, set the event loop policy to a WindowsSelector policy
|
||||
if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith('win'):
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
|
||||
|
||||
# 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, 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())
|
||||
```
|
||||
pyenv install <python version number>
|
||||
pyenv virtualenv <python version number> <env name>
|
||||
pyenv activate <env name>
|
||||
```
|
||||
|
||||
</br>
|
||||
- [conda](https://docs.conda.io/en/latest/)
|
||||
|
||||
#### Getting a miner if you know the IP
|
||||
##### Installing `pyasic`
|
||||
|
||||
`python -m pip install pyasic` or `poetry install`
|
||||
|
||||
##### Additional Developer Setup
|
||||
```
|
||||
poetry install --with dev
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
##### Building Documentation Locally
|
||||
```
|
||||
poetry install --with docs
|
||||
python docs/generate_miners.py
|
||||
poetry run mkdocs serve
|
||||
```
|
||||
|
||||
---
|
||||
## 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
|
||||
import sys
|
||||
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
|
||||
# Fix whatsminer bug
|
||||
# if the computer is windows, set the event loop policy to a WindowsSelector policy
|
||||
if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith('win'):
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
import asyncio # asyncio for handling the async part
|
||||
from pyasic.network import MinerNetwork # miner network handles the scanning
|
||||
|
||||
|
||||
# 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 MinerFactory().get_miner(miner_ip)
|
||||
|
||||
# Get data from the miner
|
||||
data = await miner.get_data()
|
||||
print(data)
|
||||
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(get_miner_data("192.168.1.69"))
|
||||
asyncio.run(scan_miners()) # run the scan asynchronously with asyncio.run()
|
||||
```
|
||||
|
||||
### Advanced data gathering
|
||||
---
|
||||
##### 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
|
||||
|
||||
If needed, this library exposes a wrapper for the miner API that can be used for advanced data gathering.
|
||||
|
||||
#### List available API commands
|
||||
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
|
||||
import sys
|
||||
from pyasic import get_miner
|
||||
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
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
|
||||
|
||||
# Fix whatsminer bug
|
||||
# if the computer is windows, set the event loop policy to a WindowsSelector policy
|
||||
if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith('win'):
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
|
||||
|
||||
async def get_api_commands(miner_ip: str):
|
||||
# Get the miner
|
||||
miner = await MinerFactory().get_miner(miner_ip)
|
||||
|
||||
# List all available commands
|
||||
print(miner.api.get_commands())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(get_api_commands("192.168.1.69"))
|
||||
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())
|
||||
```
|
||||
|
||||
#### Use miner API commands to gather data
|
||||
---
|
||||
## Miner control
|
||||
|
||||
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` 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
|
||||
import sys
|
||||
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
|
||||
# Fix whatsminer bug
|
||||
# if the computer is windows, set the event loop policy to a WindowsSelector policy
|
||||
if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith('win'):
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
from pyasic import get_miner
|
||||
|
||||
|
||||
async def get_api_commands(miner_ip: str):
|
||||
# Get the miner
|
||||
miner = await MinerFactory().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)
|
||||
|
||||
|
||||
async def set_fault_light():
|
||||
miner = await get_miner("192.168.1.20")
|
||||
|
||||
# call control function
|
||||
await miner.fault_light_on()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(get_api_commands("192.168.1.69"))
|
||||
asyncio.run(set_fault_light())
|
||||
```
|
||||
|
||||
---
|
||||
## Helper dataclasses
|
||||
|
||||
##### `MinerConfig` and `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()`.
|
||||
|
||||
---
|
||||
|
||||
##### 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 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,
|
||||
```
|
||||
|
||||
@@ -1,24 +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` 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 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 -
|
||||
|
||||
#### [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
|
||||
|
||||
33
docs/data/error_codes.md
Normal file
33
docs/data/error_codes.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# pyasic
|
||||
<br>
|
||||
|
||||
## Whatsminer Error Codes
|
||||
::: pyasic.data.error_codes.WhatsminerError
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
<br>
|
||||
|
||||
## Braiins OS Error Codes
|
||||
::: pyasic.data.error_codes.BraiinsOSError
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
<br>
|
||||
|
||||
## X19 Error Codes
|
||||
::: pyasic.data.error_codes.X19Error
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
<br>
|
||||
|
||||
## Innosilicon Error Codes
|
||||
::: pyasic.data.error_codes.InnosiliconError
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
@@ -1,8 +1,22 @@
|
||||
# pyasic
|
||||
## Miner Data
|
||||
|
||||
## Miner Data
|
||||
::: pyasic.data.MinerData
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## HashBoard Data
|
||||
::: pyasic.data.HashBoard
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## Fan Data
|
||||
::: pyasic.data.Fan
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
204
docs/generate_miners.py
Normal file
204
docs/generate_miners.py
Normal file
@@ -0,0 +1,204 @@
|
||||
import asyncio
|
||||
import importlib
|
||||
import os
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
|
||||
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.LUCKYMINER:
|
||||
return "Stock Firmware Lucky Miners"
|
||||
case MinerTypes.ICERIVER:
|
||||
return "Stock Firmware IceRiver Miners"
|
||||
case MinerTypes.HAMMER:
|
||||
return "Stock Firmware Hammer Miners"
|
||||
case MinerTypes.VOLCMINER:
|
||||
return "Stock Firmware Volcminers"
|
||||
case MinerTypes.ELPHAPEX:
|
||||
return "Stock Firmware Elphapex Miners"
|
||||
case MinerTypes.MSKMINER:
|
||||
return "MSKMiner Firmware Miners"
|
||||
raise TypeError("Unknown miner backend, cannot generate docs")
|
||||
|
||||
|
||||
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 = """
|
||||
- [{}] Shutdowns
|
||||
- [{}] Power Modes
|
||||
- [{}] Setpoints
|
||||
- [{}] Presets
|
||||
|
||||
::: {}
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
"""
|
||||
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.
|
||||
|
||||
Keep in mind that some functionality is only supported for specific miners or firmwares, please check the page for your miner to make sure the functionality you need is supported.
|
||||
|
||||
##### 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 = {}
|
||||
done = []
|
||||
|
||||
for m in MINER_CLASSES:
|
||||
for t in sorted(MINER_CLASSES[m], key=lambda x: x or ""):
|
||||
if t is not None and MINER_CLASSES[m][t] not in done:
|
||||
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)
|
||||
done.append(miner)
|
||||
|
||||
|
||||
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):
|
||||
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:
|
||||
obj = item("1.1.1.1")
|
||||
header = obj.model
|
||||
file.write(MINER_HEADER_FORMAT.format(header))
|
||||
file.write(
|
||||
DATA_FORMAT.format(
|
||||
"x" if obj.supports_shutdown else " ",
|
||||
"x" if obj.supports_power_modes else " ",
|
||||
"x" if obj.supports_autotuning else " ",
|
||||
"x" if obj.supports_presets else " ",
|
||||
path(item),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
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 = minstance("1.1.1.1").model
|
||||
file.write(
|
||||
MINER_DETAILS.format(
|
||||
make(minstance), mtype, create_url_str(model), model
|
||||
)
|
||||
)
|
||||
file.write(MINER_TYPE_CLOSER)
|
||||
file.write(BACKEND_TYPE_CLOSER)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root_directory = Path(__file__).parent.joinpath("miners")
|
||||
create_directory_structure(root_directory, m_data)
|
||||
create_supported_types(root_directory)
|
||||
316
docs/index.md
316
docs/index.md
@@ -1,101 +1,289 @@
|
||||
# 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://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
|
||||
|
||||
# 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()
|
||||
async def scan_miners():# (3)!
|
||||
network = MinerNetwork.from_subnet("192.168.1.50/24")# (4)!
|
||||
|
||||
miners = await network.scan()# (5)!
|
||||
print(miners)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(scan_miners()) # run the scan asynchronously with asyncio.run()
|
||||
if __name__ == "__main__":
|
||||
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)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(get_miners()) # get the miners asynchronously with asyncio.run()
|
||||
|
||||
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())# (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].
|
||||
|
||||
### One miner
|
||||
```python
|
||||
import asyncio
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
import asyncio# (1)!
|
||||
from pyasic import get_miner# (2)!
|
||||
|
||||
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
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(gather_miner_data())
|
||||
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())# (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()
|
||||
|
||||
# we need to asyncio.gather() all the miners get_data() functions to make them run together
|
||||
async def gather_miner_data():# (3)!
|
||||
network = MinerNetwork.from_subnet("192.168.1.50/24")# (4)!
|
||||
miners = await network.scan()# (5)!
|
||||
|
||||
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())
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(gather_miner_data())# (8)!
|
||||
```
|
||||
|
||||
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().
|
||||
|
||||
## 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.
|
||||
|
||||
[`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].
|
||||
|
||||
### Usage
|
||||
```python
|
||||
import asyncio# (1)!
|
||||
from pyasic import get_miner# (2)!
|
||||
|
||||
|
||||
async def set_fault_light():# (3)!
|
||||
miner = await get_miner("192.168.1.20")# (4)!
|
||||
|
||||
await miner.fault_light_on()# (5)!
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(set_fault_light())# (6)!
|
||||
```
|
||||
|
||||
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().
|
||||
|
||||
|
||||
## Helper dataclasses
|
||||
---
|
||||
|
||||
### [`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()`.
|
||||
|
||||
---
|
||||
|
||||
### [`MinerData`][pyasic.data.MinerData]
|
||||
|
||||
[`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.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
|
||||
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`][pyasic.config.MinerConfig]
|
||||
|
||||
[`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()`][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,
|
||||
```
|
||||
|
||||
29
docs/miners/antminer/X15.md
Normal file
29
docs/miners/antminer/X15.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# pyasic
|
||||
## X15 Models
|
||||
|
||||
## Z15 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.cgminer.X15.Z15.CGMinerZ15
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## Z15 Pro (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X15.Z15.BMMinerZ15Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
@@ -1,59 +1,211 @@
|
||||
# pyasic
|
||||
## X17 Models
|
||||
|
||||
## S17
|
||||
## S17 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## S17+
|
||||
## S17 Pro (Stock)
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17_Plus.BMMinerS17Plus
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## S17 Pro
|
||||
## S17+ (Stock)
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17_Pro.BMMinerS17Pro
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## S17e
|
||||
## S17e (Stock)
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17e.BMMinerS17e
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## T17
|
||||
## T17 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## T17+
|
||||
## T17+ (Stock)
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17_Plus.BMMinerT17Plus
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## T17e (Stock)
|
||||
|
||||
## T17e
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17e.BMMinerT17e
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## S17 (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S17 Pro (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S17+ (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S17e (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## T17 (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## T17+ (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## T17e (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S17 Pro (VNish)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [x] Presets
|
||||
|
||||
::: pyasic.miners.antminer.vnish.X17.S17.VNishS17Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S17+ (VNish)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [x] Presets
|
||||
|
||||
::: pyasic.miners.antminer.vnish.X17.S17.VNishS17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
328
docs/miners/antminer/X21.md
Normal file
328
docs/miners/antminer/X21.md
Normal file
@@ -0,0 +1,328 @@
|
||||
# pyasic
|
||||
## X21 Models
|
||||
|
||||
## S21 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21 Hydro (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21Hydro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21 Pro (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21+ (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21+ Hydro (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21PlusHydro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## T21 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X21.T21.BMMinerT21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21 (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21 Hydro (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21Hydro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21 Pro (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21+ (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21+ Hydro (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21PlusHydro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## T21 (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X21.T21.BOSMinerT21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21 (VNish)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [x] Presets
|
||||
|
||||
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21 Hydro (VNish)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [x] Presets
|
||||
|
||||
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21Hydro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21 Pro (VNish)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [x] Presets
|
||||
|
||||
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21+ (VNish)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [x] Presets
|
||||
|
||||
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21+ Hydro (VNish)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [x] Presets
|
||||
|
||||
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21PlusHydro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## T21 (VNish)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [x] Presets
|
||||
|
||||
::: pyasic.miners.antminer.vnish.X21.T21.VNishT21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21 (ePIC)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.epic.X21.S21.ePICS21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21 Pro (ePIC)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.epic.X21.S21.ePICS21Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## T21 (ePIC)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.epic.X21.T21.ePICT21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21 (LuxOS)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [x] Presets
|
||||
|
||||
::: pyasic.miners.antminer.luxos.X21.S21.LUXMinerS21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## T21 (LuxOS)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [x] Presets
|
||||
|
||||
::: pyasic.miners.antminer.luxos.X21.T21.LUXMinerT21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S21 (MaraFW)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.marathon.X21.S21.MaraS21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## T21 (MaraFW)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.marathon.X21.T21.MaraT21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
81
docs/miners/antminer/X3.md
Normal file
81
docs/miners/antminer/X3.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# pyasic
|
||||
## X3 Models
|
||||
|
||||
## D3 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.cgminer.X3.D3.CGMinerD3
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## HS3 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X3.HS3.BMMinerHS3
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## KA3 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X3.KA3.BMMinerKA3
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## KS3 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X3.KS3.BMMinerKS3
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## L3+ (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X3.L3.BMMinerL3Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## L3+ (VNish)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [x] Presets
|
||||
|
||||
::: pyasic.miners.antminer.vnish.X3.L3.VNishL3Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
42
docs/miners/antminer/X5.md
Normal file
42
docs/miners/antminer/X5.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# pyasic
|
||||
## X5 Models
|
||||
|
||||
## DR5 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.cgminer.X5.DR5.CGMinerDR5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## KS5 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X5.KS5.BMMinerKS5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## KS5 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X5.KS5.BMMinerKS5Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
55
docs/miners/antminer/X7.md
Normal file
55
docs/miners/antminer/X7.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# pyasic
|
||||
## X7 Models
|
||||
|
||||
## D7 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X7.D7.BMMinerD7
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## K7 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X7.K7.BMMinerK7
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## L7 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X7.L7.BMMinerL7
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## L7 (VNish)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [x] Presets
|
||||
|
||||
::: pyasic.miners.antminer.vnish.X7.L7.VNishL7
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
@@ -1,26 +1,146 @@
|
||||
# pyasic
|
||||
## X9 Models
|
||||
|
||||
## S9
|
||||
## D9 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X9.D9.BMMinerD9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## E9Pro (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X9.E9.BMMinerE9Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## L9 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X9.L9.BMMinerL9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S9 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## S9i
|
||||
## S9i (Stock)
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X9.S9i.BMMinerS9i
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9i
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## T9
|
||||
## S9j (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9j
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## T9 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X9.T9.BMMinerT9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## S9 (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X9.S9.BOSMinerS9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## L9 (VNish)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [x] Presets
|
||||
|
||||
::: pyasic.miners.antminer.vnish.X9.L9.VNishL9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## T9 (Hive)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.antminer.hiveon.X9.T9.HiveonT9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## S9 (LuxOS)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [x] Presets
|
||||
|
||||
::: pyasic.miners.antminer.luxos.X9.S9.LUXMinerS9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
|
||||
42
docs/miners/auradine/AD.md
Normal file
42
docs/miners/auradine/AD.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# pyasic
|
||||
## AD Models
|
||||
|
||||
## AT1500 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.auradine.flux.AD.AT1.AuradineFluxAT1500
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## AT2860 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.auradine.flux.AD.AT2.AuradineFluxAT2860
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## AT2880 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.auradine.flux.AD.AT2.AuradineFluxAT2880
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
29
docs/miners/auradine/AI.md
Normal file
29
docs/miners/auradine/AI.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# pyasic
|
||||
## AI Models
|
||||
|
||||
## AI2500 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.auradine.flux.AI.AI2.AuradineFluxAI2500
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## AI3680 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.auradine.flux.AI.AI3.AuradineFluxAI3680
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
29
docs/miners/auradine/AT.md
Normal file
29
docs/miners/auradine/AT.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# pyasic
|
||||
## AT Models
|
||||
|
||||
## AD2500 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.auradine.flux.AT.AD2.AuradineFluxAD2500
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## AD3500 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.auradine.flux.AT.AD3.AuradineFluxAD3500
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
@@ -1,26 +1,42 @@
|
||||
# pyasic
|
||||
## A10X Models
|
||||
|
||||
## A1026
|
||||
## Avalon 1026 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A10X.A1026.CGMinerAvalon1026
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## A1047
|
||||
## Avalon 1047 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A10X.A1047.CGMinerAvalon1047
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## A1066
|
||||
## Avalon 1066 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A10X.A1066.CGMinerAvalon1066
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
|
||||
29
docs/miners/avalonminer/A11X.md
Normal file
29
docs/miners/avalonminer/A11X.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# pyasic
|
||||
## A11X Models
|
||||
|
||||
## Avalon 1126 Pro (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A11X.A1126.CGMinerAvalon1126Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## Avalon 1166 Pro (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A11X.A1166.CGMinerAvalon1166Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
16
docs/miners/avalonminer/A12X.md
Normal file
16
docs/miners/avalonminer/A12X.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# pyasic
|
||||
## A12X Models
|
||||
|
||||
## Avalon 1246 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A12X.A1246.CGMinerAvalon1246
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
16
docs/miners/avalonminer/A15X.md
Normal file
16
docs/miners/avalonminer/A15X.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# pyasic
|
||||
## A15X Models
|
||||
|
||||
## Avalon 1566 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A15X.A1566.CGMinerAvalon1566
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
@@ -1,26 +1,42 @@
|
||||
# pyasic
|
||||
## A7X Models
|
||||
|
||||
## A721
|
||||
## Avalon 721 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A7X.A721.CGMinerAvalon721
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## A741
|
||||
## Avalon 741 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A7X.A741.CGMinerAvalon741
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## A761
|
||||
## Avalon 761 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A7X.A761.CGMinerAvalon761
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
|
||||
@@ -1,26 +1,42 @@
|
||||
# pyasic
|
||||
## A8X Models
|
||||
|
||||
## A821
|
||||
## Avalon 821 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A8X.A821.CGMinerAvalon821
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## A841
|
||||
## Avalon 841 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A8X.A841.CGMinerAvalon841
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## A851
|
||||
## Avalon 851 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A8X.A851.CGMinerAvalon851
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
# pyasic
|
||||
## A9X Models
|
||||
|
||||
## A921
|
||||
## Avalon 921 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A9X.A921.CGMinerAvalon921
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
|
||||
29
docs/miners/avalonminer/nano.md
Normal file
29
docs/miners/avalonminer/nano.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# pyasic
|
||||
## nano Models
|
||||
|
||||
## Avalon Nano 3s (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.nano.nano3.CGMinerAvalonNano3s
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## Avalon Nano 3 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.nano.nano3.CGMinerAvalonNano3
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
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,14 @@
|
||||
# pyasic
|
||||
## Hiveon Backend
|
||||
## Modern Hiveon Backend
|
||||
|
||||
::: pyasic.miners._backends.hiveon.Hiveon
|
||||
::: pyasic.miners.backends.hiveon.HiveonModern
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## Old Hiveon Backend
|
||||
::: pyasic.miners.backends.hiveon.HiveonOld
|
||||
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
|
||||
12
docs/miners/base_miner.md
Normal file
12
docs/miners/base_miner.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# pyasic
|
||||
## Base Miner
|
||||
[`BaseMiner`][pyasic.miners.base.BaseMiner] is the basis for all miner classes, they all subclass (usually indirectly) from this class.
|
||||
|
||||
This class inherits from the [`MinerProtocol`][pyasic.miners.base.MinerProtocol], which outlines functionality for miners.
|
||||
|
||||
You may not instantiate this class on its own, only subclass from it.
|
||||
|
||||
::: pyasic.miners.base.BaseMiner
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
55
docs/miners/bitaxe/BM.md
Normal file
55
docs/miners/bitaxe/BM.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# pyasic
|
||||
## BM Models
|
||||
|
||||
## Ultra (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.bitaxe.espminer.BM.BM1366.BitAxeUltra
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## Supra (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.bitaxe.espminer.BM.BM1368.BitAxeSupra
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## Gamma (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.bitaxe.espminer.BM.BM1370.BitAxeGamma
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## Max (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.bitaxe.espminer.BM.BM1397.BitAxeMax
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
42
docs/miners/blockminer/blockminer.md
Normal file
42
docs/miners/blockminer/blockminer.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# pyasic
|
||||
## blockminer Models
|
||||
|
||||
## BlockMiner 520i (ePIC)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.blockminer.epic.blockminer.blockminer.ePICBlockMiner520i
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## BlockMiner 720i (ePIC)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.blockminer.epic.blockminer.blockminer.ePICBlockMiner720i
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## BlockMiner eLITE 1.0 (ePIC)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.blockminer.epic.blockminer.blockminer.ePICBlockMinerELITE1
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
29
docs/miners/braiins/BMM.md
Normal file
29
docs/miners/braiins/BMM.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# pyasic
|
||||
## BMM Models
|
||||
|
||||
## BMM100 (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.braiins.braiins.BMM.BMM.BraiinsBMM100
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## BMM101 (BOS+)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [x] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.braiins.braiins.BMM.BMM.BraiinsBMM101
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
42
docs/miners/elphapex/DGX.md
Normal file
42
docs/miners/elphapex/DGX.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# pyasic
|
||||
## DGX Models
|
||||
|
||||
## DG1 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.elphapex.daoge.DGX.DG1.ElphapexDG1
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## DG1+ (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.elphapex.daoge.DGX.DG1.ElphapexDG1Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## DG1Home (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.elphapex.daoge.DGX.DG1.ElphapexDG1Home
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
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
|
||||
16
docs/miners/goldshell/Byte.md
Normal file
16
docs/miners/goldshell/Byte.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# pyasic
|
||||
## Byte Models
|
||||
|
||||
## Byte (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.goldshell.bfgminer.Byte.Byte.GoldshellByte
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
16
docs/miners/goldshell/MiniDoge.md
Normal file
16
docs/miners/goldshell/MiniDoge.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# pyasic
|
||||
## Mini Doge Models
|
||||
|
||||
## Mini Doge (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.goldshell.bfgminer.MiniDoge.MiniDoge.GoldshellMiniDoge
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
42
docs/miners/goldshell/X5.md
Normal file
42
docs/miners/goldshell/X5.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# pyasic
|
||||
## X5 Models
|
||||
|
||||
## CK5 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.goldshell.bfgminer.X5.CK5.GoldshellCK5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## HS5 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.goldshell.bfgminer.X5.HS5.GoldshellHS5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## KD5 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.goldshell.bfgminer.X5.KD5.GoldshellKD5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
29
docs/miners/goldshell/XBox.md
Normal file
29
docs/miners/goldshell/XBox.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# pyasic
|
||||
## XBox Models
|
||||
|
||||
## KD Box II (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.goldshell.bfgminer.XBox.KDBox.GoldshellKDBoxII
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## KD Box Pro (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.goldshell.bfgminer.XBox.KDBox.GoldshellKDBoxPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
16
docs/miners/goldshell/XMax.md
Normal file
16
docs/miners/goldshell/XMax.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# pyasic
|
||||
## XMax Models
|
||||
|
||||
## KD Max (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.goldshell.bfgminer.XMax.KDMax.GoldshellKDMax
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
16
docs/miners/hammer/DX.md
Normal file
16
docs/miners/hammer/DX.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# pyasic
|
||||
## DX Models
|
||||
|
||||
## D10 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.hammer.blackminer.DX.D10.HammerD10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
120
docs/miners/iceriver/KSX.md
Normal file
120
docs/miners/iceriver/KSX.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# pyasic
|
||||
## KSX Models
|
||||
|
||||
## KS0 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS0.IceRiverKS0
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## KS1 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS1.IceRiverKS1
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## KS2 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS2.IceRiverKS2
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## KS3 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS3.IceRiverKS3
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## KS3L (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS3.IceRiverKS3L
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## KS3M (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS3.IceRiverKS3M
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## KS5 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS5.IceRiverKS5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## KS5L (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS5.IceRiverKS5L
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## KS5M (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.iceriver.iceminer.KSX.KS5.IceRiverKS5M
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
16
docs/miners/innosilicon/A10X.md
Normal file
16
docs/miners/innosilicon/A10X.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# pyasic
|
||||
## A10X Models
|
||||
|
||||
## A10X (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.innosilicon.cgminer.A10X.A10X.InnosiliconA10X
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
29
docs/miners/innosilicon/A11X.md
Normal file
29
docs/miners/innosilicon/A11X.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# pyasic
|
||||
## A11X Models
|
||||
|
||||
## A11 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.innosilicon.cgminer.A11X.A11.InnosiliconA11
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## A11MX (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.innosilicon.cgminer.A11X.A11M.InnosiliconA11MX
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
16
docs/miners/innosilicon/T3X.md
Normal file
16
docs/miners/innosilicon/T3X.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# pyasic
|
||||
## T3X Models
|
||||
|
||||
## T3H+ (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.innosilicon.cgminer.T3X.T3H.InnosiliconT3HPlus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
29
docs/miners/luckyminer/LV.md
Normal file
29
docs/miners/luckyminer/LV.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# pyasic
|
||||
## LV Models
|
||||
|
||||
## LV07 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.luckyminer.espminer.LV.LV07.LuckyMinerLV07
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## LV08 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.luckyminer.espminer.LV.LV08.LuckyMinerLV08
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
@@ -1,8 +1,36 @@
|
||||
# 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
|
||||
heading_level: 4
|
||||
<br>
|
||||
|
||||
## AnyMiner
|
||||
::: pyasic.miners.base.AnyMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
[`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
16
docs/miners/volcminer/DX.md
Normal file
16
docs/miners/volcminer/DX.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# pyasic
|
||||
## DX Models
|
||||
|
||||
## D1 (Stock)
|
||||
|
||||
- [ ] Shutdowns
|
||||
- [ ] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.volcminer.blackminer.DX.D1.VolcMinerD1
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
@@ -1,75 +1,172 @@
|
||||
# pyasic
|
||||
## M2X Models
|
||||
|
||||
## M20S
|
||||
## M20P V10 (Stock)
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20S
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20P.BTMinerM20PV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## M20SV10
|
||||
## M20P V30 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20P.BTMinerM20PV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## M20S+ V30 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlusV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## M20S V10 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## M20SV20
|
||||
## M20S V20 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## M20S+
|
||||
## M20S V30 (Stock)
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlus
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## M21
|
||||
## M20 V10 (Stock)
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## M21S+ V20 (Stock)
|
||||
|
||||
## M21S
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21S
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlusV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## M21SV20
|
||||
## M21S V20 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## M21SV60
|
||||
## M21S V60 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## M21S+
|
||||
## M21S V70 (Stock)
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlus
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV70
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
heading_level: 0
|
||||
|
||||
## M21 V10 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
## M29 V10 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M29.BTMinerM29V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1277
docs/miners/whatsminer/M5X.md
Normal file
1277
docs/miners/whatsminer/M5X.md
Normal file
File diff suppressed because it is too large
Load Diff
1212
docs/miners/whatsminer/M6X.md
Normal file
1212
docs/miners/whatsminer/M6X.md
Normal file
File diff suppressed because it is too large
Load Diff
16
docs/miners/whatsminer/M7X.md
Normal file
16
docs/miners/whatsminer/M7X.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# pyasic
|
||||
## M7X Models
|
||||
|
||||
## M70 VM30 (Stock)
|
||||
|
||||
- [x] Shutdowns
|
||||
- [x] Power Modes
|
||||
- [ ] Setpoints
|
||||
- [ ] Presets
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M7X.M70.BTMinerM70VM30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 0
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# pyasic
|
||||
## Miner Network 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
|
||||
149
mkdocs.yml
149
mkdocs.yml
@@ -1,43 +1,118 @@
|
||||
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.tasklist:
|
||||
custom_checkbox: 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"
|
||||
|
||||
- Network:
|
||||
- Miner Network: "network/miner_network.md"
|
||||
- Miner Network Range: "network/miner_network_range.md"
|
||||
- Data:
|
||||
- Miner Data: "data/miner_data.md"
|
||||
- Config:
|
||||
- Miner Config: "config/miner_config.md"
|
||||
- Advanced:
|
||||
- Miner APIs:
|
||||
- Base: "API/api.md"
|
||||
- BMMiner: "API/bmminer.md"
|
||||
- BOSMiner: "API/bosminer.md"
|
||||
- BTMiner: "API/btminer.md"
|
||||
- CGMiner: "API/cgminer.md"
|
||||
- Unknown: "API/unknown.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"
|
||||
- Braiins Mini Miners: "miners/braiins/BMM.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"
|
||||
- Whatsminer M7X: "miners/whatsminer/M7X.md"
|
||||
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
||||
- Innosilicon A10X: "miners/innosilicon/A10X.md"
|
||||
- Innosilicon A11X: "miners/innosilicon/A11X.md"
|
||||
- Goldshell Byte: "miners/goldshell/Byte.md"
|
||||
- Goldshell Mini Doge: "miners/goldshell/MiniDoge.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"
|
||||
- Volcminer DX: "miners/volcminer/DX.md"
|
||||
- Base Miner: "miners/base_miner.md"
|
||||
- Settings:
|
||||
- Settings: "settings/settings.md"
|
||||
|
||||
plugins:
|
||||
- mkdocstrings
|
||||
|
||||
1857
poetry.lock
generated
1857
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,241 +0,0 @@
|
||||
import asyncio
|
||||
import json
|
||||
import ipaddress
|
||||
import warnings
|
||||
import logging
|
||||
from typing import Union
|
||||
|
||||
|
||||
class APIError(Exception):
|
||||
def __init__(self, *args):
|
||||
if args:
|
||||
self.message = args[0]
|
||||
else:
|
||||
self.message = None
|
||||
|
||||
def __str__(self):
|
||||
if self.message:
|
||||
return f"{self.message}"
|
||||
else:
|
||||
return "Incorrect API parameters."
|
||||
|
||||
|
||||
class APIWarning(Warning):
|
||||
def __init__(self, *args):
|
||||
if args:
|
||||
self.message = args[0]
|
||||
else:
|
||||
self.message = None
|
||||
|
||||
def __str__(self):
|
||||
if self.message:
|
||||
return f"{self.message}"
|
||||
else:
|
||||
return "Incorrect API parameters."
|
||||
|
||||
|
||||
class BaseMinerAPI:
|
||||
def __init__(self, ip: str, port: int = 4028) -> None:
|
||||
# api port, should be 4028
|
||||
self.port = port
|
||||
# ip address of the miner
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
|
||||
def get_commands(self) -> list:
|
||||
"""Get a list of command accessible to a specific type of API on the miner.
|
||||
|
||||
Returns:
|
||||
A list of all API commands that the miner supports.
|
||||
"""
|
||||
return [
|
||||
func
|
||||
for func in
|
||||
# each function in self
|
||||
dir(self)
|
||||
if callable(getattr(self, func)) and
|
||||
# no __ methods
|
||||
not func.startswith("__") and
|
||||
# remove all functions that are in this base class
|
||||
func
|
||||
not in [
|
||||
func
|
||||
for func in dir(BaseMinerAPI)
|
||||
if callable(getattr(BaseMinerAPI, func))
|
||||
]
|
||||
]
|
||||
|
||||
def _check_commands(self, *commands):
|
||||
allowed_commands = self.get_commands()
|
||||
return_commands = []
|
||||
for command in [*commands]:
|
||||
if command in allowed_commands:
|
||||
return_commands.append(command)
|
||||
else:
|
||||
warnings.warn(
|
||||
f"""Removing incorrect command: {command}
|
||||
If you are sure you want to use this command please use API.send_command("{command}", ignore_errors=True) instead.""",
|
||||
APIWarning,
|
||||
)
|
||||
return return_commands
|
||||
|
||||
async def multicommand(
|
||||
self, *commands: str, ignore_x19_error: bool = False
|
||||
) -> dict:
|
||||
"""Creates and sends multiple commands as one command to the miner.
|
||||
|
||||
Parameters:
|
||||
*commands: The commands to send as a multicommand to the miner.
|
||||
ignore_x19_error: Whether or not to ignore errors raised by x19 miners when using the "+" delimited style.
|
||||
"""
|
||||
logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}")
|
||||
# make sure we can actually run each command, otherwise they will fail
|
||||
commands = self._check_commands(*commands)
|
||||
# standard multicommand format is "command1+command2"
|
||||
# doesnt work for S19 which uses the backup _x19_multicommand
|
||||
command = "+".join(commands)
|
||||
try:
|
||||
data = await self.send_command(command, x19_command=ignore_x19_error)
|
||||
except APIError:
|
||||
logging.debug(f"{self.ip}: Handling X19 multicommand.")
|
||||
data = await self._x19_multicommand(command.split("+"))
|
||||
logging.debug(f"{self.ip}: Received multicommand data.")
|
||||
return data
|
||||
|
||||
async def _x19_multicommand(self, *commands):
|
||||
data = None
|
||||
try:
|
||||
data = {}
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
data[cmd] = []
|
||||
data[cmd].append(await self.send_command(cmd, x19_command=True))
|
||||
except APIError as e:
|
||||
raise APIError(e)
|
||||
except Exception as e:
|
||||
logging.warning(f"{self.ip}: API Multicommand Error: {e.__name__} - {e}")
|
||||
return data
|
||||
|
||||
async def send_command(
|
||||
self,
|
||||
command: Union[str, bytes],
|
||||
parameters: Union[str, int, bool] = None,
|
||||
ignore_errors: bool = False,
|
||||
x19_command: bool = False,
|
||||
) -> dict:
|
||||
"""Send an API command to the miner and return the result.
|
||||
|
||||
Parameters:
|
||||
command: The command to sent to the miner.
|
||||
parameters: Any additional parameters to be sent with the command.
|
||||
ignore_errors: Whether or not to raise APIError when the command returns an error.
|
||||
x19_command: Whether this is a command for an x19 that may be an issue (such as a "+" delimited multicommand)
|
||||
|
||||
Returns:
|
||||
The return data from the API command parsed from JSON into a dict.
|
||||
"""
|
||||
try:
|
||||
# get reader and writer streams
|
||||
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
|
||||
# handle OSError 121
|
||||
except OSError as e:
|
||||
if e.winerror == "121":
|
||||
logging.warning("Semaphore Timeout has Expired.")
|
||||
return {}
|
||||
|
||||
# create the command
|
||||
cmd = {"command": command}
|
||||
if parameters:
|
||||
cmd["parameter"] = parameters
|
||||
|
||||
# send the command
|
||||
writer.write(json.dumps(cmd).encode("utf-8"))
|
||||
await writer.drain()
|
||||
|
||||
# instantiate data
|
||||
data = b""
|
||||
|
||||
# loop to receive all the data
|
||||
try:
|
||||
while True:
|
||||
d = await reader.read(4096)
|
||||
if not d:
|
||||
break
|
||||
data += d
|
||||
except Exception as e:
|
||||
logging.warning(f"{self.ip}: API Command Error: {e.__name__} - {e}")
|
||||
|
||||
data = self._load_api_data(data)
|
||||
|
||||
# close the connection
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
|
||||
# check for if the user wants to allow errors to return
|
||||
if not ignore_errors:
|
||||
# validate the command succeeded
|
||||
validation = self._validate_command_output(data)
|
||||
if not validation[0]:
|
||||
if not x19_command:
|
||||
logging.warning(f"{self.ip}: API Command Error: {validation[1]}")
|
||||
raise APIError(validation[1])
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def _validate_command_output(data: dict) -> tuple:
|
||||
# check if the data returned is correct or an error
|
||||
# if status isn't a key, it is a multicommand
|
||||
if "STATUS" not in data.keys():
|
||||
for key in data.keys():
|
||||
# make sure not to try to turn id into a dict
|
||||
if not key == "id":
|
||||
# make sure they succeeded
|
||||
if "STATUS" in data[key][0].keys():
|
||||
if data[key][0]["STATUS"][0]["STATUS"] not in ["S", "I"]:
|
||||
# this is an error
|
||||
return False, f"{key}: " + data[key][0]["STATUS"][0]["Msg"]
|
||||
elif "id" not in data.keys():
|
||||
if data["STATUS"] not in ["S", "I"]:
|
||||
return False, data["Msg"]
|
||||
else:
|
||||
# make sure the command succeeded
|
||||
if type(data["STATUS"]) == str:
|
||||
if data["STATUS"] in ["RESTART"]:
|
||||
return True, None
|
||||
elif data["STATUS"][0]["STATUS"] not in ("S", "I"):
|
||||
# this is an error
|
||||
if data["STATUS"][0]["STATUS"] not in ("S", "I"):
|
||||
return False, data["STATUS"][0]["Msg"]
|
||||
return True, None
|
||||
|
||||
@staticmethod
|
||||
def _load_api_data(data: bytes) -> dict:
|
||||
str_data = None
|
||||
try:
|
||||
# some json from the API returns with a null byte (\x00) on the end
|
||||
if data.endswith(b"\x00"):
|
||||
# handle the null byte
|
||||
str_data = data.decode("utf-8")[:-1]
|
||||
else:
|
||||
# no null byte
|
||||
str_data = data.decode("utf-8")
|
||||
# fix an error with a btminer return having an extra comma that breaks json.loads()
|
||||
str_data = str_data.replace(",}", "}")
|
||||
# fix an error with a btminer return having a newline that breaks json.loads()
|
||||
str_data = str_data.replace("\n", "")
|
||||
# fix an error with a bmminer return not having a specific comma that breaks json.loads()
|
||||
str_data = str_data.replace("}{", "},{")
|
||||
# fix an error with a bmminer return having a specific comma that breaks json.loads()
|
||||
str_data = str_data.replace("[,{", "[{")
|
||||
# fix an error with Avalonminers returning inf and nan
|
||||
str_data = str_data.replace("inf", "0")
|
||||
str_data = str_data.replace("nan", "0")
|
||||
# fix whatever this garbage from avalonminers is `,"id":1}`
|
||||
if str_data.startswith(","):
|
||||
str_data = f"{{{str_data[1:]}"
|
||||
# parse the json
|
||||
parsed_data = json.loads(str_data)
|
||||
# handle bad json
|
||||
except json.decoder.JSONDecodeError as e:
|
||||
raise APIError(f"Decode Error {e}: {str_data}")
|
||||
return parsed_data
|
||||
@@ -1,680 +0,0 @@
|
||||
from pyasic.API import 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, port: int = 4028) -> None:
|
||||
super().__init__(ip, port)
|
||||
|
||||
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")
|
||||
@@ -1,855 +0,0 @@
|
||||
import asyncio
|
||||
import re
|
||||
import json
|
||||
import hashlib
|
||||
import binascii
|
||||
import base64
|
||||
import logging
|
||||
|
||||
from passlib.handlers.md5_crypt import md5_crypt
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
|
||||
from pyasic.API import BaseMinerAPI, APIError
|
||||
from pyasic.settings import PyasicSettings
|
||||
|
||||
|
||||
### IMPORTANT ###
|
||||
# you need to change the password of the miners using the Whatsminer
|
||||
# tool, then you can set them back to admin with this tool, but they
|
||||
# must be changed to something else and set back to admin with this
|
||||
# or the privileged API will not work using admin as the password. If
|
||||
# you change the password, you can pass that to the this class as pwd,
|
||||
# or add it as the Whatsminer_pwd in the settings.toml file.
|
||||
|
||||
|
||||
def _crypt(word: str, salt: str) -> str:
|
||||
"""Encrypts a word with a salt, using a standard salt format.
|
||||
|
||||
Encrypts a word using a salt with the format
|
||||
'\s*\$(\d+)\$([\w\./]*)\$'. If this format is not used, a
|
||||
ValueError is raised.
|
||||
|
||||
Parameters:
|
||||
word: The word to be encrypted.
|
||||
salt: The salt to encrypt the word.
|
||||
|
||||
Returns:
|
||||
An MD5 hash of the word with the salt.
|
||||
"""
|
||||
# compile a standard format for the salt
|
||||
standard_salt = re.compile("\s*\$(\d+)\$([\w\./]*)\$")
|
||||
# check if the salt matches
|
||||
match = standard_salt.match(salt)
|
||||
# if the matching fails, the salt is incorrect
|
||||
if not match:
|
||||
raise ValueError("Salt format is not correct.")
|
||||
# save the matched salt in a new variable
|
||||
new_salt = match.group(2)
|
||||
# encrypt the word with the salt using md5
|
||||
result = md5_crypt.hash(word, salt=new_salt)
|
||||
return result
|
||||
|
||||
|
||||
def _add_to_16(string: str) -> bytes:
|
||||
"""Add null bytes to a string until the length is a multiple 16
|
||||
|
||||
Parameters:
|
||||
string: The string to lengthen to a multiple of 16 and encode.
|
||||
|
||||
Returns:
|
||||
The input string as bytes with a multiple of 16 as the length.
|
||||
"""
|
||||
while len(string) % 16 != 0:
|
||||
string += "\0"
|
||||
return str.encode(string) # return bytes
|
||||
|
||||
|
||||
def parse_btminer_priviledge_data(token_data: dict, data: dict):
|
||||
"""Parses data returned from the BTMiner privileged API.
|
||||
|
||||
Parses data from the BTMiner privileged API using the the token
|
||||
from the API in an AES format.
|
||||
|
||||
Parameters:
|
||||
token_data: The token information from self.get_token().
|
||||
data: The data to parse, returned from the API.
|
||||
|
||||
Returns:
|
||||
A decoded dict version of the privileged command output.
|
||||
"""
|
||||
# get the encoded data from the dict
|
||||
enc_data = data["enc"]
|
||||
# get the aes key from the token data
|
||||
aeskey = hashlib.sha256(token_data["host_passwd_md5"].encode()).hexdigest()
|
||||
# unhexlify the aes key
|
||||
aeskey = binascii.unhexlify(aeskey.encode())
|
||||
# create the required decryptor
|
||||
aes = Cipher(algorithms.AES(aeskey), modes.ECB())
|
||||
decryptor = aes.decryptor()
|
||||
# decode the message with the decryptor
|
||||
ret_msg = json.loads(
|
||||
decryptor.update(base64.decodebytes(bytes(enc_data, encoding="utf8")))
|
||||
.rstrip(b"\0")
|
||||
.decode("utf8")
|
||||
)
|
||||
return ret_msg
|
||||
|
||||
|
||||
def create_privileged_cmd(token_data: dict, command: dict) -> bytes:
|
||||
"""Create a privileged command to send to the BTMiner API.
|
||||
|
||||
Creates a privileged command using the token from the API and the
|
||||
command as a dict of {'command': cmd}, with cmd being any command
|
||||
that the miner API accepts.
|
||||
|
||||
Parameters:
|
||||
token_data: The token information from self.get_token().
|
||||
command: The command to turn into a privileged command.
|
||||
|
||||
Returns:
|
||||
The encrypted privileged command to be sent to the miner.
|
||||
"""
|
||||
# add token to command
|
||||
command["token"] = token_data["host_sign"]
|
||||
# encode host_passwd data and get hexdigest
|
||||
aeskey = hashlib.sha256(token_data["host_passwd_md5"].encode()).hexdigest()
|
||||
# unhexlify the encoded host_passwd
|
||||
aeskey = binascii.unhexlify(aeskey.encode())
|
||||
# create a new AES key
|
||||
aes = Cipher(algorithms.AES(aeskey), modes.ECB())
|
||||
encryptor = aes.encryptor()
|
||||
# dump the command to json
|
||||
api_json_str = json.dumps(command)
|
||||
# encode the json command with the aes key
|
||||
api_json_str_enc = (
|
||||
base64.encodebytes(encryptor.update(_add_to_16(api_json_str)))
|
||||
.decode("utf-8")
|
||||
.replace("\n", "")
|
||||
)
|
||||
# label the data as being encoded
|
||||
data_enc = {"enc": 1, "data": api_json_str_enc}
|
||||
# dump the labeled data to json
|
||||
api_packet_str = json.dumps(data_enc)
|
||||
return api_packet_str.encode("utf-8")
|
||||
|
||||
|
||||
class BTMinerAPI(BaseMinerAPI):
|
||||
"""An abstraction of the API for MicroBT Whatsminers, BTMiner.
|
||||
|
||||
Each method corresponds to an API command in BMMiner.
|
||||
|
||||
This class abstracts use of the BTMiner 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.
|
||||
|
||||
All privileged commands for BTMiner's API require that you change
|
||||
the password of the miners using the Whatsminer tool, and it can be
|
||||
changed back to admin with this tool after. Set the new password
|
||||
either by passing it to the __init__ method, or changing it in
|
||||
settings.toml.
|
||||
|
||||
Additionally, the API commands for the privileged API must be
|
||||
encoded using a token from the miner, all privileged commands do
|
||||
this automatically for you and will decode the output to look like
|
||||
a normal output from a miner API.
|
||||
|
||||
Parameters:
|
||||
ip: The IP of the miner to reference the API on.
|
||||
port: The port to reference the API on. Default is 4028.
|
||||
pwd: The admin password of the miner. Default is admin.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
ip: str,
|
||||
port: int = 4028,
|
||||
pwd: str = PyasicSettings().global_whatsminer_password,
|
||||
):
|
||||
super().__init__(ip, port)
|
||||
self.admin_pwd = pwd
|
||||
self.current_token = None
|
||||
|
||||
async def send_command(
|
||||
self,
|
||||
command: str or bytes,
|
||||
parameters: str or int or bool = None,
|
||||
ignore_errors: bool = False,
|
||||
**kwargs,
|
||||
) -> dict:
|
||||
# check if command is a string
|
||||
# if its bytes its encoded and needs to be sent raw
|
||||
if isinstance(command, str):
|
||||
# if it is a string, put it into the standard command format
|
||||
command = json.dumps({"command": command}).encode("utf-8")
|
||||
try:
|
||||
# get reader and writer streams
|
||||
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
|
||||
# handle OSError 121
|
||||
except OSError as e:
|
||||
if e.winerror == "121":
|
||||
print("Semaphore Timeout has Expired.")
|
||||
return {}
|
||||
|
||||
# send the command
|
||||
writer.write(command)
|
||||
await writer.drain()
|
||||
|
||||
# instantiate data
|
||||
data = b""
|
||||
|
||||
# loop to receive all the data
|
||||
try:
|
||||
while True:
|
||||
d = await reader.read(4096)
|
||||
if not d:
|
||||
break
|
||||
data += d
|
||||
except Exception as e:
|
||||
logging.info(f"{str(self.ip)}: {e}")
|
||||
|
||||
data = self._load_api_data(data)
|
||||
|
||||
# close the connection
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
|
||||
# check if the returned data is encoded
|
||||
if "enc" in data.keys():
|
||||
# try to parse the encoded data
|
||||
try:
|
||||
data = parse_btminer_priviledge_data(self.current_token, data)
|
||||
except Exception as e:
|
||||
logging.info(f"{str(self.ip)}: {e}")
|
||||
|
||||
if not ignore_errors:
|
||||
# if it fails to validate, it is likely an error
|
||||
validation = self._validate_command_output(data)
|
||||
if not validation[0]:
|
||||
raise APIError(validation[1])
|
||||
|
||||
# return the parsed json as a dict
|
||||
return data
|
||||
|
||||
async def get_token(self) -> dict:
|
||||
"""Gets token information from the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
An encoded token and md5 password, which are used for the privileged API.
|
||||
</details>
|
||||
"""
|
||||
# get the token
|
||||
data = await self.send_command("get_token")
|
||||
|
||||
# encrypt the admin password with the salt
|
||||
pwd = _crypt(self.admin_pwd, "$1$" + data["Msg"]["salt"] + "$")
|
||||
pwd = pwd.split("$")
|
||||
|
||||
# take the 4th item from the pwd split
|
||||
host_passwd_md5 = pwd[3]
|
||||
|
||||
# encrypt the pwd with the time and new salt
|
||||
tmp = _crypt(pwd[3] + data["Msg"]["time"], "$1$" + data["Msg"]["newsalt"] + "$")
|
||||
tmp = tmp.split("$")
|
||||
|
||||
# take the 4th item from the encrypted pwd split
|
||||
host_sign = tmp[3]
|
||||
|
||||
# set the current token
|
||||
self.current_token = {
|
||||
"host_sign": host_sign,
|
||||
"host_passwd_md5": host_passwd_md5,
|
||||
}
|
||||
return self.current_token
|
||||
|
||||
#### PRIVILEGED COMMANDS ####
|
||||
# Please read the top of this file to learn
|
||||
# how to configure the Whatsminer API to
|
||||
# use these commands.
|
||||
|
||||
async def update_pools(
|
||||
self,
|
||||
pool_1: str,
|
||||
worker_1: str,
|
||||
passwd_1: str,
|
||||
pool_2: str = None,
|
||||
worker_2: str = None,
|
||||
passwd_2: str = None,
|
||||
pool_3: str = None,
|
||||
worker_3: str = None,
|
||||
passwd_3: str = None,
|
||||
) -> dict:
|
||||
"""Update the pools of the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Update the pools of the miner using the API, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Parameters:
|
||||
pool_1: The URL to update pool 1 to.
|
||||
worker_1: The worker name for pool 1 to update to.
|
||||
passwd_1: The password for pool 1 to update to.
|
||||
pool_2: The URL to update pool 2 to.
|
||||
worker_2: The worker name for pool 2 to update to.
|
||||
passwd_2: The password for pool 2 to update to.
|
||||
pool_3: The URL to update pool 3 to.
|
||||
worker_3: The worker name for pool 3 to update to.
|
||||
passwd_3: The password for pool 3 to update to.
|
||||
|
||||
Returns:
|
||||
A dict from the API to confirm the pools were updated.
|
||||
</details>
|
||||
"""
|
||||
# get the token and password from the miner
|
||||
token_data = await self.get_token()
|
||||
|
||||
# parse pool data
|
||||
if not pool_1:
|
||||
raise APIError("No pools set.")
|
||||
elif pool_2 and pool_3:
|
||||
command = {
|
||||
"cmd": "update_pools",
|
||||
"pool1": pool_1,
|
||||
"worker1": worker_1,
|
||||
"passwd1": passwd_1,
|
||||
"pool2": pool_2,
|
||||
"worker2": worker_2,
|
||||
"passwd2": passwd_2,
|
||||
"pool3": pool_3,
|
||||
"worker3": worker_3,
|
||||
"passwd3": passwd_3,
|
||||
}
|
||||
elif pool_2:
|
||||
command = {
|
||||
"cmd": "update_pools",
|
||||
"pool1": pool_1,
|
||||
"worker1": worker_1,
|
||||
"passwd1": passwd_1,
|
||||
"pool2": pool_2,
|
||||
"worker2": worker_2,
|
||||
"passwd2": passwd_2,
|
||||
}
|
||||
else:
|
||||
command = {
|
||||
"cmd": "update_pools",
|
||||
"pool1": pool_1,
|
||||
"worker1": worker_1,
|
||||
"passwd1": passwd_1,
|
||||
}
|
||||
# encode the command with the token data
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
# send the command
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def restart(self) -> dict:
|
||||
"""Restart BTMiner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Restart BTMiner using the API, only works after changing
|
||||
the password of the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
A reply informing of the restart.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "restart_btminer"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def power_off(self, respbefore: bool = True) -> dict:
|
||||
"""Power off the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Power off the miner using the API, only works after changing
|
||||
the password of the miner using the Whatsminer tool.
|
||||
|
||||
Parameters:
|
||||
respbefore: Whether to respond before powering off.
|
||||
Returns:
|
||||
A reply informing of the status of powering off.
|
||||
</details>
|
||||
"""
|
||||
if respbefore:
|
||||
command = {"cmd": "power_off", "respbefore": "true"}
|
||||
else:
|
||||
command = {"cmd": "power_off", "respbefore": "false"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def power_on(self) -> dict:
|
||||
"""Power on the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Power on the miner using the API, only works after changing
|
||||
the password of the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of powering on.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "power_on"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def reset_led(self) -> dict:
|
||||
"""Reset the LED on the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Reset the LED on the miner using the API, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of resetting the LED.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "set_led", "param": "auto"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def set_led(
|
||||
self,
|
||||
color: str = "red",
|
||||
period: int = 2000,
|
||||
duration: int = 1000,
|
||||
start: int = 0,
|
||||
) -> dict:
|
||||
"""Set the LED on the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set the LED on the miner using the API, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Parameters:
|
||||
color: The LED color to set, either 'red' or 'green'.
|
||||
period: The flash cycle in ms.
|
||||
duration: LED on time in the cycle in ms.
|
||||
start: LED on time offset in the cycle in ms.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting the LED.
|
||||
</details>
|
||||
"""
|
||||
command = {
|
||||
"cmd": "set_led",
|
||||
"color": color,
|
||||
"period": period,
|
||||
"duration": duration,
|
||||
"start": start,
|
||||
}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def set_low_power(self) -> dict:
|
||||
"""Set low power mode on the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set low power mode on the miner using the API, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting low power mode.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "set_low_power"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def update_firmware(self): # noqa - static
|
||||
"""Not implemented."""
|
||||
# to be determined if this will be added later
|
||||
# requires a file stream in bytes
|
||||
return NotImplementedError
|
||||
|
||||
async def reboot(self) -> dict:
|
||||
"""Reboot the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of the reboot.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "reboot"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def factory_reset(self) -> dict:
|
||||
"""Reset the miner to factory defaults.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of the reset.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "factory_reset"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def update_pwd(self, old_pwd: str, new_pwd: str) -> dict:
|
||||
"""Update the admin user's password.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Update the admin user's password, only works after changing the
|
||||
password of the miner using the Whatsminer tool. New password
|
||||
has a max length of 8 bytes, using letters, numbers, and
|
||||
underscores.
|
||||
|
||||
Parameters:
|
||||
old_pwd: The old admin password.
|
||||
new_pwd: The new password to set.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting the password.
|
||||
"""
|
||||
# check if password length is greater than 8 bytes
|
||||
if len(new_pwd.encode("utf-8")) > 8:
|
||||
raise APIError(
|
||||
f"New password too long, the max length is 8. "
|
||||
f"Password size: {len(new_pwd.encode('utf-8'))}"
|
||||
)
|
||||
command = {"cmd": "update_pwd", "old": old_pwd, "new": new_pwd}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def set_target_freq(self, percent: int) -> dict:
|
||||
"""Update the target frequency.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Update the target frequency, only works after changing the
|
||||
password of the miner using the Whatsminer tool. The new
|
||||
frequency must be between -10% and 100%.
|
||||
|
||||
Parameters:
|
||||
percent: The frequency % to set.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting the frequency.
|
||||
</details>
|
||||
"""
|
||||
if not -10 < percent < 100:
|
||||
raise APIError(
|
||||
f"Frequency % is outside of the allowed "
|
||||
f"range. Please set a % between -10 and "
|
||||
f"100"
|
||||
)
|
||||
command = {"cmd": "set_target_freq", "percent": str(percent)}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def enable_fast_boot(self) -> dict:
|
||||
"""Turn on fast boot.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Turn on fast boot, only works after changing the password of
|
||||
the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of enabling fast boot.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "enable_btminer_fast_boot"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def disable_fast_boot(self) -> dict:
|
||||
"""Turn off fast boot.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Turn off fast boot, only works after changing the password of
|
||||
the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of disabling fast boot.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "disable_btminer_fast_boot"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def enable_web_pools(self) -> dict:
|
||||
"""Turn on web pool updates.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Turn on web pool updates, only works after changing the
|
||||
password of the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of enabling web pools.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "enable_web_pools"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def disable_web_pools(self) -> dict:
|
||||
"""Turn off web pool updates.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Turn off web pool updates, only works after changing the
|
||||
password of the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of disabling web pools.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "disable_web_pools"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def set_hostname(self, hostname: str) -> dict:
|
||||
"""Set the hostname of the miner.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set the hostname of the miner, only works after changing the
|
||||
password of the miner using the Whatsminer tool.
|
||||
|
||||
Parameters:
|
||||
hostname: The new hostname to use.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting the hostname.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "set_hostname", "hostname": hostname}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def set_power_pct(self, percent: int) -> dict:
|
||||
"""Set the power percentage of the miner.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set the power percentage of the miner, only works after changing
|
||||
the password of the miner using the Whatsminer tool.
|
||||
|
||||
Parameters:
|
||||
percent: The power percentage to set.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting the power percentage.
|
||||
</details>
|
||||
"""
|
||||
|
||||
if not 0 < percent < 100:
|
||||
raise APIError(
|
||||
f"Power PCT % is outside of the allowed "
|
||||
f"range. Please set a % between 0 and "
|
||||
f"100"
|
||||
)
|
||||
command = {"cmd": "set_power_pct", "percent": str(percent)}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def pre_power_on(self, complete: bool, msg: str) -> dict:
|
||||
"""Configure or check status of pre power on.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Configure or check status of pre power on, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Parameters:
|
||||
complete: check whether pre power on is complete.
|
||||
msg: ## the message to check.
|
||||
* `wait for adjust temp`
|
||||
* `adjust complete`
|
||||
* `adjust continue`
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of pre power on.
|
||||
</details>
|
||||
"""
|
||||
|
||||
if not msg == "wait for adjust temp" or "adjust complete" or "adjust continue":
|
||||
raise APIError(
|
||||
"Message is incorrect, please choose one of "
|
||||
'["wait for adjust temp", '
|
||||
'"adjust complete", '
|
||||
'"adjust continue"]'
|
||||
)
|
||||
if complete:
|
||||
complete = "true"
|
||||
else:
|
||||
complete = "false"
|
||||
command = {"cmd": "pre_power_on", "complete": complete, "msg": msg}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
#### END privileged COMMANDS ####
|
||||
|
||||
async def summary(self) -> dict:
|
||||
"""Get the summary status from the miner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Summary status of the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("summary")
|
||||
|
||||
async def pools(self) -> dict:
|
||||
"""Get the pool status from the miner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Pool status of the miner.
|
||||
</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) -> dict:
|
||||
"""Get data on each PGA/ASC with their details, ignoring blacklisted and zombie devices.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Data on each PGA/ASC with their details.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("edevs")
|
||||
|
||||
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 get_psu(self) -> dict:
|
||||
"""Get data on the PSU and power information.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Data on the PSU and power information.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("get_psu")
|
||||
|
||||
async def version(self) -> dict:
|
||||
"""Get version data for the miner. Wraps `self.get_version()`.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Get version data for the miner. This calls another function,
|
||||
self.get_version(), but is named version to stay consistent
|
||||
with the other miner APIs.
|
||||
|
||||
Returns:
|
||||
|
||||
Version data for the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.get_version()
|
||||
|
||||
async def get_version(self) -> dict:
|
||||
"""Get version data for the miner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Version data for the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("get_version")
|
||||
|
||||
async def status(self) -> dict:
|
||||
"""Get BTMiner status and firmware version.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
BTMiner status and firmware version.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("status")
|
||||
|
||||
async def get_miner_info(self) -> dict:
|
||||
"""Get general miner info.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
General miner info.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("get_miner_info")
|
||||
@@ -1,80 +0,0 @@
|
||||
from pyasic.API import BaseMinerAPI
|
||||
|
||||
|
||||
class UnknownAPI(BaseMinerAPI):
|
||||
"""An abstraction of an API for a miner which is unknown.
|
||||
|
||||
This class is designed to try to be a intersection of as many miner APIs
|
||||
and API commands as possible (API ⋂ API), to ensure that it can be used
|
||||
with as many APIs as possible.
|
||||
"""
|
||||
|
||||
def __init__(self, ip, port=4028):
|
||||
super().__init__(ip, port)
|
||||
|
||||
async def asccount(self) -> dict:
|
||||
return await self.send_command("asccount")
|
||||
|
||||
async def asc(self, n: int) -> dict:
|
||||
return await self.send_command("asc", parameters=n)
|
||||
|
||||
async def devdetails(self) -> dict:
|
||||
return await self.send_command("devdetails")
|
||||
|
||||
async def devs(self) -> dict:
|
||||
return await self.send_command("devs")
|
||||
|
||||
async def edevs(self, old: bool = False) -> dict:
|
||||
if old:
|
||||
return await self.send_command("edevs", parameters="old")
|
||||
else:
|
||||
return await self.send_command("edevs")
|
||||
|
||||
async def pools(self) -> dict:
|
||||
return await self.send_command("pools")
|
||||
|
||||
async def summary(self) -> dict:
|
||||
return await self.send_command("summary")
|
||||
|
||||
async def stats(self) -> dict:
|
||||
return await self.send_command("stats")
|
||||
|
||||
async def version(self) -> dict:
|
||||
return await self.send_command("version")
|
||||
|
||||
async def estats(self) -> dict:
|
||||
return await self.send_command("estats")
|
||||
|
||||
async def check(self) -> dict:
|
||||
return await self.send_command("check")
|
||||
|
||||
async def coin(self) -> dict:
|
||||
return await self.send_command("coin")
|
||||
|
||||
async def lcd(self) -> dict:
|
||||
return await self.send_command("lcd")
|
||||
|
||||
async def switchpool(self, n: int) -> dict:
|
||||
# BOS has not implemented this yet, they will in the future
|
||||
raise NotImplementedError
|
||||
# return await self.send_command("switchpool", parameters=n)
|
||||
|
||||
async def enablepool(self, n: int) -> dict:
|
||||
# BOS has not implemented this yet, they will in the future
|
||||
raise NotImplementedError
|
||||
# return await self.send_command("enablepool", parameters=n)
|
||||
|
||||
async def disablepool(self, n: int) -> dict:
|
||||
# BOS has not implemented this yet, they will in the future
|
||||
raise NotImplementedError
|
||||
# return await self.send_command("disablepool", parameters=n)
|
||||
|
||||
async def addpool(self, url: str, username: str, password: str) -> dict:
|
||||
# BOS has not implemented this yet, they will in the future
|
||||
raise NotImplementedError
|
||||
# return await self.send_command("addpool", parameters=f"{url},{username},{password}")
|
||||
|
||||
async def removepool(self, n: int) -> dict:
|
||||
# BOS has not implemented this yet, they will in the future
|
||||
raise NotImplementedError
|
||||
# return await self.send_command("removepool", parameters=n)
|
||||
@@ -0,0 +1,28 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 importlib.metadata
|
||||
|
||||
from pyasic import settings
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import MinerData
|
||||
from pyasic.errors import APIError, APIWarning
|
||||
from pyasic.miners import *
|
||||
from pyasic.network import MinerNetwork
|
||||
from pyasic.rpc import *
|
||||
from pyasic.ssh import *
|
||||
from pyasic.web import *
|
||||
|
||||
__version__ = importlib.metadata.version("pyasic")
|
||||
|
||||
@@ -1,396 +1,380 @@
|
||||
from dataclasses import dataclass, asdict
|
||||
from typing import Literal, List
|
||||
import random
|
||||
import string
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 toml
|
||||
import yaml
|
||||
import json
|
||||
import time
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
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)
|
||||
|
||||
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_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_avalon(self, user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a string usable by an Avalonminer device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
username = self.username
|
||||
if user_suffix:
|
||||
username = f"{username}{user_suffix}"
|
||||
|
||||
pool = ",".join([self.url, username, self.password])
|
||||
return pool
|
||||
|
||||
def as_bos(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a dict usable by an BOSMiner device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
username = self.username
|
||||
if user_suffix:
|
||||
username = f"{username}{user_suffix}"
|
||||
|
||||
pool = {"url": self.url, "user": username, "password": self.password}
|
||||
return pool
|
||||
|
||||
|
||||
@dataclass
|
||||
class _PoolGroup:
|
||||
"""A dataclass for pool group information.
|
||||
|
||||
Attributes:
|
||||
quota: The group quota.
|
||||
group_name: The name of the pool group.
|
||||
pools: A list of pools in this group.
|
||||
"""
|
||||
|
||||
quota: int = 1
|
||||
group_name: str = None
|
||||
pools: List[_Pool] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if not self.group_name:
|
||||
self.group_name = "".join(
|
||||
random.choice(string.ascii_uppercase + string.digits) for _ in range(6)
|
||||
) # generate random pool group name in case it isn't set
|
||||
|
||||
def from_dict(self, data: dict):
|
||||
"""Convert raw pool group data as a dict to usable data and save it to this class.
|
||||
|
||||
Parameters:
|
||||
data: The raw config data to convert.
|
||||
"""
|
||||
pools = []
|
||||
for key in data.keys():
|
||||
if key in ["name", "group_name"]:
|
||||
self.group_name = data[key]
|
||||
if key == "quota":
|
||||
self.quota = data[key]
|
||||
if key in ["pools", "pool"]:
|
||||
for pool in data[key]:
|
||||
pools.append(_Pool().from_dict(pool))
|
||||
self.pools = pools
|
||||
return self
|
||||
|
||||
def as_x19(self, user_suffix: str = None) -> List[dict]:
|
||||
"""Convert the data in this class to a list usable by an X19 device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
pools = []
|
||||
for pool in self.pools[:3]:
|
||||
pools.append(pool.as_x19(user_suffix=user_suffix))
|
||||
return pools
|
||||
|
||||
def as_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 = 10.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
|
||||
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."""
|
||||
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."""
|
||||
return toml.dumps(self.as_dict())
|
||||
|
||||
def as_yaml(self) -> str:
|
||||
"""Convert the data in this class to yaml."""
|
||||
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.
|
||||
"""
|
||||
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 == "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_dict(self, data: dict):
|
||||
"""Convert an output dict of this class back into usable data and save it to this class.
|
||||
|
||||
Parameters:
|
||||
data: The dict config data to convert.
|
||||
"""
|
||||
pool_groups = []
|
||||
for group in data["pool_groups"]:
|
||||
pool_groups.append(_PoolGroup().from_dict(group))
|
||||
for key in data.keys():
|
||||
if getattr(self, key) and not key == "pool_groups":
|
||||
setattr(self, key, data[key])
|
||||
self.pool_groups = pool_groups
|
||||
return self
|
||||
|
||||
def from_toml(self, data: str):
|
||||
"""Convert output toml of this class back into usable data and save it to this class.
|
||||
|
||||
Parameters:
|
||||
data: The toml config data to convert.
|
||||
"""
|
||||
return self.from_dict(toml.loads(data))
|
||||
|
||||
def from_yaml(self, data: str):
|
||||
"""Convert output yaml of this class back into usable data and save it to this class.
|
||||
|
||||
Parameters:
|
||||
data: The yaml config data to convert.
|
||||
"""
|
||||
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
|
||||
|
||||
def as_x19(self, user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a config usable by an X19 device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
cfg = {
|
||||
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
|
||||
"bitmain-fan-ctrl": False,
|
||||
"bitmain-fan-pwn": 100,
|
||||
def as_am_modern(self, user_suffix: str | None = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for modern Antminers."""
|
||||
return {
|
||||
**self.fan_mode.as_am_modern(),
|
||||
"freq-level": "100",
|
||||
**self.mining_mode.as_am_modern(),
|
||||
**self.pools.as_am_modern(user_suffix=user_suffix),
|
||||
**self.temperature.as_am_modern(),
|
||||
}
|
||||
|
||||
if not self.temp_mode == "auto":
|
||||
cfg["bitmain-fan-ctrl"] = True
|
||||
|
||||
if self.fan_speed:
|
||||
cfg["bitmain-fan-ctrl"] = str(self.fan_speed)
|
||||
|
||||
return json.dumps(cfg)
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a config usable by an Avalonminer device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
cfg = self.pool_groups[0].as_avalon(user_suffix=user_suffix)
|
||||
return cfg
|
||||
|
||||
def as_bos(self, model: str = "S9", user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a config usable by an BOSMiner device.
|
||||
|
||||
Parameters:
|
||||
model: The model of the miner to be used in the format portion of the config.
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
cfg = {
|
||||
"format": {
|
||||
"version": "1.2+",
|
||||
"model": f"Antminer {model}",
|
||||
"generator": "Upstream Config Utility",
|
||||
"timestamp": int(time.time()),
|
||||
},
|
||||
"group": [
|
||||
group.as_bos(user_suffix=user_suffix) for group in self.pool_groups
|
||||
],
|
||||
"temp_control": {
|
||||
"mode": self.temp_mode,
|
||||
"target_temp": self.temp_target,
|
||||
"hot_temp": self.temp_hot,
|
||||
"dangerous_temp": self.temp_dangerous,
|
||||
},
|
||||
def as_hiveon_modern(self, user_suffix: str | None = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for modern Hiveon."""
|
||||
return {
|
||||
**self.fan_mode.as_hiveon_modern(),
|
||||
"freq-level": "100",
|
||||
**self.mining_mode.as_hiveon_modern(),
|
||||
**self.pools.as_hiveon_modern(user_suffix=user_suffix),
|
||||
**self.temperature.as_hiveon_modern(),
|
||||
}
|
||||
|
||||
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_elphapex(self, user_suffix: str | None = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for modern Elphapex."""
|
||||
return {
|
||||
**self.fan_mode.as_elphapex(),
|
||||
"fc-freq-level": "100",
|
||||
**self.mining_mode.as_elphapex(),
|
||||
**self.pools.as_elphapex(user_suffix=user_suffix),
|
||||
**self.temperature.as_elphapex(),
|
||||
}
|
||||
|
||||
if self.asicboost:
|
||||
cfg["hash_chain_global"] = {}
|
||||
cfg["hash_chain_global"]["asic_boost"] = self.asicboost
|
||||
def as_wm(self, user_suffix: str | None = 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(),
|
||||
}
|
||||
|
||||
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_btminer_v3(self, user_suffix: str | None = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for Whatsminers running BTMiner V3."""
|
||||
return {
|
||||
"set.miner.pools": self.pools.as_btminer_v3()
|
||||
** self.mining_mode.as_btminer_v3()
|
||||
}
|
||||
|
||||
return toml.dumps(cfg)
|
||||
def as_am_old(self, user_suffix: str | None = 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 = 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 = 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 = None) -> dict:
|
||||
"""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(),
|
||||
}
|
||||
|
||||
def as_bosminer(self, user_suffix: str | None = 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),
|
||||
}
|
||||
|
||||
def as_boser(self, user_suffix: str | None = 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),
|
||||
}
|
||||
|
||||
def as_epic(self, user_suffix: str | None = 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),
|
||||
}
|
||||
|
||||
def as_auradine(self, user_suffix: str | None = 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 = 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_espminer(self, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
**self.fan_mode.as_espminer(),
|
||||
**self.temperature.as_espminer(),
|
||||
**self.mining_mode.as_espminer(),
|
||||
**self.pools.as_espminer(user_suffix=user_suffix),
|
||||
}
|
||||
|
||||
def as_luxos(self, user_suffix: str | None = 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 = 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_hiveon_modern(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for Hiveon."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_hiveon_modern(web_conf),
|
||||
mining_mode=MiningModeConfig.from_hiveon_modern(web_conf),
|
||||
fan_mode=FanModeConfig.from_hiveon_modern(web_conf),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_elphapex(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for modern Antminers."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_elphapex(web_conf),
|
||||
mining_mode=MiningModeConfig.from_elphapex(web_conf),
|
||||
fan_mode=FanModeConfig.from_elphapex(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_goldshell_list(cls, web_conf: list) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for Goldshell miners."""
|
||||
return cls(pools=PoolConfig.from_goldshell(web_conf))
|
||||
|
||||
@classmethod
|
||||
def from_goldshell_byte(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for Goldshell Byte miners."""
|
||||
return cls(pools=PoolConfig.from_goldshell_byte(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], web_perf_summary: 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, web_perf_summary
|
||||
),
|
||||
)
|
||||
|
||||
@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_espminer(cls, web_system_info: dict) -> "MinerConfig":
|
||||
return cls(
|
||||
pools=PoolConfig.from_espminer(web_system_info),
|
||||
fan_mode=FanModeConfig.from_espminer(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,
|
||||
rpc_config: dict,
|
||||
rpc_profiles: 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),
|
||||
mining_mode=MiningModeConfig.from_luxos(
|
||||
rpc_config=rpc_config, rpc_profiles=rpc_profiles
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_hammer(cls, *args, **kwargs) -> "MinerConfig":
|
||||
return cls.from_am_modern(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_btminer_v3(
|
||||
cls, rpc_pools: dict, rpc_settings: dict, rpc_device_info: dict
|
||||
) -> "MinerConfig":
|
||||
return cls(
|
||||
pools=PoolConfig.from_btminer_v3(rpc_pools=rpc_pools["msg"]),
|
||||
mining_mode=MiningModeConfig.from_btminer_v3(
|
||||
rpc_device_info=rpc_device_info, rpc_settings=rpc_settings
|
||||
),
|
||||
)
|
||||
|
||||
153
pyasic/config/base.py
Normal file
153
pyasic/config/base.py
Normal file
@@ -0,0 +1,153 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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_hiveon_modern(self) -> dict:
|
||||
return self.value.as_hiveon_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_espminer(self) -> dict:
|
||||
return self.value.as_espminer()
|
||||
|
||||
def as_luxos(self) -> dict:
|
||||
return self.value.as_luxos()
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return self.value.as_elphapex()
|
||||
|
||||
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_hiveon_modern(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_am_old(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_btminer_v3(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_espminer(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_luxos(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {}
|
||||
|
||||
def __getitem__(self, item):
|
||||
try:
|
||||
return getattr(self, item)
|
||||
except AttributeError:
|
||||
raise KeyError
|
||||
421
pyasic/config/fans.py
Normal file
421
pyasic/config/fans.py
Normal file
@@ -0,0 +1,421 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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_hiveon_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": False, "bitmain-fan-pwn": "100"}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"fc-fan-ctrl": False, "fc-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_espminer(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_hiveon_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwm": str(self.speed)}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"fc-fan-ctrl": True, "fc-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_espminer(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_hiveon_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwm": "0"}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"fc-fan-ctrl": True, "fc-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_hiveon_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_elphapex(cls, web_conf: dict):
|
||||
if web_conf.get("fc-fan-ctrl") is not None:
|
||||
fan_manual = web_conf["fc-fan-ctrl"]
|
||||
if fan_manual:
|
||||
speed = int(web_conf["fc-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)
|
||||
if "disabled" in keys:
|
||||
conf = {}
|
||||
if "fanSpeedRatio" in temperature_conf["disabled"].keys():
|
||||
conf["speed"] = int(temperature_conf["disabled"]["fanSpeedRatio"])
|
||||
return cls.manual(**conf)
|
||||
return cls.default()
|
||||
|
||||
@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_espminer(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],
|
||||
)
|
||||
873
pyasic/config/mining/__init__.py
Normal file
873
pyasic/config/mining/__init__.py
Normal file
@@ -0,0 +1,873 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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_hiveon_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
def as_btminer_v3(self) -> dict:
|
||||
return {"set.miner.service": "start", "set.miner.power_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}}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {"autotuning": {"enabled": True}}
|
||||
|
||||
|
||||
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_hiveon_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "1"}
|
||||
return {"miner-mode": 1}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 1}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
def as_btminer_v3(self) -> dict:
|
||||
return {"set.miner.service": "stop"}
|
||||
|
||||
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_hiveon_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "3"}
|
||||
return {"miner-mode": 3}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 3}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
def as_btminer_v3(self) -> dict:
|
||||
return {"set.miner.service": "start", "set.miner.power_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_hiveon_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
def as_btminer_v3(self) -> dict:
|
||||
return {"set.miner.service": "start", "set.miner.power_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_hiveon_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
if self.power is not None:
|
||||
return {"mode": self.mode, self.mode: {"wattage": self.power}}
|
||||
return {}
|
||||
|
||||
def as_btminer_v3(self) -> dict:
|
||||
return {"set.miner.service": "start", "set.miner.power_limit": self.power}
|
||||
|
||||
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()
|
||||
power_target_kwargs = {}
|
||||
if self.scaling.step is not None:
|
||||
power_target_kwargs["power_step"] = Power(self.scaling.step)
|
||||
if self.scaling.minimum is not None:
|
||||
power_target_kwargs["min_power_target"] = Power(self.scaling.minimum)
|
||||
cfg["set_dps"] = SetDpsRequest(
|
||||
save_action=SaveAction.SAVE_AND_APPLY,
|
||||
enable=True,
|
||||
**sd_cfg,
|
||||
target=DpsTarget(power_target=DpsPowerTarget(**power_target_kwargs)),
|
||||
)
|
||||
|
||||
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_hiveon_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
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}
|
||||
|
||||
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()
|
||||
hashrate_target_kwargs = {}
|
||||
if self.scaling.step is not None:
|
||||
hashrate_target_kwargs["hashrate_step"] = TeraHashrate(
|
||||
self.scaling.step
|
||||
)
|
||||
if self.scaling.minimum is not None:
|
||||
hashrate_target_kwargs["min_hashrate_target"] = TeraHashrate(
|
||||
self.scaling.minimum
|
||||
)
|
||||
cfg["set_dps"] = SetDpsRequest(
|
||||
save_action=SaveAction.SAVE_AND_APPLY,
|
||||
enable=True,
|
||||
**sd_cfg,
|
||||
target=DpsTarget(
|
||||
hashrate_target=DpsHashrateTarget(**hashrate_target_kwargs)
|
||||
),
|
||||
)
|
||||
|
||||
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],
|
||||
web_perf_summary: dict,
|
||||
) -> "MiningModePreset":
|
||||
active_preset = web_perf_summary.get("current_preset")
|
||||
|
||||
if active_preset is 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],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_luxos(cls, rpc_config: dict, rpc_profiles: dict) -> "MiningModePreset":
|
||||
active_preset = cls.get_active_preset_from_luxos(rpc_config, rpc_profiles)
|
||||
return cls(
|
||||
active_preset=active_preset,
|
||||
available_presets=[
|
||||
MiningPreset.from_luxos(p) for p in rpc_profiles["PROFILES"]
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_active_preset_from_luxos(
|
||||
cls, rpc_config: dict, rpc_profiles: dict
|
||||
) -> MiningPreset:
|
||||
active_preset = None
|
||||
active_profile = rpc_config["CONFIG"][0]["Profile"]
|
||||
for profile in rpc_profiles["PROFILES"]:
|
||||
if profile["Profile Name"] == active_profile:
|
||||
active_preset = profile
|
||||
return MiningPreset.from_luxos(active_preset)
|
||||
|
||||
|
||||
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_hiveon_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
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_elphapex(self) -> dict:
|
||||
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_hiveon_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_elphapex(cls, web_conf: dict):
|
||||
if web_conf.get("fc-work-mode") is not None:
|
||||
work_mode = web_conf["fc-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"),
|
||||
)
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_vnish(
|
||||
cls, web_settings: dict, web_presets: list[dict], web_perf_summary: 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, web_perf_summary
|
||||
)
|
||||
|
||||
@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_btminer_v3(cls, rpc_device_info: dict, rpc_settings: dict):
|
||||
try:
|
||||
is_mining = rpc_device_info["msg"]["miner"]["working"] == "true"
|
||||
if not is_mining:
|
||||
return cls.sleep()
|
||||
power_limit = rpc_settings["msg"]["power-limit"]
|
||||
if not power_limit == 0:
|
||||
return cls.power_tuning(power=power_limit)
|
||||
power_mode = rpc_settings["msg"]["power-mode"]
|
||||
if power_mode == "normal":
|
||||
return cls.normal()
|
||||
if power_mode == "high":
|
||||
return cls.high()
|
||||
if power_mode == "low":
|
||||
return cls.low()
|
||||
|
||||
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()
|
||||
|
||||
@classmethod
|
||||
def from_luxos(cls, rpc_config: dict, rpc_profiles: dict):
|
||||
preset_info = MiningModePreset.from_luxos(rpc_config, rpc_profiles)
|
||||
return cls.preset(
|
||||
active_preset=preset_info.active_preset,
|
||||
available_presets=preset_info.available_presets,
|
||||
)
|
||||
|
||||
|
||||
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 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,
|
||||
],
|
||||
)
|
||||
54
pyasic/config/mining/presets.py
Normal file
54
pyasic/config/mining/presets.py
Normal file
@@ -0,0 +1,54 @@
|
||||
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 | None = None
|
||||
frequency: int | None = None
|
||||
voltage: float | None = None
|
||||
|
||||
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("GH", "")
|
||||
.replace("MH", "")
|
||||
.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,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_luxos(cls, profile: dict):
|
||||
return cls(
|
||||
name=profile["Profile Name"],
|
||||
power=profile["Watts"],
|
||||
hashrate=round(profile["Hashrate"]),
|
||||
tuned=profile["IsTuned"],
|
||||
frequency=profile["Frequency"],
|
||||
voltage=profile["Voltage"],
|
||||
)
|
||||
129
pyasic/config/mining/scaling.py
Normal file
129
pyasic/config/mining/scaling.py
Normal file
@@ -0,0 +1,129 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 pyasic.config.base import MinerConfigValue
|
||||
|
||||
|
||||
class ScalingShutdown(MinerConfigValue):
|
||||
enabled: bool = False
|
||||
duration: int | None = 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 = None
|
||||
minimum: int | None = None
|
||||
shutdown: ScalingShutdown | None = 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 = 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 = 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)
|
||||
737
pyasic/config/pools.py
Normal file
737
pyasic/config/pools.py
Normal file
@@ -0,0 +1,737 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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_hiveon_modern(self, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
"url": self.url,
|
||||
"user": f"{self.user}{user_suffix or ''}",
|
||||
"pass": self.password,
|
||||
}
|
||||
|
||||
def as_elphapex(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_btminer_v3(self, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
f"pool": self.url,
|
||||
f"worker": f"{self.user}{user_suffix or ''}",
|
||||
f"passwd": 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_espminer(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_btminer_v3(cls, api_pool: dict) -> "Pool":
|
||||
return cls(url=api_pool["url"], user=api_pool["account"], 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"]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_hiveon_modern(cls, web_pool: dict) -> "Pool":
|
||||
return cls(
|
||||
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_elphapex(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.get("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_espminer(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_hiveon_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_hiveon_modern(user_suffix=user_suffix))
|
||||
else:
|
||||
pools.append(Pool(url="", user="", password="").as_hiveon_modern())
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
def as_elphapex(self, user_suffix: str | None = None) -> list:
|
||||
pools = []
|
||||
idx = 0
|
||||
while idx < 3:
|
||||
if len(self.pools) > idx:
|
||||
pools.append(self.pools[idx].as_elphapex(user_suffix=user_suffix))
|
||||
else:
|
||||
pools.append(Pool(url="", user="", password="").as_elphapex())
|
||||
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_btminer_v3(self, user_suffix: str | None = None) -> list:
|
||||
return [pool.as_btminer_v3(user_suffix) for pool in self.pools[:3]]
|
||||
|
||||
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_espminer(self, user_suffix: str | None = None) -> dict:
|
||||
return self.pools[0].as_espminer(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_btminer_v3(cls, api_pool_list: list) -> "PoolGroup":
|
||||
pools = []
|
||||
for pool in api_pool_list:
|
||||
pools.append(Pool.from_btminer_v3(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_hiveon_modern(cls, web_pool_list: list) -> "PoolGroup":
|
||||
pools = []
|
||||
for pool in web_pool_list:
|
||||
pools.append(Pool.from_hiveon_modern(pool))
|
||||
return cls(pools=pools)
|
||||
|
||||
@classmethod
|
||||
def from_elphapex(cls, web_pool_list: list) -> "PoolGroup":
|
||||
pools = []
|
||||
for pool in web_pool_list:
|
||||
pools.append(Pool.from_elphapex(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", 1),
|
||||
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_espminer(cls, web_system_info: dict) -> "PoolGroup":
|
||||
return cls(pools=[Pool.from_espminer(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_hiveon_modern(self, user_suffix: str | None = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {"pools": self.groups[0].as_hiveon_modern(user_suffix=user_suffix)}
|
||||
return {"pools": PoolGroup().as_hiveon_modern()}
|
||||
|
||||
def as_elphapex(self, user_suffix: str | None = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {"pools": self.groups[0].as_elphapex(user_suffix=user_suffix)}
|
||||
return {"pools": PoolGroup().as_elphapex()}
|
||||
|
||||
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_btminer_v3(self, user_suffix: str | None = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {"pools": self.groups[0].as_btminer_v3(user_suffix=user_suffix)}
|
||||
return {"pools": PoolGroup().as_btminer_v3()}
|
||||
|
||||
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_espminer(self, user_suffix: str | None = None) -> dict:
|
||||
return self.groups[0].as_espminer(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_btminer_v3(cls, rpc_pools: dict) -> "PoolConfig":
|
||||
try:
|
||||
pool_data = rpc_pools["pools"]
|
||||
except KeyError:
|
||||
return PoolConfig.default()
|
||||
pool_data = sorted(pool_data, key=lambda x: int(x["id"]))
|
||||
|
||||
return cls(groups=[PoolGroup.from_btminer_v3(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":
|
||||
try:
|
||||
pool_data = web_conf["pools"]
|
||||
except KeyError:
|
||||
return cls(groups=[])
|
||||
|
||||
return cls(groups=[PoolGroup.from_am_modern(pool_data)])
|
||||
|
||||
@classmethod
|
||||
def from_hiveon_modern(cls, web_conf: dict) -> "PoolConfig":
|
||||
try:
|
||||
pool_data = web_conf["pools"]
|
||||
except KeyError:
|
||||
return cls(groups=[])
|
||||
|
||||
return cls(groups=[PoolGroup.from_hiveon_modern(pool_data)])
|
||||
|
||||
@classmethod
|
||||
def from_elphapex(cls, web_conf: dict) -> "PoolConfig":
|
||||
pool_data = web_conf["pools"]
|
||||
|
||||
return cls(groups=[PoolGroup.from_elphapex(pool_data)])
|
||||
|
||||
@classmethod
|
||||
def from_goldshell(cls, web_pools: list) -> "PoolConfig":
|
||||
return cls(groups=[PoolGroup.from_goldshell(web_pools)])
|
||||
|
||||
@classmethod
|
||||
def from_goldshell_byte(cls, web_pools: list) -> "PoolConfig":
|
||||
return cls(
|
||||
groups=[
|
||||
PoolGroup.from_goldshell(g["pools"])
|
||||
for g in web_pools
|
||||
if len(g["pools"]) > 0
|
||||
]
|
||||
)
|
||||
|
||||
@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_espminer(cls, web_system_info: dict) -> "PoolConfig":
|
||||
return cls(groups=[PoolGroup.from_espminer(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"]
|
||||
]
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user