Compare commits
475 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
26ae6ebfb2 | ||
|
|
e65cb0573d | ||
|
|
f8590b0c5f | ||
|
|
43b4992cee | ||
|
|
98e2cfae84 | ||
|
|
cb01c1a8ee | ||
|
|
36a273ec2b | ||
|
|
6a0dc03b9d |
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__.:
|
||||
2
.github/workflows/python-publish.yml
vendored
2
.github/workflows/python-publish.yml
vendored
@@ -19,4 +19,4 @@ jobs:
|
||||
- name: Build using poetry and publish to PyPi
|
||||
uses: JRubics/poetry-publish@v1.11
|
||||
with:
|
||||
pypi_token: ${{ secrets.PYPI_API_KEY }}
|
||||
pypi_token: ${{ secrets.PYPI_API_KEY }}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ pyvenv.cfg
|
||||
.env/
|
||||
bin/
|
||||
lib/
|
||||
.idea/
|
||||
|
||||
27
.pre-commit-config.yaml
Normal file
27
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.3.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.6.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.10.1
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort (python)
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: unittest
|
||||
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
|
||||
stages: [commit]
|
||||
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
|
||||
113
README.md
113
README.md
@@ -5,102 +5,82 @@
|
||||
[](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://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
|
||||
[](https://www.codefactor.io/repository/github/upstreamdata/pyasic)
|
||||
## Documentation and Supported Miners
|
||||
Documentation is located on Read the Docs as [pyasic](https://pyasic.readthedocs.io/en/latest/).
|
||||
|
||||
## Usage
|
||||
Supported miners are listed in the docs, [here](https://pyasic.readthedocs.io/en/latest/miners/supported_types/).
|
||||
|
||||
### Standard Usage
|
||||
You can install pyasic directly from pip with the command `pip install pyasic`
|
||||
## Installation
|
||||
You can install pyasic directly from pip with the command `pip install pyasic`.
|
||||
|
||||
For those of you who aren't comfortable with code and developer tools, there are windows builds of GUI applications that use this library here -> (https://drive.google.com/drive/folders/1DjR8UOS_g0ehfiJcgmrV0FFoqFvE9akW?usp=sharing)
|
||||
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).
|
||||
|
||||
### 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.
|
||||
## Developer Setup
|
||||
It is highly reccommended that you contribute to this project through [`pyasic-super`](https://github.com/UpstreamData/pyasic-super) using its submodules. This allows testing in conjunction with other `pyasic` related programs.
|
||||
|
||||
You can also use poetry by initializing and running ```poetry install```
|
||||
|
||||
### Interfacing with miners programmatically
|
||||
|
||||
##### 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
|
||||
|
||||
# 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())
|
||||
```
|
||||
|
||||
##### It is likely a good idea to use this code in your program anyway to be preventative.
|
||||
<br>
|
||||
|
||||
To write your own custom programs with this repo, you have many options.
|
||||
This repo uses poetry for dependencies, which can be installed by following the guide on their website [here](https://python-poetry.org/docs/#installation).
|
||||
|
||||
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.
|
||||
After you have poetry installed, run `poetry install --with dev`, or `poetry install --with dev,docs` if you want to include packages required for documentation.
|
||||
|
||||
There are 2 main ways to get a miner and it's functions via scanning or via the MinerFactory.
|
||||
Finally, initialize pre-commit hooks with `poetry run pre-commit install`.
|
||||
|
||||
### Documentation Testing
|
||||
Testing the documentation can be done by running `poetry run mkdocs serve`, whcih will serve the documentation locally on port 8000.
|
||||
|
||||
## Interfacing with miners programmatically
|
||||
|
||||
There are 2 main ways to get a miner (and the functions attached to it), via scanning or via the `MinerFactory()`.
|
||||
|
||||
#### Scanning for miners
|
||||
```python
|
||||
import asyncio
|
||||
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
|
||||
# The standard mask is /24 (x.x.x.0-255), and you can pass any IP address in the subnet
|
||||
net = MinerNetwork("192.168.1.69", mask=24)
|
||||
# Scan the network for miners
|
||||
# This function returns a list of miners of the correct type as a class
|
||||
miners: list = await net.scan_network_for_miners()
|
||||
|
||||
|
||||
# We can now get data from any of these miners
|
||||
# To do them all we have to create a list of tasks and gather them
|
||||
tasks = [miner.get_data() for miner in miners]
|
||||
# Gather all tasks asynchronously and run them
|
||||
data = await asyncio.gather(*tasks)
|
||||
|
||||
|
||||
# Data is now a list of MinerData, and we can reference any part of that
|
||||
# Print out all data for now
|
||||
for item in data:
|
||||
print(item)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(scan_and_get_data())
|
||||
```
|
||||
|
||||
</br>
|
||||
|
||||
#### Getting a miner if you know the IP
|
||||
```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
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
miner = await get_miner(miner_ip)
|
||||
|
||||
# Get data from the miner
|
||||
data = await miner.get_data()
|
||||
print(data)
|
||||
@@ -113,27 +93,26 @@ if __name__ == "__main__":
|
||||
|
||||
If needed, this library exposes a wrapper for the miner API that can be used for advanced data gathering.
|
||||
|
||||
You can see more information on basic usage of the APIs past this example in the docs [here](https://pyasic.readthedocs.io/en/latest/API/api/).
|
||||
|
||||
Please see the appropriate API documentation page (pyasic docs -> Advanced -> Miner APIs -> your API type) for a link to that specific miner's API documentation page and more information.
|
||||
|
||||
#### List available API commands
|
||||
```python
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from pyasic.miners.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)
|
||||
|
||||
miner = await get_miner(miner_ip)
|
||||
|
||||
# List all available commands
|
||||
print(miner.api.get_commands())
|
||||
|
||||
|
||||
# Can also be called explicitly with the function miner.api.get_commands()
|
||||
print(miner.api.commands)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(get_api_commands("192.168.1.69"))
|
||||
```
|
||||
@@ -144,26 +123,20 @@ The miner API commands will raise an `APIError` if they fail with a bad status c
|
||||
|
||||
```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)
|
||||
|
||||
miner = await get_miner(miner_ip)
|
||||
|
||||
# Run the devdetails command
|
||||
# This is equivalent to await miner.api.send_command("devdetails")
|
||||
devdetails: dict = await miner.api.devdetails()
|
||||
print(devdetails)
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(get_api_commands("192.168.1.69"))
|
||||
```
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
## 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`.
|
||||
Each miner that is a subclass of [`BaseMiner`][pyasic.miners.BaseMiner] should have an API linked to it as `Miner.api`.
|
||||
|
||||
All API implementations inherit from [`BaseMinerAPI`][pyasic.API.BaseMinerAPI], which implements the basic communications protocols.
|
||||
|
||||
BaseMinerAPI should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
|
||||
[`BaseMinerAPI`][pyasic.API.BaseMinerAPI] should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
|
||||
[`BaseMinerAPI`][pyasic.API.BaseMinerAPI] cannot be instantiated directly, it will raise a `TypeError`.
|
||||
Use these instead -
|
||||
|
||||
#### [BFGMiner API][pyasic.API.bfgminer.BFGMinerAPI]
|
||||
#### [BMMiner API][pyasic.API.bmminer.BMMinerAPI]
|
||||
#### [BOSMiner API][pyasic.API.bosminer.BOSMinerAPI]
|
||||
#### [BTMiner API][pyasic.API.btminer.BTMinerAPI]
|
||||
|
||||
7
docs/API/bfgminer.md
Normal file
7
docs/API/bfgminer.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## BFGMinerAPI
|
||||
::: pyasic.API.bfgminer.BFGMinerAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
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
|
||||
@@ -6,3 +6,10 @@
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## HashBoard Data
|
||||
::: pyasic.data.HashBoard
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
211
docs/index.md
211
docs/index.md
@@ -5,6 +5,8 @@
|
||||
[](https://pypi.org/project/pyasic/)
|
||||
[](https://pypi.org/project/pyasic/)
|
||||
[](https://pyasic.readthedocs.io/en/latest/)
|
||||
[](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
|
||||
[](https://www.codefactor.io/repository/github/upstreamdata/pyasic)
|
||||
|
||||
## Intro
|
||||
Welcome to pyasic! Pyasic uses an asynchronous method of communicating with asic miners on your network, which makes it super fast.
|
||||
@@ -27,35 +29,41 @@ 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()
|
||||
print(miners)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(scan_miners()) # run the scan asynchronously with asyncio.run()
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## Creating miners based on IP
|
||||
If you already know the IP address of your miner or miners, you can use the [`MinerFactory`][pyasic.miners.miner_factory.MinerFactory] to communicate and identify the miners.
|
||||
The function [`MinerFactory().get_miner()`][pyasic.miners.miner_factory.MinerFactory.get_miner] will return any miner it found at the IP address specified, or an `UnknownMiner` if it cannot identify the miner.
|
||||
If you already know the IP address of your miner or miners, you can use the [`MinerFactory`][pyasic.miners.miner_factory.MinerFactory] to communicate and identify the miners, or an abstraction of its functionality, [`get_miner()`][pyasic.miners.get_miner].
|
||||
The function [`get_miner()`][pyasic.miners.get_miner] will return any miner it found at the IP address specified, or an `UnknownMiner` if it cannot identify the miner.
|
||||
```python
|
||||
import asyncio # asyncio for handling the async part
|
||||
from pyasic.miners.miner_factory import MinerFactory # miner factory handles miners creation
|
||||
from pyasic import get_miner # handles miner creation
|
||||
|
||||
|
||||
async def get_miners(): # define async scan function to allow awaiting
|
||||
# get the miner with miner factory
|
||||
# miner factory is a singleton, and will always use the same object and cache
|
||||
# this means you can always call it as MinerFactory().get_miner()
|
||||
miner_1 = await MinerFactory().get_miner("192.168.1.75")
|
||||
miner_2 = await MinerFactory().get_miner("192.168.1.76")
|
||||
# get the miner with the miner factory
|
||||
# the miner factory is a singleton, and will always use the same object and cache
|
||||
# this means you can always call it as MinerFactory().get_miner(), or just get_miner()
|
||||
miner_1 = await get_miner("192.168.1.75")
|
||||
miner_2 = await get_miner("192.168.1.76")
|
||||
print(miner_1, miner_2)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# can also gather these, since they are async
|
||||
tasks = [get_miner("192.168.1.75"), get_miner("192.168.1.76")]
|
||||
miners = await asyncio.gather(*tasks)
|
||||
print(miners)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(get_miners()) # get the miners asynchronously with asyncio.run()
|
||||
```
|
||||
|
||||
@@ -64,7 +72,7 @@ if __name__ == "__main__":
|
||||
## 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.
|
||||
This function will return an instance of the dataclass [`MinerData`][pyasic.data.MinerData] with all data it can gather from the miner.
|
||||
Each piece of data in a [`MinerData`][pyasic.data.MinerData] instance can be referenced by getting it as an attribute, such as [`MinerData().hashrate`][pyasic.data.MinerData].
|
||||
```python
|
||||
import asyncio
|
||||
@@ -76,7 +84,7 @@ async def gather_miner_data():
|
||||
print(miner_data) # all data from the dataclass
|
||||
print(miner_data.hashrate) # hashrate of the miner in TH/s
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(gather_miner_data())
|
||||
```
|
||||
|
||||
@@ -89,13 +97,184 @@ from pyasic.network import MinerNetwork # miner network handles the scanning
|
||||
async def gather_miner_data(): # define async scan function to allow awaiting
|
||||
network = MinerNetwork("192.168.1.50")
|
||||
miners = await network.scan_network_for_miners()
|
||||
|
||||
|
||||
# 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__":
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(gather_miner_data())
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## Controlling miners via pyasic
|
||||
Every miner class in pyasic must implement all the control functions defined in [`BaseMiner`][pyasic.miners.BaseMiner].
|
||||
|
||||
These functions are
|
||||
[`check_light`](#check-light),
|
||||
[`fault_light_off`](#fault-light-off),
|
||||
[`fault_light_on`](#fault-light-on),
|
||||
[`get_config`](#get-config),
|
||||
[`get_data`](#get-data),
|
||||
[`get_errors`](#get-errors),
|
||||
[`get_hostname`](#get-hostname),
|
||||
[`get_model`](#get-model),
|
||||
[`reboot`](#reboot),
|
||||
[`restart_backend`](#restart-backend),
|
||||
[`stop_mining`](#stop-mining),
|
||||
[`resume_mining`](#resume-mining),
|
||||
[`send_config`](#send-config), and
|
||||
[`set_power_limit`](#set-power-limit).
|
||||
|
||||
<br>
|
||||
|
||||
### Check Light
|
||||
::: pyasic.miners.BaseMiner.check_light
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Fault Light Off
|
||||
::: pyasic.miners.BaseMiner.fault_light_off
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Fault Light On
|
||||
::: pyasic.miners.BaseMiner.fault_light_on
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Get Config
|
||||
::: pyasic.miners.BaseMiner.get_config
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Get Data
|
||||
::: pyasic.miners.BaseMiner.get_data
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Get Errors
|
||||
::: pyasic.miners.BaseMiner.get_errors
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Get Hostname
|
||||
::: pyasic.miners.BaseMiner.get_hostname
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Get Model
|
||||
::: pyasic.miners.BaseMiner.get_model
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Reboot
|
||||
::: pyasic.miners.BaseMiner.reboot
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Restart Backend
|
||||
::: pyasic.miners.BaseMiner.restart_backend
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Stop Mining
|
||||
::: pyasic.miners.BaseMiner.stop_mining
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Resume Mining
|
||||
::: pyasic.miners.BaseMiner.resume_mining
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Send Config
|
||||
::: pyasic.miners.BaseMiner.send_config
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
### Set Power Limit
|
||||
::: pyasic.miners.BaseMiner.set_power_limit
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
<br>
|
||||
|
||||
## [`MinerConfig`][pyasic.config.MinerConfig] and [`MinerData`][pyasic.data.MinerData]
|
||||
|
||||
Pyasic implements a few dataclasses as helpers to make data return types consistent across different miners and miner APIs. The different fields of these dataclasses can all be viewed with the classmethod `cls.fields()`.
|
||||
|
||||
<br>
|
||||
|
||||
### [`MinerData`][pyasic.data.MinerData]
|
||||
|
||||
[`MinerData`][pyasic.data.MinerData] is a return from the [`get_data()`](#get-data) function, and is used to have a consistent dataset across all returns.
|
||||
|
||||
You can call [`MinerData.asdict()`][pyasic.data.MinerData.asdict] to get the dataclass as a dictionary, and there are many other helper functions contained in the class to convert to different data formats.
|
||||
|
||||
[`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)
|
||||
```
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
### [`MinerConfig`][pyasic.config.MinerConfig]
|
||||
|
||||
[`MinerConfig`][pyasic.config.MinerConfig] is pyasic's way to represent a configuration file from a miner.
|
||||
It is the return from [`get_config()`](#get-config).
|
||||
|
||||
Each miner has a unique way to convert the [`MinerConfig`][pyasic.config.MinerConfig] to their specific type, there are helper functions in the class.
|
||||
In most cases these helper functions should not be used, as [`send_config()`](#send-config) takes a [`MinerConfig`][pyasic.config.MinerConfig] and will do the conversion to the right type for you.
|
||||
|
||||
10
docs/miners/antminer/X15.md
Normal file
10
docs/miners/antminer/X15.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# pyasic
|
||||
## X15 Models
|
||||
|
||||
## Z15
|
||||
::: pyasic.miners.antminer.cgminer.X15.Z15.CGMinerZ15
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
## X17 Models
|
||||
|
||||
## S17
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17
|
||||
handler: python
|
||||
options:
|
||||
@@ -10,31 +9,27 @@
|
||||
heading_level: 4
|
||||
|
||||
## S17+
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17_Plus.BMMinerS17Plus
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S17 Pro
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17_Pro.BMMinerS17Pro
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S17e
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17e.BMMinerS17e
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T17
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17
|
||||
handler: python
|
||||
options:
|
||||
@@ -42,18 +37,79 @@
|
||||
heading_level: 4
|
||||
|
||||
## T17+
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17_Plus.BMMinerT17Plus
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## T17e
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17e.BMMinerT17e
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S17 (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S17+ (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S17 Pro (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S17e (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T17 (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T17+ (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T17e (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S17+ (VNish)
|
||||
::: pyasic.miners.antminer.vnish.X17.S17.VNishS17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S17 Pro (VNish)
|
||||
::: pyasic.miners.antminer.vnish.X17.S17.VNishS17Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
@@ -2,51 +2,170 @@
|
||||
## X19 Models
|
||||
|
||||
## S19
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19L
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19L
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19_Pro.BMMinerS19Pro
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## S19a
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19a.BMMinerS19a
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## S19j
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19j
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19j.BMMinerS19j
|
||||
## S19j No PIC
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jNoPIC
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro+
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19ProPlus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19j_Pro.BMMinerS19jPro
|
||||
## S19 XP
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19XP
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19a
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19a
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19a Pro
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19aPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T19
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.T19.BMMinerT19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19j
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j No PIC (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jNoPIC
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T19 (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X19.T19.BOSMinerT19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 (VNish)
|
||||
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro (VNish)
|
||||
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j (VNish)
|
||||
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19j
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro (VNish)
|
||||
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19jPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19a (VNish)
|
||||
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19a
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19a Pro (VNish)
|
||||
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19aPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T19 (VNish)
|
||||
::: pyasic.miners.antminer.vnish.X19.T19.VNishT19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
31
docs/miners/antminer/X3.md
Normal file
31
docs/miners/antminer/X3.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# pyasic
|
||||
## X3 Models
|
||||
|
||||
## D3
|
||||
::: pyasic.miners.antminer.cgminer.X3.D3.CGMinerD3
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## HS3
|
||||
::: pyasic.miners.antminer.bmminer.X3.HS3.BMMinerHS3
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## L3+
|
||||
::: pyasic.miners.antminer.bmminer.X3.L3.BMMinerL3Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## L3+ (VNish)
|
||||
::: pyasic.miners.antminer.vnish.X3.L3.VnishL3Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
10
docs/miners/antminer/X5.md
Normal file
10
docs/miners/antminer/X5.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# pyasic
|
||||
## X5 Models
|
||||
|
||||
## DR5
|
||||
::: pyasic.miners.antminer.cgminer.X5.DR5.CGMinerDR5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
10
docs/miners/antminer/X7.md
Normal file
10
docs/miners/antminer/X7.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# pyasic
|
||||
## X7 Models
|
||||
|
||||
## L7
|
||||
::: pyasic.miners.antminer.bmminer.X7.L7.BMMinerL7
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
# pyasic
|
||||
## X9 Models
|
||||
|
||||
## S9
|
||||
## E9Pro
|
||||
::: pyasic.miners.antminer.bmminer.X9.E9.BMMinerE9Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S9
|
||||
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9
|
||||
handler: python
|
||||
options:
|
||||
@@ -10,17 +16,37 @@
|
||||
heading_level: 4
|
||||
|
||||
## S9i
|
||||
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9i
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X9.S9i.BMMinerS9i
|
||||
## S9j
|
||||
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9j
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T9
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X9.T9.BMMinerT9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S9 (BOS)
|
||||
::: pyasic.miners.antminer.bosminer.X9.S9.BOSMinerS9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T9 (Hiveon)
|
||||
::: pyasic.miners.antminer.hiveon.X9.T9.HiveonT9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
# pyasic
|
||||
## A10X Models
|
||||
|
||||
## A1026
|
||||
|
||||
## Avalon 1026
|
||||
::: pyasic.miners.avalonminer.cgminer.A10X.A1026.CGMinerAvalon1026
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A1047
|
||||
|
||||
## Avalon 1047
|
||||
::: pyasic.miners.avalonminer.cgminer.A10X.A1047.CGMinerAvalon1047
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A1066
|
||||
|
||||
## Avalon 1066
|
||||
::: pyasic.miners.avalonminer.cgminer.A10X.A1066.CGMinerAvalon1066
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
10
docs/miners/avalonminer/A11X.md
Normal file
10
docs/miners/avalonminer/A11X.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# pyasic
|
||||
## A11X Models
|
||||
|
||||
## Avalon 1166 Pro
|
||||
::: pyasic.miners.avalonminer.cgminer.A11X.A1166.CGMinerAvalon1166Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
10
docs/miners/avalonminer/A12X.md
Normal file
10
docs/miners/avalonminer/A12X.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# pyasic
|
||||
## A12X Models
|
||||
|
||||
## Avalon 1246
|
||||
::: pyasic.miners.avalonminer.cgminer.A12X.A1246.CGMinerAvalon1246
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
# pyasic
|
||||
## A7X Models
|
||||
|
||||
## A721
|
||||
|
||||
## Avalon 721
|
||||
::: pyasic.miners.avalonminer.cgminer.A7X.A721.CGMinerAvalon721
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A741
|
||||
|
||||
## Avalon 741
|
||||
::: pyasic.miners.avalonminer.cgminer.A7X.A741.CGMinerAvalon741
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A761
|
||||
|
||||
## Avalon 761
|
||||
::: pyasic.miners.avalonminer.cgminer.A7X.A761.CGMinerAvalon761
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
# pyasic
|
||||
## A8X Models
|
||||
|
||||
## A821
|
||||
|
||||
## Avalon 821
|
||||
::: pyasic.miners.avalonminer.cgminer.A8X.A821.CGMinerAvalon821
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A841
|
||||
|
||||
## Avalon 841
|
||||
::: pyasic.miners.avalonminer.cgminer.A8X.A841.CGMinerAvalon841
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## A851
|
||||
|
||||
## Avalon 851
|
||||
::: pyasic.miners.avalonminer.cgminer.A8X.A851.CGMinerAvalon851
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# pyasic
|
||||
## A9X Models
|
||||
|
||||
## A921
|
||||
|
||||
## Avalon 921
|
||||
::: pyasic.miners.avalonminer.cgminer.A9X.A921.CGMinerAvalon921
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
8
docs/miners/backends/bfgminer.md
Normal file
8
docs/miners/backends/bfgminer.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# pyasic
|
||||
## BFGMiner Backend
|
||||
|
||||
::: pyasic.miners.backends.bfgminer.BFGMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
@@ -1,7 +1,7 @@
|
||||
# pyasic
|
||||
## BMMiner Backend
|
||||
|
||||
::: pyasic.miners._backends.bmminer.BMMiner
|
||||
::: pyasic.miners.backends.bmminer.BMMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# pyasic
|
||||
## BOSMiner Backend
|
||||
|
||||
::: pyasic.miners._backends.bosminer.BOSMiner
|
||||
::: pyasic.miners.backends.bosminer.BOSMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# pyasic
|
||||
## BTMiner Backend
|
||||
|
||||
::: pyasic.miners._backends.btminer.BTMiner
|
||||
::: pyasic.miners.backends.btminer.BTMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# pyasic
|
||||
## CGMiner Backend
|
||||
|
||||
::: pyasic.miners._backends.cgminer.CGMiner
|
||||
::: pyasic.miners.backends.cgminer.CGMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# pyasic
|
||||
## Hiveon Backend
|
||||
|
||||
::: pyasic.miners._backends.hiveon.Hiveon
|
||||
::: pyasic.miners.backends.hiveon.Hiveon
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
10
docs/miners/base_miner.md
Normal file
10
docs/miners/base_miner.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# pyasic
|
||||
## Base Miner
|
||||
[`BaseMiner`][pyasic.miners.BaseMiner] is the basis for all miner classes, they all subclass (usually indirectly) from this class.
|
||||
|
||||
You may not instantiate this class on its own, only subclass from it. Trying to instantiate an instance of this class will raise `TypeError`.
|
||||
|
||||
::: pyasic.miners.BaseMiner
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
24
docs/miners/goldshell/X5.md
Normal file
24
docs/miners/goldshell/X5.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# pyasic
|
||||
## X5 Models
|
||||
|
||||
## CK5
|
||||
::: pyasic.miners.goldshell.bfgminer.X5.CK5.BFGMinerCK5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## HS5
|
||||
::: pyasic.miners.goldshell.bfgminer.X5.HS5.BFGMinerHS5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## KD5
|
||||
::: pyasic.miners.goldshell.bfgminer.X5.KD5.BFGMinerKD5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
10
docs/miners/goldshell/XMax.md
Normal file
10
docs/miners/goldshell/XMax.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# pyasic
|
||||
## XMax Models
|
||||
|
||||
## KD Max
|
||||
::: pyasic.miners.goldshell.bfgminer.XMax.KDMax.BFGMinerKDMax
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
10
docs/miners/innosilicon/A10X.md
Normal file
10
docs/miners/innosilicon/A10X.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# pyasic
|
||||
## A10X Models
|
||||
|
||||
## A10X
|
||||
::: pyasic.miners.innosilicon.cgminer.A10X.A10X.CGMinerA10X
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
10
docs/miners/innosilicon/T3X.md
Normal file
10
docs/miners/innosilicon/T3X.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# pyasic
|
||||
## T3X Models
|
||||
|
||||
## T3H+
|
||||
::: pyasic.miners.innosilicon.cgminer.T3X.T3H.CGMinerT3HPlus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
@@ -6,3 +6,23 @@
|
||||
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.miner_factory.AnyMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
[`AnyMiner`][pyasic.miners.miner_factory.AnyMiner] is a placeholder type variable used for typing returns of functions.
|
||||
A function returning [`AnyMiner`][pyasic.miners.miner_factory.AnyMiner] will always return a subclass of [`BaseMiner`][pyasic.miners.BaseMiner],
|
||||
and is used to specify a function returning some arbitrary type of miner class instance.
|
||||
|
||||
@@ -3,71 +3,440 @@
|
||||
|
||||
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.
|
||||
|
||||
## Miner List
|
||||
|
||||
##### pyasic currently supports the following miners and subtypes:
|
||||
* Braiins OS+ Devices:
|
||||
* All devices supported by BraiinsOS+ are supported here.
|
||||
* Stock Firmware Whatsminers:
|
||||
* M3X Series:
|
||||
* [M30S][pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30S]:
|
||||
* [VE10][pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE10]
|
||||
* [VG20][pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVG20]
|
||||
* [VE20][pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE20]
|
||||
* [V50][pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV50]
|
||||
* [M30S+][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlus]:
|
||||
* [VF20][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVF20]
|
||||
* [VE40][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE40]
|
||||
* [VG60][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG60]
|
||||
* [M30S++][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlus]:
|
||||
* [VG30][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVG30]
|
||||
* [VG40][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVG40]
|
||||
* [M31S][pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31S]
|
||||
* [M31S+][pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlus]:
|
||||
* [VE20][pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE20]
|
||||
* [M32S][pyasic.miners.whatsminer.btminer.M3X.M32S.BTMinerM32S]
|
||||
* M2X Series:
|
||||
* [M20S][pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20S]:
|
||||
* [V10][pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV10]
|
||||
* [V20][pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV20]
|
||||
* [M20S+][pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlus]
|
||||
* [M21][pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21]
|
||||
* [M21S][pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21S]:
|
||||
* [V20][pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV20]
|
||||
* [V60][pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV60]
|
||||
* [M21S+][pyasic.miners.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlus]
|
||||
* Stock Firmware Antminers:
|
||||
* X19 Series:
|
||||
* [S19][pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19]
|
||||
* [S19 Pro][pyasic.miners.antminer.bmminer.X19.S19_Pro.BMMinerS19Pro]
|
||||
* [S19a][pyasic.miners.antminer.bmminer.X19.S19a.BMMinerS19a]
|
||||
* [S19j][pyasic.miners.antminer.bmminer.X19.S19j.BMMinerS19j]
|
||||
* [S19j Pro][pyasic.miners.antminer.bmminer.X19.S19j_Pro.BMMinerS19jPro]
|
||||
* [T19][pyasic.miners.antminer.bmminer.X19.T19.BMMinerT19]
|
||||
* X17 Series:
|
||||
* [S17][pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17]
|
||||
* [S17+][pyasic.miners.antminer.bmminer.X17.S17_Plus.BMMinerS17Plus]
|
||||
* [S17 Pro][pyasic.miners.antminer.bmminer.X17.S17_Pro.BMMinerS17Pro]
|
||||
* [S17e][pyasic.miners.antminer.bmminer.X17.S17e.BMMinerS17e]
|
||||
* [T17][pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17]
|
||||
* [T17+][pyasic.miners.antminer.bmminer.X17.T17_Plus.BMMinerT17Plus]
|
||||
* [T17e][pyasic.miners.antminer.bmminer.X17.T17e.BMMinerT17e]
|
||||
* X9 Series:
|
||||
* [S9][pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9]
|
||||
* [S9i][pyasic.miners.antminer.bmminer.X9.S9i.BMMinerS9i]
|
||||
* [T9][pyasic.miners.antminer.bmminer.X9.T9.BMMinerT9]
|
||||
* Stock Firmware Avalonminers:
|
||||
* A7X Series:
|
||||
* [A721][pyasic.miners.avalonminer.cgminer.A7X.A721.CGMinerAvalon721]
|
||||
* [A741][pyasic.miners.avalonminer.cgminer.A7X.A741.CGMinerAvalon741]
|
||||
* [A761][pyasic.miners.avalonminer.cgminer.A7X.A761.CGMinerAvalon761]
|
||||
* A8X Series:
|
||||
* [A821][pyasic.miners.avalonminer.cgminer.A8X.A821.CGMinerAvalon821]
|
||||
* [A841][pyasic.miners.avalonminer.cgminer.A8X.A841.CGMinerAvalon841]
|
||||
* [A851][pyasic.miners.avalonminer.cgminer.A8X.A851.CGMinerAvalon851]
|
||||
* A9X Series:
|
||||
* [A921][pyasic.miners.avalonminer.cgminer.A9X.A921.CGMinerAvalon921]
|
||||
* A10X Series:
|
||||
* [A1026][pyasic.miners.avalonminer.cgminer.A10X.A1026.CGMinerAvalon1026]
|
||||
* [A1047][pyasic.miners.avalonminer.cgminer.A10X.A1047.CGMinerAvalon1047]
|
||||
* [A1066][pyasic.miners.avalonminer.cgminer.A10X.A1066.CGMinerAvalon1066]
|
||||
<style>
|
||||
details {
|
||||
margin:0px;
|
||||
padding-top:0px;
|
||||
padding-bottom:0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<details>
|
||||
<summary>Stock Firmware Antminers:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>X3 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X3#d3">D3</a></li>
|
||||
<li><a href="../antminer/X3#hs3">HS3</a></li>
|
||||
<li><a href="../antminer/X3#l3_1">L3+</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X5 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X5#dr5">DR5</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X7 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X7#l7">L7</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X9 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X9#e9pro">E9Pro</a></li>
|
||||
<li><a href="../antminer/X9#s9">S9</a></li>
|
||||
<li><a href="../antminer/X9#s9i">S9i</a></li>
|
||||
<li><a href="../antminer/X9#s9j">S9j</a></li>
|
||||
<li><a href="../antminer/X9#t9">T9</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X15 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X15#z15">Z15</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X17 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X17#s17">S17</a></li>
|
||||
<li><a href="../antminer/X17#s17_1">S17+</a></li>
|
||||
<li><a href="../antminer/X17#s17-pro">S17 Pro</a></li>
|
||||
<li><a href="../antminer/X17#s17e">S17e</a></li>
|
||||
<li><a href="../antminer/X17#t17">T17</a></li>
|
||||
<li><a href="../antminer/X17#t17_1">T17+</a></li>
|
||||
<li><a href="../antminer/X17#t17e">T17e</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X19 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X19#s19">S19</a></li>
|
||||
<li><a href="../antminer/X19#s19l">S19L</a></li>
|
||||
<li><a href="../antminer/X19#s19-pro">S19 Pro</a></li>
|
||||
<li><a href="../antminer/X19#s19j">S19j</a></li>
|
||||
<li><a href="../antminer/X19#s19j-no-pic">S19j No PIC</a></li>
|
||||
<li><a href="../antminer/X19#s19-pro_1">S19 Pro+</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro">S19j Pro</a></li>
|
||||
<li><a href="../antminer/X19#s19-xp">S19 XP</a></li>
|
||||
<li><a href="../antminer/X19#s19a">S19a</a></li>
|
||||
<li><a href="../antminer/X19#s19a-pro">S19a Pro</a></li>
|
||||
<li><a href="../antminer/X19#t19">T19</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Stock Firmware Whatsminers:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>M2X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M2X#m20-v10">M20 V10</a></li>
|
||||
<li><a href="../whatsminer/M2X#m20s-v10">M20S V10</a></li>
|
||||
<li><a href="../whatsminer/M2X#m20s-v20">M20S V20</a></li>
|
||||
<li><a href="../whatsminer/M2X#m20s-v30">M20S V30</a></li>
|
||||
<li><a href="../whatsminer/M2X#m20s_1-v30">M20S+ V30</a></li>
|
||||
<li><a href="../whatsminer/M2X#m21-v10">M21 V10</a></li>
|
||||
<li><a href="../whatsminer/M2X#m21s-v20">M21S V20</a></li>
|
||||
<li><a href="../whatsminer/M2X#m21s-v60">M21S V60</a></li>
|
||||
<li><a href="../whatsminer/M2X#m21s-v70">M21S V70</a></li>
|
||||
<li><a href="../whatsminer/M2X#m21s_1-v20">M21S+ V20</a></li>
|
||||
<li><a href="../whatsminer/M2X#m29-v10">M29 V10</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>M3X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M3X#m30-v10">M30 V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30-v20">M30 V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v10">M30S V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v20">M30S V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v30">M30S V30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v40">M30S V40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v50">M30S V50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v60">M30S V60</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v70">M30S V70</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-v80">M30S V80</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-ve10">M30S VE10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-ve20">M30S VE20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-ve30">M30S VE30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-ve40">M30S VE40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-ve50">M30S VE50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-ve60">M30S VE60</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-ve70">M30S VE70</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vf10">M30S VF10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vf20">M30S VF20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vf30">M30S VF30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vg10">M30S VG10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vg20">M30S VG20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vg30">M30S VG30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vg40">M30S VG40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vh10">M30S VH10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vh20">M30S VH20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vh30">M30S VH30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vh40">M30S VH40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vh50">M30S VH50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vh60">M30S VH60</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s-vi20">M30S VI20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v10">M30S+ V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v20">M30S+ V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v30">M30S+ V30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v40">M30S+ V40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v50">M30S+ V50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v60">M30S+ V60</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v70">M30S+ V70</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v80">M30S+ V80</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v90">M30S+ V90</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-v100">M30S+ V100</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-ve30">M30S+ VE30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-ve40">M30S+ VE40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-ve50">M30S+ VE50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-ve60">M30S+ VE60</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-ve70">M30S+ VE70</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-ve80">M30S+ VE80</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-ve90">M30S+ VE90</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-ve100">M30S+ VE100</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vf20">M30S+ VF20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vf30">M30S+ VF30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vg30">M30S+ VG30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vg40">M30S+ VG40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vg50">M30S+ VG50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vg60">M30S+ VG60</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vh10">M30S+ VH10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vh20">M30S+ VH20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vh30">M30S+ VH30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vh40">M30S+ VH40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vh50">M30S+ VH50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1-vh60">M30S+ VH60</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-v10">M30S++ V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-v20">M30S++ V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-ve30">M30S++ VE30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-ve40">M30S++ VE40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-ve50">M30S++ VE50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-vf40">M30S++ VF40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-vg30">M30S++ VG30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-vg40">M30S++ VG40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-vg50">M30S++ VG50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-vh10">M30S++ VH10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-vh20">M30S++ VH20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-vh30">M30S++ VH30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-vh40">M30S++ VH40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-vh50">M30S++ VH50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-vh60">M30S++ VH60</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-vh70">M30S++ VH70</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-vh80">M30S++ VH80</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-vh90">M30S++ VH90</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-vh100">M30S++ VH100</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-vj20">M30S++ VJ20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m30s_1_1-vj30">M30S++ VJ30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31-v10">M31 V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31-v20">M31 V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-v10">M31S V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-v20">M31S V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-v30">M31S V30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-v40">M31S V40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-v50">M31S V50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-v60">M31S V60</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-v70">M31S V70</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-v80">M31S V80</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-v90">M31S V90</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-ve10">M31S VE10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-ve20">M31S VE20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s-ve30">M31S VE30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31se-v10">M31SE V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31se-v20">M31SE V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31se-v30">M31SE V30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31h-v40">M31H V40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-v10">M31S+ V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-v20">M31S+ V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-v30">M31S+ V30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-v40">M31S+ V40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-v50">M31S+ V50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-v60">M31S+ V60</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-v80">M31S+ V80</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-v90">M31S+ V90</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-v100">M31S+ V100</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-ve10">M31S+ VE10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-ve20">M31S+ VE20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-ve30">M31S+ VE30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-ve40">M31S+ VE40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-ve50">M31S+ VE50</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-ve60">M31S+ VE60</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-ve80">M31S+ VE80</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-vf20">M31S+ VF20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-vf30">M31S+ VF30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-vg20">M31S+ VG20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m31s_1-vg30">M31S+ VG30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m32-v10">M32 V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m32-v20">M32 V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33-v10">M33 V10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33-v20">M33 V20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33-v30">M33 V30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33s-vg30">M33S VG30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33s_1-vh20">M33S+ VH20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33s_1-vh30">M33S+ VH30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33s_1_1-vh20">M33S++ VH20</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33s_1_1-vh30">M33S++ VH30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m33s_1_1-vg40">M33S++ VG40</a></li>
|
||||
<li><a href="../whatsminer/M3X#m34s_1-ve10">M34S+ VE10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m36s-ve10">M36S VE10</a></li>
|
||||
<li><a href="../whatsminer/M3X#m36s_1-vg30">M36S+ VG30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m36s_1_1-vh30">M36S++ VH30</a></li>
|
||||
<li><a href="../whatsminer/M3X#m39-v20">M39 V20</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>M5X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../whatsminer/M5X#m50-vg30">M50 VG30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh10">M50 VH10</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh20">M50 VH20</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh30">M50 VH30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh40">M50 VH40</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh50">M50 VH50</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh60">M50 VH60</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh70">M50 VH70</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vh80">M50 VH80</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vj10">M50 VJ10</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vj20">M50 VJ20</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50-vj30">M50 VJ30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s-vj10">M50S VJ10</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s-vj20">M50S VJ20</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s-vj30">M50S VJ30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s-vh10">M50S VH10</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s-vh20">M50S VH20</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s-vh30">M50S VH30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s-vh40">M50S VH40</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s-vh50">M50S VH50</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s_1-vh30">M50S+ VH30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s_1-vh40">M50S+ VH40</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s_1-vj30">M50S+ VJ30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s_1-vk20">M50S+ VK20</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s_1_1-vk10">M50S++ VK10</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s_1_1-vk20">M50S++ VK20</a></li>
|
||||
<li><a href="../whatsminer/M5X#m50s_1_1-vk30">M50S++ VK30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m53-vh30">M53 VH30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m53s-vh30">M53S VH30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m53s_1-vj30">M53S+ VJ30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m56-vh30">M56 VH30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m56s-vh30">M56S VH30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m56s_1-vj30">M56S+ VJ30</a></li>
|
||||
<li><a href="../whatsminer/M5X#m59-vh30">M59 VH30</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Stock Firmware Avalonminers:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>A7X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../avalonminer/A7X#avalon-721">Avalon 721</a></li>
|
||||
<li><a href="../avalonminer/A7X#avalon-741">Avalon 741</a></li>
|
||||
<li><a href="../avalonminer/A7X#avalon-761">Avalon 761</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>A8X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../avalonminer/A8X#avalon-821">Avalon 821</a></li>
|
||||
<li><a href="../avalonminer/A8X#avalon-841">Avalon 841</a></li>
|
||||
<li><a href="../avalonminer/A8X#avalon-851">Avalon 851</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>A9X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../avalonminer/A9X#avalon-921">Avalon 921</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>A10X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../avalonminer/A10X#avalon-1026">Avalon 1026</a></li>
|
||||
<li><a href="../avalonminer/A10X#avalon-1047">Avalon 1047</a></li>
|
||||
<li><a href="../avalonminer/A10X#avalon-1066">Avalon 1066</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>A11X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../avalonminer/A11X#avalon-1166-pro">Avalon 1166 Pro</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>A12X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../avalonminer/A12X#avalon-1246">Avalon 1246</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Stock Firmware Innosilicons:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>T3X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../innosilicon/T3X#t3h_1">T3H+</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>A10X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../innosilicon/A10X#a10x">A10X</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Stock Firmware Goldshells:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>X5 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../goldshell/X5#ck5">CK5</a></li>
|
||||
<li><a href="../goldshell/X5#hs5">HS5</a></li>
|
||||
<li><a href="../goldshell/X5#kd5">KD5</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>XMax Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../goldshell/XMax#kd-max">KD Max</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>BOS+ Firmware Miners:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>X9 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X9#s9-bos">S9 (BOS)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X17 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X17#s17-bos">S17 (BOS)</a></li>
|
||||
<li><a href="../antminer/X17#s17_1-bos">S17+ (BOS)</a></li>
|
||||
<li><a href="../antminer/X17#s17-pro-bos">S17 Pro (BOS)</a></li>
|
||||
<li><a href="../antminer/X17#s17e-bos">S17e (BOS)</a></li>
|
||||
<li><a href="../antminer/X17#t17-bos">T17 (BOS)</a></li>
|
||||
<li><a href="../antminer/X17#t17_1-bos">T17+ (BOS)</a></li>
|
||||
<li><a href="../antminer/X17#t17e-bos">T17e (BOS)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X19 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X19#s19-bos">S19 (BOS)</a></li>
|
||||
<li><a href="../antminer/X19#s19-pro-bos">S19 Pro (BOS)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-bos">S19j (BOS)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-no-pic-bos">S19j No PIC (BOS)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro-bos">S19j Pro (BOS)</a></li>
|
||||
<li><a href="../antminer/X19#t19-bos">T19 (BOS)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Vnish Firmware Miners:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>X3 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X3#l3_1-vnish">L3+ (VNish)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X17 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X17#s17_1-vnish">S17+ (VNish)</a></li>
|
||||
<li><a href="../antminer/X17#s17-pro-vnish">S17 Pro (VNish)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X19 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X19#s19-vnish">S19 (VNish)</a></li>
|
||||
<li><a href="../antminer/X19#s19-pro-vnish">S19 Pro (VNish)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-vnish">S19j (VNish)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
|
||||
<li><a href="../antminer/X19#s19a-vnish">S19a (VNish)</a></li>
|
||||
<li><a href="../antminer/X19#s19a-pro-vnish">S19a Pro (VNish)</a></li>
|
||||
<li><a href="../antminer/X19#t19-vnish">T19 (VNish)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>HiveOS Firmware Miners:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>X9 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X9#t9-hiveon">T9 (Hiveon)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
@@ -1,75 +1,80 @@
|
||||
# pyasic
|
||||
## M2X Models
|
||||
|
||||
## M20S
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20S
|
||||
## M20 V10
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M20SV10
|
||||
|
||||
## M20S V10
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M20SV20
|
||||
|
||||
## M20S V20
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M20S+
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlus
|
||||
## M20S V30
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M21
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21
|
||||
## M20S+ V30
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlusV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## M21S
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21S
|
||||
## M21 V10
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M21SV20
|
||||
|
||||
## M21S V20
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M21SV60
|
||||
|
||||
## M21S V60
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M21S+
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlus
|
||||
## M21S V70
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV70
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M21S+ V20
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlusV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M29 V10
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M29.BTMinerM29V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
@@ -1,131 +1,955 @@
|
||||
# pyasic
|
||||
## M3X Models
|
||||
|
||||
## M30S
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30S
|
||||
## M30 V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30.BTMinerM30V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30SVE10
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE10
|
||||
## M30 V20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30.BTMinerM30V20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30SVG20
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVG20
|
||||
## M30S V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30SVE20
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE20
|
||||
## M30S V20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30SV50
|
||||
## M30S V30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S V40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S V50
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlus
|
||||
## M30S V60
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+VF20
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVF20
|
||||
## M30S V70
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV70
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+VE40
|
||||
## M30S V80
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV80
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VE10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VE20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VE30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VE40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VE50
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VE60
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VE70
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE70
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VF10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVF10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VF20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVF20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VF30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVF30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VG10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVG10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VG20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVG20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VG30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVG30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VG40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVG40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VH10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVH10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VH20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVH20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VH30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VH40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVH40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VH50
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVH50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VH60
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVH60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S VI20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVI20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ V20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ V30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ V40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ V50
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ V60
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ V70
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV70
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ V80
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV80
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ V90
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV90
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ V100
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV100
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VE30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VE40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+VG60
|
||||
## M30S+ VE50
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VE60
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VE70
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE70
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VE80
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE80
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VE90
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE90
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VE100
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE100
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VF20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVF20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VF30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVF30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VG30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VG40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VG50
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VG60
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlus
|
||||
## M30S+ VH10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVH10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++VG30
|
||||
## M30S+ VH20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVH20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VH30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VH40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVH40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VH50
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVH50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+ VH60
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVH60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++ V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++ V20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++ VE30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVE30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++ VE40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVE40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++ VE50
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVE50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++ VF40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVF40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++ VG30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVG30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S+VG40
|
||||
|
||||
## M30S++ VG40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVG40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## M31S
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31S
|
||||
## M30S++ VG50
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVG50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlus
|
||||
## M30S++ VH10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+VE20
|
||||
## M30S++ VH20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++ VH30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++ VH40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++ VH50
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++ VH60
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++ VH70
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH70
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++ VH80
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH80
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++ VH90
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH90
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++ VH100
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH100
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++ VJ20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVJ20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M30S++ VJ30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVJ30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31 V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31.BTMinerM31V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31 V20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31.BTMinerM31V20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S V20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S V30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S V40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S V50
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S V60
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S V70
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV70
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S V80
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV80
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S V90
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV90
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S VE10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SVE10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S VE20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SVE20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S VE30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SVE30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31SE V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31SE.BTMinerM31SEV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31SE V20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31SE.BTMinerM31SEV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31SE V30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31SE.BTMinerM31SEV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31H V40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31H.BTMinerM31HV40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ V20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ V30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ V40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ V50
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ V60
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ V80
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV80
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ V90
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV90
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ V100
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV100
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ VE10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ VE20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M32S
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M32S.BTMinerM32S
|
||||
## M31S+ VE30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ VE40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ VE50
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ VE60
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ VE80
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE80
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ VF20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVF20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ VF30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVF30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ VG20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVG20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M31S+ VG30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVG30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M32 V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M32.BTMinerM32V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M32 V20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M32.BTMinerM32V20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M33 V10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M33.BTMinerM33V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M33 V20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M33.BTMinerM33V20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M33 V30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M33.BTMinerM33V30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M33S VG30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M33S.BTMinerM33SVG30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M33S+ VH20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M33S_Plus.BTMinerM33SPlusVH20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M33S+ VH30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M33S_Plus.BTMinerM33SPlusVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M33S++ VH20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M33S_Plus_Plus.BTMinerM33SPlusPlusVH20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M33S++ VH30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M33S_Plus_Plus.BTMinerM33SPlusPlusVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M33S++ VG40
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M33S_Plus_Plus.BTMinerM33SPlusPlusVG40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M34S+ VE10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M34S_Plus.BTMinerM34SPlusVE10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M36S VE10
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M36S.BTMinerM36SVE10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M36S+ VG30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M36S_Plus.BTMinerM36SPlusVG30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M36S++ VH30
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M36S_Plus_Plus.BTMinerM36SPlusPlusVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M39 V20
|
||||
::: pyasic.miners.whatsminer.btminer.M3X.M39.BTMinerM39V20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
241
docs/miners/whatsminer/M5X.md
Normal file
241
docs/miners/whatsminer/M5X.md
Normal file
@@ -0,0 +1,241 @@
|
||||
# pyasic
|
||||
## M5X Models
|
||||
|
||||
## M50 VG30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VG30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50 VH10
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50 VH20
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50 VH30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50 VH40
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50 VH50
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50 VH60
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50 VH70
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH70
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50 VH80
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH80
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50 VJ10
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50 VJ20
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50 VJ30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S VJ10
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S VJ20
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S VJ30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S VH10
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S VH20
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S VH30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S VH40
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S VH50
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S+ VH30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S+ VH40
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S+ VJ30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVJ30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S+ VK20
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVK20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S++ VK10
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S++ VK20
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M50S++ VK30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M53 VH30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M53S VH30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M53S+ VJ30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVJ30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M56 VH30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M56.BTMinerM56VH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M56S VH30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M56S.BTMinerM56SVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M56S+ VJ30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVJ30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## M59 VH30
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M59.BTMinerM59VH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# pyasic
|
||||
## Miner Network Range
|
||||
|
||||
`MinerNetworkRange` is a class used by [`MinerNetwork`][pyasic.network.MinerNetwork] to handle any constructor stings.
|
||||
[`MinerNetworkRange`][pyasic.network.net_range.MinerNetworkRange] is a class used by [`MinerNetwork`][pyasic.network.MinerNetwork] to handle any constructor stings.
|
||||
The goal is to emulate what is produced by `ipaddress.ip_network` by allowing [`MinerNetwork`][pyasic.network.MinerNetwork] to get a list of hosts.
|
||||
This allows this class to be the [`MinerNetwork.network`][pyasic.network.MinerNetwork] and hence be used for scanning.
|
||||
|
||||
|
||||
84
mkdocs.yml
84
mkdocs.yml
@@ -1,43 +1,55 @@
|
||||
site_name: pyasic
|
||||
repo_url: https://github.com/UpstreamData/pyasic
|
||||
nav:
|
||||
- Introduction: "index.md"
|
||||
- Miners:
|
||||
- Supported Miners: "miners/supported_types.md"
|
||||
- Miner Factory: "miners/miner_factory.md"
|
||||
- Backends:
|
||||
- BMMiner: "miners/backends/bmminer.md"
|
||||
- BOSMiner: "miners/backends/bosminer.md"
|
||||
- BTMiner: "miners/backends/btminer.md"
|
||||
- CGMiner: "miners/backends/cgminer.md"
|
||||
- Hiveon: "miners/backends/hiveon.md"
|
||||
- Introduction: "index.md"
|
||||
- Miners:
|
||||
- Supported Miners: "miners/supported_types.md"
|
||||
- Miner Factory: "miners/miner_factory.md"
|
||||
- Network:
|
||||
- Miner Network: "network/miner_network.md"
|
||||
- Miner Network Range: "network/miner_network_range.md"
|
||||
- Dataclasses:
|
||||
- Miner Data: "data/miner_data.md"
|
||||
- Error Codes: "data/error_codes.md"
|
||||
- Miner Config: "config/miner_config.md"
|
||||
- Advanced:
|
||||
- Miner APIs:
|
||||
- Intro: "API/api.md"
|
||||
- BFGMiner: "API/bfgminer.md"
|
||||
- BMMiner: "API/bmminer.md"
|
||||
- BOSMiner: "API/bosminer.md"
|
||||
- BTMiner: "API/btminer.md"
|
||||
- CGMiner: "API/cgminer.md"
|
||||
- Unknown: "API/unknown.md"
|
||||
- Backends:
|
||||
- BMMiner: "miners/backends/bmminer.md"
|
||||
- BOSMiner: "miners/backends/bosminer.md"
|
||||
- BFGMiner: "miners/backends/bfgminer.md"
|
||||
- BTMiner: "miners/backends/btminer.md"
|
||||
- CGMiner: "miners/backends/cgminer.md"
|
||||
- Hiveon: "miners/backends/hiveon.md"
|
||||
- Classes:
|
||||
- Antminer X3: "miners/antminer/X3.md"
|
||||
- Antminer X5: "miners/antminer/X5.md"
|
||||
- Antminer X7: "miners/antminer/X7.md"
|
||||
- Antminer X9: "miners/antminer/X9.md"
|
||||
- Antminer X15: "miners/antminer/X15.md"
|
||||
- Antminer X17: "miners/antminer/X17.md"
|
||||
- Antminer X19: "miners/antminer/X19.md"
|
||||
- Avalon 7X: "miners/avalonminer/A7X.md"
|
||||
- Avalon 8X: "miners/avalonminer/A8X.md"
|
||||
- Avalon 9X: "miners/avalonminer/A9X.md"
|
||||
- Avalon 10X: "miners/avalonminer/A10X.md"
|
||||
- Whatsminer M2X: "miners/whatsminer/M2X.md"
|
||||
- Whatsminer M3X: "miners/whatsminer/M3X.md"
|
||||
- Whatsminer M5X: "miners/whatsminer/M5X.md"
|
||||
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
||||
- Innosilicon A10X: "miners/innosilicon/A10X.md"
|
||||
- Goldshell CKX: "miners/goldshell/CKX.md"
|
||||
- Goldshell HSX: "miners/goldshell/HSX.md"
|
||||
- Goldshell KDX: "miners/goldshell/KDX.md"
|
||||
- Base Miner: "miners/base_miner.md"
|
||||
|
||||
- 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"
|
||||
|
||||
plugins:
|
||||
- mkdocstrings
|
||||
|
||||
396
poetry.lock
generated
396
poetry.lock
generated
@@ -1,396 +0,0 @@
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "3.6.1"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6.2"
|
||||
|
||||
[package.dependencies]
|
||||
idna = ">=2.8"
|
||||
sniffio = ">=1.1"
|
||||
|
||||
[package.extras]
|
||||
doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"]
|
||||
test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"]
|
||||
trio = ["trio (>=0.16)"]
|
||||
|
||||
[[package]]
|
||||
name = "asyncssh"
|
||||
version = "2.11.0"
|
||||
description = "AsyncSSH: Asynchronous SSHv2 client and server library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">= 3.6"
|
||||
|
||||
[package.dependencies]
|
||||
cryptography = ">=3.1"
|
||||
typing-extensions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
bcrypt = ["bcrypt (>=3.1.3)"]
|
||||
fido2 = ["fido2 (>=0.9.2)"]
|
||||
gssapi = ["gssapi (>=1.2.0)"]
|
||||
libnacl = ["libnacl (>=1.4.2)"]
|
||||
pkcs11 = ["python-pkcs11 (>=0.7.0)"]
|
||||
pyopenssl = ["pyOpenSSL (>=17.0.0)"]
|
||||
pywin32 = ["pywin32 (>=227)"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2022.6.15"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "1.15.1"
|
||||
description = "Foreign Function Interface for Python calling C code."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
pycparser = "*"
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "37.0.4"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
cffi = ">=1.12"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
|
||||
docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
|
||||
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
|
||||
sdist = ["setuptools_rust (>=0.11.4)"]
|
||||
ssh = ["bcrypt (>=3.1.5)"]
|
||||
test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.12.0"
|
||||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "0.15.0"
|
||||
description = "A minimal low-level HTTP client."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
anyio = ">=3.0.0,<4.0.0"
|
||||
certifi = "*"
|
||||
h11 = ">=0.11,<0.13"
|
||||
sniffio = ">=1.0.0,<2.0.0"
|
||||
|
||||
[package.extras]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (>=1.0.0,<2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.23.0"
|
||||
description = "The next generation HTTP client."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = "*"
|
||||
httpcore = ">=0.15.0,<0.16.0"
|
||||
rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
|
||||
sniffio = "*"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotlicffi", "brotli"]
|
||||
cli = ["click (>=8.0.0,<9.0.0)", "rich (>=10,<13)", "pygments (>=2.0.0,<3.0.0)"]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (>=1.0.0,<2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.3"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "passlib"
|
||||
version = "1.7.4"
|
||||
description = "comprehensive password hashing framework supporting over 30 schemes"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.extras]
|
||||
argon2 = ["argon2-cffi (>=18.2.0)"]
|
||||
bcrypt = ["bcrypt (>=3.1.0)"]
|
||||
build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.1)"]
|
||||
totp = ["cryptography"]
|
||||
|
||||
[[package]]
|
||||
name = "pyaml"
|
||||
version = "21.10.1"
|
||||
description = "PyYAML-based module to produce pretty and readable YAML-serialized data"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
PyYAML = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.21"
|
||||
description = "C parser in Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0"
|
||||
description = "YAML parser and emitter for Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "rfc3986"
|
||||
version = "1.5.0"
|
||||
description = "Validating URI References per RFC 3986"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
idna = {version = "*", optional = true, markers = "extra == \"idna2008\""}
|
||||
|
||||
[package.extras]
|
||||
idna2008 = ["idna"]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.2.0"
|
||||
description = "Sniff out which async library your code is running under"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.3.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "8d93eafd928d7fed4b0a00d13e46982c2d4310c37acb2faec7e7a477b3f35e9c"
|
||||
|
||||
[metadata.files]
|
||||
anyio = [
|
||||
{file = "anyio-3.6.1-py3-none-any.whl", hash = "sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be"},
|
||||
{file = "anyio-3.6.1.tar.gz", hash = "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b"},
|
||||
]
|
||||
asyncssh = [
|
||||
{file = "asyncssh-2.11.0-py3-none-any.whl", hash = "sha256:7302348cbd54c58d3259da17f13e77912de1b005e366b15c8b183d948c8a91a8"},
|
||||
{file = "asyncssh-2.11.0.tar.gz", hash = "sha256:59c36ce77ba9dda8dd57ad875776e7105ddb1fa851bc039bb3aeadeac4f67b56"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
|
||||
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
|
||||
]
|
||||
cffi = [
|
||||
{file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
|
||||
{file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
|
||||
{file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
|
||||
{file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"},
|
||||
{file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
|
||||
{file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"},
|
||||
{file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
|
||||
{file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
|
||||
]
|
||||
cryptography = [
|
||||
{file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"},
|
||||
{file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"},
|
||||
{file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"},
|
||||
{file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"},
|
||||
{file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"},
|
||||
{file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"},
|
||||
{file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"},
|
||||
{file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"},
|
||||
{file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"},
|
||||
{file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"},
|
||||
{file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"},
|
||||
{file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"},
|
||||
]
|
||||
h11 = [
|
||||
{file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
|
||||
{file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"},
|
||||
]
|
||||
httpcore = [
|
||||
{file = "httpcore-0.15.0-py3-none-any.whl", hash = "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6"},
|
||||
{file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"},
|
||||
]
|
||||
httpx = [
|
||||
{file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"},
|
||||
{file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
|
||||
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
|
||||
]
|
||||
passlib = [
|
||||
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
|
||||
{file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
|
||||
]
|
||||
pyaml = [
|
||||
{file = "pyaml-21.10.1-py2.py3-none-any.whl", hash = "sha256:19985ed303c3a985de4cf8fd329b6d0a5a5b5c9035ea240eccc709ebacbaf4a0"},
|
||||
{file = "pyaml-21.10.1.tar.gz", hash = "sha256:c6519fee13bf06e3bb3f20cacdea8eba9140385a7c2546df5dbae4887f768383"},
|
||||
]
|
||||
pycparser = [
|
||||
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
|
||||
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
|
||||
]
|
||||
pyyaml = [
|
||||
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
|
||||
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
|
||||
]
|
||||
rfc3986 = [
|
||||
{file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
|
||||
{file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
|
||||
]
|
||||
sniffio = [
|
||||
{file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
|
||||
{file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
|
||||
]
|
||||
toml = [
|
||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
|
||||
{file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
|
||||
]
|
||||
@@ -1,37 +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 asyncio
|
||||
import json
|
||||
import ipaddress
|
||||
import warnings
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import warnings
|
||||
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."
|
||||
from pyasic.errors import APIError, APIWarning
|
||||
|
||||
|
||||
class BaseMinerAPI:
|
||||
@@ -41,16 +32,114 @@ class BaseMinerAPI:
|
||||
# ip address of the miner
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls is BaseMinerAPI:
|
||||
raise TypeError(f"Only children of '{cls.__name__}' may be instantiated")
|
||||
return object.__new__(cls)
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}: {str(self.ip)}"
|
||||
|
||||
async def send_command(
|
||||
self,
|
||||
command: Union[str, bytes],
|
||||
parameters: Union[str, int, bool] = None,
|
||||
ignore_errors: bool = False,
|
||||
allow_warning: bool = True,
|
||||
**kwargs,
|
||||
) -> dict:
|
||||
"""Send an API command to the miner and return the result.
|
||||
|
||||
Parameters:
|
||||
command: The command to sent to the miner.
|
||||
parameters: Any additional parameters to be sent with the command.
|
||||
ignore_errors: Whether to raise APIError when the command returns an error.
|
||||
allow_warning: Whether to warn if the command fails.
|
||||
|
||||
Returns:
|
||||
The return data from the API command parsed from JSON into a dict.
|
||||
"""
|
||||
logging.debug(
|
||||
f"{self} - (Send Privileged Command) - {command} "
|
||||
+ f"with args {parameters}"
|
||||
if parameters
|
||||
else ""
|
||||
)
|
||||
# create the command
|
||||
cmd = {"command": command, **kwargs}
|
||||
if parameters:
|
||||
cmd["parameter"] = parameters
|
||||
|
||||
# send the command
|
||||
data = await self._send_bytes(json.dumps(cmd).encode("utf-8"))
|
||||
|
||||
data = self._load_api_data(data)
|
||||
|
||||
# check for if the user wants to allow errors to return
|
||||
if not ignore_errors:
|
||||
# validate the command succeeded
|
||||
validation = self._validate_command_output(data)
|
||||
if not validation[0]:
|
||||
if allow_warning:
|
||||
logging.warning(
|
||||
f"{self.ip}: API Command Error: {command}: {validation[1]}"
|
||||
)
|
||||
raise APIError(validation[1])
|
||||
|
||||
logging.debug(f"{self} - (Send Command) - Received data.")
|
||||
return data
|
||||
|
||||
# Privileged command handler, only used by whatsminers, defined here for consistency.
|
||||
async def send_privileged_command(self, *args, **kwargs) -> dict:
|
||||
return await self.send_command(*args, **kwargs)
|
||||
|
||||
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||
"""Creates and sends multiple commands as one command to the miner.
|
||||
|
||||
Parameters:
|
||||
*commands: The commands to send as a multicommand to the miner.
|
||||
allow_warning: A boolean to supress APIWarnings.
|
||||
|
||||
"""
|
||||
while True:
|
||||
# make sure we can actually run each command, otherwise they will fail
|
||||
commands = self._check_commands(*commands)
|
||||
# standard multicommand format is "command1+command2"
|
||||
# standard format doesn't work for X19
|
||||
command = "+".join(commands)
|
||||
try:
|
||||
data = await self.send_command(command, allow_warning=allow_warning)
|
||||
except APIError as e:
|
||||
# try to identify the error
|
||||
if ":" in e.message:
|
||||
err_command = e.message.split(":")[0]
|
||||
if err_command in commands:
|
||||
commands.remove(err_command)
|
||||
continue
|
||||
return {command: [{}] for command in commands}
|
||||
logging.debug(f"{self} - (Multicommand) - Received data")
|
||||
data["multicommand"] = True
|
||||
return data
|
||||
|
||||
@property
|
||||
def commands(self) -> list:
|
||||
return self.get_commands()
|
||||
|
||||
def get_commands(self) -> list:
|
||||
"""Get a list of command accessible to a specific type of API on the miner."""
|
||||
"""Get a list of command accessible to a specific type of API on the miner.
|
||||
|
||||
Returns:
|
||||
A list of all API commands that the miner supports.
|
||||
"""
|
||||
return [
|
||||
func
|
||||
for func in
|
||||
# each function in self
|
||||
dir(self)
|
||||
if not func == "commands"
|
||||
if callable(getattr(self, func)) and
|
||||
# no __ methods
|
||||
not func.startswith("__") and
|
||||
# no __ or _ methods
|
||||
not func.startswith("__") and not func.startswith("_") and
|
||||
# remove all functions that are in this base class
|
||||
func
|
||||
not in [
|
||||
@@ -60,102 +149,76 @@ class BaseMinerAPI:
|
||||
]
|
||||
]
|
||||
|
||||
async def multicommand(
|
||||
self, *commands: str, ignore_x19_error: bool = False
|
||||
) -> dict:
|
||||
"""Creates and sends multiple commands as one command to the miner."""
|
||||
logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}")
|
||||
# split the commands into a proper list
|
||||
user_commands = [*commands]
|
||||
allowed_commands = self.get_commands()
|
||||
# make sure we can actually run the command, otherwise it will fail
|
||||
commands = [command for command in user_commands if command in allowed_commands]
|
||||
for item in list(set(user_commands) - set(commands)):
|
||||
warnings.warn(
|
||||
f"""Removing incorrect command: {item}
|
||||
If you are sure you want to use this command please use API.send_command("{item}", ignore_errors=True) instead.""",
|
||||
APIWarning,
|
||||
)
|
||||
# standard multicommand format is "command1+command2"
|
||||
# doesnt work for S19 which is dealt with in the send command function
|
||||
command = "+".join(commands)
|
||||
data = None
|
||||
try:
|
||||
data = await self.send_command(command, x19_command=ignore_x19_error)
|
||||
except APIError:
|
||||
try:
|
||||
data = {}
|
||||
# S19 handler, try again
|
||||
for cmd in command.split("+"):
|
||||
data[cmd] = []
|
||||
data[cmd].append(await self.send_command(cmd))
|
||||
except APIError as e:
|
||||
raise APIError(e)
|
||||
except Exception as e:
|
||||
logging.warning(f"{self.ip}: API Multicommand Error: {e}")
|
||||
if data:
|
||||
logging.debug(f"{self.ip}: Received multicommand data.")
|
||||
return data
|
||||
def _check_commands(self, *commands):
|
||||
allowed_commands = self.commands
|
||||
return_commands = []
|
||||
|
||||
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."""
|
||||
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 _send_bytes(self, data: bytes, timeout: int = 100) -> bytes:
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Sending")
|
||||
try:
|
||||
# get reader and writer streams
|
||||
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
|
||||
# handle OSError 121
|
||||
except OSError as e:
|
||||
if e.winerror == "121":
|
||||
logging.warning("Semaphore Timeout has Expired.")
|
||||
return {}
|
||||
|
||||
# create the command
|
||||
cmd = {"command": command}
|
||||
if parameters is not None:
|
||||
cmd["parameter"] = parameters
|
||||
if e.errno == 121:
|
||||
logging.warning(
|
||||
f"{self} - ([Hidden] Send Bytes) - Semaphore timeout expired."
|
||||
)
|
||||
return b"{}"
|
||||
|
||||
# send the command
|
||||
writer.write(json.dumps(cmd).encode("utf-8"))
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Writing")
|
||||
writer.write(data)
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Draining")
|
||||
await writer.drain()
|
||||
|
||||
# instantiate data
|
||||
data = b""
|
||||
try:
|
||||
ret_data = await asyncio.wait_for(reader.read(4096), timeout=timeout)
|
||||
except ConnectionAbortedError:
|
||||
return b"{}"
|
||||
try:
|
||||
# Fix for stupid whatsminer bug, reboot/restart seem to not load properly in the loop
|
||||
# have to receive, save the data, check if there is more data by reading with a short timeout
|
||||
# append that data if there is more, and then onto the main loop.
|
||||
ret_data += await asyncio.wait_for(reader.read(1), timeout=1)
|
||||
except asyncio.TimeoutError:
|
||||
return ret_data
|
||||
|
||||
# loop to receive all the data
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Receiving")
|
||||
try:
|
||||
while True:
|
||||
d = await reader.read(4096)
|
||||
if not d:
|
||||
break
|
||||
data += d
|
||||
try:
|
||||
d = await asyncio.wait_for(reader.read(4096), timeout=timeout)
|
||||
if not d:
|
||||
break
|
||||
ret_data += d
|
||||
except (asyncio.CancelledError, asyncio.TimeoutError) as e:
|
||||
raise e
|
||||
except (asyncio.CancelledError, asyncio.TimeoutError) as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
logging.warning(f"{self.ip}: API Command Error: {e}")
|
||||
|
||||
data = self.load_api_data(data)
|
||||
logging.warning(f"{self} - ([Hidden] Send Bytes) - API Command Error {e}")
|
||||
|
||||
# close the connection
|
||||
logging.debug(f"{self} - ([Hidden] Send Bytes) - Closing")
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
|
||||
# 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
|
||||
return ret_data
|
||||
|
||||
@staticmethod
|
||||
def validate_command_output(data: dict) -> tuple:
|
||||
"""Check if the returned command output is correctly formatted."""
|
||||
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():
|
||||
@@ -182,34 +245,44 @@ If you are sure you want to use this command please use API.send_command("{item}
|
||||
return True, None
|
||||
|
||||
@staticmethod
|
||||
def load_api_data(data: bytes) -> dict:
|
||||
"""Convert API data from JSON to dict"""
|
||||
str_data = None
|
||||
def _load_api_data(data: bytes) -> dict:
|
||||
# some json from the API returns with a null byte (\x00) on the end
|
||||
if data.endswith(b"\x00"):
|
||||
# handle the null byte
|
||||
str_data = data.decode("utf-8")[:-1]
|
||||
else:
|
||||
# no null byte
|
||||
str_data = data.decode("utf-8")
|
||||
# fix an error with a btminer return having an extra comma that breaks json.loads()
|
||||
str_data = str_data.replace(",}", "}")
|
||||
# fix an error with a btminer return having a newline that breaks json.loads()
|
||||
str_data = str_data.replace("\n", "")
|
||||
# fix an error with a bmminer return not having a specific comma that breaks json.loads()
|
||||
str_data = str_data.replace("}{", "},{")
|
||||
# fix an error with a bmminer return having a specific comma that breaks json.loads()
|
||||
str_data = str_data.replace("[,{", "[{")
|
||||
# fix an error with a btminer return having a missing comma. (2023-01-06 version)
|
||||
str_data = str_data.replace('""temp0', '","temp0')
|
||||
# fix an error with Avalonminers returning inf and nan
|
||||
str_data = str_data.replace("info", "1nfo")
|
||||
str_data = str_data.replace("inf", "0")
|
||||
str_data = str_data.replace("1nfo", "info")
|
||||
str_data = str_data.replace("nan", "0")
|
||||
# fix whatever this garbage from avalonminers is `,"id":1}`
|
||||
if str_data.startswith(","):
|
||||
str_data = f"{{{str_data[1:]}"
|
||||
# try to fix an error with overflowing the receive buffer
|
||||
# this can happen in cases such as bugged btminers returning arbitrary length error info with 100s of errors.
|
||||
if not str_data.endswith("}"):
|
||||
str_data = ",".join(str_data.split(",")[:-1]) + "}"
|
||||
|
||||
# fix a really nasty bug with whatsminer API v2.0.4 where they return a list structured like a dict
|
||||
if re.search(r"\"error_code\":\[\".+\"\]", str_data):
|
||||
str_data = str_data.replace("[", "{").replace("]", "}")
|
||||
|
||||
# parse the json
|
||||
try:
|
||||
# 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
|
||||
|
||||
674
pyasic/API/bfgminer.py
Normal file
674
pyasic/API/bfgminer.py
Normal file
@@ -0,0 +1,674 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
|
||||
from pyasic.API import APIError, BaseMinerAPI
|
||||
|
||||
|
||||
class BFGMinerAPI(BaseMinerAPI):
|
||||
"""An abstraction of the BFGMiner API.
|
||||
|
||||
Each method corresponds to an API command in BFGMiner.
|
||||
|
||||
[BFGMiner API documentation](https://github.com/luke-jr/bfgminer/blob/bfgminer/README.RPC)
|
||||
|
||||
This class abstracts use of the BFGMiner API, as well as the
|
||||
methods for sending commands to it. The self.send_command()
|
||||
function handles sending a command to the miner asynchronously, and
|
||||
as such is the base for many of the functions in this class, which
|
||||
rely on it to send the command for them.
|
||||
|
||||
Parameters:
|
||||
ip: The IP of the miner to reference the API on.
|
||||
port: The port to reference the API on. Default is 4028.
|
||||
"""
|
||||
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0", port: int = 4028):
|
||||
super().__init__(ip, port)
|
||||
self.api_ver = api_ver
|
||||
|
||||
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||
# make sure we can actually run each command, otherwise they will fail
|
||||
commands = self._check_commands(*commands)
|
||||
# standard multicommand format is "command1+command2"
|
||||
# doesn't work for S19 which uses the backup _x19_multicommand
|
||||
command = "+".join(commands)
|
||||
try:
|
||||
data = await self.send_command(command, allow_warning=allow_warning)
|
||||
except APIError:
|
||||
logging.debug(f"{self} - (Multicommand) - Handling X19 multicommand.")
|
||||
data = await self._x19_multicommand(*command.split("+"))
|
||||
data["multicommand"] = True
|
||||
return data
|
||||
|
||||
async def _x19_multicommand(self, *commands) -> dict:
|
||||
data = None
|
||||
try:
|
||||
data = {}
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
data[cmd] = []
|
||||
data[cmd].append(await self.send_command(cmd, allow_warning=True))
|
||||
except APIError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logging.warning(
|
||||
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"
|
||||
)
|
||||
return data
|
||||
|
||||
async def version(self) -> dict:
|
||||
"""Get miner version info.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Miner version information.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("version")
|
||||
|
||||
async def config(self) -> dict:
|
||||
"""Get some basic configuration info.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
## Some miner configuration information:
|
||||
* ASC Count <- the number of ASCs
|
||||
* PGA Count <- the number of PGAs
|
||||
* Pool Count <- the number of Pools
|
||||
* Strategy <- the current pool strategy
|
||||
* Log Interval <- the interval of logging
|
||||
* Device Code <- list of compiled device drivers
|
||||
* OS <- the current operating system
|
||||
* Failover-Only <- failover-only setting
|
||||
* Scan Time <- scan-time setting
|
||||
* Queue <- queue setting
|
||||
* Expiry <- expiry setting
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("config")
|
||||
|
||||
async def summary(self) -> dict:
|
||||
"""Get the status summary of the miner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
The status summary of the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("summary")
|
||||
|
||||
async def pools(self) -> dict:
|
||||
"""Get pool information.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Miner pool information.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pools")
|
||||
|
||||
async def devs(self) -> dict:
|
||||
"""Get data on each PGA/ASC with their details.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on each PGA/ASC with their details.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("devs")
|
||||
|
||||
async def procs(self) -> dict:
|
||||
"""Get data on each processor with their details.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on each processor with their details.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("procs")
|
||||
|
||||
async def devscan(self, info: str = "") -> dict:
|
||||
"""Get data on each processor with their details.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
info: Info to scan for device by.
|
||||
|
||||
Returns:
|
||||
Data on each processor with their details.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("devscan", parameters=info)
|
||||
|
||||
async def pga(self, n: int) -> dict:
|
||||
"""Get data from PGA n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The PGA number to get data from.
|
||||
|
||||
Returns:
|
||||
Data on the PGA n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pga", parameters=n)
|
||||
|
||||
async def proc(self, n: int = 0) -> dict:
|
||||
"""Get data processor n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The processor to get data on.
|
||||
|
||||
Returns:
|
||||
Data on processor n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("proc", parameters=n)
|
||||
|
||||
async def pgacount(self) -> dict:
|
||||
"""Get data fon all PGAs.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on the PGAs connected.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pgacount")
|
||||
|
||||
async def proccount(self) -> dict:
|
||||
"""Get data fon all processors.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on the processors connected.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("proccount")
|
||||
|
||||
async def switchpool(self, n: int) -> dict:
|
||||
"""Switch pools to pool n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The pool to switch to.
|
||||
|
||||
Returns:
|
||||
A confirmation of switching to pool n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("switchpool", parameters=n)
|
||||
|
||||
async def enablepool(self, n: int) -> dict:
|
||||
"""Enable pool n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The pool to enable.
|
||||
|
||||
Returns:
|
||||
A confirmation of enabling pool n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("enablepool", parameters=n)
|
||||
|
||||
async def addpool(self, url: str, username: str, password: str) -> dict:
|
||||
"""Add a pool to the miner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
url: The URL of the new pool to add.
|
||||
username: The users username on the new pool.
|
||||
password: The worker password on the new pool.
|
||||
|
||||
Returns:
|
||||
A confirmation of adding the pool.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command(
|
||||
"addpool", parameters=f"{url},{username},{password}"
|
||||
)
|
||||
|
||||
async def poolpriority(self, *n: int) -> dict:
|
||||
"""Set pool priority.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
*n: Pools in order of priority.
|
||||
|
||||
Returns:
|
||||
A confirmation of setting pool priority.
|
||||
</details>
|
||||
"""
|
||||
pools = f"{','.join([str(item) for item in n])}"
|
||||
return await self.send_command("poolpriority", parameters=pools)
|
||||
|
||||
async def poolquota(self, n: int, q: int) -> dict:
|
||||
"""Set pool quota.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: Pool number to set quota on.
|
||||
q: Quota to set the pool to.
|
||||
|
||||
Returns:
|
||||
A confirmation of setting pool quota.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("poolquota", parameters=f"{n},{q}")
|
||||
|
||||
async def disablepool(self, n: int) -> dict:
|
||||
"""Disable a pool.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: Pool to disable.
|
||||
|
||||
Returns:
|
||||
A confirmation of diabling the pool.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("disablepool", parameters=n)
|
||||
|
||||
async def removepool(self, n: int) -> dict:
|
||||
"""Remove a pool.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: Pool to remove.
|
||||
|
||||
Returns:
|
||||
A confirmation of removing the pool.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("removepool", parameters=n)
|
||||
|
||||
async def save(self, filename: str = None) -> dict:
|
||||
"""Save the config.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
filename: Filename to save the config as.
|
||||
|
||||
Returns:
|
||||
A confirmation of saving the config.
|
||||
</details>
|
||||
"""
|
||||
if filename:
|
||||
return await self.send_command("save", parameters=filename)
|
||||
else:
|
||||
return await self.send_command("save")
|
||||
|
||||
async def quit(self) -> dict:
|
||||
"""Quit CGMiner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
A single "BYE" before CGMiner quits.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("quit")
|
||||
|
||||
async def notify(self) -> dict:
|
||||
"""Notify the user of past errors.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
The last status and count of each devices problem(s).
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("notify")
|
||||
|
||||
async def privileged(self) -> dict:
|
||||
"""Check if you have privileged access.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
The STATUS section with an error if you have no privileged access, or success if you have privileged access.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("privileged")
|
||||
|
||||
async def pgaenable(self, n: int) -> dict:
|
||||
"""Enable PGA n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The PGA to enable.
|
||||
|
||||
Returns:
|
||||
A confirmation of enabling PGA n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pgaenable", parameters=n)
|
||||
|
||||
async def pgadisable(self, n: int) -> dict:
|
||||
"""Disable PGA n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The PGA to disable.
|
||||
|
||||
Returns:
|
||||
A confirmation of disabling PGA n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pgadisable", parameters=n)
|
||||
|
||||
async def pgarestart(self, n: int) -> dict:
|
||||
"""Restart PGA n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The PGA to restart.
|
||||
|
||||
Returns:
|
||||
A confirmation of restarting PGA n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pgadisable", parameters=n)
|
||||
|
||||
async def pgaidentify(self, n: int) -> dict:
|
||||
"""Identify PGA n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The PGA to identify.
|
||||
|
||||
Returns:
|
||||
A confirmation of identifying PGA n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pgaidentify", parameters=n)
|
||||
|
||||
async def procenable(self, n: int) -> dict:
|
||||
"""Enable processor n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The processor to enable.
|
||||
|
||||
Returns:
|
||||
A confirmation of enabling processor n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("procenable", parameters=n)
|
||||
|
||||
async def procdisable(self, n: int) -> dict:
|
||||
"""Disable processor n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The processor to disable.
|
||||
|
||||
Returns:
|
||||
A confirmation of disabling processor n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("procdisable", parameters=n)
|
||||
|
||||
async def procrestart(self, n: int) -> dict:
|
||||
"""Restart processor n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The processor to restart.
|
||||
|
||||
Returns:
|
||||
A confirmation of restarting processor n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("procdisable", parameters=n)
|
||||
|
||||
async def procidentify(self, n: int) -> dict:
|
||||
"""Identify processor n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The processor to identify.
|
||||
|
||||
Returns:
|
||||
A confirmation of identifying processor n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("procidentify", parameters=n)
|
||||
|
||||
async def devdetails(self) -> dict:
|
||||
"""Get data on all devices with their static details.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on all devices with their static details.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("devdetails")
|
||||
|
||||
async def restart(self) -> dict:
|
||||
"""Restart CGMiner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
A reply informing of the restart.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("restart")
|
||||
|
||||
async def stats(self) -> dict:
|
||||
"""Get stats of each device/pool with more than 1 getwork.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Stats of each device/pool with more than 1 getwork.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("stats")
|
||||
|
||||
async def check(self, command: str) -> dict:
|
||||
"""Check if the command command exists in CGMiner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
command: The command to check.
|
||||
|
||||
Returns:
|
||||
## Information about a command:
|
||||
* Exists (Y/N) <- the command exists in this version
|
||||
* Access (Y/N) <- you have access to use the command
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("check", parameters=command)
|
||||
|
||||
async def failover_only(self, failover: bool) -> dict:
|
||||
"""Set failover-only.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
failover: What to set failover-only to.
|
||||
|
||||
Returns:
|
||||
Confirmation of setting failover-only.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("failover-only", parameters=failover)
|
||||
|
||||
async def coin(self) -> dict:
|
||||
"""Get information on the current coin.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
## Information about the current coin being mined:
|
||||
* Hash Method <- the hashing algorithm
|
||||
* Current Block Time <- blocktime as a float, 0 means none
|
||||
* Current Block Hash <- the hash of the current block, blank means none
|
||||
* LP <- whether LP is in use on at least 1 pool
|
||||
* Network Difficulty: the current network difficulty
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("coin")
|
||||
|
||||
async def debug(self, setting: str) -> dict:
|
||||
"""Set a debug setting.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
setting: Which setting to switch to.
|
||||
## Options are:
|
||||
* Silent
|
||||
* Quiet
|
||||
* Verbose
|
||||
* Debug
|
||||
* RPCProto
|
||||
* PerDevice
|
||||
* WorkTime
|
||||
* Normal
|
||||
|
||||
Returns:
|
||||
Data on which debug setting was enabled or disabled.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("debug", parameters=setting)
|
||||
|
||||
async def setconfig(self, name: str, n: int) -> dict:
|
||||
"""Set config of name to value n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
name: The name of the config setting to set.
|
||||
## Options are:
|
||||
* queue
|
||||
* scantime
|
||||
* expiry
|
||||
n: The value to set the 'name' setting to.
|
||||
|
||||
Returns:
|
||||
The results of setting config of name to n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("setconfig", parameters=f"{name},{n}")
|
||||
|
||||
async def pgaset(self, n: int, opt: str, val: int = None) -> dict:
|
||||
"""Set PGA option opt to val on PGA n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Options:
|
||||
```
|
||||
MMQ -
|
||||
opt: clock
|
||||
val: 2 - 250 (multiple of 2)
|
||||
XBS -
|
||||
opt: clock
|
||||
val: 2 - 250 (multiple of 2)
|
||||
```
|
||||
|
||||
Parameters:
|
||||
n: The PGA to set the options on.
|
||||
opt: The option to set. Setting this to 'help' returns a help message.
|
||||
val: The value to set the option to.
|
||||
|
||||
Returns:
|
||||
Confirmation of setting PGA n with opt[,val].
|
||||
</details>
|
||||
"""
|
||||
if val:
|
||||
return await self.send_command("pgaset", parameters=f"{n},{opt},{val}")
|
||||
else:
|
||||
return await self.send_command("pgaset", parameters=f"{n},{opt}")
|
||||
|
||||
async def pprocset(self, n: int, opt: str, val: int = None) -> dict:
|
||||
"""Set processor option opt to val on processor n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Options:
|
||||
```
|
||||
MMQ -
|
||||
opt: clock
|
||||
val: 2 - 250 (multiple of 2)
|
||||
XBS -
|
||||
opt: clock
|
||||
val: 2 - 250 (multiple of 2)
|
||||
```
|
||||
|
||||
Parameters:
|
||||
n: The PGA to set the options on.
|
||||
opt: The option to set. Setting this to 'help' returns a help message.
|
||||
val: The value to set the option to.
|
||||
|
||||
Returns:
|
||||
Confirmation of setting PGA n with opt[,val].
|
||||
</details>
|
||||
"""
|
||||
if val:
|
||||
return await self.send_command("pgaset", parameters=f"{n},{opt},{val}")
|
||||
else:
|
||||
return await self.send_command("pgaset", parameters=f"{n},{opt}")
|
||||
|
||||
async def zero(self, which: str, summary: bool) -> dict:
|
||||
"""Zero a device.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
which: Which device to zero. Setting this to 'all' zeros all devices. Setting this to 'bestshare' zeros only the bestshare values for each pool and global.
|
||||
summary: Whether or not to show a full summary.
|
||||
|
||||
|
||||
Returns:
|
||||
the STATUS section with info on the zero and optional summary.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("zero", parameters=f"{which},{summary}")
|
||||
@@ -1,4 +1,21 @@
|
||||
from pyasic.API import BaseMinerAPI
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
import logging
|
||||
|
||||
from pyasic.API import APIError, BaseMinerAPI
|
||||
|
||||
|
||||
class BMMinerAPI(BaseMinerAPI):
|
||||
@@ -19,8 +36,43 @@ class BMMinerAPI(BaseMinerAPI):
|
||||
port: The port to reference the API on. Default is 4028.
|
||||
"""
|
||||
|
||||
def __init__(self, ip: str, port: int = 4028) -> None:
|
||||
super().__init__(ip, port)
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0", port: int = 4028) -> None:
|
||||
super().__init__(ip, port=port)
|
||||
self.api_ver = api_ver
|
||||
|
||||
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||
# make sure we can actually run each command, otherwise they will fail
|
||||
commands = self._check_commands(*commands)
|
||||
# standard multicommand format is "command1+command2"
|
||||
# doesn't work for S19 which uses the backup _x19_multicommand
|
||||
command = "+".join(commands)
|
||||
try:
|
||||
data = await self.send_command(command, allow_warning=allow_warning)
|
||||
except APIError:
|
||||
logging.debug(f"{self} - (Multicommand) - Handling X19 multicommand.")
|
||||
data = await self._x19_multicommand(
|
||||
*command.split("+"), allow_warning=allow_warning
|
||||
)
|
||||
data["multicommand"] = True
|
||||
return data
|
||||
|
||||
async def _x19_multicommand(self, *commands, allow_warning: bool = True) -> dict:
|
||||
data = None
|
||||
try:
|
||||
data = {}
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
data[cmd] = []
|
||||
data[cmd].append(
|
||||
await self.send_command(cmd, allow_warning=allow_warning)
|
||||
)
|
||||
except APIError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logging.warning(
|
||||
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"
|
||||
)
|
||||
return data
|
||||
|
||||
async def version(self) -> dict:
|
||||
"""Get miner version info.
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.API import BaseMinerAPI
|
||||
|
||||
|
||||
@@ -19,8 +35,9 @@ class BOSMinerAPI(BaseMinerAPI):
|
||||
port: The port to reference the API on. Default is 4028.
|
||||
"""
|
||||
|
||||
def __init__(self, ip: str, port: int = 4028):
|
||||
super().__init__(ip, port)
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0", port: int = 4028) -> None:
|
||||
super().__init__(ip, port=port)
|
||||
self.api_ver = api_ver
|
||||
|
||||
async def asccount(self) -> dict:
|
||||
"""Get data on the number of ASC devices and their info.
|
||||
@@ -152,7 +169,7 @@ class BOSMinerAPI(BaseMinerAPI):
|
||||
return await self.send_command("estats")
|
||||
|
||||
async def check(self, command: str) -> dict:
|
||||
"""Check if the command command exists in BOSMiner.
|
||||
"""Check if the command `command` exists in BOSMiner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
|
||||
@@ -1,24 +1,43 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
import json
|
||||
import hashlib
|
||||
import binascii
|
||||
import base64
|
||||
import binascii
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from typing import Union
|
||||
|
||||
from passlib.handlers.md5_crypt import md5_crypt
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from passlib.handlers.md5_crypt import md5_crypt
|
||||
|
||||
from pyasic.API import BaseMinerAPI, APIError
|
||||
from pyasic.settings import WHATSMINER_PWD
|
||||
|
||||
from pyasic.API import BaseMinerAPI
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.misc import api_min_version
|
||||
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,
|
||||
# you change the password, you can pass that to this class as pwd,
|
||||
# or add it as the Whatsminer_pwd in the settings.toml file.
|
||||
|
||||
|
||||
@@ -67,7 +86,7 @@ def _add_to_16(string: str) -> bytes:
|
||||
def parse_btminer_priviledge_data(token_data: dict, data: dict):
|
||||
"""Parses data returned from the BTMiner privileged API.
|
||||
|
||||
Parses data from the BTMiner privileged API using the the token
|
||||
Parses data from the BTMiner privileged API using the token
|
||||
from the API in an AES format.
|
||||
|
||||
Parameters:
|
||||
@@ -109,6 +128,7 @@ def create_privileged_cmd(token_data: dict, command: dict) -> bytes:
|
||||
Returns:
|
||||
The encrypted privileged command to be sent to the miner.
|
||||
"""
|
||||
logging.debug(f"(Create Prilileged Command) - Creating Privileged Command")
|
||||
# add token to command
|
||||
command["token"] = token_data["host_sign"]
|
||||
# encode host_passwd data and get hexdigest
|
||||
@@ -161,66 +181,91 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
pwd: The admin password of the miner. Default is admin.
|
||||
"""
|
||||
|
||||
def __init__(self, ip: str, port: int = 4028, pwd: str = WHATSMINER_PWD):
|
||||
super().__init__(ip, port)
|
||||
self.admin_pwd = pwd
|
||||
self.current_token = None
|
||||
|
||||
async def send_command(
|
||||
def __init__(
|
||||
self,
|
||||
command: str or bytes,
|
||||
parameters: str or int or bool = None,
|
||||
ip: str,
|
||||
api_ver: str = "0.0.0",
|
||||
port: int = 4028,
|
||||
pwd: str = PyasicSettings().global_whatsminer_password,
|
||||
):
|
||||
super().__init__(ip, port)
|
||||
self.pwd = pwd
|
||||
self.current_token = None
|
||||
self.api_ver = api_ver
|
||||
|
||||
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||
"""Creates and sends multiple commands as one command to the miner.
|
||||
|
||||
Parameters:
|
||||
*commands: The commands to send as a multicommand to the miner.
|
||||
allow_warning: A boolean to supress APIWarnings.
|
||||
"""
|
||||
# make sure we can actually run each command, otherwise they will fail
|
||||
commands = self._check_commands(*commands)
|
||||
# standard multicommand format is "command1+command2"
|
||||
# commands starting with "get_" aren't supported, but we can fake that
|
||||
get_commands_data = {}
|
||||
for command in list(commands):
|
||||
if command.startswith("get_"):
|
||||
commands.remove(command)
|
||||
# send seperately and append later
|
||||
try:
|
||||
get_commands_data[command] = [
|
||||
await self.send_command(command, allow_warning=allow_warning)
|
||||
]
|
||||
except APIError:
|
||||
get_commands_data[command] = [{}]
|
||||
|
||||
command = "+".join(commands)
|
||||
try:
|
||||
main_data = await self.send_command(command, allow_warning=allow_warning)
|
||||
except APIError:
|
||||
main_data = {command: [{}] for command in commands}
|
||||
logging.debug(f"{self} - (Multicommand) - Received data")
|
||||
|
||||
data = dict(**main_data, **get_commands_data)
|
||||
data["multicommand"] = True
|
||||
return data
|
||||
|
||||
async def send_privileged_command(
|
||||
self,
|
||||
command: Union[str, bytes],
|
||||
ignore_errors: bool = False,
|
||||
timeout: int = 10,
|
||||
**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")
|
||||
logging.debug(
|
||||
f"{self} - (Send Privileged Command) - {command} " + f"with args {kwargs}"
|
||||
if len(kwargs) > 0
|
||||
else ""
|
||||
)
|
||||
command = {"cmd": command, **kwargs}
|
||||
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
|
||||
logging.debug(f"{self} - (Send Privileged Command) - Sending")
|
||||
try:
|
||||
# get reader and writer streams
|
||||
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
|
||||
# handle OSError 121
|
||||
except OSError as e:
|
||||
if e.winerror == "121":
|
||||
print("Semaphore Timeout has Expired.")
|
||||
return {}
|
||||
data = await self._send_bytes(enc_command, timeout)
|
||||
except (asyncio.CancelledError, asyncio.TimeoutError):
|
||||
if ignore_errors:
|
||||
return {}
|
||||
raise APIError("No data was returned from the API.")
|
||||
|
||||
# send the command
|
||||
writer.write(command)
|
||||
await writer.drain()
|
||||
if not data:
|
||||
if ignore_errors:
|
||||
return {}
|
||||
raise APIError("No data was returned from the API.")
|
||||
data = self._load_api_data(data)
|
||||
|
||||
# instantiate data
|
||||
data = b""
|
||||
|
||||
# loop to receive all the data
|
||||
try:
|
||||
while True:
|
||||
d = await reader.read(4096)
|
||||
if not d:
|
||||
break
|
||||
data += d
|
||||
data = parse_btminer_priviledge_data(self.current_token, data)
|
||||
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)
|
||||
validation = self._validate_command_output(data)
|
||||
if not validation[0]:
|
||||
raise APIError(validation[1])
|
||||
|
||||
@@ -236,11 +281,18 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
An encoded token and md5 password, which are used for the privileged API.
|
||||
</details>
|
||||
"""
|
||||
logging.debug(f"{self} - (Get Token) - Getting token")
|
||||
if self.current_token:
|
||||
if self.current_token[
|
||||
"timestamp"
|
||||
] > datetime.datetime.now() - datetime.timedelta(minutes=30):
|
||||
return self.current_token
|
||||
|
||||
# get the token
|
||||
data = await self.send_command("get_token")
|
||||
|
||||
# encrypt the admin password with the salt
|
||||
pwd = _crypt(self.admin_pwd, "$1$" + data["Msg"]["salt"] + "$")
|
||||
pwd = _crypt(self.pwd, "$1$" + data["Msg"]["salt"] + "$")
|
||||
pwd = pwd.split("$")
|
||||
|
||||
# take the 4th item from the pwd split
|
||||
@@ -257,7 +309,11 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
self.current_token = {
|
||||
"host_sign": host_sign,
|
||||
"host_passwd_md5": host_passwd_md5,
|
||||
"timestamp": datetime.datetime.now(),
|
||||
}
|
||||
logging.debug(
|
||||
f"{self} - (Get Token) - Gathered token data: {self.current_token}"
|
||||
)
|
||||
return self.current_token
|
||||
|
||||
#### PRIVILEGED COMMANDS ####
|
||||
@@ -299,46 +355,18 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
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)
|
||||
return await self.send_privileged_command(
|
||||
"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,
|
||||
)
|
||||
|
||||
async def restart(self) -> dict:
|
||||
"""Restart BTMiner using the API.
|
||||
@@ -352,10 +380,7 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
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)
|
||||
return await self.send_privileged_command("restart_btminer")
|
||||
|
||||
async def power_off(self, respbefore: bool = True) -> dict:
|
||||
"""Power off the miner using the API.
|
||||
@@ -372,12 +397,8 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
</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)
|
||||
return await self.send_privileged_command("power_off", respbefore="true")
|
||||
return await self.send_privileged_command("power_off", respbefore="false")
|
||||
|
||||
async def power_on(self) -> dict:
|
||||
"""Power on the miner using the API.
|
||||
@@ -388,14 +409,10 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
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)
|
||||
return await self.send_privileged_command("power_on")
|
||||
|
||||
async def reset_led(self) -> dict:
|
||||
"""Reset the LED on the miner using the API.
|
||||
@@ -406,20 +423,17 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of resetting the LED.
|
||||
</details>
|
||||
"""
|
||||
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)
|
||||
return await self.set_led(auto=True)
|
||||
|
||||
async def set_led(
|
||||
self,
|
||||
auto: bool = True,
|
||||
color: str = "red",
|
||||
period: int = 2000,
|
||||
duration: int = 1000,
|
||||
period: int = 60,
|
||||
duration: int = 20,
|
||||
start: int = 0,
|
||||
) -> dict:
|
||||
"""Set the LED on the miner using the API.
|
||||
@@ -430,25 +444,20 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Parameters:
|
||||
auto: Whether or not to reset the LED to auto mode.
|
||||
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)
|
||||
if auto:
|
||||
return await self.send_privileged_command("set_led", param="auto")
|
||||
return await self.send_privileged_command(
|
||||
"set_led", color=color, period=period, duration=duration, start=start
|
||||
)
|
||||
|
||||
async def set_low_power(self) -> dict:
|
||||
"""Set low power mode on the miner using the API.
|
||||
@@ -459,14 +468,10 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting low power mode.
|
||||
</details>
|
||||
"""
|
||||
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)
|
||||
return await self.send_privileged_command("set_low_power")
|
||||
|
||||
async def update_firmware(self): # noqa - static
|
||||
"""Not implemented."""
|
||||
@@ -474,20 +479,23 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
# requires a file stream in bytes
|
||||
return NotImplementedError
|
||||
|
||||
async def reboot(self) -> dict:
|
||||
async def reboot(self, timeout: int = 10) -> dict:
|
||||
"""Reboot the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of the reboot.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "reboot"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
try:
|
||||
d = await asyncio.wait_for(
|
||||
self.send_privileged_command("reboot"), timeout=timeout
|
||||
)
|
||||
except (asyncio.CancelledError, asyncio.TimeoutError):
|
||||
return {}
|
||||
else:
|
||||
return d
|
||||
|
||||
async def factory_reset(self) -> dict:
|
||||
"""Reset the miner to factory defaults.
|
||||
@@ -495,14 +503,10 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<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)
|
||||
return await self.send_privileged_command("factory_reset")
|
||||
|
||||
async def update_pwd(self, old_pwd: str, new_pwd: str) -> dict:
|
||||
"""Update the admin user's password.
|
||||
@@ -519,19 +523,40 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
old_pwd: The old admin password.
|
||||
new_pwd: The new password to set.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting the password.
|
||||
"""
|
||||
self.pwd = old_pwd
|
||||
# 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)
|
||||
try:
|
||||
data = await self.send_privileged_command(
|
||||
"update_pwd", old=old_pwd, new=new_pwd
|
||||
)
|
||||
except APIError as e:
|
||||
raise e
|
||||
self.pwd = new_pwd
|
||||
return data
|
||||
|
||||
async def net_config(
|
||||
self,
|
||||
ip: str = None,
|
||||
mask: str = None,
|
||||
gate: str = None,
|
||||
dns: str = None,
|
||||
host: str = None,
|
||||
dhcp: bool = True,
|
||||
):
|
||||
if dhcp:
|
||||
return await self.send_privileged_command("net_config", param="dhcp")
|
||||
if None in [ip, mask, gate, dns, host]:
|
||||
raise APIError("Incorrect parameters.")
|
||||
return await self.send_privileged_command(
|
||||
"net_config", ip=ip, mask=mask, gate=gate, dns=dns, host=host
|
||||
)
|
||||
|
||||
async def set_target_freq(self, percent: int) -> dict:
|
||||
"""Update the target frequency.
|
||||
@@ -546,7 +571,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
Parameters:
|
||||
percent: The frequency % to set.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting the frequency.
|
||||
</details>
|
||||
"""
|
||||
@@ -556,10 +580,9 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
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)
|
||||
return await self.send_privileged_command(
|
||||
"set_target_freq", percent=str(percent)
|
||||
)
|
||||
|
||||
async def enable_fast_boot(self) -> dict:
|
||||
"""Turn on fast boot.
|
||||
@@ -571,14 +594,10 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
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)
|
||||
return await self.send_privileged_command("enable_btminer_fast_boot")
|
||||
|
||||
async def disable_fast_boot(self) -> dict:
|
||||
"""Turn off fast boot.
|
||||
@@ -590,14 +609,10 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
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)
|
||||
return await self.send_privileged_command("disable_btminer_fast_boot")
|
||||
|
||||
async def enable_web_pools(self) -> dict:
|
||||
"""Turn on web pool updates.
|
||||
@@ -609,14 +624,10 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
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)
|
||||
return await self.send_privileged_command("enable_web_pools")
|
||||
|
||||
async def disable_web_pools(self) -> dict:
|
||||
"""Turn off web pool updates.
|
||||
@@ -628,14 +639,10 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
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)
|
||||
return await self.send_privileged_command("disable_web_pools")
|
||||
|
||||
async def set_hostname(self, hostname: str) -> dict:
|
||||
"""Set the hostname of the miner.
|
||||
@@ -649,17 +656,13 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
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)
|
||||
return await self.send_privileged_command("set_hostname", hostname=hostname)
|
||||
|
||||
async def set_power_pct(self, percent: int) -> dict:
|
||||
"""Set the power percentage of the miner.
|
||||
"""Set the power percentage of the miner based on current power. Used for temporary adjustment.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
@@ -670,7 +673,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
Parameters:
|
||||
percent: The power percentage to set.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting the power percentage.
|
||||
</details>
|
||||
"""
|
||||
@@ -681,10 +683,7 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
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)
|
||||
return await self.send_privileged_command("set_power_pct", percent=str(percent))
|
||||
|
||||
async def pre_power_on(self, complete: bool, msg: str) -> dict:
|
||||
"""Configure or check status of pre power on.
|
||||
@@ -702,7 +701,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
* `adjust complete`
|
||||
* `adjust continue`
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of pre power on.
|
||||
</details>
|
||||
"""
|
||||
@@ -715,13 +713,133 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
'"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)
|
||||
return await self.send_privileged_command(
|
||||
"pre_power_on", complete="true", msg=msg
|
||||
)
|
||||
return await self.send_privileged_command(
|
||||
"pre_power_on", complete="false", msg=msg
|
||||
)
|
||||
|
||||
### ADDED IN V2.0.5 Whatsminer API ###
|
||||
@api_min_version("2.0.5")
|
||||
async def set_temp_offset(self, temp_offset: int) -> dict:
|
||||
"""Set the offset of miner hash board target temperature.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set the offset of miner hash board target temperature, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Parameters:
|
||||
temp_offset: Target temperature offset.
|
||||
Returns:
|
||||
A reply informing of the status of setting temp offset.
|
||||
</details>
|
||||
|
||||
"""
|
||||
if not -30 < temp_offset < 0:
|
||||
raise APIError(
|
||||
f"Temp offset is outside of the allowed "
|
||||
f"range. Please set a number between -30 and "
|
||||
f"0."
|
||||
)
|
||||
|
||||
return await self.send_privileged_command(
|
||||
"set_temp_offset", temp_offset=temp_offset
|
||||
)
|
||||
|
||||
@api_min_version("2.0.5")
|
||||
async def adjust_power_limit(self, power_limit: int) -> dict:
|
||||
"""Set the upper limit of the miner's power. Cannot be higher than the ordinary power of the machine.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set the upper limit of the miner's power, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
The miner will reboot after this is set.
|
||||
|
||||
Parameters:
|
||||
power_limit: New power limit.
|
||||
Returns:
|
||||
A reply informing of the status of setting power limit.
|
||||
</details>
|
||||
|
||||
"""
|
||||
return await self.send_privileged_command(
|
||||
"adjust_power_limit", power_limit=str(power_limit)
|
||||
)
|
||||
|
||||
@api_min_version("2.0.5")
|
||||
async def adjust_upfreq_speed(self, upfreq_speed: int) -> dict:
|
||||
"""Set the upfreq speed, 0 is the normal speed, 9 is the fastest speed.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set the upfreq speed, 0 is the normal speed, 9 is the fastest speed, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
The faster the speed, the greater the final hash rate and power deviation, and the stability
|
||||
may be impacted. Fast boot mode cannot be used at the same time.
|
||||
|
||||
Parameters:
|
||||
upfreq_speed: New upfreq speed.
|
||||
Returns:
|
||||
A reply informing of the status of setting upfreq speed.
|
||||
</details>
|
||||
"""
|
||||
if not 0 < upfreq_speed < 9:
|
||||
raise APIError(
|
||||
f"Upfreq speed is outside of the allowed "
|
||||
f"range. Please set a number between 0 (Normal) and "
|
||||
f"9 (Fastest)."
|
||||
)
|
||||
return await self.send_privileged_command(
|
||||
"adjust_upfreq_speed", upfreq_speed=upfreq_speed
|
||||
)
|
||||
|
||||
@api_min_version("2.0.5")
|
||||
async def set_poweroff_cool(self, poweroff_cool: bool) -> dict:
|
||||
"""Set whether to cool the machine when mining is stopped.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set whether to cool the machine when mining is stopped, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Parameters:
|
||||
poweroff_cool: Whether to cool the miner during power off mode.
|
||||
Returns:
|
||||
A reply informing of the status of setting power off cooling mode.
|
||||
</details>
|
||||
"""
|
||||
|
||||
return await self.send_privileged_command(
|
||||
"set_poweroff_cool", poweroff_cool=int(poweroff_cool)
|
||||
)
|
||||
|
||||
@api_min_version("2.0.5")
|
||||
async def set_fan_zero_speed(self, fan_zero_speed: bool) -> dict:
|
||||
"""Sets whether the fan speed supports the lowest 0 speed.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Sets whether the fan speed supports the lowest 0 speed, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
Parameters:
|
||||
fan_zero_speed: Whether the fan is allowed to support 0 speed.
|
||||
Returns:
|
||||
A reply informing of the status of setting fan minimum speed.
|
||||
</details>
|
||||
|
||||
"""
|
||||
return await self.send_privileged_command(
|
||||
"set_fan_zero_speed", fan_zero_speed=int(fan_zero_speed)
|
||||
)
|
||||
|
||||
#### END privileged COMMANDS ####
|
||||
|
||||
@@ -731,7 +849,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Summary status of the miner.
|
||||
</details>
|
||||
"""
|
||||
@@ -743,7 +860,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Pool status of the miner.
|
||||
</details>
|
||||
"""
|
||||
@@ -755,7 +871,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Data on each PGA/ASC with their details.
|
||||
</details>
|
||||
"""
|
||||
@@ -767,7 +882,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Data on each PGA/ASC with their details.
|
||||
</details>
|
||||
"""
|
||||
@@ -779,7 +893,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Data on all devices with their static details.
|
||||
</details>
|
||||
"""
|
||||
@@ -791,7 +904,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Data on the PSU and power information.
|
||||
</details>
|
||||
"""
|
||||
@@ -807,7 +919,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
with the other miner APIs.
|
||||
|
||||
Returns:
|
||||
|
||||
Version data for the miner.
|
||||
</details>
|
||||
"""
|
||||
@@ -819,7 +930,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
Version data for the miner.
|
||||
</details>
|
||||
"""
|
||||
@@ -831,7 +941,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
BTMiner status and firmware version.
|
||||
</details>
|
||||
"""
|
||||
@@ -843,8 +952,21 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
|
||||
General miner info.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("get_miner_info")
|
||||
return await self.send_command("get_miner_info", allow_warning=False)
|
||||
|
||||
@api_min_version("2.0.1")
|
||||
async def get_error_code(self) -> dict:
|
||||
"""Get a list of error codes from the miner.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
Get a list of error codes from the miner. Replaced `summary` as the location of error codes with API version 2.0.4.
|
||||
|
||||
Returns:
|
||||
A list of error codes on the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("get_error_code", allow_warning=False)
|
||||
|
||||
@@ -1,4 +1,22 @@
|
||||
from pyasic.API import BaseMinerAPI
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
|
||||
from pyasic.API import APIError, BaseMinerAPI
|
||||
|
||||
|
||||
class CGMinerAPI(BaseMinerAPI):
|
||||
@@ -19,8 +37,39 @@ class CGMinerAPI(BaseMinerAPI):
|
||||
port: The port to reference the API on. Default is 4028.
|
||||
"""
|
||||
|
||||
def __init__(self, ip: str, port: int = 4028):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0", port: int = 4028):
|
||||
super().__init__(ip, port)
|
||||
self.api_ver = api_ver
|
||||
|
||||
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||
# make sure we can actually run each command, otherwise they will fail
|
||||
commands = self._check_commands(*commands)
|
||||
# standard multicommand format is "command1+command2"
|
||||
# doesn't work for S19 which uses the backup _x19_multicommand
|
||||
command = "+".join(commands)
|
||||
try:
|
||||
data = await self.send_command(command, allow_warning=allow_warning)
|
||||
except APIError:
|
||||
logging.debug(f"{self} - (Multicommand) - Handling X19 multicommand.")
|
||||
data = await self._x19_multicommand(*command.split("+"))
|
||||
data["multicommand"] = True
|
||||
return data
|
||||
|
||||
async def _x19_multicommand(self, *commands) -> dict:
|
||||
data = None
|
||||
try:
|
||||
data = {}
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
data[cmd] = []
|
||||
data[cmd].append(await self.send_command(cmd, allow_warning=True))
|
||||
except APIError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logging.warning(
|
||||
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"
|
||||
)
|
||||
return data
|
||||
|
||||
async def version(self) -> dict:
|
||||
"""Get miner version info.
|
||||
|
||||
@@ -1,16 +1,33 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.API import BaseMinerAPI
|
||||
|
||||
|
||||
class UnknownAPI(BaseMinerAPI):
|
||||
"""An abstraction of an API for a miner which is unknown.
|
||||
|
||||
This class is designed to try to be a intersection of as many miner APIs
|
||||
This class is designed to try to be an intersection of as many miner APIs
|
||||
and API commands as possible (API ⋂ API), to ensure that it can be used
|
||||
with as many APIs as possible.
|
||||
"""
|
||||
|
||||
def __init__(self, ip, port=4028):
|
||||
def __init__(self, ip, api_ver: str = "0.0.0", port: int = 4028):
|
||||
super().__init__(ip, port)
|
||||
self.api_ver = api_ver
|
||||
|
||||
async def asccount(self) -> dict:
|
||||
return await self.send_command("asccount")
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.API.bmminer import BMMinerAPI
|
||||
from pyasic.API.bosminer import BOSMinerAPI
|
||||
from pyasic.API.btminer import BTMinerAPI
|
||||
from pyasic.API.cgminer import CGMinerAPI
|
||||
from pyasic.API.unknown import UnknownAPI
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import (
|
||||
BraiinsOSError,
|
||||
InnosiliconError,
|
||||
MinerData,
|
||||
WhatsminerError,
|
||||
X19Error,
|
||||
)
|
||||
from pyasic.errors import APIError, APIWarning
|
||||
from pyasic.miners import get_miner
|
||||
from pyasic.miners.base import AnyMiner
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
from pyasic.miners.miner_listener import MinerListener
|
||||
from pyasic.network import MinerNetwork
|
||||
from pyasic.settings import PyasicSettings
|
||||
|
||||
__all__ = [
|
||||
"BMMinerAPI",
|
||||
"BOSMinerAPI",
|
||||
"BTMinerAPI",
|
||||
"CGMinerAPI",
|
||||
"UnknownAPI",
|
||||
"MinerConfig",
|
||||
"MinerData",
|
||||
"BraiinsOSError",
|
||||
"InnosiliconError",
|
||||
"WhatsminerError",
|
||||
"X19Error",
|
||||
"APIError",
|
||||
"APIWarning",
|
||||
"get_miner",
|
||||
"AnyMiner",
|
||||
"MinerFactory",
|
||||
"MinerListener",
|
||||
"MinerNetwork",
|
||||
"PyasicSettings",
|
||||
]
|
||||
|
||||
@@ -1,12 +1,35 @@
|
||||
from dataclasses import dataclass, asdict
|
||||
from typing import Literal, List
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
from dataclasses import asdict, dataclass, fields
|
||||
from enum import IntEnum
|
||||
from typing import List, Literal
|
||||
|
||||
import toml
|
||||
import yaml
|
||||
import json
|
||||
import time
|
||||
|
||||
|
||||
class X19PowerMode(IntEnum):
|
||||
Normal = 0
|
||||
Sleep = 1
|
||||
LPM = 3
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -23,6 +46,10 @@ class _Pool:
|
||||
username: str = ""
|
||||
password: str = ""
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
def from_dict(self, data: dict):
|
||||
"""Convert raw pool data as a dict to usable data and save it to this class.
|
||||
|
||||
@@ -38,6 +65,19 @@ class _Pool:
|
||||
self.password = data[key]
|
||||
return self
|
||||
|
||||
def as_wm(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a dict usable by an Whatsminer device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
username = self.username
|
||||
if user_suffix:
|
||||
username = f"{username}{user_suffix}"
|
||||
|
||||
pool = {"url": self.url, "user": username, "pass": self.password}
|
||||
return pool
|
||||
|
||||
def as_x19(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a dict usable by an X19 device.
|
||||
|
||||
@@ -51,6 +91,49 @@ class _Pool:
|
||||
pool = {"url": self.url, "user": username, "pass": self.password}
|
||||
return pool
|
||||
|
||||
def as_x17(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a dict usable by an X5 device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
username = self.username
|
||||
if user_suffix:
|
||||
username = f"{username}{user_suffix}"
|
||||
|
||||
pool = {"url": self.url, "user": username, "pass": self.password}
|
||||
return pool
|
||||
|
||||
def as_goldshell(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a dict usable by a goldshell device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
username = self.username
|
||||
if user_suffix:
|
||||
username = f"{username}{user_suffix}"
|
||||
|
||||
pool = {"url": self.url, "user": username, "pass": self.password}
|
||||
return pool
|
||||
|
||||
def as_inno(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a dict usable by an Innosilicon device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
username = self.username
|
||||
if user_suffix:
|
||||
username = f"{username}{user_suffix}"
|
||||
|
||||
pool = {
|
||||
f"Pool": self.url,
|
||||
f"UserName": username,
|
||||
f"Password": self.password,
|
||||
}
|
||||
return pool
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a string usable by an Avalonminer device.
|
||||
|
||||
@@ -92,6 +175,10 @@ class _PoolGroup:
|
||||
group_name: str = None
|
||||
pools: List[_Pool] = None
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
def __post_init__(self):
|
||||
if not self.group_name:
|
||||
self.group_name = "".join(
|
||||
@@ -127,6 +214,82 @@ class _PoolGroup:
|
||||
pools.append(pool.as_x19(user_suffix=user_suffix))
|
||||
return pools
|
||||
|
||||
def as_x17(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a list usable by an X17 device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
pools = {
|
||||
"_ant_pool1url": "",
|
||||
"_ant_pool1user": "",
|
||||
"_ant_pool1pw": "",
|
||||
"_ant_pool2url": "",
|
||||
"_ant_pool2user": "",
|
||||
"_ant_pool2pw": "",
|
||||
"_ant_pool3url": "",
|
||||
"_ant_pool3user": "",
|
||||
"_ant_pool3pw": "",
|
||||
}
|
||||
for idx, pool in enumerate(self.pools[:3]):
|
||||
pools[f"_ant_pool{idx+1}url"] = pool.as_x17(user_suffix=user_suffix)["url"]
|
||||
pools[f"_ant_pool{idx+1}user"] = pool.as_x17(user_suffix=user_suffix)[
|
||||
"user"
|
||||
]
|
||||
pools[f"_ant_pool{idx+1}pw"] = pool.as_x17(user_suffix=user_suffix)["pass"]
|
||||
|
||||
return pools
|
||||
|
||||
def as_goldshell(self, user_suffix: str = None) -> list:
|
||||
"""Convert the data in this class to a list usable by a goldshell device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
return [pool.as_goldshell(user_suffix=user_suffix) for pool in self.pools[:3]]
|
||||
|
||||
def as_inno(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a list usable by an Innosilicon device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
pools = {
|
||||
"Pool1": None,
|
||||
"UserName1": None,
|
||||
"Password1": None,
|
||||
"Pool2": None,
|
||||
"UserName2": None,
|
||||
"Password2": None,
|
||||
"Pool3": None,
|
||||
"UserName3": None,
|
||||
"Password3": None,
|
||||
}
|
||||
for idx, pool in enumerate(self.pools[:3]):
|
||||
pool_data = pool.as_inno(user_suffix=user_suffix)
|
||||
for key in pool_data:
|
||||
pools[f"{key}{idx+1}"] = pool_data[key]
|
||||
return pools
|
||||
|
||||
def as_wm(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a list usable by a Whatsminer device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
pools = {}
|
||||
for i in range(1, 4):
|
||||
if i <= len(self.pools):
|
||||
pool_wm = self.pools[i - 1].as_wm(user_suffix)
|
||||
pools[f"pool_{i}"] = pool_wm["url"]
|
||||
pools[f"worker_{i}"] = pool_wm["user"]
|
||||
pools[f"passwd_{i}"] = pool_wm["pass"]
|
||||
else:
|
||||
pools[f"pool_{i}"] = ""
|
||||
pools[f"worker_{i}"] = ""
|
||||
pools[f"passwd_{i}"] = ""
|
||||
return pools
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a dict usable by an Avalonminer device.
|
||||
|
||||
@@ -164,7 +327,9 @@ class MinerConfig:
|
||||
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_mode: Autotuning mode, either "wattage" or "hashrate".
|
||||
autotuning_wattage: The wattage to use when autotuning.
|
||||
autotuning_hashrate: The hashrate to use when autotuning.
|
||||
dps_enabled: Whether or not to enable dynamic power scaling.
|
||||
dps_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.
|
||||
@@ -177,15 +342,18 @@ class MinerConfig:
|
||||
temp_mode: Literal["auto", "manual", "disabled"] = "auto"
|
||||
temp_target: float = 70.0
|
||||
temp_hot: float = 80.0
|
||||
temp_dangerous: float = 10.0
|
||||
temp_dangerous: float = 100.0
|
||||
|
||||
minimum_fans: int = None
|
||||
fan_speed: Literal[tuple(range(101))] = None # noqa - Ignore weird Literal usage
|
||||
|
||||
asicboost: bool = None
|
||||
|
||||
miner_mode: IntEnum = X19PowerMode.Normal
|
||||
autotuning_enabled: bool = True
|
||||
autotuning_wattage: int = 900
|
||||
autotuning_mode: Literal["power", "hashrate"] = None
|
||||
autotuning_wattage: int = None
|
||||
autotuning_hashrate: int = None
|
||||
|
||||
dps_enabled: bool = None
|
||||
dps_power_step: int = None
|
||||
@@ -193,20 +361,29 @@ class MinerConfig:
|
||||
dps_shutdown_enabled: bool = None
|
||||
dps_shutdown_duration: float = None
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
"""Convert the data in this class to a dict."""
|
||||
logging.debug(f"MinerConfig - (To Dict) - Dumping Dict config")
|
||||
data_dict = asdict(self)
|
||||
for key in asdict(self).keys():
|
||||
if isinstance(data_dict[key], IntEnum):
|
||||
data_dict[key] = data_dict[key].value
|
||||
if data_dict[key] is None:
|
||||
del data_dict[key]
|
||||
return data_dict
|
||||
|
||||
def as_toml(self) -> str:
|
||||
"""Convert the data in this class to toml."""
|
||||
logging.debug(f"MinerConfig - (To TOML) - Dumping TOML config")
|
||||
return toml.dumps(self.as_dict())
|
||||
|
||||
def as_yaml(self) -> str:
|
||||
"""Convert the data in this class to yaml."""
|
||||
logging.debug(f"MinerConfig - (To YAML) - Dumping YAML config")
|
||||
return yaml.dump(self.as_dict(), sort_keys=False)
|
||||
|
||||
def from_raw(self, data: dict):
|
||||
@@ -216,7 +393,11 @@ class MinerConfig:
|
||||
Parameters:
|
||||
data: The raw config data to convert.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (From Raw) - Loading raw config")
|
||||
pool_groups = []
|
||||
if isinstance(data, list):
|
||||
# goldshell config list
|
||||
data = {"pools": data}
|
||||
for key in data.keys():
|
||||
if key == "pools":
|
||||
pool_groups.append(_PoolGroup().from_dict({"pools": data[key]}))
|
||||
@@ -229,14 +410,17 @@ class MinerConfig:
|
||||
self.temp_mode = "manual"
|
||||
if data.get("bitmain-fan-pwm"):
|
||||
self.fan_speed = int(data["bitmain-fan-pwm"])
|
||||
elif key == "bitmain-work-mode":
|
||||
if data[key]:
|
||||
self.miner_mode = X19PowerMode(int(data[key]))
|
||||
elif key == "fan_control":
|
||||
for _key in data[key].keys():
|
||||
for _key in data[key]:
|
||||
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():
|
||||
for _key in data[key]:
|
||||
if _key == "mode":
|
||||
self.temp_mode = data[key][_key]
|
||||
elif _key == "target_temp":
|
||||
@@ -251,19 +435,25 @@ class MinerConfig:
|
||||
self.asicboost = data[key]["asic_boost"]
|
||||
|
||||
if key == "autotuning":
|
||||
for _key in data[key].keys():
|
||||
for _key in data[key]:
|
||||
if _key == "enabled":
|
||||
self.autotuning_enabled = data[key][_key]
|
||||
elif _key == "psu_power_limit":
|
||||
self.autotuning_wattage = data[key][_key]
|
||||
elif _key == "power_target":
|
||||
self.autotuning_wattage = data[key][_key]
|
||||
elif _key == "hashrate_target":
|
||||
self.autotuning_hashrate = data[key][_key]
|
||||
elif _key == "mode":
|
||||
self.autotuning_mode = data[key][_key].replace("_target", "")
|
||||
|
||||
if key == "power_scaling":
|
||||
for _key in data[key].keys():
|
||||
if key in ["power_scaling", "performance_scaling"]:
|
||||
for _key in data[key]:
|
||||
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":
|
||||
elif _key in ["min_psu_power_limit", "min_power_target"]:
|
||||
self.dps_min_power = data[key][_key]
|
||||
elif _key == "shutdown_enabled":
|
||||
self.dps_shutdown_enabled = data[key][_key]
|
||||
@@ -273,18 +463,40 @@ class MinerConfig:
|
||||
self.pool_groups = pool_groups
|
||||
return self
|
||||
|
||||
def from_api(self, pools: list):
|
||||
"""Convert list output from the `AnyMiner.api.pools()` command into a usable data and save it to this class.
|
||||
|
||||
Parameters:
|
||||
pools: The list of pool data to convert.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (From API) - Loading API config")
|
||||
_pools = []
|
||||
for pool in pools:
|
||||
url = pool.get("URL")
|
||||
user = pool.get("User")
|
||||
_pools.append({"url": url, "user": user, "pass": "123"})
|
||||
self.pool_groups = [_PoolGroup().from_dict({"pools": _pools})]
|
||||
return self
|
||||
|
||||
def from_dict(self, data: dict):
|
||||
"""Convert an output dict of this class back into usable data and save it to this class.
|
||||
|
||||
Parameters:
|
||||
data: The dict config data to convert.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (From Dict) - Loading Dict config")
|
||||
pool_groups = []
|
||||
for group in data["pool_groups"]:
|
||||
pool_groups.append(_PoolGroup().from_dict(group))
|
||||
for key in data.keys():
|
||||
if getattr(self, key) and not key == "pool_groups":
|
||||
for key in data:
|
||||
if (
|
||||
hasattr(self, key)
|
||||
and not key == "pool_groups"
|
||||
and not key == "miner_mode"
|
||||
):
|
||||
setattr(self, key, data[key])
|
||||
if key == "miner_mode":
|
||||
self.miner_mode = X19PowerMode(data[key])
|
||||
self.pool_groups = pool_groups
|
||||
return self
|
||||
|
||||
@@ -294,6 +506,7 @@ class MinerConfig:
|
||||
Parameters:
|
||||
data: The toml config data to convert.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (From TOML) - Loading TOML config")
|
||||
return self.from_dict(toml.loads(data))
|
||||
|
||||
def from_yaml(self, data: str):
|
||||
@@ -302,27 +515,75 @@ class MinerConfig:
|
||||
Parameters:
|
||||
data: The yaml config data to convert.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (From YAML) - Loading YAML config")
|
||||
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
|
||||
|
||||
def as_x19(self, user_suffix: str = None) -> str:
|
||||
def as_wm(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a config usable by a Whatsminer device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (As Whatsminer) - Generating Whatsminer config")
|
||||
return {
|
||||
"pools": self.pool_groups[0].as_wm(user_suffix=user_suffix),
|
||||
"wattage": self.autotuning_wattage,
|
||||
}
|
||||
|
||||
def as_inno(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a config usable by an Innosilicon device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (As Inno) - Generating Innosilicon config")
|
||||
return self.pool_groups[0].as_inno(user_suffix=user_suffix)
|
||||
|
||||
def as_x19(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a config usable by an X19 device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (As X19) - Generating X19 config")
|
||||
cfg = {
|
||||
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
|
||||
"bitmain-fan-ctrl": False,
|
||||
"bitmain-fan-pwn": 100,
|
||||
"bitmain-fan-pwn": "100",
|
||||
"freq-level": "100",
|
||||
"miner-mode": str(self.miner_mode.value),
|
||||
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
|
||||
}
|
||||
|
||||
if not self.temp_mode == "auto":
|
||||
cfg["bitmain-fan-ctrl"] = True
|
||||
|
||||
if self.fan_speed:
|
||||
cfg["bitmain-fan-ctrl"] = str(self.fan_speed)
|
||||
cfg["bitmain-fan-pwn"] = str(self.fan_speed)
|
||||
|
||||
return json.dumps(cfg)
|
||||
if self.miner_mode == X19PowerMode.Sleep:
|
||||
cfg["freq-level"] = "0"
|
||||
|
||||
return cfg
|
||||
|
||||
def as_x17(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a config usable by an X5 device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
cfg = self.pool_groups[0].as_x17(user_suffix=user_suffix)
|
||||
|
||||
return cfg
|
||||
|
||||
def as_goldshell(self, user_suffix: str = None) -> list:
|
||||
"""Convert the data in this class to a config usable by a goldshell device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
cfg = self.pool_groups[0].as_goldshell(user_suffix=user_suffix)
|
||||
|
||||
return cfg
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a config usable by an Avalonminer device.
|
||||
@@ -330,6 +591,7 @@ class MinerConfig:
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (As Avalon) - Generating AvalonMiner config")
|
||||
cfg = self.pool_groups[0].as_avalon(user_suffix=user_suffix)
|
||||
return cfg
|
||||
|
||||
@@ -340,11 +602,12 @@ class MinerConfig:
|
||||
model: The model of the miner to be used in the format portion of the config.
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (As BOS) - Generating BOSMiner config")
|
||||
cfg = {
|
||||
"format": {
|
||||
"version": "1.2+",
|
||||
"model": f"Antminer {model}",
|
||||
"generator": "Upstream Config Utility",
|
||||
"model": f"Antminer {model.replace('j', 'J')}",
|
||||
"generator": "pyasic",
|
||||
"timestamp": int(time.time()),
|
||||
},
|
||||
"group": [
|
||||
@@ -361,14 +624,31 @@ class MinerConfig:
|
||||
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
|
||||
cfg["autotuning"]["enabled"] = True
|
||||
else:
|
||||
cfg["autotuning"]["enabled"] = False
|
||||
if self.autotuning_mode:
|
||||
cfg["format"]["version"] = "2.0"
|
||||
cfg["autotuning"]["mode"] = self.autotuning_mode + "_target"
|
||||
if self.autotuning_wattage:
|
||||
cfg["autotuning"]["power_target"] = self.autotuning_wattage
|
||||
elif self.autotuning_hashrate:
|
||||
cfg["autotuning"]["hashrate_target"] = self.autotuning_hashrate
|
||||
else:
|
||||
if self.autotuning_wattage:
|
||||
cfg["autotuning"]["psu_power_limit"] = self.autotuning_wattage
|
||||
|
||||
if self.asicboost:
|
||||
cfg["hash_chain_global"] = {}
|
||||
cfg["hash_chain_global"]["asic_boost"] = self.asicboost
|
||||
|
||||
if self.minimum_fans is not None or self.fan_speed is not None:
|
||||
cfg["fan_control"] = {}
|
||||
if self.minimum_fans is not None:
|
||||
cfg["fan_control"]["min_fans"] = self.minimum_fans
|
||||
if self.fan_speed is not None:
|
||||
cfg["fan_control"]["speed"] = self.fan_speed
|
||||
|
||||
if any(
|
||||
[
|
||||
getattr(self, item)
|
||||
@@ -387,7 +667,10 @@ class MinerConfig:
|
||||
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 cfg["format"]["version"] == "2.0":
|
||||
cfg["power_scaling"]["min_power_target"] = self.dps_min_power
|
||||
else:
|
||||
cfg["power_scaling"]["min_psu_power_limit"] = self.dps_min_power
|
||||
if self.dps_shutdown_enabled:
|
||||
cfg["power_scaling"]["shutdown_enabled"] = self.dps_shutdown_enabled
|
||||
if self.dps_shutdown_duration:
|
||||
|
||||
@@ -1,5 +1,62 @@
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from datetime import datetime
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 copy
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Union
|
||||
|
||||
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
|
||||
|
||||
|
||||
@dataclass
|
||||
class HashBoard:
|
||||
"""A Dataclass to standardize hashboard data.
|
||||
|
||||
Attributes:
|
||||
slot: The slot of the board as an int.
|
||||
hashrate: The hashrate of the board in TH/s as a float.
|
||||
temp: The temperature of the PCB as an int.
|
||||
chip_temp: The temperature of the chips as an int.
|
||||
chips: The chip count of the board as an int.
|
||||
expected_chips: The ideal chip count of the board as an int.
|
||||
missing: Whether the board is returned from the miners data as a bool.
|
||||
"""
|
||||
|
||||
slot: int = 0
|
||||
hashrate: float = 0.0
|
||||
temp: int = -1
|
||||
chip_temp: int = -1
|
||||
chips: int = 0
|
||||
expected_chips: int = 0
|
||||
missing: bool = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class Fan:
|
||||
"""A Dataclass to standardize fan data.
|
||||
|
||||
Attributes:
|
||||
speed: The speed of the fan.
|
||||
"""
|
||||
|
||||
speed: int = -1
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -9,84 +66,143 @@ class MinerData:
|
||||
Attributes:
|
||||
ip: The IP of the miner as a str.
|
||||
datetime: The time and date this data was generated.
|
||||
mac: The MAC address of the miner as a str.
|
||||
model: The model of the miner as a str.
|
||||
make: The make of the miner as a str.
|
||||
api_ver: The current api version on the miner as a str.
|
||||
fw_ver: The current firmware version on the miner as a str.
|
||||
hostname: The network hostname of the miner as a str.
|
||||
hashrate: The hashrate of the miner in TH/s as a float.
|
||||
left_board_hashrate: The hashrate of the left board of the miner in TH/s as a float.
|
||||
center_board_hashrate: The hashrate of the center board of the miner in TH/s as a float.
|
||||
right_board_hashrate: The hashrate of the right board of the miner in TH/s as a float.
|
||||
hashrate: The hashrate of the miner in TH/s as a float. Calculated automatically.
|
||||
_hashrate: Backup for hashrate found via API instead of hashboards.
|
||||
nominal_hashrate: The factory nominal hashrate of the miner in TH/s as a float.
|
||||
hashboards: A list of hashboards on the miner with their statistics.
|
||||
temperature_avg: The average temperature across the boards. Calculated automatically.
|
||||
env_temp: The environment temps as a float.
|
||||
left_board_temp: The temp of the left PCB as an int.
|
||||
left_board_chip_temp: The temp of the left board chips as an int.
|
||||
center_board_temp: The temp of the center PCB as an int.
|
||||
center_board_chip_temp: The temp of the center board chips as an int.
|
||||
right_board_temp: The temp of the right PCB as an int.
|
||||
right_board_chip_temp: The temp of the right board chips as an int.
|
||||
wattage: Current power draw of the miner as an int.
|
||||
wattage_limit: Power limit of the miner as an int.
|
||||
fan_1: The speed of the first fan as an int.
|
||||
fan_2: The speed of the second fan as an int.
|
||||
fan_3: The speed of the third fan as an int.
|
||||
fan_4: The speed of the fourth fan as an int.
|
||||
left_chips: The number of chips online in the left board as an int.
|
||||
center_chips: The number of chips online in the left board as an int.
|
||||
right_chips: The number of chips online in the left board as an int.
|
||||
fans: A list of fans on the miner with their speeds.
|
||||
fan_psu: The speed of the PSU on the fan if the miner collects it.
|
||||
total_chips: The total number of chips on all boards. Calculated automatically.
|
||||
ideal_chips: The ideal number of chips in the miner as an int.
|
||||
perecent_ideal: The percent of total chips out of the ideal count. Calculated automatically.
|
||||
nominal: The nominal amount of chips in the miner. Calculated automatically.
|
||||
percent_ideal_chips: The percent of total chips out of the ideal count. Calculated automatically.
|
||||
percent_ideal_hashrate: The percent of total hashrate out of the ideal hashrate. Calculated automatically.
|
||||
percent_ideal_wattage: The percent of total wattage out of the ideal wattage. Calculated automatically.
|
||||
nominal: Whether the number of chips in the miner is nominal. Calculated automatically.
|
||||
pool_split: The pool split as a str.
|
||||
pool_1_url: The first pool url on the miner as a str.
|
||||
pool_1_user: The first pool user on the miner as a str.
|
||||
pool_2_url: The second pool url on the miner as a str.
|
||||
pool_2_user: The second pool user on the miner as a str.
|
||||
errors: A list of errors on the miner.
|
||||
fault_light: Whether the fault light is on as a boolean.
|
||||
efficiency: Efficiency of the miner in J/TH (Watts per TH/s). Calculated automatically.
|
||||
"""
|
||||
|
||||
ip: str
|
||||
datetime: datetime = None
|
||||
mac: str = "00:00:00:00:00:00"
|
||||
model: str = "Unknown"
|
||||
make: str = "Unknown"
|
||||
api_ver: str = "Unknown"
|
||||
fw_ver: str = "Unknown"
|
||||
hostname: str = "Unknown"
|
||||
hashrate: float = 0
|
||||
left_board_hashrate: float = 0
|
||||
center_board_hashrate: float = 0
|
||||
right_board_hashrate: float = 0
|
||||
hashrate: float = field(init=False)
|
||||
_hashrate: float = 0
|
||||
nominal_hashrate: float = 0
|
||||
hashboards: List[HashBoard] = field(default_factory=list)
|
||||
ideal_hashboards: int = 1
|
||||
temperature_avg: int = field(init=False)
|
||||
env_temp: float = 0
|
||||
left_board_temp: int = 0
|
||||
left_board_chip_temp: int = 0
|
||||
center_board_temp: int = 0
|
||||
center_board_chip_temp: int = 0
|
||||
right_board_temp: int = 0
|
||||
right_board_chip_temp: int = 0
|
||||
wattage: int = 0
|
||||
wattage_limit: int = 0
|
||||
fan_1: int = -1
|
||||
fan_2: int = -1
|
||||
fan_3: int = -1
|
||||
fan_4: int = -1
|
||||
left_chips: int = 0
|
||||
center_chips: int = 0
|
||||
right_chips: int = 0
|
||||
env_temp: float = -1.0
|
||||
wattage: int = -1
|
||||
wattage_limit: int = -1
|
||||
fans: List[Fan] = field(default_factory=list)
|
||||
fan_psu: int = -1
|
||||
total_chips: int = field(init=False)
|
||||
ideal_chips: int = 1
|
||||
percent_ideal: float = field(init=False)
|
||||
nominal: int = field(init=False)
|
||||
percent_ideal_chips: float = field(init=False)
|
||||
percent_ideal_hashrate: float = field(init=False)
|
||||
percent_ideal_wattage: float = field(init=False)
|
||||
nominal: bool = field(init=False)
|
||||
pool_split: str = "0"
|
||||
pool_1_url: str = "Unknown"
|
||||
pool_1_user: str = "Unknown"
|
||||
pool_2_url: str = ""
|
||||
pool_2_user: str = ""
|
||||
errors: list = field(default_factory=list)
|
||||
errors: List[
|
||||
Union[WhatsminerError, BraiinsOSError, X19Error, InnosiliconError]
|
||||
] = field(default_factory=list)
|
||||
fault_light: Union[bool, None] = None
|
||||
efficiency: int = field(init=False)
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return [f.name for f in fields(cls)]
|
||||
|
||||
def __post_init__(self):
|
||||
self.datetime = datetime.now()
|
||||
self.datetime = datetime.now(timezone.utc).astimezone()
|
||||
|
||||
def __getitem__(self, item):
|
||||
try:
|
||||
return getattr(self, item)
|
||||
except AttributeError:
|
||||
raise KeyError(f"{item}")
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
return setattr(self, key, value)
|
||||
|
||||
def __iter__(self):
|
||||
return iter([item for item in self.asdict()])
|
||||
|
||||
def __truediv__(self, other):
|
||||
return self // other
|
||||
|
||||
def __floordiv__(self, other):
|
||||
cp = copy.deepcopy(self)
|
||||
for key in self:
|
||||
item = getattr(self, key)
|
||||
if isinstance(item, int):
|
||||
setattr(cp, key, item // other)
|
||||
if isinstance(item, float):
|
||||
setattr(cp, key, round(item / other, 2))
|
||||
return cp
|
||||
|
||||
def __add__(self, other):
|
||||
if not isinstance(other, MinerData):
|
||||
raise TypeError("Cannot add MinerData to non MinerData type.")
|
||||
cp = copy.deepcopy(self)
|
||||
for key in self:
|
||||
item = getattr(self, key)
|
||||
other_item = getattr(other, key)
|
||||
if item is None:
|
||||
item = 0
|
||||
if other_item is None:
|
||||
other_item = 0
|
||||
|
||||
if isinstance(item, int):
|
||||
setattr(cp, key, item + other_item)
|
||||
if isinstance(item, float):
|
||||
setattr(cp, key, item + other_item)
|
||||
if isinstance(item, str):
|
||||
setattr(cp, key, "")
|
||||
if isinstance(item, list):
|
||||
setattr(cp, key, item + other_item)
|
||||
if isinstance(item, bool):
|
||||
setattr(cp, key, item & other_item)
|
||||
return cp
|
||||
|
||||
@property
|
||||
def hashrate(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) > 0:
|
||||
return round(sum(map(lambda x: x.hashrate, self.hashboards)), 2)
|
||||
return self._hashrate
|
||||
|
||||
@hashrate.setter
|
||||
def hashrate(self, val):
|
||||
self._hashrate = val
|
||||
|
||||
@property
|
||||
def total_chips(self): # noqa - Skip PyCharm inspection
|
||||
return self.right_chips + self.center_chips + self.left_chips
|
||||
return sum([hb.chips for hb in self.hashboards])
|
||||
|
||||
@total_chips.setter
|
||||
def total_chips(self, val):
|
||||
@@ -101,24 +217,42 @@ class MinerData:
|
||||
pass
|
||||
|
||||
@property
|
||||
def percent_ideal(self): # noqa - Skip PyCharm inspection
|
||||
def percent_ideal_chips(self): # noqa - Skip PyCharm inspection
|
||||
if self.total_chips == 0 or self.ideal_chips == 0:
|
||||
return 0
|
||||
return round((self.total_chips / self.ideal_chips) * 100)
|
||||
|
||||
@percent_ideal.setter
|
||||
def percent_ideal(self, val):
|
||||
@percent_ideal_chips.setter
|
||||
def percent_ideal_chips(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def percent_ideal_hashrate(self): # noqa - Skip PyCharm inspection
|
||||
if self.hashrate == 0 or self.nominal_hashrate == 0:
|
||||
return 0
|
||||
return round((self.hashrate / self.nominal_hashrate) * 100)
|
||||
|
||||
@percent_ideal_hashrate.setter
|
||||
def percent_ideal_hashrate(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def percent_ideal_wattage(self): # noqa - Skip PyCharm inspection
|
||||
if self.wattage_limit == 0 or self.wattage == 0:
|
||||
return 0
|
||||
return round((self.wattage / self.wattage_limit) * 100)
|
||||
|
||||
@percent_ideal_wattage.setter
|
||||
def percent_ideal_wattage(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def temperature_avg(self): # noqa - Skip PyCharm inspection
|
||||
total_temp = 0
|
||||
temp_count = 0
|
||||
for temp in [
|
||||
self.left_board_chip_temp,
|
||||
self.center_board_chip_temp,
|
||||
self.right_board_chip_temp,
|
||||
]:
|
||||
if temp and not temp == 0:
|
||||
total_temp += temp
|
||||
for hb in self.hashboards:
|
||||
if hb.temp and not hb.temp == -1:
|
||||
total_temp += hb.temp
|
||||
temp_count += 1
|
||||
if not temp_count > 0:
|
||||
return 0
|
||||
@@ -128,5 +262,108 @@ class MinerData:
|
||||
def temperature_avg(self, val):
|
||||
pass
|
||||
|
||||
def asdict(self):
|
||||
@property
|
||||
def efficiency(self): # noqa - Skip PyCharm inspection
|
||||
if self.hashrate == 0 or self.wattage == -1:
|
||||
return 0
|
||||
return round(self.wattage / self.hashrate)
|
||||
|
||||
@efficiency.setter
|
||||
def efficiency(self, val):
|
||||
pass
|
||||
|
||||
def asdict(self) -> dict:
|
||||
"""Get this dataclass as a dictionary.
|
||||
|
||||
Returns:
|
||||
A dictionary version of this class.
|
||||
"""
|
||||
logging.debug(f"MinerData - (To Dict) - Dumping Dict data")
|
||||
return asdict(self)
|
||||
|
||||
def as_json(self) -> str:
|
||||
"""Get this dataclass as JSON.
|
||||
|
||||
Returns:
|
||||
A JSON version of this class.
|
||||
"""
|
||||
logging.debug(f"MinerData - (To JSON) - Dumping JSON data")
|
||||
data = self.asdict()
|
||||
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
|
||||
return json.dumps(data)
|
||||
|
||||
def as_csv(self) -> str:
|
||||
"""Get this dataclass as CSV.
|
||||
|
||||
Returns:
|
||||
A CSV version of this class with no headers.
|
||||
"""
|
||||
logging.debug(f"MinerData - (To CSV) - Dumping CSV data")
|
||||
data = self.asdict()
|
||||
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
|
||||
errs = []
|
||||
for error in data["errors"]:
|
||||
errs.append(error["error_message"])
|
||||
data["errors"] = "; ".join(errs)
|
||||
data_list = [str(data[item]) for item in data]
|
||||
return ",".join(data_list)
|
||||
|
||||
def as_influxdb(self, measurement_name: str = "miner_data") -> str:
|
||||
"""Get this dataclass as [influxdb line protocol](https://docs.influxdata.com/influxdb/v2.4/reference/syntax/line-protocol/).
|
||||
|
||||
Parameters:
|
||||
measurement_name: The name of the measurement to insert into in influxdb.
|
||||
|
||||
Returns:
|
||||
A influxdb line protocol version of this class.
|
||||
"""
|
||||
logging.debug(f"MinerData - (To InfluxDB) - Dumping InfluxDB data")
|
||||
tag_data = [measurement_name]
|
||||
field_data = []
|
||||
|
||||
tags = ["ip", "mac", "model", "hostname"]
|
||||
for attribute in self:
|
||||
if attribute in tags:
|
||||
escaped_data = self[attribute].replace(" ", "\\ ")
|
||||
tag_data.append(f"{attribute}={escaped_data}")
|
||||
continue
|
||||
elif str(attribute).startswith("_"):
|
||||
continue
|
||||
elif isinstance(self[attribute], str):
|
||||
field_data.append(f'{attribute}="{self[attribute]}"')
|
||||
continue
|
||||
elif isinstance(self[attribute], bool):
|
||||
field_data.append(f"{attribute}={str(self[attribute]).lower()}")
|
||||
continue
|
||||
elif isinstance(self[attribute], int):
|
||||
field_data.append(f"{attribute}={self[attribute]}")
|
||||
continue
|
||||
elif isinstance(self[attribute], float):
|
||||
field_data.append(f"{attribute}={self[attribute]}")
|
||||
continue
|
||||
elif attribute == "fault_light" and not self[attribute]:
|
||||
field_data.append(f"{attribute}=false")
|
||||
continue
|
||||
elif attribute == "errors":
|
||||
for idx, item in enumerate(self[attribute]):
|
||||
field_data.append(f'error_{idx+1}="{item.error_message}"')
|
||||
elif attribute == "hashboards":
|
||||
for idx, item in enumerate(self[attribute]):
|
||||
field_data.append(f"hashboard_{idx+1}_hashrate={item.hashrate}")
|
||||
field_data.append(f"hashboard_{idx+1}_temperature={item.temp}")
|
||||
field_data.append(
|
||||
f"hashboard_{idx+1}_chip_temperature={item.chip_temp}"
|
||||
)
|
||||
field_data.append(f"hashboard_{idx+1}_chips={item.chips}")
|
||||
field_data.append(
|
||||
f"hashboard_{idx+1}_expected_chips={item.expected_chips}"
|
||||
)
|
||||
elif attribute == "fans":
|
||||
for idx, item in enumerate(self[attribute]):
|
||||
field_data.append(f"fan_{idx+1}={item.speed}")
|
||||
|
||||
tags_str = ",".join(tag_data)
|
||||
field_str = ",".join(field_data)
|
||||
timestamp = str(int(time.mktime(self.datetime.timetuple()) * 1e9))
|
||||
|
||||
return " ".join([tags_str, field_str, timestamp])
|
||||
|
||||
37
pyasic/data/error_codes/X19.py
Normal file
37
pyasic/data/error_codes/X19.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from dataclasses import asdict, dataclass, fields
|
||||
|
||||
|
||||
@dataclass
|
||||
class X19Error:
|
||||
"""A Dataclass to handle error codes of X19 miners.
|
||||
|
||||
Attributes:
|
||||
error_message: The error message as a string.
|
||||
error_code: The error code as an int. 0 if the message is not assigned a code.
|
||||
"""
|
||||
|
||||
error_message: str
|
||||
error_code: int = 0
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
@@ -1,2 +1,26 @@
|
||||
from .whatsminer import WhatsminerError
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from typing import TypeVar
|
||||
|
||||
from .bos import BraiinsOSError
|
||||
from .innosilicon import InnosiliconError
|
||||
from .whatsminer import WhatsminerError
|
||||
from .X19 import X19Error
|
||||
|
||||
MinerErrorData = TypeVar(
|
||||
"MinerErrorData", WhatsminerError, BraiinsOSError, X19Error, InnosiliconError
|
||||
)
|
||||
|
||||
@@ -1,11 +1,37 @@
|
||||
from dataclasses import dataclass, asdict
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from dataclasses import asdict, dataclass, fields
|
||||
|
||||
|
||||
@dataclass
|
||||
class BraiinsOSError:
|
||||
"""A Dataclass to handle error codes of BraiinsOS+ miners."""
|
||||
"""A Dataclass to handle error codes of BraiinsOS+ miners.
|
||||
|
||||
Attributes:
|
||||
error_message: The error message as a string.
|
||||
error_code: The error code as an int. 0 if the message is not assigned a code.
|
||||
"""
|
||||
|
||||
error_message: str
|
||||
error_code: int = 0
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
|
||||
71
pyasic/data/error_codes/innosilicon.py
Normal file
71
pyasic/data/error_codes/innosilicon.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
|
||||
|
||||
@dataclass
|
||||
class InnosiliconError:
|
||||
"""A Dataclass to handle error codes of Innosilicon miners.
|
||||
|
||||
Attributes:
|
||||
error_code: The error code as an int.
|
||||
error_message: The error message as a string. Automatically found from the error code.
|
||||
"""
|
||||
|
||||
error_code: int
|
||||
error_message: str = field(init=False)
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
@property
|
||||
def error_message(self): # noqa - Skip PyCharm inspection
|
||||
if self.error_code in ERROR_CODES:
|
||||
return ERROR_CODES[self.error_code]
|
||||
return "Unknown error type."
|
||||
|
||||
@error_message.setter
|
||||
def error_message(self, val):
|
||||
pass
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
|
||||
|
||||
ERROR_CODES = {
|
||||
21: "The PLUG signal of the hash board is not detected.",
|
||||
22: "Power I2C communication is abnormal.",
|
||||
23: "The SPI of all hash boards is blocked.",
|
||||
24: "Some of the hash boards fail to connect to the SPI'.",
|
||||
25: "Hashboard failed to set frequency.",
|
||||
26: "Hashboard failed to set voltage.",
|
||||
27: "Chip BIST test failed.",
|
||||
28: "Hashboard SPI communication is abnormal.",
|
||||
29: "Power I2C communication is abnormal.",
|
||||
30: "Pool connection failed.",
|
||||
31: "Individual chips are damaged.",
|
||||
32: "Over temperature protection.",
|
||||
33: "Hashboard fault.",
|
||||
34: "The data cables are not connected in the correct order.",
|
||||
35: "No power output.",
|
||||
36: "Hashboard fault.",
|
||||
37: "Control board and/or hashboard do not match.",
|
||||
40: "Power output is abnormal.",
|
||||
41: "Power output is abnormal.",
|
||||
42: "Hashboard fault.",
|
||||
}
|
||||
@@ -1,18 +1,82 @@
|
||||
from dataclasses import dataclass, field, asdict
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
|
||||
C_N_CODES = ["52", "53", "54", "55", "56"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class WhatsminerError:
|
||||
"""A Dataclass to handle error codes of Whatsminers."""
|
||||
"""A Dataclass to handle error codes of Whatsminers.
|
||||
|
||||
Attributes:
|
||||
error_code: The error code as an int.
|
||||
error_message: The error message as a string. Automatically found from the error code.
|
||||
"""
|
||||
|
||||
error_code: int
|
||||
error_message: str = field(init=False)
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
@property
|
||||
def error_message(self): # noqa - Skip PyCharm inspection
|
||||
if self.error_code in ERROR_CODES:
|
||||
return ERROR_CODES[self.error_code]
|
||||
return "Unknown error type."
|
||||
if len(str(self.error_code)) > 3 and str(self.error_code)[:2] in C_N_CODES:
|
||||
# 55 error code base has chip numbers, so the format is
|
||||
# 55 -> board num len 1 -> chip num len 3
|
||||
err_type = 55
|
||||
err_subtype = int(str(self.error_code)[2:3])
|
||||
err_value = int(str(self.error_code)[3:])
|
||||
else:
|
||||
err_type = int(str(self.error_code)[:-2])
|
||||
err_subtype = int(str(self.error_code)[-2:-1])
|
||||
err_value = int(str(self.error_code)[-1:])
|
||||
try:
|
||||
select_err_type = ERROR_CODES[err_type]
|
||||
if err_subtype in select_err_type:
|
||||
select_err_subtype = select_err_type[err_subtype]
|
||||
if err_value in select_err_subtype:
|
||||
return select_err_subtype[err_value]
|
||||
elif "n" in select_err_subtype:
|
||||
return select_err_subtype[
|
||||
"n" # noqa: picks up `select_err_subtype["n"]` as not being numeric?
|
||||
].replace("{n}", str(err_value))
|
||||
else:
|
||||
return "Unknown error type."
|
||||
elif "n" in select_err_type:
|
||||
select_err_subtype = select_err_type[
|
||||
"n" # noqa: picks up `select_err_subtype["n"]` as not being numeric?
|
||||
]
|
||||
if err_value in select_err_subtype:
|
||||
return select_err_subtype[err_value]
|
||||
elif "c" in select_err_subtype:
|
||||
return (
|
||||
select_err_subtype["c"]
|
||||
.replace( # noqa: picks up `select_err_subtype["n"]` as not being numeric?
|
||||
"{n}", str(err_subtype)
|
||||
)
|
||||
.replace("{c}", str(err_value))
|
||||
)
|
||||
else:
|
||||
return "Unknown error type."
|
||||
except KeyError:
|
||||
return "Unknown error type."
|
||||
|
||||
@error_message.setter
|
||||
def error_message(self, val):
|
||||
@@ -23,130 +87,252 @@ class WhatsminerError:
|
||||
|
||||
|
||||
ERROR_CODES = {
|
||||
110: "Intake fan speed error.",
|
||||
111: "Exhaust fan speed error.",
|
||||
120: "Intake fan speed error. Fan speed deviates by more than 2000.",
|
||||
121: "Exhaust fan speed error. Fan speed deviates by more than 2000.",
|
||||
130: "Intake fan speed error. Fan speed deviates by more than 3000.",
|
||||
131: "Exhaust fan speed error. Fan speed deviates by more than 3000.",
|
||||
140: "Fan speed too high.",
|
||||
200: "Power probing error. No power found.",
|
||||
201: "Power supply and configuration file don't match.",
|
||||
202: "Power output voltage error.",
|
||||
203: "Power protecting due to high environment temperature.",
|
||||
204: "Power current protecting due to high environment temperature.",
|
||||
205: "Power current error.",
|
||||
206: "Power input low voltage error.",
|
||||
207: "Power input current protecting due to bad power input.",
|
||||
210: "Power error.",
|
||||
213: "Power input voltage and current do not match power output.",
|
||||
216: "Power remained unchanged for a long time.",
|
||||
217: "Power set enable error.",
|
||||
218: "Power input voltage is lower than 230V for high power mode.",
|
||||
233: "Power output high temperature protection error.",
|
||||
234: "Power output high temperature protection error.",
|
||||
235: "Power output high temperature protection error.",
|
||||
236: "Power output high current protection error.",
|
||||
237: "Power output high current protection error.",
|
||||
238: "Power output high current protection error.",
|
||||
239: "Power output high voltage protection error.",
|
||||
240: "Power output low voltage protection error.",
|
||||
241: "Power output current imbalance error.",
|
||||
243: "Power input high temperature protection error.",
|
||||
244: "Power input high temperature protection error.",
|
||||
245: "Power input high temperature protection error.",
|
||||
246: "Power input high voltage protection error.",
|
||||
247: "Power input high voltage protection error.",
|
||||
248: "Power input high current protection error.",
|
||||
249: "Power input high current protection error.",
|
||||
250: "Power input low voltage protection error.",
|
||||
251: "Power input low voltage protection error.",
|
||||
253: "Power supply fan error.",
|
||||
254: "Power supply fan error.",
|
||||
255: "Power output high power protection error.",
|
||||
256: "Power output high power protection error.",
|
||||
257: "Input over current protection of power supply on primary side.",
|
||||
263: "Power communication warning.",
|
||||
264: "Power communication error.",
|
||||
267: "Power watchdog protection.",
|
||||
268: "Power output high current protection.",
|
||||
269: "Power input high current protection.",
|
||||
270: "Power input high voltage protection.",
|
||||
271: "Power input low voltage protection.",
|
||||
272: "Excessive power supply output warning.",
|
||||
273: "Power input too high warning.",
|
||||
274: "Power fan warning.",
|
||||
275: "Power high temperature warning.",
|
||||
300: "Right board temperature sensor detection error.",
|
||||
301: "Center board temperature sensor detection error.",
|
||||
302: "Left board temperature sensor detection error.",
|
||||
320: "Right board temperature reading error.",
|
||||
321: "Center board temperature reading error.",
|
||||
322: "Left board temperature reading error.",
|
||||
329: "Control board temperature sensor communication error.",
|
||||
350: "Right board temperature protecting.",
|
||||
351: "Center board temperature protecting.",
|
||||
352: "Left board temperature protecting.",
|
||||
360: "Hashboard high temperature error.",
|
||||
410: "Right board eeprom detection error.",
|
||||
411: "Center board eeprom detection error.",
|
||||
412: "Left board eeprom detection error.",
|
||||
420: "Right board eeprom parsing error.",
|
||||
421: "Center board eeprom parsing error.",
|
||||
422: "Left board eeprom parsing error.",
|
||||
430: "Right board chip bin type error.",
|
||||
431: "Center board chip bin type error.",
|
||||
432: "Left board chip bin type error.",
|
||||
440: "Right board eeprom chip number X error.",
|
||||
441: "Center board eeprom chip number X error.",
|
||||
442: "Left board eeprom chip number X error.",
|
||||
450: "Right board eeprom xfer error.",
|
||||
451: "Center board eeprom xfer error.",
|
||||
452: "Left board eeprom xfer error.",
|
||||
510: "Right board miner type error.",
|
||||
511: "Center board miner type error.",
|
||||
512: "Left board miner type error.",
|
||||
520: "Right board bin type error.",
|
||||
521: "Center board bin type error.",
|
||||
522: "Left board bin type error.",
|
||||
530: "Right board not found.",
|
||||
531: "Center board not found.",
|
||||
532: "Left board not found.",
|
||||
540: "Right board error reading chip id.",
|
||||
541: "Center board error reading chip id.",
|
||||
542: "Left board error reading chip id.",
|
||||
550: "Right board has bad chips.",
|
||||
551: "Center board has bad chips.",
|
||||
552: "Left board has bad chips.",
|
||||
560: "Right board loss of balance error.",
|
||||
561: "Center board loss of balance error.",
|
||||
562: "Left board loss of balance error.",
|
||||
600: "Environment temperature is too high.",
|
||||
610: "Environment temperature is too high for high performance mode.",
|
||||
701: "Control board no support chip.",
|
||||
710: "Control board rebooted as an exception.",
|
||||
712: "Control board rebooted as an exception.",
|
||||
800: "CGMiner checksum error.",
|
||||
801: "System monitor checksum error.",
|
||||
802: "Remote daemon checksum error.",
|
||||
2010: "All pools are disabled.",
|
||||
2020: "Pool 0 connection failed.",
|
||||
2021: "Pool 1 connection failed.",
|
||||
2022: "Pool 2 connection failed.",
|
||||
2030: "High rejection rate on pool.",
|
||||
2040: "The pool does not support asicboost mode.",
|
||||
2310: "Hashrate is too low.",
|
||||
2320: "Hashrate is too low.",
|
||||
2340: "Hashrate loss is too high.",
|
||||
2350: "Hashrate loss is too high.",
|
||||
5070: "Right hashboard water velocity is abnormal.",
|
||||
5071: "Center hashboard water velocity is abnormal.",
|
||||
5072: "Left hashboard water velocity is abnormal.",
|
||||
5110: "Right hashboard frequency up timeout.",
|
||||
5111: "Center hashboard frequency up timeout.",
|
||||
5112: "Left hashboard frequency up timeout.",
|
||||
8410: "Software version error.",
|
||||
100001: "/antiv/signature illegal.",
|
||||
100002: "/antiv/dig/init.d illegal.",
|
||||
100003: "/antiv/dig/pf_partial.dig illegal.",
|
||||
1: { # Fan error
|
||||
0: {0: "Fan unknown."},
|
||||
1: { # Fan speed error of 1000+
|
||||
0: "Intake fan speed error.",
|
||||
1: "Exhaust fan speed error.",
|
||||
},
|
||||
2: { # Fan speed error of 2000+
|
||||
0: "Intake fan speed error. Fan speed deviates by more than 2000.",
|
||||
1: "Exhaust fan speed error. Fan speed deviates by more than 2000.",
|
||||
},
|
||||
3: { # Fan speed error of 3000+
|
||||
0: "Intake fan speed error. Fan speed deviates by more than 3000.",
|
||||
1: "Exhaust fan speed error. Fan speed deviates by more than 3000.",
|
||||
},
|
||||
4: {0: "Fan speed too high."}, # High speed
|
||||
},
|
||||
2: { # Power error
|
||||
0: {
|
||||
0: "Power probing error. No power found.",
|
||||
1: "Power supply and configuration file don't match.",
|
||||
2: "Power output voltage error.",
|
||||
3: "Power protecting due to high environment temperature.",
|
||||
4: "Power current protecting due to high environment temperature.",
|
||||
5: "Power current error.",
|
||||
6: "Power input low voltage error.",
|
||||
7: "Power input current protecting due to bad power input.",
|
||||
8: "Power power error.",
|
||||
9: "Power voltage offset error.",
|
||||
},
|
||||
1: {
|
||||
0: "Power error.",
|
||||
1: "Power iout error, please reboot.",
|
||||
2: "Power vout error, reach vout border. Border: [1150, 1500]",
|
||||
3: "Power input voltage and current do not match power output.",
|
||||
4: "Power pin did not change.",
|
||||
5: "Power vout set error.",
|
||||
6: "Power remained unchanged for a long time.",
|
||||
7: "Power set enable error.",
|
||||
8: "Power input voltage is lower than 230V for high power mode.",
|
||||
},
|
||||
3: {
|
||||
3: "Power output high temperature protection error.",
|
||||
4: "Power output high temperature protection error.",
|
||||
5: "Power output high temperature protection error.",
|
||||
6: "Power output high current protection error.",
|
||||
7: "Power output high current protection error.",
|
||||
8: "Power output high current protection error.",
|
||||
9: "Power output high voltage protection error.",
|
||||
},
|
||||
4: {
|
||||
0: "Power output low voltage protection error.",
|
||||
1: "Power output current imbalance error.",
|
||||
3: "Power input high temperature protection error.",
|
||||
4: "Power input high temperature protection error.",
|
||||
5: "Power input high temperature protection error.",
|
||||
6: "Power input high voltage protection error.",
|
||||
7: "Power input high voltage protection error.",
|
||||
8: "Power input high current protection error.",
|
||||
9: "Power input high current protection error.",
|
||||
},
|
||||
5: {
|
||||
0: "Power input low voltage protection error.",
|
||||
1: "Power input low voltage protection error.",
|
||||
3: "Power supply fan error.",
|
||||
4: "Power supply fan error.",
|
||||
5: "Power output high power protection error.",
|
||||
6: "Power output high power protection error.",
|
||||
7: "Input over current protection of power supply on primary side.",
|
||||
},
|
||||
6: {
|
||||
3: "Power communication warning.",
|
||||
4: "Power communication error.",
|
||||
7: "Power watchdog protection.",
|
||||
8: "Power output high current protection.",
|
||||
9: "Power input high current protection.",
|
||||
},
|
||||
7: {
|
||||
0: "Power input high voltage protection.",
|
||||
1: "Power input low voltage protection.",
|
||||
2: "Excessive power supply output warning.",
|
||||
3: "Power input too high warning.",
|
||||
4: "Power fan warning.",
|
||||
5: "Power high temperature warning.",
|
||||
},
|
||||
},
|
||||
3: { # temperature error
|
||||
0: { # sensor detection error
|
||||
"n": "Slot {n} temperature sensor detection error."
|
||||
},
|
||||
2: { # temperature reading error
|
||||
"n": "Slot {n} temperature reading error.",
|
||||
9: "Control board temperature sensor communication error.",
|
||||
},
|
||||
5: {"n": "Slot {n} temperature protecting."}, # temperature protection
|
||||
6: {0: "Hashboard high temperature error."}, # high temp
|
||||
8: {
|
||||
0: "Humidity sensor not found.",
|
||||
1: "Humidity sensor read error.",
|
||||
2: "Humidity sensor read error.",
|
||||
3: "Humidity sensor protecting.",
|
||||
},
|
||||
},
|
||||
4: { # EEPROM error
|
||||
0: {0: "Eeprom unknown error."},
|
||||
1: {"n": "Slot {n} eeprom detection error."}, # EEPROM detection error
|
||||
2: {"n": "Slot {n} eeprom parsing error."}, # EEPROM parsing error
|
||||
3: {"n": "Slot {n} chip bin type error."}, # chip bin error
|
||||
4: {"n": "Slot {n} eeprom chip number X error."}, # EEPROM chip number error
|
||||
5: {"n": "Slot {n} eeprom xfer error."}, # EEPROM xfer error
|
||||
},
|
||||
5: { # hashboard error
|
||||
0: {0: "Board unknown error."},
|
||||
1: {"n": "Slot {n} miner type error."}, # board miner type error
|
||||
2: {"n": "Slot {n} bin type error."}, # chip bin type error
|
||||
3: {"n": "Slot {n} not found."}, # board not found error
|
||||
4: {"n": "Slot {n} error reading chip id."}, # reading chip id error
|
||||
5: {"n": "Slot {n} has bad chips."}, # board has bad chips error
|
||||
6: {"n": "Slot {n} loss of balance error."}, # loss of balance error
|
||||
7: {"n": "Slot {n} xfer error chip."}, # xfer error
|
||||
8: {"n": "Slot {n} reset error."}, # reset error
|
||||
9: {"n": "Slot {n} frequency too low."}, # freq error
|
||||
},
|
||||
6: { # env temp error
|
||||
0: {0: "Environment temperature is too high."}, # normal env temp error
|
||||
1: { # high power env temp error
|
||||
0: "Environment temperature is too high for high performance mode."
|
||||
},
|
||||
},
|
||||
7: { # control board error
|
||||
0: {0: "MAC address invalid", 1: "Control board no support chip."},
|
||||
1: {
|
||||
0: "Control board rebooted as an exception.",
|
||||
1: "Control board rebooted as exception and cpufreq reduced, please upgrade the firmware",
|
||||
2: "Control board rebooted as an exception.",
|
||||
},
|
||||
},
|
||||
8: { # checksum error
|
||||
0: {
|
||||
0: "CGMiner checksum error.",
|
||||
1: "System monitor checksum error.",
|
||||
2: "Remote daemon checksum error.",
|
||||
}
|
||||
},
|
||||
9: {0: {1: "Power rate error."}}, # power rate error
|
||||
20: { # pool error
|
||||
1: {0: "All pools are disabled."}, # all disabled error
|
||||
2: {"n": "Pool {n} connection failed."}, # pool connection failed error
|
||||
3: {0: "High rejection rate on pool."}, # rejection rate error
|
||||
4: { # asicboost not supported error
|
||||
0: "The pool does not support asicboost mode."
|
||||
},
|
||||
},
|
||||
21: {1: {"n": "Slot {n} factory test step failed."}},
|
||||
23: { # hashrate error
|
||||
1: {0: "Hashrate is too low."},
|
||||
2: {0: "Hashrate is too low."},
|
||||
3: {0: "Hashrate loss is too high."},
|
||||
4: {0: "Hashrate loss is too high."},
|
||||
5: {0: "Hashrate loss."},
|
||||
},
|
||||
50: { # water velocity error/voltage error
|
||||
1: {"n": "Slot {n} chip voltage too low."},
|
||||
2: {"n": "Slot {n} chip voltage changed."},
|
||||
3: {"n": "Slot {n} chip temperature difference is too large."},
|
||||
4: {"n": "Slot {n} chip hottest temperature difference is too large."},
|
||||
7: {"n": "Slot {n} water velocity is abnormal."}, # abnormal water velocity
|
||||
8: {0: "Chip temp calibration failed, please restore factory settings."},
|
||||
9: {"n": "Slot {n} chip temp calibration check no balance."},
|
||||
},
|
||||
51: { # frequency error
|
||||
1: {"n": "Slot {n} frequency up timeout."}, # frequency up timeout
|
||||
7: {"n": "Slot {n} frequency up timeout."}, # frequency up timeout
|
||||
},
|
||||
52: {"n": {"c": "Slot {n} chip {c} error nonce."}},
|
||||
53: {"n": {"c": "Slot {n} chip {c} too few nonce."}},
|
||||
54: {"n": {"c": "Slot {n} chip {c} temp protected."}},
|
||||
55: {"n": {"c": "Slot {n} chip {c} has been reset."}},
|
||||
56: {"n": {"c": "Slot {n} chip {c} does not return to the nonce."}},
|
||||
80: {
|
||||
0: {0: "The tool version is too low, please update."},
|
||||
1: {0: "Low freq."},
|
||||
2: {0: "Low hashrate."},
|
||||
3: {5: "High env temp."},
|
||||
},
|
||||
81: {
|
||||
0: {0: "Chip data error."},
|
||||
},
|
||||
82: {
|
||||
0: {0: "Power version error."},
|
||||
1: {0: "Miner type error."},
|
||||
2: {0: "Version info error."},
|
||||
},
|
||||
83: {
|
||||
0: {0: "Empty level error."},
|
||||
},
|
||||
84: {
|
||||
0: {0: "Old firmware."},
|
||||
1: {0: "Software version error."},
|
||||
},
|
||||
85: {
|
||||
"n": {
|
||||
0: "Hashrate substandard L{n}.",
|
||||
1: "Power consumption substandard L{n}.",
|
||||
2: "Fan speed substandard L{n}.",
|
||||
3: "Fan speed substandard L{n}.",
|
||||
4: "Voltage substandard L{n}.",
|
||||
},
|
||||
},
|
||||
86: {
|
||||
0: {0: "Missing product serial #."},
|
||||
1: {0: "Missing product type."},
|
||||
2: {
|
||||
0: "Missing miner serial #.",
|
||||
1: "Wrong miner serial # length.",
|
||||
},
|
||||
3: {
|
||||
0: "Missing power serial #.",
|
||||
1: "Wrong power serial #.",
|
||||
2: "Fault miner serial #.",
|
||||
},
|
||||
4: {
|
||||
0: "Missing power model.",
|
||||
1: "Wrong power model name.",
|
||||
2: "Wrong power model vout.",
|
||||
3: "Wrong power model rate.",
|
||||
4: "Wrong power model format.",
|
||||
},
|
||||
5: {0: "Wrong hash board struct."},
|
||||
6: {0: "Wrong miner cooling type."},
|
||||
7: {0: "Missing PCB serial #."},
|
||||
},
|
||||
87: {0: {0: "Miner power mismatch."}},
|
||||
99: {9: {9: "Miner unknown error."}},
|
||||
1000: {
|
||||
0: {
|
||||
0: "Security library error, please upgrade firmware",
|
||||
1: "/antiv/signature illegal.",
|
||||
2: "/antiv/dig/init.d illegal.",
|
||||
3: "/antiv/dig/pf_partial.dig illegal.",
|
||||
},
|
||||
},
|
||||
1001: {0: {0: "Security BTMiner removed, please upgrade firmware."}},
|
||||
1100: {
|
||||
0: {
|
||||
0: "Security illegal file, please upgrade firmware.",
|
||||
1: "Security virus 0001 is removed, please upgrade firmware.",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
59
pyasic/errors/__init__.py
Normal file
59
pyasic/errors/__init__.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
|
||||
class APIError(Exception):
|
||||
def __init__(self, *args):
|
||||
if args:
|
||||
self.message = args[0]
|
||||
else:
|
||||
self.message = None
|
||||
|
||||
def __str__(self):
|
||||
if self.message:
|
||||
if self.message == "can't access write cmd":
|
||||
return f"{self.message}, please make sure your miner has been unlocked."
|
||||
return f"{self.message}"
|
||||
else:
|
||||
return "Incorrect API parameters."
|
||||
|
||||
|
||||
class PhaseBalancingError(Exception):
|
||||
def __init__(self, *args):
|
||||
if args:
|
||||
self.message = args[0]
|
||||
else:
|
||||
self.message = None
|
||||
|
||||
def __str__(self):
|
||||
if self.message:
|
||||
return f"{self.message}"
|
||||
else:
|
||||
return "Failed to balance phase."
|
||||
|
||||
|
||||
class APIWarning(Warning):
|
||||
def __init__(self, *args):
|
||||
if args:
|
||||
self.message = args[0]
|
||||
else:
|
||||
self.message = None
|
||||
|
||||
def __str__(self):
|
||||
if self.message:
|
||||
return f"{self.message}"
|
||||
else:
|
||||
return "Incorrect API parameters."
|
||||
337
pyasic/load/__init__.py
Normal file
337
pyasic/load/__init__.py
Normal file
@@ -0,0 +1,337 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
from typing import List, Union
|
||||
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners import AnyMiner
|
||||
from pyasic.miners.backends import AntminerModern, BOSMiner, BTMiner
|
||||
from pyasic.miners.types import S9, S17, T17, S17e, S17Plus, S17Pro, T17e, T17Plus
|
||||
|
||||
FAN_USAGE = 50 # 50 W per fan
|
||||
|
||||
|
||||
class MinerLoadBalancer:
|
||||
"""A load balancer for miners. Can be passed a list of `AnyMiner`, or a list of phases (lists of `AnyMiner`)."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
phases: Union[List[List[AnyMiner]], None] = None,
|
||||
):
|
||||
self.phases = [_MinerPhaseBalancer(phase) for phase in phases]
|
||||
|
||||
async def balance(self, wattage: int) -> int:
|
||||
phase_wattage = wattage // len(self.phases)
|
||||
setpoints = await asyncio.gather(
|
||||
*[phase.get_balance_setpoints(phase_wattage) for phase in self.phases]
|
||||
)
|
||||
tasks = []
|
||||
total_wattage = 0
|
||||
for setpoint in setpoints:
|
||||
wattage_set = 0
|
||||
for miner in setpoint:
|
||||
if setpoint[miner]["set"] == "on":
|
||||
wattage_set += setpoint[miner]["max"]
|
||||
tasks.append(setpoint[miner]["miner"].resume_mining())
|
||||
elif setpoint[miner]["set"] == "off":
|
||||
wattage_set += setpoint[miner]["min"]
|
||||
tasks.append(setpoint[miner]["miner"].stop_mining())
|
||||
else:
|
||||
wattage_set += setpoint[miner]["set"]
|
||||
tasks.append(
|
||||
setpoint[miner]["miner"].set_power_limit(setpoint[miner]["set"])
|
||||
)
|
||||
total_wattage += wattage_set
|
||||
await asyncio.gather(*tasks)
|
||||
return total_wattage
|
||||
|
||||
|
||||
class _MinerPhaseBalancer:
|
||||
def __init__(self, miners: List[AnyMiner]):
|
||||
self.miners = {
|
||||
str(miner.ip): {
|
||||
"miner": miner,
|
||||
"set": 0,
|
||||
"min": miner.fan_count * FAN_USAGE,
|
||||
}
|
||||
for miner in miners
|
||||
}
|
||||
for miner in miners:
|
||||
if (
|
||||
isinstance(miner, BTMiner)
|
||||
and not (miner.model.startswith("M2") if miner.model else True)
|
||||
) or isinstance(miner, BOSMiner):
|
||||
if isinstance(miner, S9):
|
||||
self.miners[str(miner.ip)]["tune"] = True
|
||||
self.miners[str(miner.ip)]["shutdown"] = True
|
||||
self.miners[str(miner.ip)]["max"] = 1400
|
||||
elif True in [
|
||||
isinstance(miner, x)
|
||||
for x in [S17, S17Plus, S17Pro, S17e, T17, T17Plus, T17e]
|
||||
]:
|
||||
self.miners[str(miner.ip)]["tune"] = True
|
||||
self.miners[str(miner.ip)]["shutdown"] = True
|
||||
self.miners[str(miner.ip)]["max"] = 2400
|
||||
else:
|
||||
self.miners[str(miner.ip)]["tune"] = True
|
||||
self.miners[str(miner.ip)]["shutdown"] = True
|
||||
self.miners[str(miner.ip)]["max"] = 3600
|
||||
elif isinstance(miner, AntminerModern):
|
||||
self.miners[str(miner.ip)]["tune"] = False
|
||||
self.miners[str(miner.ip)]["shutdown"] = True
|
||||
self.miners[str(miner.ip)]["max"] = 3600
|
||||
elif isinstance(miner, BTMiner):
|
||||
self.miners[str(miner.ip)]["tune"] = False
|
||||
self.miners[str(miner.ip)]["shutdown"] = True
|
||||
self.miners[str(miner.ip)]["max"] = 3600
|
||||
if miner.model:
|
||||
if miner.model.startswith("M2"):
|
||||
self.miners[str(miner.ip)]["tune"] = False
|
||||
self.miners[str(miner.ip)]["shutdown"] = True
|
||||
self.miners[str(miner.ip)]["max"] = 2400
|
||||
else:
|
||||
self.miners[str(miner.ip)]["tune"] = False
|
||||
self.miners[str(miner.ip)]["shutdown"] = False
|
||||
self.miners[str(miner.ip)]["max"] = 3600
|
||||
self.miners[str(miner.ip)]["min"] = 3600
|
||||
|
||||
async def balance(self, wattage: int) -> int:
|
||||
setpoint = await self.get_balance_setpoints(wattage)
|
||||
wattage_set = 0
|
||||
tasks = []
|
||||
for miner in setpoint:
|
||||
if setpoint[miner]["set"] == "on":
|
||||
wattage_set += setpoint[miner]["max"]
|
||||
tasks.append(setpoint[miner]["miner"].resume_mining())
|
||||
elif setpoint[miner]["set"] == "off":
|
||||
wattage_set += setpoint[miner]["min"]
|
||||
tasks.append(setpoint[miner]["miner"].stop_mining())
|
||||
else:
|
||||
wattage_set += setpoint[miner]["set"]
|
||||
tasks.append(
|
||||
setpoint[miner]["miner"].set_power_limit(setpoint[miner]["set"])
|
||||
)
|
||||
await asyncio.gather(*tasks)
|
||||
return wattage_set
|
||||
|
||||
async def get_balance_setpoints(self, wattage: int) -> dict:
|
||||
# gather data needed to optimize shutdown only miners
|
||||
dp = ["hashrate", "wattage", "wattage_limit", "hashboards"]
|
||||
data = await asyncio.gather(
|
||||
*[
|
||||
self.miners[miner]["miner"].get_data(data_to_get=dp)
|
||||
for miner in self.miners
|
||||
]
|
||||
)
|
||||
pct_ideal_list = [d.percent_ideal for d in data]
|
||||
pct_ideal = 0
|
||||
if len(pct_ideal_list) > 0:
|
||||
pct_ideal = sum(pct_ideal_list) / len(pct_ideal_list)
|
||||
|
||||
wattage = round(wattage * 1 / (pct_ideal / 100))
|
||||
|
||||
for data_point in data:
|
||||
if (not self.miners[data_point.ip]["tune"]) and (
|
||||
not self.miners[data_point.ip]["shutdown"]
|
||||
):
|
||||
# cant do anything with it so need to find a semi-accurate power limit
|
||||
if not data_point.wattage_limit == -1:
|
||||
self.miners[data_point.ip]["max"] = int(data_point.wattage_limit)
|
||||
self.miners[data_point.ip]["min"] = int(data_point.wattage_limit)
|
||||
elif not data_point.wattage == -1:
|
||||
self.miners[data_point.ip]["max"] = int(data_point.wattage)
|
||||
self.miners[data_point.ip]["min"] = int(data_point.wattage)
|
||||
|
||||
max_tune_wattage = sum(
|
||||
[miner["max"] for miner in self.miners.values() if miner["tune"]]
|
||||
)
|
||||
max_shutdown_wattage = sum(
|
||||
[
|
||||
miner["max"]
|
||||
for miner in self.miners.values()
|
||||
if (not miner["tune"]) and (miner["shutdown"])
|
||||
]
|
||||
)
|
||||
max_other_wattage = sum(
|
||||
[
|
||||
miner["max"]
|
||||
for miner in self.miners.values()
|
||||
if (not miner["tune"]) and (not miner["shutdown"])
|
||||
]
|
||||
)
|
||||
min_tune_wattage = sum(
|
||||
[miner["min"] for miner in self.miners.values() if miner["tune"]]
|
||||
)
|
||||
min_shutdown_wattage = sum(
|
||||
[
|
||||
miner["min"]
|
||||
for miner in self.miners.values()
|
||||
if (not miner["tune"]) and (miner["shutdown"])
|
||||
]
|
||||
)
|
||||
# min_other_wattage = sum([miner["min"] for miner in self.miners.values() if (not miner["tune"]) and (not miner["shutdown"])])
|
||||
|
||||
# make sure wattage isnt set too high
|
||||
if wattage > (max_tune_wattage + max_shutdown_wattage + max_other_wattage):
|
||||
raise APIError(
|
||||
f"Wattage setpoint is too high, setpoint: {wattage}W, max: {max_tune_wattage + max_shutdown_wattage + max_other_wattage}W"
|
||||
) # PhaseBalancingError(f"Wattage setpoint is too high, setpoint: {wattage}W, max: {max_tune_wattage + max_shutdown_wattage + max_other_wattage}W")
|
||||
|
||||
# should now know wattage limits and which can be tuned/shutdown
|
||||
# check if 1/2 max of the miners which can be tuned is low enough
|
||||
if (max_tune_wattage / 2) + max_shutdown_wattage + max_other_wattage < wattage:
|
||||
useable_wattage = wattage - (max_other_wattage + max_shutdown_wattage)
|
||||
useable_miners = len(
|
||||
[m for m in self.miners.values() if (m["set"] == 0) and (m["tune"])]
|
||||
)
|
||||
if not useable_miners == 0:
|
||||
watts_per_miner = useable_wattage // useable_miners
|
||||
# loop through and set useable miners to wattage
|
||||
for miner in self.miners:
|
||||
if (self.miners[miner]["set"] == 0) and (
|
||||
self.miners[miner]["tune"]
|
||||
):
|
||||
self.miners[miner]["set"] = watts_per_miner
|
||||
elif self.miners[miner]["set"] == 0 and (
|
||||
self.miners[miner]["shutdown"]
|
||||
):
|
||||
self.miners[miner]["set"] = "on"
|
||||
|
||||
# check if shutting down miners will help
|
||||
elif (
|
||||
max_tune_wattage / 2
|
||||
) + min_shutdown_wattage + max_other_wattage < wattage:
|
||||
# tuneable inclusive since could be S9 BOS+ and S19 Stock, would rather shut down the S9, tuneable should always support shutdown
|
||||
useable_wattage = wattage - (
|
||||
min_tune_wattage + max_other_wattage + min_shutdown_wattage
|
||||
)
|
||||
for miner in sorted(
|
||||
[miner for miner in self.miners.values() if miner["shutdown"]],
|
||||
key=lambda x: x["max"],
|
||||
reverse=True,
|
||||
):
|
||||
if miner["tune"]:
|
||||
miner_min_watt_use = miner["max"] / 2
|
||||
useable_wattage -= miner_min_watt_use - miner["min"]
|
||||
if useable_wattage < 0:
|
||||
useable_wattage += miner_min_watt_use - miner["min"]
|
||||
self.miners[str(miner["miner"].ip)]["set"] = "off"
|
||||
else:
|
||||
miner_min_watt_use = miner["max"]
|
||||
useable_wattage -= miner_min_watt_use - miner["min"]
|
||||
if useable_wattage < 0:
|
||||
useable_wattage += miner_min_watt_use - miner["min"]
|
||||
self.miners[str(miner["miner"].ip)]["set"] = "off"
|
||||
|
||||
new_shutdown_wattage = sum(
|
||||
[
|
||||
miner["max"] if miner["set"] == 0 else miner["min"]
|
||||
for miner in self.miners.values()
|
||||
if miner["shutdown"] and not miner["tune"]
|
||||
]
|
||||
)
|
||||
new_tune_wattage = sum(
|
||||
[
|
||||
miner["min"]
|
||||
for miner in self.miners.values()
|
||||
if miner["tune"] and miner["set"] == "off"
|
||||
]
|
||||
)
|
||||
|
||||
useable_wattage = wattage - (
|
||||
new_tune_wattage + max_other_wattage + new_shutdown_wattage
|
||||
)
|
||||
useable_miners = len(
|
||||
[m for m in self.miners.values() if (m["set"] == 0) and (m["tune"])]
|
||||
)
|
||||
|
||||
if not useable_miners == 0:
|
||||
watts_per_miner = useable_wattage // useable_miners
|
||||
# loop through and set useable miners to wattage
|
||||
for miner in self.miners:
|
||||
if (self.miners[miner]["set"] == 0) and (
|
||||
self.miners[miner]["tune"]
|
||||
):
|
||||
self.miners[miner]["set"] = watts_per_miner
|
||||
elif self.miners[miner]["set"] == 0 and (
|
||||
self.miners[miner]["shutdown"]
|
||||
):
|
||||
self.miners[miner]["set"] = "on"
|
||||
|
||||
# check if shutting down tuneable miners will do it
|
||||
elif min_tune_wattage + min_shutdown_wattage + max_other_wattage < wattage:
|
||||
# all miners that can be shutdown need to be
|
||||
for miner in self.miners:
|
||||
if (not self.miners[miner]["tune"]) and (
|
||||
self.miners[miner]["shutdown"]
|
||||
):
|
||||
self.miners[miner]["set"] = "off"
|
||||
# calculate wattage usable by tuneable miners
|
||||
useable_wattage = wattage - (
|
||||
min_tune_wattage + max_other_wattage + min_shutdown_wattage
|
||||
)
|
||||
|
||||
# loop through miners to see how much is actually useable
|
||||
# sort the largest first
|
||||
for miner in sorted(
|
||||
[
|
||||
miner
|
||||
for miner in self.miners.values()
|
||||
if miner["tune"] and miner["shutdown"]
|
||||
],
|
||||
key=lambda x: x["max"],
|
||||
reverse=True,
|
||||
):
|
||||
# add min to useable wattage since it was removed earlier, and remove 1/2 tuner max
|
||||
useable_wattage -= (miner["max"] / 2) - miner["min"]
|
||||
if useable_wattage < 0:
|
||||
useable_wattage += (miner["max"] / 2) - miner["min"]
|
||||
self.miners[str(miner["miner"].ip)]["set"] = "off"
|
||||
|
||||
new_tune_wattage = sum(
|
||||
[
|
||||
miner["min"]
|
||||
for miner in self.miners.values()
|
||||
if miner["tune"] and miner["set"] == "off"
|
||||
]
|
||||
)
|
||||
|
||||
useable_wattage = wattage - (
|
||||
new_tune_wattage + max_other_wattage + min_shutdown_wattage
|
||||
)
|
||||
useable_miners = len(
|
||||
[m for m in self.miners.values() if (m["set"] == 0) and (m["tune"])]
|
||||
)
|
||||
|
||||
if not useable_miners == 0:
|
||||
watts_per_miner = useable_wattage // useable_miners
|
||||
# loop through and set useable miners to wattage
|
||||
for miner in self.miners:
|
||||
if (self.miners[miner]["set"] == 0) and (
|
||||
self.miners[miner]["tune"]
|
||||
):
|
||||
self.miners[miner]["set"] = watts_per_miner
|
||||
elif self.miners[miner]["set"] == 0 and (
|
||||
self.miners[miner]["shutdown"]
|
||||
):
|
||||
self.miners[miner]["set"] = "on"
|
||||
else:
|
||||
raise APIError(
|
||||
f"Wattage setpoint is too low, setpoint: {wattage}W, min: {min_tune_wattage + min_shutdown_wattage + max_other_wattage}W"
|
||||
) # PhaseBalancingError(f"Wattage setpoint is too low, setpoint: {wattage}W, min: {min_tune_wattage + min_shutdown_wattage + max_other_wattage}W")
|
||||
|
||||
return self.miners
|
||||
@@ -1,9 +1,26 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
from pyasic.settings import DEBUG, LOGFILE
|
||||
|
||||
from pyasic.settings import PyasicSettings
|
||||
|
||||
|
||||
def init_logger():
|
||||
if LOGFILE:
|
||||
if PyasicSettings().logfile:
|
||||
logging.basicConfig(
|
||||
filename="logfile.txt",
|
||||
filemode="a",
|
||||
@@ -18,7 +35,7 @@ def init_logger():
|
||||
|
||||
_logger = logging.getLogger()
|
||||
|
||||
if DEBUG:
|
||||
if PyasicSettings().debug:
|
||||
_logger.setLevel(logging.DEBUG)
|
||||
logging.getLogger("asyncssh").setLevel(logging.DEBUG)
|
||||
else:
|
||||
|
||||
@@ -1,104 +1,26 @@
|
||||
import asyncssh
|
||||
import logging
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import ipaddress
|
||||
from typing import Union
|
||||
|
||||
from pyasic.data import MinerData
|
||||
from pyasic.miners.base import AnyMiner, BaseMiner
|
||||
from pyasic.miners.miner_factory import miner_factory
|
||||
|
||||
|
||||
class BaseMiner:
|
||||
def __init__(self, *args) -> None:
|
||||
self.ip = None
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
self.api = None
|
||||
self.api_type = None
|
||||
self.model = None
|
||||
self.light = None
|
||||
self.hostname = None
|
||||
self.nominal_chips = 1
|
||||
self.version = None
|
||||
self.fan_count = 2
|
||||
self.config = None
|
||||
|
||||
def __repr__(self):
|
||||
return f"{'' if not self.api_type else self.api_type} {'' if not self.model else self.model}: {str(self.ip)}"
|
||||
|
||||
def __lt__(self, other):
|
||||
return ipaddress.ip_address(self.ip) < ipaddress.ip_address(other.ip)
|
||||
|
||||
def __gt__(self, other):
|
||||
return ipaddress.ip_address(self.ip) > ipaddress.ip_address(other.ip)
|
||||
|
||||
def __eq__(self, other):
|
||||
return ipaddress.ip_address(self.ip) == ipaddress.ip_address(other.ip)
|
||||
|
||||
async def _get_ssh_connection(self) -> asyncssh.connect:
|
||||
"""Create a new asyncssh connection"""
|
||||
try:
|
||||
conn = await asyncssh.connect(
|
||||
str(self.ip),
|
||||
known_hosts=None,
|
||||
username=self.uname,
|
||||
password=self.pwd,
|
||||
server_host_key_algs=["ssh-rsa"],
|
||||
)
|
||||
return conn
|
||||
except asyncssh.misc.PermissionDenied:
|
||||
try:
|
||||
conn = await asyncssh.connect(
|
||||
str(self.ip),
|
||||
known_hosts=None,
|
||||
username="root",
|
||||
password="admin",
|
||||
server_host_key_algs=["ssh-rsa"],
|
||||
)
|
||||
return conn
|
||||
except Exception as e:
|
||||
# logging.warning(f"{self} raised an exception: {e}")
|
||||
raise e
|
||||
except OSError as e:
|
||||
logging.warning(f"Connection refused: {self}")
|
||||
raise e
|
||||
except Exception as e:
|
||||
# logging.warning(f"{self} raised an exception: {e}")
|
||||
raise e
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
return False
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
return False
|
||||
|
||||
async def send_file(self, src, dest):
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
await asyncssh.scp(src, (conn, dest))
|
||||
|
||||
async def check_light(self):
|
||||
return self.light
|
||||
|
||||
async def get_board_info(self):
|
||||
return None
|
||||
|
||||
async def get_config(self):
|
||||
return None
|
||||
|
||||
async def get_hostname(self):
|
||||
return None
|
||||
|
||||
async def get_model(self):
|
||||
return None
|
||||
|
||||
async def reboot(self):
|
||||
return False
|
||||
|
||||
async def restart_backend(self):
|
||||
return False
|
||||
|
||||
async def send_config(self, *args, **kwargs):
|
||||
return None
|
||||
|
||||
async def get_mac(self):
|
||||
return None
|
||||
|
||||
async def get_data(self) -> MinerData:
|
||||
return MinerData(ip=str(self.ip))
|
||||
# abstracted version of get miner that is easier to access
|
||||
async def get_miner(ip: Union[ipaddress.ip_address, str]) -> AnyMiner:
|
||||
return await miner_factory.get_miner(ip)
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
from .bmminer import BMMiner
|
||||
from .bosminer import BOSMiner
|
||||
from .btminer import BTMiner
|
||||
from .cgminer import CGMiner
|
||||
from .hiveon import Hiveon
|
||||
@@ -1,292 +0,0 @@
|
||||
import ipaddress
|
||||
import logging
|
||||
from typing import Union
|
||||
|
||||
|
||||
from pyasic.API.bmminer import BMMinerAPI
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
from pyasic.data import MinerData
|
||||
|
||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
||||
|
||||
|
||||
class BMMiner(BaseMiner):
|
||||
"""Base handler for BMMiner based miners."""
|
||||
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
self.api = BMMinerAPI(ip)
|
||||
self.api_type = "BMMiner"
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
|
||||
async def get_model(self) -> Union[str, None]:
|
||||
"""Get miner model.
|
||||
|
||||
Returns:
|
||||
Miner model or None.
|
||||
"""
|
||||
# check if model is cached
|
||||
if self.model:
|
||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||
return self.model
|
||||
|
||||
# get devdetails data
|
||||
version_data = await self.api.devdetails()
|
||||
|
||||
# if we get data back, parse it for model
|
||||
if version_data:
|
||||
# handle Antminer BMMiner as a base
|
||||
self.model = version_data["DEVDETAILS"][0]["Model"].replace("Antminer ", "")
|
||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||
return self.model
|
||||
|
||||
# if we don't get devdetails, log a failed attempt
|
||||
logging.warning(f"Failed to get model for miner: {self}")
|
||||
return None
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
"""Get miner hostname.
|
||||
|
||||
Returns:
|
||||
The hostname of the miner as a string or "?"
|
||||
"""
|
||||
if self.hostname:
|
||||
return self.hostname
|
||||
try:
|
||||
# open an ssh connection
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
# if we get the connection, check hostname
|
||||
if conn is not None:
|
||||
# get output of the hostname file
|
||||
data = await conn.run("cat /proc/sys/kernel/hostname")
|
||||
host = data.stdout.strip()
|
||||
|
||||
# return hostname data
|
||||
logging.debug(f"Found hostname for {self.ip}: {host}")
|
||||
self.hostname = host
|
||||
return self.hostname
|
||||
else:
|
||||
# return ? if we fail to get hostname with no ssh connection
|
||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||
return "?"
|
||||
except Exception:
|
||||
# return ? if we fail to get hostname with an exception
|
||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||
return "?"
|
||||
|
||||
async def send_ssh_command(self, cmd: str) -> Union[str, None]:
|
||||
"""Send a command to the miner over ssh.
|
||||
|
||||
Parameters:
|
||||
cmd: The command to run.
|
||||
|
||||
Returns:
|
||||
Result of the command or None.
|
||||
"""
|
||||
result = None
|
||||
|
||||
# open an ssh connection
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
# 3 retries
|
||||
for i in range(3):
|
||||
try:
|
||||
# run the command and get the result
|
||||
result = await conn.run(cmd)
|
||||
result = result.stdout
|
||||
|
||||
except Exception as e:
|
||||
# if the command fails, log it
|
||||
logging.warning(f"{self} command {cmd} error: {e}")
|
||||
|
||||
# on the 3rd retry, return None
|
||||
if i == 3:
|
||||
return
|
||||
continue
|
||||
# return the result, either command output or None
|
||||
return result
|
||||
|
||||
async def get_config(self) -> Union[list, None]:
|
||||
"""Get the pool configuration of the miner.
|
||||
|
||||
Returns:
|
||||
Pool config data or None.
|
||||
"""
|
||||
# get pool data
|
||||
pools = await self.api.pools()
|
||||
pool_data = []
|
||||
|
||||
# ensure we got pool data
|
||||
if not pools:
|
||||
return
|
||||
|
||||
# parse all the pools
|
||||
for pool in pools["POOLS"]:
|
||||
pool_data.append({"url": pool["URL"], "user": pool["User"], "pwd": "123"})
|
||||
return pool_data
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
"""Reboot the miner.
|
||||
|
||||
Returns:
|
||||
The result of rebooting the miner.
|
||||
"""
|
||||
logging.debug(f"{self}: Sending reboot command.")
|
||||
_ret = await self.send_ssh_command("reboot")
|
||||
logging.debug(f"{self}: Reboot command completed.")
|
||||
if isinstance(_ret, str):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def get_data(self) -> MinerData:
|
||||
"""Get data from the miner.
|
||||
|
||||
Returns:
|
||||
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
||||
"""
|
||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||
|
||||
board_offset = -1
|
||||
fan_offset = -1
|
||||
|
||||
model = await self.get_model()
|
||||
hostname = await self.get_hostname()
|
||||
mac = await self.get_mac()
|
||||
|
||||
if model:
|
||||
data.model = model
|
||||
|
||||
if hostname:
|
||||
data.hostname = hostname
|
||||
|
||||
if mac:
|
||||
data.mac = mac
|
||||
|
||||
miner_data = None
|
||||
for i in range(DATA_RETRIES):
|
||||
miner_data = await self.api.multicommand(
|
||||
"summary", "pools", "stats", ignore_x19_error=True
|
||||
)
|
||||
if miner_data:
|
||||
break
|
||||
|
||||
if not miner_data:
|
||||
return data
|
||||
|
||||
summary = miner_data.get("summary")[0]
|
||||
pools = miner_data.get("pools")[0]
|
||||
stats = miner_data.get("stats")[0]
|
||||
|
||||
if summary:
|
||||
hr = summary.get("SUMMARY")
|
||||
if hr:
|
||||
if len(hr) > 0:
|
||||
hr = hr[0].get("GHS av")
|
||||
if hr:
|
||||
data.hashrate = round(hr / 1000, 2)
|
||||
|
||||
if stats:
|
||||
boards = stats.get("STATS")
|
||||
if boards:
|
||||
if len(boards) > 0:
|
||||
for board_num in range(1, 16, 5):
|
||||
for _b_num in range(5):
|
||||
b = boards[1].get(f"chain_acn{board_num + _b_num}")
|
||||
|
||||
if b and not b == 0 and board_offset == -1:
|
||||
board_offset = board_num
|
||||
if board_offset == -1:
|
||||
board_offset = 1
|
||||
|
||||
data.left_chips = boards[1].get(f"chain_acn{board_offset}")
|
||||
data.center_chips = boards[1].get(f"chain_acn{board_offset+1}")
|
||||
data.right_chips = boards[1].get(f"chain_acn{board_offset+2}")
|
||||
|
||||
data.left_board_hashrate = round(
|
||||
float(boards[1].get(f"chain_rate{board_offset}")) / 1000, 2
|
||||
)
|
||||
data.center_board_hashrate = round(
|
||||
float(boards[1].get(f"chain_rate{board_offset+1}")) / 1000, 2
|
||||
)
|
||||
data.right_board_hashrate = round(
|
||||
float(boards[1].get(f"chain_rate{board_offset+2}")) / 1000, 2
|
||||
)
|
||||
|
||||
if stats:
|
||||
temp = stats.get("STATS")
|
||||
if temp:
|
||||
if len(temp) > 1:
|
||||
for fan_num in range(1, 8, 4):
|
||||
for _f_num in range(4):
|
||||
f = temp[1].get(f"fan{fan_num + _f_num}")
|
||||
if f and not f == 0 and fan_offset == -1:
|
||||
fan_offset = fan_num
|
||||
if fan_offset == -1:
|
||||
fan_offset = 1
|
||||
for fan in range(self.fan_count):
|
||||
setattr(
|
||||
data, f"fan_{fan + 1}", temp[1].get(f"fan{fan_offset+fan}")
|
||||
)
|
||||
|
||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
||||
env_temp_list = []
|
||||
for item in range(3):
|
||||
board_temp = temp[1].get(f"temp{item + board_offset}")
|
||||
chip_temp = temp[1].get(f"temp2_{item + board_offset}")
|
||||
setattr(data, f"{board_map[item]}_chip_temp", chip_temp)
|
||||
setattr(data, f"{board_map[item]}_temp", board_temp)
|
||||
if f"temp_pcb{item}" in temp[1].keys():
|
||||
env_temp = temp[1][f"temp_pcb{item}"].split("-")[0]
|
||||
if not env_temp == 0:
|
||||
env_temp_list.append(int(env_temp))
|
||||
data.env_temp = sum(env_temp_list) / len(env_temp_list)
|
||||
|
||||
if pools:
|
||||
pool_1 = None
|
||||
pool_2 = None
|
||||
pool_1_user = None
|
||||
pool_2_user = None
|
||||
pool_1_quota = 1
|
||||
pool_2_quota = 1
|
||||
quota = 0
|
||||
for pool in pools.get("POOLS"):
|
||||
if not pool_1_user:
|
||||
pool_1_user = pool.get("User")
|
||||
pool_1 = pool["URL"]
|
||||
pool_1_quota = pool["Quota"]
|
||||
elif not pool_2_user:
|
||||
pool_2_user = pool.get("User")
|
||||
pool_2 = pool["URL"]
|
||||
pool_2_quota = pool["Quota"]
|
||||
if not pool.get("User") == pool_1_user:
|
||||
if not pool_2_user == pool.get("User"):
|
||||
pool_2_user = pool.get("User")
|
||||
pool_2 = pool["URL"]
|
||||
pool_2_quota = pool["Quota"]
|
||||
if pool_2_user and not pool_2_user == pool_1_user:
|
||||
quota = f"{pool_1_quota}/{pool_2_quota}"
|
||||
|
||||
if pool_1:
|
||||
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
|
||||
"stratum2+tcp://", ""
|
||||
)
|
||||
data.pool_1_url = pool_1
|
||||
|
||||
if pool_1_user:
|
||||
data.pool_1_user = pool_1_user
|
||||
|
||||
if pool_2:
|
||||
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
|
||||
"stratum2+tcp://", ""
|
||||
)
|
||||
data.pool_2_url = pool_2
|
||||
|
||||
if pool_2_user:
|
||||
data.pool_2_user = pool_2_user
|
||||
|
||||
if quota:
|
||||
data.pool_split = str(quota)
|
||||
|
||||
return data
|
||||
@@ -1,418 +0,0 @@
|
||||
import ipaddress
|
||||
import logging
|
||||
import json
|
||||
from typing import Union
|
||||
|
||||
import toml
|
||||
|
||||
|
||||
from pyasic.miners import BaseMiner
|
||||
from pyasic.API.bosminer import BOSMinerAPI
|
||||
from pyasic.API import APIError
|
||||
|
||||
from pyasic.data.error_codes import BraiinsOSError
|
||||
from pyasic.data import MinerData
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
|
||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
||||
|
||||
|
||||
class BOSMiner(BaseMiner):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
self.api = BOSMinerAPI(ip)
|
||||
self.api_type = "BOSMiner"
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
self.config = None
|
||||
|
||||
async def send_ssh_command(self, cmd: str) -> Union[str, None]:
|
||||
"""Send a command to the miner over ssh.
|
||||
|
||||
Returns:
|
||||
Result of the command or None.
|
||||
"""
|
||||
result = None
|
||||
|
||||
# open an ssh connection
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
# 3 retries
|
||||
for i in range(3):
|
||||
try:
|
||||
# run the command and get the result
|
||||
result = await conn.run(cmd)
|
||||
result = result.stdout
|
||||
except Exception as e:
|
||||
# if the command fails, log it
|
||||
logging.warning(f"{self} command {cmd} error: {e}")
|
||||
|
||||
# on the 3rd retry, return None
|
||||
if i == 3:
|
||||
return
|
||||
continue
|
||||
# return the result, either command output or None
|
||||
return str(result)
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
"""Sends command to turn on fault light on the miner."""
|
||||
logging.debug(f"{self}: Sending fault_light on command.")
|
||||
self.light = True
|
||||
_ret = await self.send_ssh_command("miner fault_light on")
|
||||
logging.debug(f"{self}: fault_light on command completed.")
|
||||
if isinstance(_ret, str):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
"""Sends command to turn off fault light on the miner."""
|
||||
logging.debug(f"{self}: Sending fault_light off command.")
|
||||
self.light = False
|
||||
_ret = await self.send_ssh_command("miner fault_light off")
|
||||
logging.debug(f"{self}: fault_light off command completed.")
|
||||
if isinstance(_ret, str):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def restart_backend(self) -> bool:
|
||||
"""Restart bosminer hashing process. Wraps [`restart_bosminer`][pyasic.miners._backends.bosminer.BOSMiner.restart_bosminer] to standardize."""
|
||||
return await self.restart_bosminer()
|
||||
|
||||
async def restart_bosminer(self) -> bool:
|
||||
"""Restart bosminer hashing process."""
|
||||
logging.debug(f"{self}: Sending bosminer restart command.")
|
||||
_ret = await self.send_ssh_command("/etc/init.d/bosminer restart")
|
||||
logging.debug(f"{self}: bosminer restart command completed.")
|
||||
if isinstance(_ret, str):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
"""Reboots power to the physical miner."""
|
||||
logging.debug(f"{self}: Sending reboot command.")
|
||||
_ret = await self.send_ssh_command("/sbin/reboot")
|
||||
logging.debug(f"{self}: Reboot command completed.")
|
||||
if isinstance(_ret, str):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def get_config(self) -> str:
|
||||
"""Gets the config for the miner and sets it as `self.config`.
|
||||
|
||||
Returns:
|
||||
The config from `self.config`.
|
||||
"""
|
||||
logging.debug(f"{self}: Getting config.")
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
logging.debug(f"{self}: Opening SFTP connection.")
|
||||
async with conn.start_sftp_client() as sftp:
|
||||
logging.debug(f"{self}: Reading config file.")
|
||||
async with sftp.open("/etc/bosminer.toml") as file:
|
||||
toml_data = toml.loads(await file.read())
|
||||
logging.debug(f"{self}: Converting config file.")
|
||||
cfg = MinerConfig().from_raw(toml_data)
|
||||
self.config = cfg
|
||||
return self.config
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
"""Get miner hostname.
|
||||
|
||||
Returns:
|
||||
The hostname of the miner as a string or "?"
|
||||
"""
|
||||
if self.hostname:
|
||||
return self.hostname
|
||||
try:
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
if conn is not None:
|
||||
data = await conn.run("cat /proc/sys/kernel/hostname")
|
||||
host = data.stdout.strip()
|
||||
logging.debug(f"Found hostname for {self.ip}: {host}")
|
||||
self.hostname = host
|
||||
return self.hostname
|
||||
else:
|
||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||
return "?"
|
||||
except Exception:
|
||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||
return "?"
|
||||
|
||||
async def get_model(self) -> Union[str, None]:
|
||||
"""Get miner model.
|
||||
|
||||
Returns:
|
||||
Miner model or None.
|
||||
"""
|
||||
# check if model is cached
|
||||
if self.model:
|
||||
logging.debug(f"Found model for {self.ip}: {self.model} (BOS)")
|
||||
return self.model + " (BOS)"
|
||||
|
||||
# get devdetails data
|
||||
try:
|
||||
version_data = await self.api.devdetails()
|
||||
except APIError as e:
|
||||
version_data = None
|
||||
if e.message == "Not ready":
|
||||
cfg = json.loads(await self.send_ssh_command("bosminer config --data"))
|
||||
model = cfg.get("data").get("format").get("model")
|
||||
if model:
|
||||
model = model.replace("Antminer ", "")
|
||||
self.model = model
|
||||
return self.model + " (BOS)"
|
||||
|
||||
# if we get data back, parse it for model
|
||||
if version_data:
|
||||
if not version_data["DEVDETAILS"] == []:
|
||||
# handle Antminer BOSMiner as a base
|
||||
self.model = version_data["DEVDETAILS"][0]["Model"].replace(
|
||||
"Antminer ", ""
|
||||
)
|
||||
logging.debug(f"Found model for {self.ip}: {self.model} (BOS)")
|
||||
return self.model + " (BOS)"
|
||||
|
||||
# if we don't get devdetails, log a failed attempt
|
||||
logging.warning(f"Failed to get model for miner: {self}")
|
||||
return None
|
||||
|
||||
async def get_version(self) -> Union[str, None]:
|
||||
"""Get miner firmware version.
|
||||
|
||||
Returns:
|
||||
Miner firmware version or None.
|
||||
"""
|
||||
# check if version is cached
|
||||
if self.version:
|
||||
logging.debug(f"Found version for {self.ip}: {self.version}")
|
||||
return self.version
|
||||
|
||||
# get output of bos version file
|
||||
version_data = await self.send_ssh_command("cat /etc/bos_version")
|
||||
|
||||
# if we get the version data, parse it
|
||||
if version_data:
|
||||
self.version = version_data.split("-")[5]
|
||||
logging.debug(f"Found version for {self.ip}: {self.version}")
|
||||
return self.version
|
||||
|
||||
# if we fail to get version, log a failed attempt
|
||||
logging.warning(f"Failed to get model for miner: {self}")
|
||||
return None
|
||||
|
||||
async def send_config(self, yaml_config, ip_user: bool = False) -> None:
|
||||
"""Configures miner with yaml config."""
|
||||
logging.debug(f"{self}: Sending config.")
|
||||
if ip_user:
|
||||
suffix = str(self.ip).split(".")[-1]
|
||||
toml_conf = (
|
||||
MinerConfig()
|
||||
.from_yaml(yaml_config)
|
||||
.as_bos(model=self.model.replace(" (BOS)", ""), user_suffix=suffix)
|
||||
)
|
||||
else:
|
||||
toml_conf = (
|
||||
MinerConfig()
|
||||
.from_yaml(yaml_config)
|
||||
.as_bos(model=self.model.replace(" (BOS)", ""))
|
||||
)
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
logging.debug(f"{self}: Opening SFTP connection.")
|
||||
async with conn.start_sftp_client() as sftp:
|
||||
logging.debug(f"{self}: Opening config file.")
|
||||
async with sftp.open("/etc/bosminer.toml", "w+") as file:
|
||||
await file.write(toml_conf)
|
||||
logging.debug(f"{self}: Restarting BOSMiner")
|
||||
await conn.run("/etc/init.d/bosminer restart")
|
||||
|
||||
async def get_data(self) -> MinerData:
|
||||
"""Get data from the miner.
|
||||
|
||||
Returns:
|
||||
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
||||
"""
|
||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||
|
||||
board_offset = -1
|
||||
fan_offset = -1
|
||||
|
||||
model = await self.get_model()
|
||||
hostname = await self.get_hostname()
|
||||
mac = await self.get_mac()
|
||||
|
||||
if model:
|
||||
data.model = model
|
||||
|
||||
if hostname:
|
||||
data.hostname = hostname
|
||||
|
||||
if mac:
|
||||
data.mac = mac
|
||||
|
||||
miner_data = None
|
||||
for i in range(DATA_RETRIES):
|
||||
try:
|
||||
miner_data = await self.api.multicommand(
|
||||
"summary",
|
||||
"temps",
|
||||
"tunerstatus",
|
||||
"pools",
|
||||
"devdetails",
|
||||
"fans",
|
||||
"devs",
|
||||
)
|
||||
except APIError as e:
|
||||
if str(e.message) == "Not ready":
|
||||
miner_data = await self.api.multicommand(
|
||||
"summary", "tunerstatus", "pools", "devs"
|
||||
)
|
||||
if miner_data:
|
||||
break
|
||||
if not miner_data:
|
||||
return data
|
||||
summary = miner_data.get("summary")
|
||||
temps = miner_data.get("temps")
|
||||
tunerstatus = miner_data.get("tunerstatus")
|
||||
pools = miner_data.get("pools")
|
||||
devdetails = miner_data.get("devdetails")
|
||||
devs = miner_data.get("devs")
|
||||
fans = miner_data.get("fans")
|
||||
|
||||
if summary:
|
||||
hr = summary[0].get("SUMMARY")
|
||||
if hr:
|
||||
if len(hr) > 0:
|
||||
hr = hr[0].get("MHS 1m")
|
||||
if hr:
|
||||
data.hashrate = round(hr / 1000000, 2)
|
||||
|
||||
if temps:
|
||||
temp = temps[0].get("TEMPS")
|
||||
if temp:
|
||||
if len(temp) > 0:
|
||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
||||
offset = 6 if temp[0]["ID"] in [6, 7, 8] else temp[0]["ID"]
|
||||
for board in temp:
|
||||
_id = board["ID"] - offset
|
||||
chip_temp = round(board["Chip"])
|
||||
board_temp = round(board["Board"])
|
||||
setattr(data, f"{board_map[_id]}_chip_temp", chip_temp)
|
||||
setattr(data, f"{board_map[_id]}_temp", board_temp)
|
||||
|
||||
if fans:
|
||||
fan_data = fans[0].get("FANS")
|
||||
if fan_data:
|
||||
for fan in range(self.fan_count):
|
||||
setattr(data, f"fan_{fan+1}", fan_data[fan]["RPM"])
|
||||
|
||||
if pools:
|
||||
pool_1 = None
|
||||
pool_2 = None
|
||||
pool_1_user = None
|
||||
pool_2_user = None
|
||||
pool_1_quota = 1
|
||||
pool_2_quota = 1
|
||||
quota = 0
|
||||
for pool in pools[0].get("POOLS"):
|
||||
if not pool_1_user:
|
||||
pool_1_user = pool.get("User")
|
||||
pool_1 = pool["URL"]
|
||||
pool_1_quota = pool["Quota"]
|
||||
elif not pool_2_user:
|
||||
pool_2_user = pool.get("User")
|
||||
pool_2 = pool["URL"]
|
||||
pool_2_quota = pool["Quota"]
|
||||
if not pool.get("User") == pool_1_user:
|
||||
if not pool_2_user == pool.get("User"):
|
||||
pool_2_user = pool.get("User")
|
||||
pool_2 = pool["URL"]
|
||||
pool_2_quota = pool["Quota"]
|
||||
if pool_2_user and not pool_2_user == pool_1_user:
|
||||
quota = f"{pool_1_quota}/{pool_2_quota}"
|
||||
|
||||
if pool_1:
|
||||
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
|
||||
"stratum2+tcp://", ""
|
||||
)
|
||||
data.pool_1_url = pool_1
|
||||
|
||||
if pool_1_user:
|
||||
data.pool_1_user = pool_1_user
|
||||
|
||||
if pool_2:
|
||||
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
|
||||
"stratum2+tcp://", ""
|
||||
)
|
||||
data.pool_2_url = pool_2
|
||||
|
||||
if pool_2_user:
|
||||
data.pool_2_user = pool_2_user
|
||||
|
||||
if quota:
|
||||
data.pool_split = str(quota)
|
||||
|
||||
if tunerstatus:
|
||||
tuner = tunerstatus[0].get("TUNERSTATUS")
|
||||
if tuner:
|
||||
if len(tuner) > 0:
|
||||
wattage = tuner[0].get("ApproximateMinerPowerConsumption")
|
||||
wattage_limit = tuner[0].get("PowerLimit")
|
||||
if wattage_limit:
|
||||
data.wattage_limit = wattage_limit
|
||||
if wattage:
|
||||
data.wattage = wattage
|
||||
|
||||
chain_status = tuner[0].get("TunerChainStatus")
|
||||
if chain_status and len(chain_status) > 0:
|
||||
board_map = {
|
||||
0: "Left board",
|
||||
1: "Center board",
|
||||
2: "Right board",
|
||||
}
|
||||
offset = (
|
||||
6
|
||||
if chain_status[0]["HashchainIndex"] in [6, 7, 8]
|
||||
else chain_status[0]["HashchainIndex"]
|
||||
)
|
||||
for board in chain_status:
|
||||
_id = board["HashchainIndex"] - offset
|
||||
if board["Status"] not in [
|
||||
"Stable",
|
||||
"Testing performance profile",
|
||||
]:
|
||||
_error = board["Status"]
|
||||
_error = _error[0].lower() + _error[1:]
|
||||
data.errors.append(
|
||||
BraiinsOSError(f"{board_map[_id]} {_error}")
|
||||
)
|
||||
|
||||
if devdetails:
|
||||
boards = devdetails[0].get("DEVDETAILS")
|
||||
if boards:
|
||||
if len(boards) > 0:
|
||||
board_map = {0: "left_chips", 1: "center_chips", 2: "right_chips"}
|
||||
offset = 6 if boards[0]["ID"] in [6, 7, 8] else boards[0]["ID"]
|
||||
for board in boards:
|
||||
_id = board["ID"] - offset
|
||||
chips = board["Chips"]
|
||||
setattr(data, board_map[_id], chips)
|
||||
|
||||
if devs:
|
||||
boards = devs[0].get("DEVS")
|
||||
if boards:
|
||||
if len(boards) > 0:
|
||||
board_map = {
|
||||
0: "left_board_hashrate",
|
||||
1: "center_board_hashrate",
|
||||
2: "right_board_hashrate",
|
||||
}
|
||||
offset = 6 if boards[0]["ID"] in [6, 7, 8] else boards[0]["ID"]
|
||||
for board in boards:
|
||||
_id = board["ID"] - offset
|
||||
hashrate = round(board["MHS 1m"] / 1000000, 2)
|
||||
setattr(data, board_map[_id], hashrate)
|
||||
return data
|
||||
|
||||
async def get_mac(self):
|
||||
result = await self.send_ssh_command("cat /sys/class/net/eth0/address")
|
||||
return result.upper().strip()
|
||||
@@ -1,50 +0,0 @@
|
||||
import logging
|
||||
|
||||
import ipaddress
|
||||
|
||||
from pyasic.API.bosminer import BOSMinerAPI
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class BOSMinerOld(BaseMiner):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
self.api = BOSMinerAPI(ip)
|
||||
self.api_type = "BOSMiner"
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
|
||||
async def send_ssh_command(self, cmd: str) -> str or None:
|
||||
"""Send a command to the miner over ssh.
|
||||
|
||||
:return: Result of the command or None.
|
||||
"""
|
||||
result = None
|
||||
|
||||
# open an ssh connection
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
# 3 retries
|
||||
for i in range(3):
|
||||
try:
|
||||
# run the command and get the result
|
||||
result = await conn.run(cmd)
|
||||
if result.stdout:
|
||||
result = result.stdout
|
||||
except Exception as e:
|
||||
if e == "SSH connection closed":
|
||||
return "Update completed."
|
||||
# if the command fails, log it
|
||||
logging.warning(f"{self} command {cmd} error: {e}")
|
||||
|
||||
# on the 3rd retry, return None
|
||||
if i == 3:
|
||||
return
|
||||
continue
|
||||
# return the result, either command output or None
|
||||
return str(result)
|
||||
|
||||
|
||||
async def update_to_plus(self):
|
||||
result = await self.send_ssh_command("opkg update && opkg install bos_plus")
|
||||
return result
|
||||
@@ -1,257 +0,0 @@
|
||||
import ipaddress
|
||||
import logging
|
||||
from typing import Union
|
||||
|
||||
|
||||
from pyasic.API.btminer import BTMinerAPI
|
||||
from pyasic.miners import BaseMiner
|
||||
from pyasic.API import APIError
|
||||
|
||||
from pyasic.data import MinerData
|
||||
from pyasic.data.error_codes import WhatsminerError
|
||||
|
||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
||||
|
||||
|
||||
class BTMiner(BaseMiner):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
self.api = BTMinerAPI(ip)
|
||||
self.api_type = "BTMiner"
|
||||
|
||||
async def get_model(self) -> Union[str, None]:
|
||||
"""Get miner model.
|
||||
|
||||
Returns:
|
||||
Miner model or None.
|
||||
"""
|
||||
if self.model:
|
||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||
return self.model
|
||||
version_data = await self.api.devdetails()
|
||||
if version_data:
|
||||
self.model = version_data["DEVDETAILS"][0]["Model"].split("V")[0]
|
||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||
return self.model
|
||||
logging.warning(f"Failed to get model for miner: {self}")
|
||||
return None
|
||||
|
||||
async def get_hostname(self) -> Union[str, None]:
|
||||
"""Get miner hostname.
|
||||
|
||||
Returns:
|
||||
The hostname of the miner as a string or None.
|
||||
"""
|
||||
if self.hostname:
|
||||
return self.hostname
|
||||
try:
|
||||
host_data = await self.api.get_miner_info()
|
||||
if host_data:
|
||||
host = host_data["Msg"]["hostname"]
|
||||
logging.debug(f"Found hostname for {self.ip}: {host}")
|
||||
self.hostname = host
|
||||
return self.hostname
|
||||
except APIError:
|
||||
logging.info(f"Failed to get hostname for miner: {self}")
|
||||
return None
|
||||
except Exception:
|
||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||
return None
|
||||
|
||||
async def get_mac(self) -> str:
|
||||
"""Get the mac address of the miner.
|
||||
|
||||
Returns:
|
||||
The mac address of the miner as a string.
|
||||
"""
|
||||
mac = ""
|
||||
data = await self.api.summary()
|
||||
if data:
|
||||
if data.get("SUMMARY"):
|
||||
if len(data["SUMMARY"]) > 0:
|
||||
_mac = data["SUMMARY"][0].get("MAC")
|
||||
if _mac:
|
||||
mac = _mac
|
||||
if mac == "":
|
||||
try:
|
||||
data = await self.api.get_miner_info()
|
||||
if data:
|
||||
if "Msg" in data.keys():
|
||||
if "mac" in data["Msg"].keys():
|
||||
mac = data["Msg"]["mac"]
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
return str(mac).upper()
|
||||
|
||||
async def get_data(self) -> MinerData:
|
||||
"""Get data from the miner.
|
||||
|
||||
Returns:
|
||||
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
||||
"""
|
||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||
|
||||
mac = None
|
||||
|
||||
try:
|
||||
model = await self.get_model()
|
||||
except APIError:
|
||||
logging.info(f"Failed to get model: {self}")
|
||||
model = None
|
||||
data.model = "Whatsminer"
|
||||
|
||||
try:
|
||||
hostname = await self.get_hostname()
|
||||
except APIError:
|
||||
logging.info(f"Failed to get hostname: {self}")
|
||||
hostname = None
|
||||
data.hostname = "Whatsminer"
|
||||
|
||||
if model:
|
||||
data.model = model
|
||||
|
||||
if hostname:
|
||||
data.hostname = hostname
|
||||
|
||||
miner_data = None
|
||||
for i in range(DATA_RETRIES):
|
||||
try:
|
||||
miner_data = await self.api.multicommand("summary", "devs", "pools")
|
||||
if miner_data:
|
||||
break
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if not miner_data:
|
||||
return data
|
||||
|
||||
summary = miner_data.get("summary")[0]
|
||||
devs = miner_data.get("devs")[0]
|
||||
pools = miner_data.get("pools")[0]
|
||||
|
||||
if summary:
|
||||
summary_data = summary.get("SUMMARY")
|
||||
if summary_data:
|
||||
if len(summary_data) > 0:
|
||||
wattage_limit = None
|
||||
if summary_data[0].get("MAC"):
|
||||
mac = summary_data[0]["MAC"]
|
||||
|
||||
if summary_data[0].get("Env Temp"):
|
||||
data.env_temp = summary_data[0]["Env Temp"]
|
||||
|
||||
if summary_data[0].get("Power Limit"):
|
||||
wattage_limit = summary_data[0]["Power Limit"]
|
||||
|
||||
data.fan_1 = summary_data[0]["Fan Speed In"]
|
||||
data.fan_2 = summary_data[0]["Fan Speed Out"]
|
||||
|
||||
hr = summary_data[0].get("MHS 1m")
|
||||
if hr:
|
||||
data.hashrate = round(hr / 1000000, 2)
|
||||
|
||||
wattage = summary_data[0].get("Power")
|
||||
if wattage:
|
||||
data.wattage = round(wattage)
|
||||
|
||||
if not wattage_limit:
|
||||
wattage_limit = round(wattage)
|
||||
|
||||
data.wattage_limit = wattage_limit
|
||||
|
||||
if summary_data[0].get("Error Code Count"):
|
||||
for i in range(summary_data[0]["Error Code Count"]):
|
||||
if summary_data[0].get(f"Error Code {i}"):
|
||||
data.errors.append(
|
||||
WhatsminerError(
|
||||
error_code=summary_data[0][f"Error Code {i}"]
|
||||
)
|
||||
)
|
||||
|
||||
if devs:
|
||||
temp_data = devs.get("DEVS")
|
||||
if temp_data:
|
||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
||||
for board in temp_data:
|
||||
_id = board["ASC"]
|
||||
chip_temp = round(board["Chip Temp Avg"])
|
||||
board_temp = round(board["Temperature"])
|
||||
hashrate = round(board["MHS 1m"] / 1000000, 2)
|
||||
setattr(data, f"{board_map[_id]}_chip_temp", chip_temp)
|
||||
setattr(data, f"{board_map[_id]}_temp", board_temp)
|
||||
setattr(data, f"{board_map[_id]}_hashrate", hashrate)
|
||||
|
||||
if devs:
|
||||
boards = devs.get("DEVS")
|
||||
if boards:
|
||||
if len(boards) > 0:
|
||||
board_map = {0: "left_chips", 1: "center_chips", 2: "right_chips"}
|
||||
if "ID" in boards[0].keys():
|
||||
id_key = "ID"
|
||||
else:
|
||||
id_key = "ASC"
|
||||
offset = boards[0][id_key]
|
||||
for board in boards:
|
||||
_id = board[id_key] - offset
|
||||
chips = board["Effective Chips"]
|
||||
setattr(data, board_map[_id], chips)
|
||||
|
||||
if pools:
|
||||
pool_1 = None
|
||||
pool_2 = None
|
||||
pool_1_user = None
|
||||
pool_2_user = None
|
||||
pool_1_quota = 1
|
||||
pool_2_quota = 1
|
||||
quota = 0
|
||||
for pool in pools.get("POOLS"):
|
||||
if not pool_1_user:
|
||||
pool_1_user = pool.get("User")
|
||||
pool_1 = pool["URL"]
|
||||
pool_1_quota = pool["Quota"]
|
||||
elif not pool_2_user:
|
||||
pool_2_user = pool.get("User")
|
||||
pool_2 = pool["URL"]
|
||||
pool_2_quota = pool["Quota"]
|
||||
if not pool.get("User") == pool_1_user:
|
||||
if not pool_2_user == pool.get("User"):
|
||||
pool_2_user = pool.get("User")
|
||||
pool_2 = pool["URL"]
|
||||
pool_2_quota = pool["Quota"]
|
||||
if pool_2_user and not pool_2_user == pool_1_user:
|
||||
quota = f"{pool_1_quota}/{pool_2_quota}"
|
||||
|
||||
if pool_1:
|
||||
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
|
||||
"stratum2+tcp://", ""
|
||||
)
|
||||
data.pool_1_url = pool_1
|
||||
|
||||
if pool_1_user:
|
||||
data.pool_1_user = pool_1_user
|
||||
|
||||
if pool_2:
|
||||
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
|
||||
"stratum2+tcp://", ""
|
||||
)
|
||||
data.pool_2_url = pool_2
|
||||
|
||||
if pool_2_user:
|
||||
data.pool_2_user = pool_2_user
|
||||
|
||||
if quota:
|
||||
data.pool_split = str(quota)
|
||||
|
||||
if not mac:
|
||||
try:
|
||||
mac = await self.get_mac()
|
||||
except APIError:
|
||||
logging.info(f"Failed to get mac: {self}")
|
||||
mac = None
|
||||
|
||||
if mac:
|
||||
data.mac = mac
|
||||
|
||||
return data
|
||||
@@ -1,292 +0,0 @@
|
||||
import ipaddress
|
||||
import logging
|
||||
from typing import Union
|
||||
|
||||
|
||||
from pyasic.API.cgminer import CGMinerAPI
|
||||
from pyasic.miners import BaseMiner
|
||||
from pyasic.API import APIError
|
||||
|
||||
from pyasic.data import MinerData
|
||||
|
||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
||||
|
||||
|
||||
class CGMiner(BaseMiner):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
self.api = CGMinerAPI(ip)
|
||||
self.api_type = "CGMiner"
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
self.config = None
|
||||
|
||||
async def get_model(self) -> Union[str, None]:
|
||||
"""Get miner model.
|
||||
|
||||
Returns:
|
||||
Miner model or None.
|
||||
"""
|
||||
if self.model:
|
||||
return self.model
|
||||
try:
|
||||
version_data = await self.api.devdetails()
|
||||
except APIError:
|
||||
return None
|
||||
if version_data:
|
||||
self.model = version_data["DEVDETAILS"][0]["Model"].replace("Antminer ", "")
|
||||
return self.model
|
||||
return None
|
||||
|
||||
async def get_hostname(self) -> Union[str, None]:
|
||||
"""Get miner hostname.
|
||||
|
||||
Returns:
|
||||
The hostname of the miner as a string or "?"
|
||||
"""
|
||||
if self.hostname:
|
||||
return self.hostname
|
||||
try:
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
if conn is not None:
|
||||
data = await conn.run("cat /proc/sys/kernel/hostname")
|
||||
host = data.stdout.strip()
|
||||
self.hostname = host
|
||||
return self.hostname
|
||||
else:
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def send_ssh_command(self, cmd: str) -> Union[str, None]:
|
||||
"""Send a command to the miner over ssh.
|
||||
|
||||
Parameters:
|
||||
cmd: The command to run.
|
||||
|
||||
Returns:
|
||||
Result of the command or None.
|
||||
"""
|
||||
result = None
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
for i in range(3):
|
||||
try:
|
||||
result = await conn.run(cmd)
|
||||
result = result.stdout
|
||||
except Exception as e:
|
||||
print(f"{cmd} error: {e}")
|
||||
if i == 3:
|
||||
return
|
||||
continue
|
||||
return result
|
||||
|
||||
async def restart_backend(self) -> bool:
|
||||
"""Restart cgminer hashing process. Wraps [`restart_cgminer`][pyasic.miners._backends.cgminer.CGMiner.restart_cgminer] to standardize."""
|
||||
return await self.restart_cgminer()
|
||||
|
||||
async def restart_cgminer(self) -> bool:
|
||||
"""Restart cgminer hashing process."""
|
||||
commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"]
|
||||
commands = ";".join(commands)
|
||||
_ret = await self.send_ssh_command(commands)
|
||||
if isinstance(_ret, str):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
"""Reboots power to the physical miner."""
|
||||
logging.debug(f"{self}: Sending reboot command.")
|
||||
_ret = await self.send_ssh_command("reboot")
|
||||
logging.debug(f"{self}: Reboot command completed.")
|
||||
if isinstance(_ret, str):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def start_cgminer(self) -> None:
|
||||
"""Start cgminer hashing process."""
|
||||
commands = [
|
||||
"mkdir -p /etc/tmp/",
|
||||
'echo "*/3 * * * * /usr/bin/cgminer-monitor" > /etc/tmp/root',
|
||||
"crontab -u root /etc/tmp/root",
|
||||
"/usr/bin/cgminer-monitor >/dev/null 2>&1",
|
||||
]
|
||||
commands = ";".join(commands)
|
||||
await self.send_ssh_command(commands)
|
||||
|
||||
async def stop_cgminer(self) -> None:
|
||||
"""Restart cgminer hashing process."""
|
||||
commands = [
|
||||
"mkdir -p /etc/tmp/",
|
||||
'echo "" > /etc/tmp/root',
|
||||
"crontab -u root /etc/tmp/root",
|
||||
"killall cgminer",
|
||||
]
|
||||
commands = ";".join(commands)
|
||||
await self.send_ssh_command(commands)
|
||||
|
||||
async def get_config(self) -> str:
|
||||
"""Gets the config for the miner and sets it as `self.config`.
|
||||
|
||||
Returns:
|
||||
The config from `self.config`.
|
||||
"""
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
command = "cat /etc/config/cgminer"
|
||||
result = await conn.run(command, check=True)
|
||||
self.config = result.stdout
|
||||
return self.config
|
||||
|
||||
async def get_data(self) -> MinerData:
|
||||
"""Get data from the miner.
|
||||
|
||||
Returns:
|
||||
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
||||
"""
|
||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||
|
||||
board_offset = -1
|
||||
fan_offset = -1
|
||||
|
||||
model = await self.get_model()
|
||||
hostname = await self.get_hostname()
|
||||
mac = await self.get_mac()
|
||||
|
||||
if model:
|
||||
data.model = model
|
||||
|
||||
if hostname:
|
||||
data.hostname = hostname
|
||||
|
||||
if mac:
|
||||
data.mac = mac
|
||||
|
||||
miner_data = None
|
||||
for i in range(DATA_RETRIES):
|
||||
miner_data = await self.api.multicommand(
|
||||
"summary", "pools", "stats", ignore_x19_error=True
|
||||
)
|
||||
if miner_data:
|
||||
break
|
||||
|
||||
if not miner_data:
|
||||
return data
|
||||
|
||||
summary = miner_data.get("summary")[0]
|
||||
pools = miner_data.get("pools")[0]
|
||||
stats = miner_data.get("stats")[0]
|
||||
|
||||
if summary:
|
||||
hr = summary.get("SUMMARY")
|
||||
if hr:
|
||||
if len(hr) > 0:
|
||||
hr = hr[0].get("GHS av")
|
||||
if hr:
|
||||
data.hashrate = round(hr / 1000, 2)
|
||||
|
||||
if stats:
|
||||
boards = stats.get("STATS")
|
||||
if boards:
|
||||
if len(boards) > 0:
|
||||
for board_num in range(1, 16, 5):
|
||||
for _b_num in range(5):
|
||||
b = boards[1].get(f"chain_acn{board_num + _b_num}")
|
||||
|
||||
if b and not b == 0 and board_offset == -1:
|
||||
board_offset = board_num
|
||||
if board_offset == -1:
|
||||
board_offset = 1
|
||||
|
||||
data.left_chips = boards[1].get(f"chain_acn{board_offset}")
|
||||
data.center_chips = boards[1].get(f"chain_acn{board_offset+1}")
|
||||
data.right_chips = boards[1].get(f"chain_acn{board_offset+2}")
|
||||
|
||||
data.left_board_hashrate = round(
|
||||
float(boards[1].get(f"chain_rate{board_offset}")) / 1000, 2
|
||||
)
|
||||
data.center_board_hashrate = round(
|
||||
float(boards[1].get(f"chain_rate{board_offset+1}")) / 1000, 2
|
||||
)
|
||||
data.right_board_hashrate = round(
|
||||
float(boards[1].get(f"chain_rate{board_offset+2}")) / 1000, 2
|
||||
)
|
||||
|
||||
if stats:
|
||||
temp = stats.get("STATS")
|
||||
if temp:
|
||||
if len(temp) > 1:
|
||||
for fan_num in range(1, 8, 4):
|
||||
for _f_num in range(4):
|
||||
f = temp[1].get(f"fan{fan_num + _f_num}")
|
||||
if f and not f == 0 and fan_offset == -1:
|
||||
fan_offset = fan_num
|
||||
if fan_offset == -1:
|
||||
fan_offset = 1
|
||||
for fan in range(self.fan_count):
|
||||
setattr(
|
||||
data, f"fan_{fan + 1}", temp[1].get(f"fan{fan_offset+fan}")
|
||||
)
|
||||
|
||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
||||
env_temp_list = []
|
||||
for item in range(3):
|
||||
board_temp = temp[1].get(f"temp{item + board_offset}")
|
||||
chip_temp = temp[1].get(f"temp2_{item + board_offset}")
|
||||
setattr(data, f"{board_map[item]}_chip_temp", chip_temp)
|
||||
setattr(data, f"{board_map[item]}_temp", board_temp)
|
||||
if f"temp_pcb{item}" in temp[1].keys():
|
||||
env_temp = temp[1][f"temp_pcb{item}"].split("-")[0]
|
||||
if not env_temp == 0:
|
||||
env_temp_list.append(int(env_temp))
|
||||
data.env_temp = sum(env_temp_list) / len(env_temp_list)
|
||||
|
||||
if pools:
|
||||
pool_1 = None
|
||||
pool_2 = None
|
||||
pool_1_user = None
|
||||
pool_2_user = None
|
||||
pool_1_quota = 1
|
||||
pool_2_quota = 1
|
||||
quota = 0
|
||||
for pool in pools.get("POOLS"):
|
||||
if not pool_1_user:
|
||||
pool_1_user = pool.get("User")
|
||||
pool_1 = pool["URL"]
|
||||
if pool.get("Quota"):
|
||||
pool_2_quota = pool.get("Quota")
|
||||
elif not pool_2_user:
|
||||
pool_2_user = pool.get("User")
|
||||
pool_2 = pool["URL"]
|
||||
if pool.get("Quota"):
|
||||
pool_2_quota = pool.get("Quota")
|
||||
if not pool.get("User") == pool_1_user:
|
||||
if not pool_2_user == pool.get("User"):
|
||||
pool_2_user = pool.get("User")
|
||||
pool_2 = pool["URL"]
|
||||
if pool.get("Quota"):
|
||||
pool_2_quota = pool.get("Quota")
|
||||
if pool_2_user and not pool_2_user == pool_1_user:
|
||||
quota = f"{pool_1_quota}/{pool_2_quota}"
|
||||
|
||||
if pool_1:
|
||||
pool_1 = pool_1.replace("stratum+tcp://", "").replace(
|
||||
"stratum2+tcp://", ""
|
||||
)
|
||||
data.pool_1_url = pool_1
|
||||
|
||||
if pool_1_user:
|
||||
data.pool_1_user = pool_1_user
|
||||
|
||||
if pool_2:
|
||||
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
|
||||
"stratum2+tcp://", ""
|
||||
)
|
||||
data.pool_2_url = pool_2
|
||||
|
||||
if pool_2_user:
|
||||
data.pool_2_user = pool_2_user
|
||||
|
||||
if quota:
|
||||
data.pool_split = str(quota)
|
||||
|
||||
return data
|
||||
@@ -1,48 +0,0 @@
|
||||
from pyasic.miners._backends import BMMiner
|
||||
import ipaddress
|
||||
|
||||
|
||||
class Hiveon(BMMiner):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
self.api_type = "Hiveon"
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
|
||||
async def get_board_info(self) -> dict:
|
||||
"""Gets data on each board and chain in the miner."""
|
||||
board_stats = await self.api.stats()
|
||||
stats = board_stats["STATS"][1]
|
||||
boards = {}
|
||||
board_chains = {0: [2, 9, 10], 1: [3, 11, 12], 2: [4, 13, 14]}
|
||||
for idx, board in enumerate(board_chains):
|
||||
boards[board] = []
|
||||
for chain in board_chains[board]:
|
||||
count = stats[f"chain_acn{chain}"]
|
||||
chips = stats[f"chain_acs{chain}"].replace(" ", "")
|
||||
if not count == 18 or "x" in chips:
|
||||
nominal = False
|
||||
else:
|
||||
nominal = True
|
||||
boards[board].append(
|
||||
{
|
||||
"chain": chain,
|
||||
"chip_count": count,
|
||||
"chip_status": chips,
|
||||
"nominal": nominal,
|
||||
}
|
||||
)
|
||||
return boards
|
||||
|
||||
async def get_bad_boards(self) -> dict:
|
||||
"""Checks for and provides list of non working boards."""
|
||||
boards = await self.get_board_info()
|
||||
bad_boards = {}
|
||||
for board in boards.keys():
|
||||
for chain in boards[board]:
|
||||
if not chain["chip_count"] == 18 or "x" in chain["chip_status"]:
|
||||
if board not in bad_boards.keys():
|
||||
bad_boards[board] = []
|
||||
bad_boards[board].append(chain)
|
||||
return bad_boards
|
||||
@@ -1,3 +0,0 @@
|
||||
from .antminer import *
|
||||
from .avalonminer import *
|
||||
from .whatsminer import *
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S17(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S17"
|
||||
self.nominal_chips = 48
|
||||
self.fan_count = 4
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S17Plus(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S17+"
|
||||
self.nominal_chips = 65
|
||||
self.fan_count = 4
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S17Pro(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S17 Pro"
|
||||
self.nominal_chips = 48
|
||||
self.fan_count = 4
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S17e(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S17e"
|
||||
self.nominal_chips = 135
|
||||
self.fan_count = 4
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class T17(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "T17"
|
||||
self.nominal_chips = 30
|
||||
self.fan_count = 4
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class T17Plus(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "T17+"
|
||||
self.nominal_chips = 44
|
||||
self.fan_count = 4
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class T17e(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "T17e"
|
||||
self.nominal_chips = 78
|
||||
self.fan_count = 4
|
||||
@@ -1,8 +0,0 @@
|
||||
from .S17 import S17
|
||||
from .S17_Plus import S17Plus
|
||||
from .S17_Pro import S17Pro
|
||||
from .S17e import S17e
|
||||
|
||||
from .T17 import T17
|
||||
from .T17_Plus import T17Plus
|
||||
from .T17e import T17e
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S19(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S19"
|
||||
self.nominal_chips = 76
|
||||
self.fan_count = 4
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S19Pro(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S19 Pro"
|
||||
self.nominal_chips = 114
|
||||
self.fan_count = 4
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S19a(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S19a"
|
||||
self.nominal_chips = 72
|
||||
self.fan_count = 4
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S19j(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S19j"
|
||||
self.nominal_chips = 114
|
||||
self.fan_count = 4
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S19jPro(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S19j Pro"
|
||||
self.nominal_chips = 126
|
||||
self.fan_count = 4
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class T19(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "T19"
|
||||
self.nominal_chips = 76
|
||||
self.fan_count = 4
|
||||
@@ -1,9 +0,0 @@
|
||||
from .S19 import S19
|
||||
from .S19_Pro import S19Pro
|
||||
|
||||
from .S19j import S19j
|
||||
from .S19j_Pro import S19jPro
|
||||
|
||||
from .S19a import S19a
|
||||
|
||||
from .T19 import T19
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S9(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S9"
|
||||
self.nominal_chips = 63
|
||||
self.fan_count = 2
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S9i(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S9i"
|
||||
self.nominal_chips = 63
|
||||
self.fan_count = 2
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class T9(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "T9"
|
||||
self.nominal_chips = 57
|
||||
self.fan_count = 2
|
||||
@@ -1,3 +0,0 @@
|
||||
from .S9 import S9
|
||||
from .S9i import S9i
|
||||
from .T9 import T9
|
||||
@@ -1,3 +0,0 @@
|
||||
from .X9 import *
|
||||
from .X17 import *
|
||||
from .X19 import *
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon1026(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 1026"
|
||||
self.nominal_chips = 80
|
||||
self.fan_count = 2
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon1047(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 1047"
|
||||
self.nominal_chips = 80
|
||||
self.fan_count = 2
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon1066(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 1066"
|
||||
self.nominal_chips = 114
|
||||
self.fan_count = 4
|
||||
@@ -1,3 +0,0 @@
|
||||
from .A1026 import Avalon1026
|
||||
from .A1047 import Avalon1047
|
||||
from .A1066 import Avalon1066
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon721(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 721"
|
||||
self.chip_count = 18 # This miner has 4 boards totaling 72
|
||||
self.fan_count = 1 # also only 1 fan
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon741(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 741"
|
||||
self.chip_count = 22 # This miner has 4 boards totaling 88
|
||||
self.fan_count = 1 # also only 1 fan
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon761(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 761"
|
||||
self.chip_count = 18 # This miner has 4 boards totaling 72
|
||||
self.fan_count = 1 # also only 1 fan
|
||||
@@ -1,3 +0,0 @@
|
||||
from .A721 import Avalon721
|
||||
from .A741 import Avalon741
|
||||
from .A761 import Avalon761
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon821(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 821"
|
||||
self.chip_count = 26 # This miner has 4 boards totaling 104
|
||||
self.fan_count = 1 # also only 1 fan
|
||||
@@ -1,10 +0,0 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon841(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 841"
|
||||
self.chip_count = 26 # This miner has 4 boards totaling 104
|
||||
self.fan_count = 1 # also only 1 fan
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user